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 clangAnalysisScalable 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/Analysis/Scalable/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/Analysis/Scalable/TUSummary/ExtractorRegistry.h"

using namespace clang::ssaf;

// NOLINTNEXTLINE(misc-use-internal-linkage)
volatile int SSAFMyExtractorAnchorSource = 0;

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. Add the following to the appropriate force-linker header:

extern volatile int SSAFMyExtractorAnchorSource;
[[maybe_unused]] static int SSAFMyExtractorAnchorDestination =
    SSAFMyExtractorAnchorSource;

For in-tree additions, add this to clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h.

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/Analysis/Scalable/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

namespace llvm {
extern template class CLANG_TEMPLATE_ABI
    Registry<clang::ssaf::MyFormat::FormatInfo>;
} // namespace llvm

2.2.2. Step 2: Register the format

//--- MyFormat.cpp
#include "MyFormat.h"
#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h"

using namespace clang::ssaf;

// NOLINTNEXTLINE(misc-use-internal-linkage)
volatile int SSAFMyFormatAnchorSource = 0;

static SerializationFormatRegistry::Add<MyFormat>
    RegisterFormat("myformat", "My awesome serialization format");

LLVM_INSTANTIATE_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 — 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/Analysis/Scalable/SSAFBuiltinForceLinker.h.

  2. Add the source files to the clangAnalysisScalable 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.