2. How to Extend the Framework¶
:::{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. :::
SSAF is designed to be extensible with new summary extractors and serialization formats. Extensions can be added in three ways:
Statically, in-tree — built as part of the upstream LLVM/Clang tree.
Statically, out-of-tree (downstream) — built in a downstream fork or project that links
clangScalableStaticAnalysisCoreas a static library.Dynamically, via plugins — loaded at runtime as shared objects.
All three approaches use the same llvm::Registry-based registration mechanism.
The key difference is how the linker sees the registration:
static libraries need force-linker anchors to prevent dead-stripping, while shared libraries do not.
2.1. Adding a summary extractor¶
A summary extractor is an ASTConsumer that inspects the AST and populates a TUSummary via the TUSummaryBuilder interface.
2.1.1. Step 1: Implement the extractor¶
//--- MyExtractor.h
#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.h"
namespace clang::ssaf {
class MyExtractor : public TUSummaryExtractor {
public:
using TUSummaryExtractor::TUSummaryExtractor;
// Override HandleTranslationUnit or any other virtual functions of an ASTConsumer...
// Use the SummaryBuilder to populate the summary while walking the AST.
};
} // namespace clang::ssaf
2.1.2. Step 2: Register the extractor¶
//--- MyExtractor.cpp
#include "MyExtractor.h"
#include "clang/ScalableStaticAnalysis/Core/TUSummary/ExtractorRegistry.h"
using namespace clang::ssaf;
namespace clang::ssaf {
// NOLINTNEXTLINE(misc-use-internal-linkage)
volatile int MyExtractorAnchorSource = 0;
} // namespace clang::ssaf
static TUSummaryExtractorRegistry::Add<MyExtractor>
RegisterExtractor("MyExtractor", "My awesome summary extractor");
The "MyExtractor" string is the name users pass to --ssaf-extract-summaries=MyExtractor.
2.1.3. Step 3: Add the force-linker anchor¶
See Force-Linker Headers for a full explanation of why this is needed.
For in-tree additions, add one line to
clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
(in alphabetical order):
ANCHOR(MyExtractorAnchorSource)
SSAFBuiltinForceLinker.h includes this .def file automatically — no
need to edit it directly.
For downstream additions, see Out-of-tree (downstream) extensions below.
2.2. Adding a serialization format¶
A serialization format controls how the TUSummary is written to (and read from) disk.
This involves more boilerplate than an extractor because each format has a per-analysis FormatInfo sub-registry.
2.2.1. Step 1: Define the format class¶
Your format class must inherit from SerializationFormat and define a FormatInfo type alias:
//--- MyFormat.h
#include "clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h"
#include "clang/Support/Compiler.h"
#include "llvm/Support/Registry.h"
namespace clang::ssaf {
class MyFormat : public SerializationFormat {
public:
// Define the type aliases: SerializerFn, DeserializerFn
using FormatInfo = FormatInfoEntry<SerializerFn, DeserializerFn>;
// Override readTUSummaryEncoding, writeTUSummary, etc.
};
} // namespace clang::ssaf
LLVM_DECLARE_REGISTRY(llvm::Registry<MyFormat::FormatInfo>)
2.2.2. Step 2: Register the format¶
//--- MyFormat.cpp
#include "MyFormat.h"
#include "clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormatRegistry.h"
using namespace clang::ssaf;
namespace clang::ssaf {
// NOLINTNEXTLINE(misc-use-internal-linkage)
volatile int MyFormatAnchorSource = 0;
} // namespace clang::ssaf
static SerializationFormatRegistry::Add<MyFormat>
RegisterFormat("myformat", "My awesome serialization format");
LLVM_DEFINE_REGISTRY(llvm::Registry<MyFormat::FormatInfo>)
The format name ("myformat") is matched against the file extension in --ssaf-tu-summary-file=output.myformat.
2.2.3. Step 3: Register per-analysis FormatInfo entries¶
For each analysis that should be serializable in your format, register a FormatInfo entry.
FormatInfo must be implemented for any of the summaries that wants to support myformat:
namespace {
using FormatInfo = MyFormat::FormatInfo;
struct MyAnalysisFormatInfo final : FormatInfo {
MyAnalysisFormatInfo() : FormatInfo{
SummaryName("MyAnalysis"),
serializeMyAnalysis,
deserializeMyAnalysis,
} {}
};
} // namespace
static llvm::Registry<FormatInfo>::Add<MyAnalysisFormatInfo>
RegisterFormatInfo("MyAnalysisFormatInfo",
"MyFormat format info for MyAnalysis");
2.2.4. Step 4: Add the force-linker anchor¶
Same pattern as for extractors — add the anchor to BuiltinAnchorSources.def
(in alphabetical order). See Adding a summary extractor Step 3,
and Force-Linker Headers.
2.3. Static extensibility¶
2.3.1. In-tree extensions¶
For extensions that are part of the upstream LLVM/Clang tree:
Add the anchor to
clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def(in alphabetical order).Add the source files to the
clangScalableStaticAnalysisCoreCMake library target.That’s it — the
SSAFForceLinker.humbrella includesSSAFBuiltinForceLinker.htransitively, so any binary that includes the umbrella will pull in the registration.
2.3.2. Out-of-tree (downstream) extensions¶
Downstream projects that maintain a fork can add their own extensions without modifying upstream files — reducing the risk of merge-conflicts:
Create a downstream force-linker header, e.g.
SSAFDownstreamForceLinker.h, containing the anchor references for downstream-only extractors and formats.Include it from
SSAFForceLinker.h(the umbrella):// In SSAFForceLinker.h #include "SSAFBuiltinForceLinker.h" // IWYU pragma: keep #include "SSAFDownstreamForceLinker.h" // IWYU pragma: keep
This is a single-line addition per downstream project, minimizing conflicts with upstream changes. Upstream will try to avoid modifying this umbrella header, making it a stable static extension point.
Add the downstream source files to the build system as usual.
2.4. Dynamic extensibility (plugins)¶
Shared libraries loaded at runtime — via dlopen / LoadLibrary or the
Clang plugin mechanism — do not need force-linker anchors, but having them also does not hurt.
When a shared object (.so / .dylib) is loaded, the dynamic linker runs all global constructors in that library unconditionally.
This means the llvm::Registry::Add<> objects execute their constructors and register themselves automatically.
To use a plugin:
Build your extractor or format as a shared library.
Load it with the Clang plugin mechanism (
-fplugin=or-load).Pass the extractor name to
--ssaf-extract-summaries=as usual.
No changes to any force-linker header are required.
The llvm::Registry infrastructure handles everything once the shared object is loaded.