| # Force-Linker Headers | |
| :::{WARNING} | |
| The framework is rapidly evolving. | |
| The documentation might be out-of-sync with the implementation. | |
| The purpose of this documentation is to give context for upcoming reviews. | |
| ::: | |
| ## The problem | |
| SSAF uses [llvm::Registry<>](https://llvm.org/doxygen/classllvm_1_1Registry.html) | |
| for decentralized registration of summary extractors and serialization formats. | |
| Each registration is a file-scope static object whose constructor adds an entry | |
| to the global registry: | |
| ```c++ | |
| // In MyExtractor.cpp | |
| static TUSummaryExtractorRegistry::Add<MyExtractor> | |
| RegisterExtractor("MyExtractor", "My summary extractor"); | |
| ``` | |
| When the translation unit containing this static object is compiled into a | |
| **static library** (`.a` / `.lib`), the static linker will only pull in | |
| object files that resolve an undefined symbol in the consuming binary. | |
| Because no code ever calls anything in `MyExtractor.o` directly, the linker | |
| discards the object file — and the registration never runs. | |
| This is not a problem for **shared libraries** (`.so` / `.dylib`), because | |
| the dynamic linker loads the entire shared object and runs all global | |
| constructors unconditionally. | |
| ## The solution: anchor symbols | |
| Each registration translation unit defines a `volatile int` **anchor symbol**: | |
| ```c++ | |
| // In MyExtractor.cpp - next to the registry Add<> object in the ``clang::ssaf`` namespace | |
| // NOLINTNEXTLINE(misc-use-internal-linkage) | |
| volatile int MyExtractorAnchorSource = 0; | |
| ``` | |
| For **in-tree** anchors, add a single `ANCHOR(...)` entry to | |
| `BuiltinAnchorSources.def` (in alphabetical order): | |
| ```c++ | |
| // In clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def | |
| ANCHOR(JSONFormatAnchorSource) | |
| ANCHOR(MyExtractorAnchorSource) // <-- Add here, in alphabetical order | |
| ``` | |
| `SSAFBuiltinForceLinker.h` includes this `.def` file automatically to | |
| generate the `extern` declarations and the `AnchorSources` array — there is | |
| no need to edit that header directly. | |
| Any translation unit that `#include`s this header now has a reference to | |
| `MyExtractorAnchorSource`, which forces the linker to pull in | |
| `MyExtractor.o` — and with it, the static `Add<>` registration object. | |
| The `volatile` qualifier is essential: without it the compiler could | |
| constant-fold the `0` and eliminate the reference entirely. | |
| These anchor symbols must be mutable (not `const`), because otherwise on MSVC | |
| `const volatile` variables would still have **internal linkage** — despite | |
| that the standard specifies that these should have **external linkage**. | |
| ### Header hierarchy | |
| ```text | |
| SSAFForceLinker.h (umbrella — include this in binaries) | |
| └── SSAFBuiltinForceLinker.h (upstream built-in anchors only) | |
| ``` | |
| - `clang/include/clang/ScalableStaticAnalysis/SSAFBuiltinForceLinker.h` — anchors for | |
| upstream-provided (built-in) extractors and formats (e.g. `JSONFormat`). | |
| - `clang/include/clang/ScalableStaticAnalysis/SSAFForceLinker.h` — umbrella header | |
| that includes `SSAFBuiltinForceLinker.h`. This is the header that | |
| downstream projects should modify to add their own force-linker includes | |
| (see {doc}`HowToExtend`). | |
| Include the umbrella header with `// IWYU pragma: keep` in any translation | |
| unit that must guarantee all registrations are active — typically the entry | |
| point of a binary that uses `clangScalableStaticAnalysisCore`: | |
| ```c++ | |
| // In ExecuteCompilerInvocation.cpp | |
| #include "clang/ScalableStaticAnalysis/SSAFForceLinker.h" // IWYU pragma: keep | |
| ``` | |
| ### Naming convention | |
| Anchor symbols follow the pattern `<Component>AnchorSource` in the `clang::ssaf` namespace. | |
| For example: | |
| - `JSONFormatAnchorSource` | |
| - `MyExtractorAnchorSource` | |
| All anchor sources are aggregated into a single `BuiltinAnchorDestination` | |
| lambda in the force-linker header (see `SSAFBuiltinForceLinker.h`). | |
| ## Considered alternatives | |
| ### `--whole-archive` / `-force_load` | |
| The linker can be instructed to include *every* object file from a static | |
| library, regardless of whether any symbols are referenced: | |
| ```bash | |
| # GNU ld / lld (Linux, BSD) | |
| -Wl,--whole-archive -lclangScalableStaticAnalysisCore -Wl,--no-whole-archive | |
| # Apple ld | |
| -Wl,-force_load,libclangScalableStaticAnalysisCore.a | |
| ``` | |
| Since CMake 3.24, the `$<LINK_LIBRARY:WHOLE_ARCHIVE,...>` generator expression | |
| provides a portable way to do the same: | |
| ```cmake | |
| target_link_libraries(clang PRIVATE | |
| "$<LINK_LIBRARY:WHOLE_ARCHIVE,clangScalableStaticAnalysisCore>") | |
| ``` | |
| **Why we did not choose this approach**: | |
| - It is a blunt instrument — *all* object files in the library are pulled in, | |
| increasing binary size. | |
| - The anchor approach only targets specific object files: only registrations | |
| whose anchors are referenced in a force-linker header are pulled in. | |
| - `--whole-archive` semantics vary across platforms and toolchains, requiring | |
| platform-specific CMake logic or the relatively new `WHOLE_ARCHIVE` | |
| generator expression. | |
| ### Explicit initialization functions | |
| An alternative is a central `initializeSSAFRegistrations()` function that | |
| explicitly calls into each registration module: | |
| ```c++ | |
| void initializeSSAFRegistrations() { | |
| initializeJSONFormat(); | |
| initializeMyExtractor(); | |
| // ... one entry per registration | |
| } | |
| ``` | |
| **Why we did not choose this approach**: | |
| - It reintroduces a centralized list that must be maintained manually, defeating | |
| the decoupled-registration benefit of `llvm::Registry`. | |
| - Adding a new extractor or format requires modifying a central file, which | |
| increases merge-conflict risk for downstream users. |