2. How to Extend the Framework — Clang 23.1.0 documentation

# Clang 23.1.0 documentation

## 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:

1. Statically, in-tree — built as part of the upstream LLVM/Clang tree.

2. Statically, out-of-tree (downstream) — built in a downstream fork or project that links `clangScalableStaticAnalysisCore` as a static library.

3. 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:

1. Add the anchor to `clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def` (in alphabetical order).

2. Add the source files to the `clangScalableStaticAnalysisCore` CMake library target.

3. That’s it — the `SSAFForceLinker.h` umbrella includes `SSAFBuiltinForceLinker.h` transitively, 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:

1. Create a downstream force-linker header, e.g. `SSAFDownstreamForceLinker.h`, containing the anchor references for downstream-only extractors and formats.

2. 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.

3. 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:

1. Build your extractor or format as a shared library.

2. Load it with the Clang plugin mechanism (`-fplugin=` or `-load`).

3. 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.