Component of the Week #40: bsls_linkcoercion
- Summary:
Provides a macro to force a link-time dependency on a symbol.
Useful for generating link-time failures when library version mismatches occur.
The bsls_linkcoercion
component provides a single macro, BSLS_LINKCOERCION_FORCE_SYMBOL_DEPENDENCY,
that forces a link-time dependency on a specified symbol. This macro is
particularly useful for preventing binary-incompatible object files from being
successfully linked together.
Why Link Coercion?
Consider a library that releases a new major version (e.g., 2.x) that is source-compatible with the previous version (1.x code compiles with 2.x headers) but not binary-compatible (1.x object files cannot link with the 2.x library due to ABI changes). If object files compiled with 1.x headers are accidentally linked against the 2.x library, the result can be subtle runtime bugs that are extremely difficult to diagnose.
Link coercion solves this problem by forcing a link-time failure when incompatible object files are linked. The technique works by:
Creating a symbol with a name that varies based on the library version
Forcing each translation unit to reference that symbol
Defining the symbol in the library for its version
If an object file is compiled with a different version’s headers than the library being linked, the symbol names won’t match, and the linker will report an undefined symbol error.
Basic Usage
To use link coercion, you need to:
Define a macro that produces different symbol names for different build configurations or different library versions
Declare the external symbol in a header of your library that is included by all headers
Use
BSLS_LINKCOERCION_FORCE_SYMBOL_DEPENDENCYto create the dependencyDefine the symbol in the
.cppfile corresponding to the header
Here’s an example that prevents linking code compiled against different major versions of a library:
// mylib_version.h
#include <bsls_linkcoercion.h>
#define MYLIB_VERSION_MAJOR 2
#if MYLIB_VERSION_MAJOR == 1
#define MYLIB_LINKSYMBOL mylib_version_1
#elif MYLIB_VERSION_MAJOR == 2
#define MYLIB_LINKSYMBOL mylib_version_2
#else
#error Unsupported MYLIB version
#endif
extern const char* MYLIB_LINKSYMBOL;
BSLS_LINKCOERCION_FORCE_SYMBOL_DEPENDENCY(const char *,
mylib_version_coercion,
MYLIB_LINKSYMBOL)
// mylib_version.cpp
#include <mylib_version.h>
const char *MYLIB_LINKSYMBOL = "mylib 2.x";
With this setup, if a client compiles their code with 2.x headers but tries to
link against a 1.x library (which only defines mylib_version_1), the linker
will fail with an undefined symbol error for mylib_version_2.
How It Works
The macro creates a static variable that references the external symbol. The implementation varies by platform to ensure the reference is not optimized away by the compiler or removed by linker garbage collection.
Because the name of the generated symbol will appear in the error message
whenever we try to link against a library that doesn’t define exactly the same
symbol we do our best to use symbol names that will be descriptive and helpful.
For example, the symbols used to be sure that the correct version of bde
libraries include the name of the library, the version of the library, and then
compiled_this_object (i.e., s_version_BSL_4_33_compiled_this_object) to
include sufficient information in the linker error to be self-explanatory.
For more information, see the documentation for bsls_linkcoercion.