blob: 701a0f6aabf84b0f40203fd46ec90eff9572be65 [file] [view] [edit]
# 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.