https://github.com/steakhal updated 
https://github.com/llvm/llvm-project/pull/184421

From aeda1b84c24530942c19b0ec283bbe0468486339 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Tue, 24 Feb 2026 17:30:17 +0000
Subject: [PATCH 1/3] [clang][ssaf] Add --ssaf-extract-summaries= and
 --ssaf-tu-summary-file= options

Along the way, it also fixes the static registration of the builtin
(upstream) formats and extractors.
---
 .../TUSummaryExtractorFrontendAction.h        |  33 ++++
 .../Scalable/SSAFBuiltinForceLinker.h         |  28 +++
 .../clang/Analysis/Scalable/SSAFForceLinker.h |  25 +++
 .../SerializationFormatRegistry.h             |   1 +
 .../clang/Basic/DiagnosticFrontendKinds.td    |  18 ++
 .../include/clang/Frontend/FrontendOptions.h  |   7 +
 clang/include/clang/Options/Options.td        |  20 ++
 clang/lib/Analysis/Scalable/CMakeLists.txt    |   1 +
 .../TUSummaryExtractorFrontendAction.cpp      | 181 ++++++++++++++++++
 .../JSONFormat/JSONFormatImpl.cpp             |   8 +
 clang/lib/Driver/ToolChains/Clang.cpp         |   3 +
 clang/lib/FrontendTool/CMakeLists.txt         |   1 +
 .../ExecuteCompilerInvocation.cpp             |   6 +
 .../Analysis/SSAF/command-line-interface.cpp  |  22 +++
 clang/test/Analysis/SSAF/help.cpp             |   7 +
 .../Analysis/Scalable/CMakeLists.txt          |   1 +
 .../TUSummaryExtractorFrontendActionTest.cpp  |  21 ++
 .../Scalable/Registries/FancyAnalysisData.cpp |   5 +
 .../Registries/MockSerializationFormat.cpp    |   4 +
 .../Registries/MockSummaryExtractor1.cpp      |   5 +
 .../Registries/MockSummaryExtractor2.cpp      |   5 +
 .../SerializationFormatRegistryTest.cpp       |   9 +-
 .../Scalable/SSAFBuiltinTestForceLinker.h     |  41 ++++
 .../Analysis/Scalable/SSAFTestForceLinker.h   |  23 +++
 .../Analysis/Scalable/TestFixture.cpp         |   1 +
 25 files changed, 473 insertions(+), 3 deletions(-)
 create mode 100644 
clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h
 create mode 100644 
clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h
 create mode 100644 clang/include/clang/Analysis/Scalable/SSAFForceLinker.h
 create mode 100644 
clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp
 create mode 100644 clang/test/Analysis/SSAF/command-line-interface.cpp
 create mode 100644 clang/test/Analysis/SSAF/help.cpp
 create mode 100644 
clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp
 create mode 100644 
clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h
 create mode 100644 clang/unittests/Analysis/Scalable/SSAFTestForceLinker.h

diff --git 
a/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h
 
b/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h
new file mode 100644
index 0000000000000..8a8c50b0a5695
--- /dev/null
+++ 
b/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h
@@ -0,0 +1,33 @@
+//===- TUSummaryExtractorFrontendAction.h -----------------------*- C++ 
-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef 
LLVM_CLANG_ANALYSIS_SCALABLE_FRONTEND_TUSUMMARYEXTRACTORFRONTENDACTION_H
+#define 
LLVM_CLANG_ANALYSIS_SCALABLE_FRONTEND_TUSUMMARYEXTRACTORFRONTENDACTION_H
+
+#include "clang/Frontend/FrontendAction.h"
+#include <memory>
+
+namespace clang::ssaf {
+
+/// Wraps the existing \c FrontendAction and injects the extractor
+/// \c ASTConsumers into the pipeline after the ASTConsumers of the wrapped
+/// action.
+class TUSummaryExtractorFrontendAction : public WrapperFrontendAction {
+public:
+  explicit TUSummaryExtractorFrontendAction(
+      std::unique_ptr<FrontendAction> WrappedAction);
+  ~TUSummaryExtractorFrontendAction();
+
+protected:
+  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
+                                                 StringRef InFile) override;
+};
+
+} // namespace clang::ssaf
+
+#endif // 
LLVM_CLANG_ANALYSIS_SCALABLE_FRONTEND_TUSUMMARYEXTRACTORFRONTENDACTION_H
diff --git a/clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h 
b/clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h
new file mode 100644
index 0000000000000..f13f131e89bc6
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h
@@ -0,0 +1,28 @@
+//===- SSAFBuiltinForceLinker.h ---------------------------------*- C++ 
-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file pulls in all built-in SSAF extractor and format registrations
+/// by referencing their anchor symbols, preventing the static linker from
+/// discarding the containing object files.
+///
+/// Include this header (with IWYU pragma: keep) in any translation unit that
+/// must guarantee these registrations are active — typically the entry point
+/// of a binary that uses clangAnalysisScalable.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_SSAFBUILTINFORCELINKER_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_SSAFBUILTINFORCELINKER_H
+
+// This anchor is used to force the linker to link the JSONFormat registration.
+extern volatile int SSAFJSONFormatAnchorSource;
+[[maybe_unused]] static int SSAFJSONFormatAnchorDestination =
+    SSAFJSONFormatAnchorSource;
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_SSAFBUILTINFORCELINKER_H
diff --git a/clang/include/clang/Analysis/Scalable/SSAFForceLinker.h 
b/clang/include/clang/Analysis/Scalable/SSAFForceLinker.h
new file mode 100644
index 0000000000000..a59c5f144a6f5
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/SSAFForceLinker.h
@@ -0,0 +1,25 @@
+//===- SSAFForceLinker.h ----------------------------------------*- C++ 
-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file pulls in all built-in SSAF extractor and format registrations
+/// by referencing their anchor symbols, preventing the static linker from
+/// discarding the containing object files.
+///
+/// Include this header (with IWYU pragma: keep) in any translation unit that
+/// must guarantee these registrations are active — typically the entry point
+/// of a binary that uses clangAnalysisScalable.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_SSAFFORCELINKER_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_SSAFFORCELINKER_H
+
+#include "SSAFBuiltinForceLinker.h" // IWYU pragma: keep
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_SSAFFORCELINKER_H
diff --git 
a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h
 
b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h
index ef060dd27c522..54c449fc0c11d 100644
--- 
a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h
+++ 
b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h
@@ -28,6 +28,7 @@
 //
 //   static SerializationFormatRegistry::Add<MyFormat>
 //     RegisterFormat("MyFormat", "My awesome serialization format");
+// TODO: Mention anchors and force linker .h
 //
 // Then implement the formatter for the specific analysis and register the
 // format info for it:
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td 
b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 5c62bb70ebd0f..a01c07d39bcd7 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -382,6 +382,24 @@ def warn_profile_data_misexpect : Warning<
 def err_extract_api_ignores_file_not_found :
   Error<"file '%0' specified by '--extract-api-ignores=' not found">, 
DefaultFatal;
 
+def err__ssaf_extract_tu_summary_file_unknown_output_format :
+  Error<"unknown output summary file format '%0' "
+  "specified by '--ssaf-tu-summary-file=%1'">, DefaultFatal;
+
+def err__ssaf_extract_tu_summary_file_unknown_format :
+  Error<"failed to parse the value of '--ssaf-tu-summary-file=%0' "
+  "the value must follow the '<path>.<format>' pattern">, DefaultFatal;
+
+def err__ssaf_must_enable_summary_extractors :
+  Error<"must enable some summary extractors using the "
+        "'--ssaf-extract-summaries=' option">, DefaultFatal;
+
+def err__ssaf_extract_summary_unknown_extractor_name :
+  Error<"no summary extractor%s0 %plural{1:was|:were}0 registered with name: 
%1">, DefaultFatal;
+
+def err__ssaf_write_tu_summary_failed :
+  Error<"failed to write TU summary to '%0': %1">, DefaultFatal;
+
 def warn_missing_symbol_graph_dir : Warning<
   "missing symbol graph output directory, defaulting to working directory">,
   InGroup<ExtractAPIMisuse>;
diff --git a/clang/include/clang/Frontend/FrontendOptions.h 
b/clang/include/clang/Frontend/FrontendOptions.h
index 9e05181ac916c..486d0c38a1412 100644
--- a/clang/include/clang/Frontend/FrontendOptions.h
+++ b/clang/include/clang/Frontend/FrontendOptions.h
@@ -543,6 +543,13 @@ class FrontendOptions {
   /// minimization hints.
   std::string DumpMinimizationHintsPath;
 
+  /// List of SSAF extractors to enable.
+  std::vector<std::string> SSAFExtractSummaries;
+
+  /// The TU summary output file with the file extension representing the file
+  /// format.
+  std::string SSAFTUSUmmaryFile;
+
 public:
   FrontendOptions()
       : DisableFree(false), RelocatablePCH(false), ShowHelp(false),
diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 1021d95e4005b..6c3eba9daa77e 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -274,6 +274,10 @@ def StaticAnalyzer_Group : OptionGroup<"<Static analyzer 
group>">,
                            DocName<"Static analyzer options">, DocBrief<[{
 Flags controlling the behavior of the Clang Static Analyzer.}]>;
 
+def SSAF_Group : OptionGroup<"<ssaf options>">,
+                 DocName<"SSAF options">, DocBrief<[{
+Flags controlling the behavior of the Scalable Static Analysis Framework 
(SSAF).}]>;
+
 // gfortran options that we recognize in the driver and pass along when
 // invoking GCC to compile Fortran code.
 def gfortran_Group : OptionGroup<"<gfortran group>">,
@@ -941,6 +945,22 @@ def W_Joined : Joined<["-"], "W">, Group<W_Group>,
 def Xanalyzer : Separate<["-"], "Xanalyzer">,
   HelpText<"Pass <arg> to the static analyzer">, MetaVarName<"<arg>">,
   Group<StaticAnalyzer_Group>;
+def _ssaf_extract_summaries :
+  CommaJoined<["--"], "ssaf-extract-summaries=">,
+  MetaVarName<"<summary-names>">,
+  Group<SSAF_Group>,
+  Visibility<[ClangOption, CC1Option]>,
+  HelpText<"Comma separated list of summary names to extract">,
+  MarshallingInfoStringVector<FrontendOpts<"SSAFExtractSummaries">>;
+def _ssaf_tu_summary_file :
+  Joined<["--"], "ssaf-tu-summary-file=">,
+  MetaVarName<"<path>.<format>">,
+  Group<SSAF_Group>,
+  Visibility<[ClangOption, CC1Option]>,
+  HelpText<
+    "The output file for the extracted summaries. "
+    "The extension selects what file format to be used.">,
+  MarshallingInfoString<FrontendOpts<"SSAFTUSUmmaryFile">>;
 def Xarch__
     : JoinedAndSeparate<["-"], "Xarch_">,
       Flags<[NoXarchOption]>,
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt 
b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 81550df4565cb..032e0b9dc75fd 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS
 add_clang_library(clangAnalysisScalable
   ASTEntityMapping.cpp
   EntityLinker/EntityLinker.cpp
+  Frontend/TUSummaryExtractorFrontendAction.cpp
   Model/BuildNamespace.cpp
   Model/EntityId.cpp
   Model/EntityIdTable.cpp
diff --git 
a/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp 
b/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp
new file mode 100644
index 0000000000000..8ea4ac3dfad49
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp
@@ -0,0 +1,181 @@
+//===- TUSummaryExtractorFrontendAction.cpp 
-------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h"
+#include "clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummaryExtractor.h"
+#include "clang/Basic/DiagnosticFrontend.h"
+#include "clang/Frontend/MultiplexConsumer.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Path.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace clang;
+using namespace ssaf;
+
+static std::optional<std::pair<llvm::StringRef, llvm::StringRef>>
+parseOutputFileFormatAndPathOrReportError(DiagnosticsEngine &Diags,
+                                          StringRef SSAFTUSUmmaryFile) {
+
+  StringRef Ext = llvm::sys::path::extension(SSAFTUSUmmaryFile);
+  StringRef FilePath = SSAFTUSUmmaryFile.drop_back(Ext.size());
+
+  if (!Ext.consume_front(".") || FilePath.empty()) {
+    Diags.Report(diag::err__ssaf_extract_tu_summary_file_unknown_format)
+        << SSAFTUSUmmaryFile;
+    return std::nullopt;
+  }
+
+  if (!isFormatRegistered(Ext)) {
+    Diags.Report(diag::err__ssaf_extract_tu_summary_file_unknown_output_format)
+        << Ext << SSAFTUSUmmaryFile;
+    return std::nullopt;
+  }
+
+  return std::pair{Ext, FilePath};
+}
+
+/// Return \c true if reported unrecognized extractors.
+static bool
+reportUnrecognizedExtractorNames(DiagnosticsEngine &Diags,
+                                 ArrayRef<std::string> SSAFExtractSummaries) {
+  if (SSAFExtractSummaries.empty()) {
+    Diags.Report(diag::err__ssaf_must_enable_summary_extractors);
+    return true;
+  }
+
+  std::vector<StringRef> UnrecognizedExtractorNames;
+  for (StringRef Name : SSAFExtractSummaries)
+    if (!isTUSummaryExtractorRegistered(Name))
+      UnrecognizedExtractorNames.push_back(Name);
+
+  if (!UnrecognizedExtractorNames.empty()) {
+    Diags.Report(diag::err__ssaf_extract_summary_unknown_extractor_name)
+        << UnrecognizedExtractorNames.size()
+        << llvm::join(UnrecognizedExtractorNames, ", ");
+    return true;
+  }
+
+  return false;
+}
+
+static std::vector<std::unique_ptr<ASTConsumer>>
+makeTUSummaryExtractors(TUSummaryBuilder &Builder,
+                        ArrayRef<std::string> SSAFExtractSummaries) {
+  std::vector<std::unique_ptr<ASTConsumer>> Extractors;
+  Extractors.reserve(SSAFExtractSummaries.size());
+  for (StringRef Name : SSAFExtractSummaries) {
+    assert(isTUSummaryExtractorRegistered(Name));
+    Extractors.push_back(makeTUSummaryExtractor(Name, Builder));
+  }
+  return Extractors;
+}
+
+namespace {
+
+/// Drives all extractor \c ASTConsumers and serializes the completed
+/// \c TUSummary.
+///
+/// Derives from \c MultiplexConsumer so every \c ASTConsumer virtual method is
+/// automatically forwarded to each extractor.
+class TUSummaryRunner final : public MultiplexConsumer {
+public:
+  static std::unique_ptr<TUSummaryRunner> create(CompilerInstance &CI,
+                                                 StringRef InFile);
+
+private:
+  TUSummaryRunner(StringRef InFile, std::unique_ptr<SerializationFormat> 
Format,
+                  const FrontendOptions &Opts);
+
+  void HandleTranslationUnit(ASTContext &Ctx) override;
+
+  TUSummary Summary;
+  TUSummaryBuilder Builder = TUSummaryBuilder(Summary);
+  std::unique_ptr<SerializationFormat> Format;
+  const FrontendOptions &Opts;
+};
+} // namespace
+
+std::unique_ptr<TUSummaryRunner> TUSummaryRunner::create(CompilerInstance &CI,
+                                                         StringRef InFile) {
+  const FrontendOptions &Opts = CI.getFrontendOpts();
+  DiagnosticsEngine &Diags = CI.getDiagnostics();
+
+  auto MaybePair =
+      parseOutputFileFormatAndPathOrReportError(Diags, Opts.SSAFTUSUmmaryFile);
+  if (!MaybePair.has_value())
+    return nullptr;
+  auto [FormatName, OutputPath] = MaybePair.value();
+
+  if (reportUnrecognizedExtractorNames(Diags, Opts.SSAFExtractSummaries))
+    return nullptr;
+
+  return std::unique_ptr<TUSummaryRunner>{
+      new TUSummaryRunner{InFile, makeFormat(FormatName), Opts}};
+}
+
+TUSummaryRunner::TUSummaryRunner(StringRef InFile,
+                                 std::unique_ptr<SerializationFormat> Format,
+                                 const FrontendOptions &Opts)
+    : MultiplexConsumer(std::vector<std::unique_ptr<ASTConsumer>>{}),
+      Summary(BuildNamespace(BuildNamespaceKind::CompilationUnit, InFile)),
+      Format(std::move(Format)), Opts(Opts) {
+  assert(this->Format);
+
+  // Now the Summary and the builders are constructed, we can also construct 
the
+  // extractors.
+  auto Extractors = makeTUSummaryExtractors(Builder, 
Opts.SSAFExtractSummaries);
+  assert(!Extractors.empty());
+
+  // We must initialize the Consumers here because the our extractors need a
+  // Builder that holds a reference to the TUSummary, which would be only
+  // initialized after the MultiplexConsumer ctor. This is the only way we can
+  // avoid the use of the TUSummary before it starts its lifetime.
+  MultiplexConsumer::Consumers = std::move(Extractors);
+}
+
+void TUSummaryRunner::HandleTranslationUnit(ASTContext &Ctx) {
+  // First, invoke the Summary Extractors.
+  MultiplexConsumer::HandleTranslationUnit(Ctx);
+
+  // Then serialize the result.
+  if (auto Err = Format->writeTUSummary(Summary, Opts.SSAFTUSUmmaryFile)) {
+    Ctx.getDiagnostics().Report(diag::err__ssaf_write_tu_summary_failed)
+        << Opts.SSAFTUSUmmaryFile << llvm::toString(std::move(Err));
+  }
+}
+
+TUSummaryExtractorFrontendAction::~TUSummaryExtractorFrontendAction() = 
default;
+
+TUSummaryExtractorFrontendAction::TUSummaryExtractorFrontendAction(
+    std::unique_ptr<FrontendAction> WrappedAction)
+    : WrapperFrontendAction(std::move(WrappedAction)) {}
+
+std::unique_ptr<ASTConsumer>
+TUSummaryExtractorFrontendAction::CreateASTConsumer(CompilerInstance &CI,
+                                                    StringRef InFile) {
+  auto WrappedConsumer = WrapperFrontendAction::CreateASTConsumer(CI, InFile);
+  if (!WrappedConsumer)
+    return nullptr;
+
+  if (auto Runner = TUSummaryRunner::create(CI, InFile)) {
+    std::vector<std::unique_ptr<ASTConsumer>> Consumers;
+    Consumers.reserve(2);
+    Consumers.push_back(std::move(WrappedConsumer));
+    Consumers.push_back(std::move(Runner));
+    return std::make_unique<MultiplexConsumer>(std::move(Consumers));
+  }
+  return WrappedConsumer;
+}
diff --git 
a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp 
b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
index 0cf35fdae6927..dc70667834ea7 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -8,10 +8,18 @@
 
 #include "JSONFormatImpl.h"
 
+#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h"
 #include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
 #include "llvm/Support/Registry.h"
 
 LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::ssaf::JSONFormat::FormatInfo>)
+static clang::ssaf::SerializationFormatRegistry::Add<clang::ssaf::JSONFormat>
+    RegisterFormat("json", "JSON serialization format");
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register JSONFormat in the SerializationFormatRegistry.
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFJSONFormatAnchorSource = 0;
 
 namespace clang::ssaf {
 
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index e860136aae5b3..226734297278c 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -7694,6 +7694,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction 
&JA,
 
   Args.AddLastArg(CmdArgs, options::OPT_fmax_tokens_EQ);
 
+  Args.AddLastArg(CmdArgs, options::OPT__ssaf_extract_summaries);
+  Args.AddLastArg(CmdArgs, options::OPT__ssaf_tu_summary_file);
+
   // Handle serialized diagnostics.
   if (Arg *A = Args.getLastArg(options::OPT__serialize_diags)) {
     CmdArgs.push_back("-serialize-diagnostic-file");
diff --git a/clang/lib/FrontendTool/CMakeLists.txt 
b/clang/lib/FrontendTool/CMakeLists.txt
index 66213f76eb968..15d44b0dfb653 100644
--- a/clang/lib/FrontendTool/CMakeLists.txt
+++ b/clang/lib/FrontendTool/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 set(link_libs
+  clangAnalysisScalable
   clangBasic
   clangCodeGen
   clangDriver
diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp 
b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
index 05f646b43e3c4..ff7cbc6cf5289 100644
--- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
+++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
@@ -11,6 +11,8 @@
 //
 
//===----------------------------------------------------------------------===//
 
+#include "clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h"
+#include "clang/Analysis/Scalable/SSAFForceLinker.h" // IWYU pragma: keep
 #include "clang/Basic/DiagnosticFrontend.h"
 #include "clang/CodeGen/CodeGenAction.h"
 #include "clang/Config/config.h"
@@ -207,6 +209,10 @@ CreateFrontendAction(CompilerInstance &CI) {
     Act = std::make_unique<ASTMergeAction>(std::move(Act),
                                             FEOpts.ASTMergeFiles);
 
+  if (!FEOpts.SSAFTUSUmmaryFile.empty()) {
+    Act = std::make_unique<ssaf::TUSummaryExtractorFrontendAction>(
+        std::move(Act));
+  }
   return Act;
 }
 
diff --git a/clang/test/Analysis/SSAF/command-line-interface.cpp 
b/clang/test/Analysis/SSAF/command-line-interface.cpp
new file mode 100644
index 0000000000000..7deb2ab17bc70
--- /dev/null
+++ b/clang/test/Analysis/SSAF/command-line-interface.cpp
@@ -0,0 +1,22 @@
+// DEFINE: %{filecheck} = FileCheck %s --match-full-lines --check-prefix
+
+// The flags should behave the same way on the clang driver and also on CC1.
+
+// RUN: not %clang     -fsyntax-only %s --ssaf-tu-summary-file=foobar          
   2>&1 | %{filecheck}=NOT-MATCHING-THE-PATTERN
+// RUN: not %clang_cc1 -fsyntax-only %s --ssaf-tu-summary-file=foobar          
   2>&1 | %{filecheck}=NOT-MATCHING-THE-PATTERN
+// RUN: not %clang     -fsyntax-only %s 
--ssaf-tu-summary-file=%t.ssaf.unknownfmt 2>&1 | 
%{filecheck}=UNKNOWN-FILE-FORMAT
+// RUN: not %clang_cc1 -fsyntax-only %s 
--ssaf-tu-summary-file=%t.ssaf.unknownfmt 2>&1 | 
%{filecheck}=UNKNOWN-FILE-FORMAT
+// RUN: not %clang     -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json    
   2>&1 | %{filecheck}=NO-EXTRACTORS-ENABLED
+// RUN: not %clang_cc1 -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json    
   2>&1 | %{filecheck}=NO-EXTRACTORS-ENABLED
+// RUN: not %clang     -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json 
--ssaf-extract-summaries=extractor1            2>&1 | 
%{filecheck}=NO-EXTRACTOR-WITH-NAME
+// RUN: not %clang_cc1 -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json 
--ssaf-extract-summaries=extractor1            2>&1 | 
%{filecheck}=NO-EXTRACTOR-WITH-NAME
+// RUN: not %clang     -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json 
--ssaf-extract-summaries=extractor1,extractor2 2>&1 | 
%{filecheck}=NO-EXTRACTORS-WITH-NAME
+// RUN: not %clang_cc1 -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json 
--ssaf-extract-summaries=extractor1,extractor2 2>&1 | 
%{filecheck}=NO-EXTRACTORS-WITH-NAME
+
+void empty() {}
+
+// NOT-MATCHING-THE-PATTERN: fatal error: failed to parse the value of 
'--ssaf-tu-summary-file=foobar' the value must follow the '<path>.<format>' 
pattern
+// UNKNOWN-FILE-FORMAT:      fatal error: unknown output summary file format 
'unknownfmt' specified by '--ssaf-tu-summary-file={{.+}}.ssaf.unknownfmt'
+// NO-EXTRACTORS-ENABLED:    fatal error: must enable some summary extractors 
using the '--ssaf-extract-summaries=' option
+// NO-EXTRACTOR-WITH-NAME:   fatal error: no summary extractor was registered 
with name: extractor1
+// NO-EXTRACTORS-WITH-NAME:  fatal error: no summary extractors were 
registered with name: extractor1, extractor2
diff --git a/clang/test/Analysis/SSAF/help.cpp 
b/clang/test/Analysis/SSAF/help.cpp
new file mode 100644
index 0000000000000..936dd3b1fae94
--- /dev/null
+++ b/clang/test/Analysis/SSAF/help.cpp
@@ -0,0 +1,7 @@
+// RUN: %clang     --help 2>&1 | FileCheck %s
+// RUN: %clang_cc1 --help 2>&1 | FileCheck %s
+
+// CHECK:       --ssaf-extract-summaries=<summary-names>
+// CHECK-NEXT:    Comma separated list of summary names to extract
+// CHECK-NEXT:  --ssaf-tu-summary-file=<path>.<format>
+// CHECK-NEXT:    The output file for the extracted summaries. The extension 
selects what file format to be used.
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt 
b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index f021263312d29..fc9e23ee7526b 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -8,6 +8,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   EntityLinkerTest.cpp
   EntityNameTest.cpp
   ErrorBuilderTest.cpp
+  Frontend/TUSummaryExtractorFrontendActionTest.cpp
   ModelStringConversionsTest.cpp
   Registries/FancyAnalysisData.cpp
   Registries/MockSerializationFormat.cpp
diff --git 
a/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp
 
b/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp
new file mode 100644
index 0000000000000..5d64b9403706d
--- /dev/null
+++ 
b/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp
@@ -0,0 +1,21 @@
+//===- TUSummaryExtractorFrontendActionTest.cpp 
---------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h"
+#include "gtest/gtest.h"
+
+using namespace clang;
+using namespace ssaf;
+
+namespace {
+
+TEST(TUSummaryExtractorFrontendActionTest, SomeTest) {
+  // TODO: Add tests.
+}
+
+} // namespace
diff --git a/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp 
b/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp
index 7faaaade6b6f0..6feb939760923 100644
--- a/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp
@@ -58,3 +58,8 @@ static 
llvm::Registry<FormatInfo>::Add<FancyAnalysisFormatInfo>
     RegisterFormatInfo("FancyAnalysisData",
                        "Format info for FancyAnalysisData for the "
                        "MockSerializationFormat format");
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register FancyAnalysisFormatInfo.
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFFancyAnalysisDataAnchorSource = 0;
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp 
b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
index 13c8e103768a1..0bcdba5640e3c 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
@@ -157,6 +157,10 @@ llvm::Error MockSerializationFormat::writeTUSummary(const 
TUSummary &Summary,
 static SerializationFormatRegistry::Add<MockSerializationFormat>
     RegisterFormat("MockSerializationFormat",
                    "A serialization format for testing");
+// This anchor is used to force the linker to link in the generated object file
+// and thus register JSONFormat in the SerializationFormatRegistry.
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFMockSerializationFormatAnchorSource = 0;
 
 llvm::Expected<TUSummaryEncoding>
 MockSerializationFormat::readTUSummaryEncoding(llvm::StringRef Path) {
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp 
b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp
index ec31e2e16bc0a..2153f49c20dfa 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp
@@ -42,3 +42,8 @@ static TUSummaryExtractorRegistry::Add<MockSummaryExtractor1>
     RegisterExtractor("MockSummaryExtractor1", "Mock summary extractor 1");
 
 } // namespace
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register MockSummaryExtractor1.
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFMockSummaryExtractor1AnchorSource = 0;
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp 
b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp
index 90127a160bfa9..61270c953ddd9 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp
@@ -42,3 +42,8 @@ static TUSummaryExtractorRegistry::Add<MockSummaryExtractor2>
     RegisterExtractor("MockSummaryExtractor2", "Mock summary extractor 2");
 
 } // namespace
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register MockSummaryExtractor2.
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFMockSummaryExtractor2AnchorSource = 0;
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
 
b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
index 8b6d1a5ae15cf..791a1fe0501e5 100644
--- 
a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
+++ 
b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
@@ -8,6 +8,7 @@
 
 #include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h"
 #include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/FileSystem.h"
@@ -15,6 +16,7 @@
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <memory>
 
@@ -53,9 +55,10 @@ TEST(SerializationFormatRegistryTest, isFormatRegistered) {
 }
 
 TEST(SerializationFormatRegistryTest, EnumeratingRegistryEntries) {
-  auto Formats = SerializationFormatRegistry::entries();
-  ASSERT_EQ(std::distance(Formats.begin(), Formats.end()), 1U);
-  EXPECT_EQ(Formats.begin()->getName(), "MockSerializationFormat");
+  auto NamesOf = [](const auto &Entry) { return Entry.getName(); };
+  auto Names = llvm::map_range(SerializationFormatRegistry::entries(), 
NamesOf);
+  using testing::UnorderedElementsAre;
+  EXPECT_THAT(Names, UnorderedElementsAre("MockSerializationFormat", "json"));
 }
 
 TEST(SerializationFormatRegistryTest, Roundtrip) {
diff --git a/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h 
b/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h
new file mode 100644
index 0000000000000..5169eb504be48
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h
@@ -0,0 +1,41 @@
+//===- SSAFBuiltinTestForceLinker.h -----------------------------*- C++ 
-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file pulls in all test-only SSAF mock extractor and format
+/// registrations by referencing their anchor symbols.
+///
+/// Include this header (with IWYU pragma: keep) in a translation unit that
+/// is compiled into the SSAF unittest binary.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFBUILTINTESTFORCELINKER_H
+#define LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFBUILTINTESTFORCELINKER_H
+
+// Force the linker to link MockSummaryExtractor1 registration.
+extern volatile int SSAFMockSummaryExtractor1AnchorSource;
+[[maybe_unused]] static int SSAFMockSummaryExtractor1AnchorDestination =
+    SSAFMockSummaryExtractor1AnchorSource;
+
+// Force the linker to link MockSummaryExtractor2 registration.
+extern volatile int SSAFMockSummaryExtractor2AnchorSource;
+[[maybe_unused]] static int SSAFMockSummaryExtractor2AnchorDestination =
+    SSAFMockSummaryExtractor2AnchorSource;
+
+// Force the linker to link MockSerializationFormat registration.
+extern volatile int SSAFMockSerializationFormatAnchorSource;
+[[maybe_unused]] static int SSAFMockSerializationFormatAnchorDestination =
+    SSAFMockSerializationFormatAnchorSource;
+
+// Force the linker to link FancyAnalysisData format info registration.
+extern volatile int SSAFFancyAnalysisDataAnchorSource;
+[[maybe_unused]] static int SSAFFancyAnalysisDataAnchorDestination =
+    SSAFFancyAnalysisDataAnchorSource;
+
+#endif // LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFBUILTINTESTFORCELINKER_H
diff --git a/clang/unittests/Analysis/Scalable/SSAFTestForceLinker.h 
b/clang/unittests/Analysis/Scalable/SSAFTestForceLinker.h
new file mode 100644
index 0000000000000..144fe1af13f95
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/SSAFTestForceLinker.h
@@ -0,0 +1,23 @@
+//===- SSAFTestForceLinker.h ------------------------------------*- C++ 
-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file pulls in all test-only SSAF mock extractor and format
+/// registrations by referencing their anchor symbols.
+///
+/// Include this header (with IWYU pragma: keep) in a translation unit that
+/// is compiled into the SSAF unittest binary.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFTESTFORCELINKER_H
+#define LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFTESTFORCELINKER_H
+
+#include "SSAFBuiltinTestForceLinker.h" // IWYU pragma: keep
+
+#endif // LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFTESTFORCELINKER_H
diff --git a/clang/unittests/Analysis/Scalable/TestFixture.cpp 
b/clang/unittests/Analysis/Scalable/TestFixture.cpp
index ef021fccd3683..d05f218ef0d93 100644
--- a/clang/unittests/Analysis/Scalable/TestFixture.cpp
+++ b/clang/unittests/Analysis/Scalable/TestFixture.cpp
@@ -7,6 +7,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "TestFixture.h"
+#include "SSAFBuiltinTestForceLinker.h" // IWYU pragma: keep
 #include "clang/Analysis/Scalable/Model/BuildNamespace.h"
 #include "clang/Analysis/Scalable/Model/EntityId.h"
 #include "clang/Analysis/Scalable/Model/EntityLinkage.h"

From 56ec1255610b4c2c7aa676db5674890a690b0c86 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Wed, 4 Mar 2026 12:50:54 +0000
Subject: [PATCH 2/3] Final touches, fixes, and tests!

---
 .../developer-docs/Extensibility.rst          | 146 ++++++++
 .../TUSummaryExtractorFrontendAction.h        |   2 +-
 .../SerializationFormatRegistry.h             |  15 +-
 .../Scalable/TUSummary/ExtractorRegistry.h    |   9 +
 .../include/clang/Frontend/FrontendOptions.h  |   2 +-
 clang/include/clang/Options/Options.td        |   2 +-
 .../TUSummaryExtractorFrontendAction.cpp      |  16 +-
 .../JSONFormat/JSONFormatImpl.cpp             |  10 +-
 .../ExecuteCompilerInvocation.cpp             |   2 +-
 .../TUSummaryExtractorFrontendActionTest.cpp  | 345 +++++++++++++++++-
 .../Scalable/Registries/FancyAnalysisData.cpp |   7 +-
 .../Registries/MockSerializationFormat.cpp    |   6 +-
 .../Registries/MockSummaryExtractor1.cpp      |   7 +-
 .../Registries/MockSummaryExtractor2.cpp      |   7 +-
 .../SerializationFormatRegistryTest.cpp       |   3 +-
 .../SummaryExtractorRegistryTest.cpp          |   1 +
 .../Scalable/SSAFBuiltinTestForceLinker.h     |  10 +
 17 files changed, 546 insertions(+), 44 deletions(-)
 create mode 100644 
clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst

diff --git 
a/clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst 
b/clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst
new file mode 100644
index 0000000000000..6c8ec7d73add4
--- /dev/null
+++ 
b/clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst
@@ -0,0 +1,146 @@
+=============
+Extensibility
+=============
+
+.. WARNING:: The framework is rapidly evolving.
+  The documentation might be out-of-sync of the implementation.
+  The purpose of this documentation to give context for upcoming reviews.
+
+This document describes how to extend the framework with new formats and 
extractors in different contexts.
+Downstream users can extend the framework - let it be in-tree or out-of-tree.
+
+There are two 
+
+Static extensibility
+********************
+
+When building SSAF, new formats and extractors are registered via 
``llvm::Registry`` translation unit local 
+
+
+Two flags control summary extraction:
+
+- ``--ssaf-extract-summaries=<name1>,<name2>,...``: Comma-separated list of 
summary extractor names to enable.
+- ``--ssaf-tu-summary-file=<path>.<format>``: Output file for the extracted 
summaries. The file extension selects the serialization format (e.g. ``.json``).
+
+Both flags are forwarded from the driver to ``-cc1``.
+
+Example invocation:
+
+.. code-block:: bash
+
+  clang --ssaf-extract-summaries=MyAwesomeAnalysis \
+        --ssaf-tu-summary-file=/tmp/out.json \
+        -fsyntax-only input.cpp
+
+Activation
+==========
+
+When ``--ssaf-tu-summary-file=`` is non-empty, ``CreateFrontendAction()`` (in 
``ExecuteCompilerInvocation.cpp``)
+wraps the original ``FrontendAction`` inside a 
``TUSummaryExtractorFrontendAction``.
+This is a ``WrapperFrontendAction``, so the wrapped action (e.g. 
``-fsyntax-only``) still runs normally.
+It could have been also the codegen action (``-c``).
+
+Lifetime of a summary extraction
+*********************************
+
+The ``TUSummaryExtractorFrontendAction`` just wraps the original frontend 
action and triggers the extraction.
+If the ``TUSummaryExtractorFrontendAction`` fails to fulfill - in other words, 
create the extractors or fails to serialize and write the extracted summaries, 
then it issues a fatal error.
+
+Details of ``CreateASTConsumer()``
+==================================
+
+#. Create the wrapped ``FrontendAction`` consumers by calling 
``CreateASTConsumer()`` on it.
+#. Create a ``TUSummaryRunner`` (via ``TUSummaryRunner::create()``). This step 
validates the command-line arguments:
+
+   #. Parse the output file path and format extension from 
``--ssaf-tu-summary-file=``. Diagnose if the value doesn't follow the 
``<path>.<format>`` pattern, or if the format is not registered.
+   #. For each requested extractor name (from ``--ssaf-extract-summaries=``), 
check if it is registered in the ``TUSummaryExtractorRegistry``, and diagnose 
if not.
+   #. Instantiate the ``SerializationFormat`` for the requested format.
+
+#. If validation fails, the ``TUSummaryRunner`` is not created, and only the 
wrapped consumer is returned (the compilation proceeds without summary 
extraction, but the error diagnostics are emitted).
+#. Otherwise, both the wrapped consumer and the ``TUSummaryRunner`` are added 
to a ``MultiplexConsumer`` and returned. This ensures the wrapped action's 
consumer runs first.
+
+Details of ``TUSummaryRunner``
+==============================
+
+``TUSummaryRunner`` derives from ``MultiplexConsumer`` so every 
``ASTConsumer`` callback is automatically forwarded to each extractor.
+
+Construction:
+
+#. Create the ``TUSummary`` and ``TUSummaryBuilder``.
+#. Call ``makeTUSummaryExtractor()`` for each requested analysis name.
+
+   #. Look up in the *summary registry* the relevant *Info* object and call 
the ``Factory`` function pointer to create the relevant ``ASTConsumer``.
+   #. A mutable ``TUSummaryBuilder`` reference is passed to the constructor, 
so the analysis can create ``EntityID`` objects and map them to 
``TUSummaryData`` objects in their implementation. Their custom metadata needs 
to inherit from ``TUSummaryData`` to achieve this.
+
+``HandleTranslationUnit()``:
+
+#. First, invoke the summary extractors (via 
``MultiplexConsumer::HandleTranslationUnit``).
+#. Then call the virtual ``writeTUSummary()`` on the serialization format, 
leading to the desired format handler (such as JSON or binary or something 
custom - provided by a plugin).
+
+   #. Create the directory structure for the enabled analyses.
+   #. Serialize ``entities``, ``entity_linkage``, etc. by calling the matching 
virtual functions, dispatching to the concrete implementation.
+   #. The same goes for each enabled analysis: serialize the ``EntityID`` to 
``TUSummaryData`` mapping using the analysis-provided ``Serialize`` function 
pointer.
+
+Implementation details
+**********************
+
+Global Registries
+=================
+
+The framework uses `llvm::Registry\<\> 
<https://llvm.org/doxygen/classllvm_1_1Registry.html>`_
+as an extension point for adding new summary analyses or serialization formats.
+Each entry in the *registry* holds a name, a description and a pointer to a 
constructor.
+
+**Pros**:
+
+  - Decentralizes the registration. There is not a single place in the source 
code where we spell out all of the analyses/formats.
+  - Plays nicely with downstream extensibility, as downstream users can add 
their own analyses/formats without touching the source code of the framework; 
while still benefiting from the upstream-provided analyses/formats.
+  - Works with static and dynamic linking. In other words, plugins as shared 
objects compose naturally.
+
+**Cons**:
+
+  - Registration slows down all ``clang`` users by a tiny amount, even if they 
don't invoke the summary extraction framework.
+  - As the registration is now decoupled, it's now a global program property; 
and potentially more difficult to reason about.
+  - Complicates testing.
+
+Force-linker headers
+--------------------
+
+Because ``llvm::Registry`` entries live in separate translation units, the 
static linker may discard the
+object files containing them if nothing else references those files.
+To prevent this, each registration file defines a ``volatile int`` anchor 
symbol (e.g. ``SSAFJSONFormatAnchorSource``),
+and a corresponding *force-linker header* references it:
+
+- ``clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h`` — for 
upstream (built-in) registrations.
+- ``clang/include/clang/Analysis/Scalable/SSAFForceLinker.h`` — umbrella 
header that includes the above (and is meant for downstream extension).
+
+Include the appropriate force-linker header (with ``// IWYU pragma: keep``) in 
any translation unit that
+must guarantee these registrations are active — typically the entry point of a 
binary that uses
+``clangAnalysisScalable``.
+
+Downstream users should have their own ``ForceLinker`` header and include it 
from the
+``clang/include/clang/Analysis/Scalable/SSAFForceLinker.h`` umbrella header to 
avoid merge-conflicts.
+
+Example for adding a custom summary extraction
+-----------------------------------------------
+
+.. code-block:: c++
+
+  //--- MyAnalysis.cpp
+  class MyAnalysis : public TUSummaryExtractor {
+    using TUSummaryExtractor::TUSummaryExtractor;
+    // Implementation...
+  };
+
+  // NOLINTNEXTLINE(misc-use-internal-linkage)
+  volatile int SSAFMyAnalysisAnchorSource = 0;
+  static TUSummaryExtractorRegistry::Add<MyAnalysis>
+    RegisterExtractor("MyAwesomeAnalysis", "The analysis produces some awesome 
results");
+
+Then add the anchor reference to the force-linker header 
(``SSAFBuiltinForceLinker.h``):
+
+.. code-block:: c++
+
+  extern volatile int SSAFMyAnalysisAnchorSource;
+  [[maybe_unused]] static int SSAFMyAnalysisAnchorDestination =
+      SSAFMyAnalysisAnchorSource;
diff --git 
a/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h
 
b/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h
index 8a8c50b0a5695..a91955d2a2139 100644
--- 
a/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h
+++ 
b/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h
@@ -17,7 +17,7 @@ namespace clang::ssaf {
 /// Wraps the existing \c FrontendAction and injects the extractor
 /// \c ASTConsumers into the pipeline after the ASTConsumers of the wrapped
 /// action.
-class TUSummaryExtractorFrontendAction : public WrapperFrontendAction {
+class TUSummaryExtractorFrontendAction final : public WrapperFrontendAction {
 public:
   explicit TUSummaryExtractorFrontendAction(
       std::unique_ptr<FrontendAction> WrappedAction);
diff --git 
a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h
 
b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h
index 54c449fc0c11d..f0f05682bd396 100644
--- 
a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h
+++ 
b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h
@@ -24,11 +24,11 @@
 //
 // Insert this code to the cpp file:
 //
-//   LLVM_INSTANTIATE_REGISTRY(llvm::Registry<MyFormat::FormatInfo>)
-//
+//   // NOLINTNEXTLINE(misc-use-internal-linkage)
+//   volatile int SSAFMyFormatAnchorSource = 0;
 //   static SerializationFormatRegistry::Add<MyFormat>
 //     RegisterFormat("MyFormat", "My awesome serialization format");
-// TODO: Mention anchors and force linker .h
+//   LLVM_INSTANTIATE_REGISTRY(llvm::Registry<MyFormat::FormatInfo>)
 //
 // Then implement the formatter for the specific analysis and register the
 // format info for it:
@@ -50,6 +50,15 @@
 //         "The MyFormat format info implementation for MyAnalysis"
 //       );
 //
+// Finally, insert a use of the new anchor symbol into the force-linker header:
+// clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h:
+//
+// This anchor is used to force the linker to link the JSONFormat registration.
+//
+//   extern volatile int SSAFMyFormatAnchorSource;
+//   [[maybe_unused]] static int SSAFMyFormatAnchorDestination =
+//       SSAFMyFormatAnchorSource;
+//
 
//===----------------------------------------------------------------------===//
 
 #ifndef CLANG_ANALYSIS_SCALABLE_SERIALIZATION_SERIALIZATION_FORMAT_REGISTRY_H
diff --git 
a/clang/include/clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h 
b/clang/include/clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h
index 29f5925ed6af6..d9147871a5101 100644
--- a/clang/include/clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h
+++ b/clang/include/clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h
@@ -9,9 +9,18 @@
 // Registry for TUSummaryExtractors, and some helper functions.
 // To register some custom extractor, insert this code:
 //
+//   // NOLINTNEXTLINE(misc-use-internal-linkage)
+//   volatile int SSAFMyExtractorAnchorSource = 0;
 //   static TUSummaryExtractorRegistry::Add<MyExtractor>
 //     X("MyExtractor", "My awesome extractor");
 //
+// Finally, insert a use of the new anchor symbol into the force-linker header:
+// clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h:
+//
+//   extern volatile int SSAFMyExtractorAnchorSource;
+//   [[maybe_unused]] static int SSAFMyExtractorAnchorDestination =
+//       SSAFMyExtractorAnchorSource;
+//
 
//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_EXTRACTORREGISTRY_H
diff --git a/clang/include/clang/Frontend/FrontendOptions.h 
b/clang/include/clang/Frontend/FrontendOptions.h
index 486d0c38a1412..0d8eb6a1b7379 100644
--- a/clang/include/clang/Frontend/FrontendOptions.h
+++ b/clang/include/clang/Frontend/FrontendOptions.h
@@ -548,7 +548,7 @@ class FrontendOptions {
 
   /// The TU summary output file with the file extension representing the file
   /// format.
-  std::string SSAFTUSUmmaryFile;
+  std::string SSAFTUSummaryFile;
 
 public:
   FrontendOptions()
diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 6c3eba9daa77e..270e29102ec63 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -960,7 +960,7 @@ def _ssaf_tu_summary_file :
   HelpText<
     "The output file for the extracted summaries. "
     "The extension selects what file format to be used.">,
-  MarshallingInfoString<FrontendOpts<"SSAFTUSUmmaryFile">>;
+  MarshallingInfoString<FrontendOpts<"SSAFTUSummaryFile">>;
 def Xarch__
     : JoinedAndSeparate<["-"], "Xarch_">,
       Flags<[NoXarchOption]>,
diff --git 
a/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp 
b/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp
index 8ea4ac3dfad49..34dd4ab709136 100644
--- a/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp
+++ b/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp
@@ -27,20 +27,20 @@ using namespace ssaf;
 
 static std::optional<std::pair<llvm::StringRef, llvm::StringRef>>
 parseOutputFileFormatAndPathOrReportError(DiagnosticsEngine &Diags,
-                                          StringRef SSAFTUSUmmaryFile) {
+                                          StringRef SSAFTUSummaryFile) {
 
-  StringRef Ext = llvm::sys::path::extension(SSAFTUSUmmaryFile);
-  StringRef FilePath = SSAFTUSUmmaryFile.drop_back(Ext.size());
+  StringRef Ext = llvm::sys::path::extension(SSAFTUSummaryFile);
+  StringRef FilePath = SSAFTUSummaryFile.drop_back(Ext.size());
 
   if (!Ext.consume_front(".") || FilePath.empty()) {
     Diags.Report(diag::err__ssaf_extract_tu_summary_file_unknown_format)
-        << SSAFTUSUmmaryFile;
+        << SSAFTUSummaryFile;
     return std::nullopt;
   }
 
   if (!isFormatRegistered(Ext)) {
     Diags.Report(diag::err__ssaf_extract_tu_summary_file_unknown_output_format)
-        << Ext << SSAFTUSUmmaryFile;
+        << Ext << SSAFTUSummaryFile;
     return std::nullopt;
   }
 
@@ -114,7 +114,7 @@ std::unique_ptr<TUSummaryRunner> 
TUSummaryRunner::create(CompilerInstance &CI,
   DiagnosticsEngine &Diags = CI.getDiagnostics();
 
   auto MaybePair =
-      parseOutputFileFormatAndPathOrReportError(Diags, Opts.SSAFTUSUmmaryFile);
+      parseOutputFileFormatAndPathOrReportError(Diags, Opts.SSAFTUSummaryFile);
   if (!MaybePair.has_value())
     return nullptr;
   auto [FormatName, OutputPath] = MaybePair.value();
@@ -151,9 +151,9 @@ void TUSummaryRunner::HandleTranslationUnit(ASTContext 
&Ctx) {
   MultiplexConsumer::HandleTranslationUnit(Ctx);
 
   // Then serialize the result.
-  if (auto Err = Format->writeTUSummary(Summary, Opts.SSAFTUSUmmaryFile)) {
+  if (auto Err = Format->writeTUSummary(Summary, Opts.SSAFTUSummaryFile)) {
     Ctx.getDiagnostics().Report(diag::err__ssaf_write_tu_summary_failed)
-        << Opts.SSAFTUSUmmaryFile << llvm::toString(std::move(Err));
+        << Opts.SSAFTUSummaryFile << llvm::toString(std::move(Err));
   }
 }
 
diff --git 
a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp 
b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
index dc70667834ea7..d62a26b921b74 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -9,17 +9,13 @@
 #include "JSONFormatImpl.h"
 
 #include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h"
-#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
 #include "llvm/Support/Registry.h"
 
-LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::ssaf::JSONFormat::FormatInfo>)
-static clang::ssaf::SerializationFormatRegistry::Add<clang::ssaf::JSONFormat>
-    RegisterFormat("json", "JSON serialization format");
-
-// This anchor is used to force the linker to link in the generated object file
-// and thus register JSONFormat in the SerializationFormatRegistry.
 // NOLINTNEXTLINE(misc-use-internal-linkage)
 volatile int SSAFJSONFormatAnchorSource = 0;
+static clang::ssaf::SerializationFormatRegistry::Add<clang::ssaf::JSONFormat>
+    RegisterFormat("json", "JSON serialization format");
+LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::ssaf::JSONFormat::FormatInfo>)
 
 namespace clang::ssaf {
 
diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp 
b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
index ff7cbc6cf5289..5f3b7f101375e 100644
--- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
+++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
@@ -209,7 +209,7 @@ CreateFrontendAction(CompilerInstance &CI) {
     Act = std::make_unique<ASTMergeAction>(std::move(Act),
                                             FEOpts.ASTMergeFiles);
 
-  if (!FEOpts.SSAFTUSUmmaryFile.empty()) {
+  if (!FEOpts.SSAFTUSummaryFile.empty()) {
     Act = std::make_unique<ssaf::TUSummaryExtractorFrontendAction>(
         std::move(Act));
   }
diff --git 
a/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp
 
b/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp
index 5d64b9403706d..573d0dbe15474 100644
--- 
a/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp
+++ 
b/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp
@@ -7,15 +7,356 @@
 
//===----------------------------------------------------------------------===//
 
 #include "clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h"
+#include "clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummaryExtractor.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendOptions.h"
+#include "clang/Frontend/TextDiagnosticBuffer.h"
+#include "clang/Lex/PreprocessorOptions.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/VirtualFileSystem.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include <memory>
+#include <string>
+#include <vector>
 
 using namespace clang;
 using namespace ssaf;
+using ::testing::Contains;
+using ::testing::UnorderedElementsAre;
+
+static auto errorsMsgsOf(const TextDiagnosticBuffer &Diags) {
+  auto Errors = llvm::make_range(Diags.err_begin(), Diags.err_end());
+  return llvm::make_second_range(Errors);
+}
+namespace {
+
+/// A no-op TUSummaryExtractor suitable for use with a real TUSummaryBuilder.
+class NoOpExtractor : public TUSummaryExtractor {
+public:
+  using TUSummaryExtractor::TUSummaryExtractor;
+  void HandleTranslationUnit(ASTContext &Ctx) override {}
+};
+} // namespace
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFNoOpExtractorAnchorSource = 0;
+static TUSummaryExtractorRegistry::Add<NoOpExtractor>
+    RegisterNoOp("NoOpExtractor", "No-op extractor for frontend action tests");
+
+namespace {
+class FailingSerializationFormat final : public SerializationFormat {
+public:
+  static llvm::Error failing(llvm::StringRef Component) {
+    return llvm::createStringError(
+        "error from always failing serialization format: " + Component);
+  }
+
+  llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override {
+    return failing("readTUSummary");
+  }
+
+  llvm::Error writeTUSummary(const TUSummary &Summary,
+                             llvm::StringRef Path) override {
+    return failing("writeTUSummary");
+  }
+
+  llvm::Expected<TUSummaryEncoding>
+  readTUSummaryEncoding(llvm::StringRef Path) override {
+    return failing("readTUSummaryEncoding");
+  }
+
+  llvm::Error writeTUSummaryEncoding(const TUSummaryEncoding &SummaryEncoding,
+                                     llvm::StringRef Path) override {
+    return failing("writeTUSummaryEncoding");
+  }
+
+  llvm::Expected<LUSummary> readLUSummary(llvm::StringRef Path) override {
+    return failing("readLUSummary");
+  }
+
+  llvm::Error writeLUSummary(const LUSummary &Summary,
+                             llvm::StringRef Path) override {
+    return failing("writeLUSummary");
+  }
+
+  llvm::Expected<LUSummaryEncoding>
+  readLUSummaryEncoding(llvm::StringRef Path) override {
+    return failing("readLUSummaryEncoding");
+  }
+
+  llvm::Error writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
+                                     llvm::StringRef Path) override {
+    return failing("writeLUSummaryEncoding");
+  }
+};
+} // namespace
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFFailingSerializationFormatAnchorSource = 0;
+static SerializationFormatRegistry::Add<FailingSerializationFormat>
+    RegisterFormat(
+        "FailingSerializationFormat",
+        "A serialization format that fails on every possible operation.");
+
+using EventLog = std::vector<std::string>;
 
 namespace {
 
-TEST(TUSummaryExtractorFrontendActionTest, SomeTest) {
-  // TODO: Add tests.
+/// An ASTConsumer that logs callback invocations into a shared log.
+class RecordingASTConsumer : public ASTConsumer {
+public:
+  RecordingASTConsumer(EventLog &Log, std::string Tag)
+      : Log(Log), Tag(std::move(Tag)) {}
+
+  void Initialize(ASTContext &Ctx) override {
+    Log.push_back(Tag + "::Initialize");
+  }
+  bool HandleTopLevelDecl(DeclGroupRef D) override {
+    Log.push_back(Tag + "::HandleTopLevelDecl");
+    return true;
+  }
+  void HandleTranslationUnit(ASTContext &Ctx) override {
+    Log.push_back(Tag + "::HandleTranslationUnit");
+  }
+
+private:
+  EventLog &Log;
+  std::string Tag;
+};
+
+/// A FrontendAction that returns a RecordingASTConsumer with the tag 
"Wrapped".
+class RecordingAction : public ASTFrontendAction {
+public:
+  EventLog &getLog() { return Log; }
+  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &,
+                                                 StringRef) override {
+    return std::make_unique<RecordingASTConsumer>(Log, /*Tag=*/"Wrapped");
+  }
+
+private:
+  EventLog Log;
+};
+
+class FailingAction : public ASTFrontendAction {
+public:
+  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &,
+                                                 StringRef) override {
+    return nullptr;
+  }
+};
+
+/// Creates a CompilerInstance configured with an in-memory "test.cc" file
+/// containing "int x = 42;".
+static std::unique_ptr<CompilerInstance>
+makeCompiler(TextDiagnosticBuffer &DiagBuf) {
+  auto Invocation = std::make_shared<CompilerInvocation>();
+  Invocation->getPreprocessorOpts().addRemappedFile(
+      "test.cc", llvm::MemoryBuffer::getMemBuffer("int x = 42;").release());
+  Invocation->getFrontendOpts().Inputs.push_back(
+      FrontendInputFile("test.cc", Language::CXX));
+  Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
+  Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
+  auto Compiler = std::make_unique<CompilerInstance>(std::move(Invocation));
+  Compiler->setVirtualFileSystem(llvm::vfs::getRealFileSystem());
+  Compiler->createDiagnostics(&DiagBuf, /*ShouldOwnClient=*/false);
+  return Compiler;
+}
+
+struct TUSummaryExtractorFrontendActionTest : testing::Test {
+  using PathString = llvm::SmallString<128>;
+  PathString TestDir;
+  TextDiagnosticBuffer DiagBuf;
+  std::unique_ptr<CompilerInstance> Compiler = makeCompiler(DiagBuf);
+
+  void SetUp() override {
+    std::error_code EC = llvm::sys::fs::createUniqueDirectory(
+        "ssaf-frontend-action-test", TestDir);
+    ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message();
+  }
+
+  void TearDown() override { llvm::sys::fs::remove_directories(TestDir); }
+
+  std::string makePath(llvm::StringRef FileOrDirectoryName) const {
+    PathString FullPath = TestDir;
+    llvm::sys::path::append(FullPath, FileOrDirectoryName);
+    return FullPath.str().str();
+  }
+};
+
+TEST_F(TUSummaryExtractorFrontendActionTest,
+       WrappedActionFailsToCreateConsumer) {
+  // Configure valid SSAF options so the failure is purely from the wrapped
+  // action, not from runner creation.
+  std::string Output = makePath("output.MockSerializationFormat");
+  Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
+  Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
+
+  TUSummaryExtractorFrontendAction ExtractorAction(
+      std::make_unique<FailingAction>());
+  Compiler->ExecuteAction(ExtractorAction);
+
+  // If the wrapped action fails, the ExtractorAction should not output.
+  EXPECT_FALSE(llvm::sys::fs::exists(Output));
+}
+
+TEST_F(TUSummaryExtractorFrontendActionTest,
+       RunnerFailsWithInvalidFormat_WrappedConsumerStillRuns) {
+  // Use an unregistered format extension so TUSummaryRunner::create fails.
+  std::string Output = makePath("output.xyz");
+  Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
+  Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
+
+  auto Wrapped = std::make_unique<RecordingAction>();
+  const EventLog &Log = Wrapped->getLog();
+  TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped));
+
+  // The runner fails, so ExecuteAction should return false due to the fatal
+  // diagnostic.
+  EXPECT_FALSE(Compiler->ExecuteAction(ExtractorAction));
+
+  // The wrapped consumer should still have run.
+  EXPECT_THAT(Log, Contains("Wrapped::Initialize"));
+  EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit"));
+
+  // Exactly one error about the unknown format.
+  EXPECT_THAT(errorsMsgsOf(DiagBuf),
+              UnorderedElementsAre(
+                  "unknown output summary file format 'xyz' specified by "
+                  "'--ssaf-tu-summary-file=" +
+                  Output + "'"));
+
+  // No output should have been created due to the failure.
+  EXPECT_FALSE(llvm::sys::fs::exists(Output));
+}
+
+TEST_F(TUSummaryExtractorFrontendActionTest,
+       RunnerFailsWithUnknownExtractor_WrappedConsumerStillRuns) {
+  std::string Output = makePath("output.MockSerializationFormat");
+  Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
+  Compiler->getFrontendOpts().SSAFExtractSummaries = {"NonExistentExtractor"};
+
+  auto Wrapped = std::make_unique<RecordingAction>();
+  const EventLog &Log = Wrapped->getLog();
+  TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped));
+  EXPECT_FALSE(Compiler->ExecuteAction(ExtractorAction));
+
+  // The wrapped consumer should still have run.
+  EXPECT_THAT(Log, Contains("Wrapped::Initialize"));
+  EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit"));
+
+  // Exactly one error about the unknown extractor.
+  EXPECT_THAT(errorsMsgsOf(DiagBuf),
+              UnorderedElementsAre("no summary extractor was registered with "
+                                   "name: NonExistentExtractor"));
+
+  // No output should have been created due to the failure.
+  EXPECT_FALSE(llvm::sys::fs::exists(Output));
+}
+
+TEST_F(TUSummaryExtractorFrontendActionTest,
+       RunnerSucceeds_ASTConsumerCallbacksPropagate) {
+  std::string Output = makePath("output.MockSerializationFormat");
+  Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
+  Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
+
+  auto Wrapped = std::make_unique<RecordingAction>();
+  const EventLog &Log = Wrapped->getLog();
+  TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped));
+  EXPECT_TRUE(Compiler->ExecuteAction(ExtractorAction));
+
+  // The wrapped all ASTConsumer callbacks should have fired, not just
+  // HandleTranslationUnit.
+  EXPECT_THAT(Log, Contains("Wrapped::Initialize"));
+  EXPECT_THAT(Log, Contains("Wrapped::HandleTopLevelDecl"));
+  EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit"));
+  EXPECT_EQ(DiagBuf.getNumErrors(), 0U);
+
+  // The runner should have written output.
+  EXPECT_TRUE(llvm::sys::fs::exists(Output));
+}
+
+// Use a custom action that checks whether the output path exists during
+// HandleTranslationUnit — it should not, because the wrapped consumer runs
+// before the runner.
+struct OrderCheckingAction : public ASTFrontendAction {
+  EventLog Log;
+  std::string OutputPath;
+
+  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
+                                                 StringRef InFile) override {
+    struct Consumer : public ASTConsumer {
+      Consumer(EventLog &Log, std::string OutputPath)
+          : Log(Log), OutputPath(std::move(OutputPath)) {}
+      void Initialize(ASTContext &) override {
+        Log.push_back("Wrapped::Initialize");
+      }
+      bool HandleTopLevelDecl(DeclGroupRef) override {
+        Log.push_back("Wrapped::HandleTopLevelDecl");
+        return true;
+      }
+      void HandleTranslationUnit(ASTContext &) override {
+        bool Exists = llvm::sys::fs::exists(OutputPath);
+        Log.push_back(std::string("OutputExistsDuringWrappedHTU=") +
+                      (Exists ? "true" : "false"));
+        Log.push_back("Wrapped::HandleTranslationUnit");
+      }
+
+      EventLog &Log;
+      std::string OutputPath;
+    };
+    return std::make_unique<Consumer>(Log, OutputPath);
+  }
+};
+TEST_F(TUSummaryExtractorFrontendActionTest,
+       RunnerSucceeds_WrappedRunsBeforeRunner) {
+  std::string Output = makePath("output.MockSerializationFormat");
+  Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
+  Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
+
+  auto Wrapped = std::make_unique<OrderCheckingAction>();
+  Wrapped->OutputPath = Output;
+  const EventLog &Log = Wrapped->Log;
+  TUSummaryExtractorFrontendAction Action(std::move(Wrapped));
+
+  EXPECT_TRUE(Compiler->ExecuteAction(Action));
+  EXPECT_EQ(DiagBuf.getNumErrors(), 0U);
+
+  // The output should NOT have existed when the wrapped consumer's
+  // HandleTranslationUnit ran (wrapped is at index 0, runner at index 1).
+  EXPECT_THAT(Log, Contains("OutputExistsDuringWrappedHTU=false"));
+
+  // After ExecuteAction, the output should exist.
+  EXPECT_TRUE(llvm::sys::fs::exists(Output));
+}
+
+TEST_F(TUSummaryExtractorFrontendActionTest, RunnerFailsToWrite) {
+  std::string Output = makePath("output.FailingSerializationFormat");
+  Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
+  Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
+
+  TUSummaryExtractorFrontendAction Action(std::make_unique<RecordingAction>());
+
+  // This should fail because the summary writing fails and emits an error
+  // diagnostic.
+  EXPECT_FALSE(Compiler->ExecuteAction(Action));
+  EXPECT_THAT(
+      errorsMsgsOf(DiagBuf),
+      UnorderedElementsAre(
+          "failed to write TU summary to '" + Output +
+          "': error from always failing serialization format: 
writeTUSummary"));
+
+  // No output should have been created due to the failure.
+  EXPECT_FALSE(llvm::sys::fs::exists(Output));
 }
 
 } // namespace
diff --git a/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp 
b/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp
index 6feb939760923..20c6856fc761d 100644
--- a/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp
@@ -54,12 +54,9 @@ struct FancyAnalysisFormatInfo final : FormatInfo {
 };
 } // namespace
 
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFFancyAnalysisDataAnchorSource = 0;
 static llvm::Registry<FormatInfo>::Add<FancyAnalysisFormatInfo>
     RegisterFormatInfo("FancyAnalysisData",
                        "Format info for FancyAnalysisData for the "
                        "MockSerializationFormat format");
-
-// This anchor is used to force the linker to link in the generated object file
-// and thus register FancyAnalysisFormatInfo.
-// NOLINTNEXTLINE(misc-use-internal-linkage)
-volatile int SSAFFancyAnalysisDataAnchorSource = 0;
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp 
b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
index 0bcdba5640e3c..84af685c7ff80 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
@@ -154,13 +154,11 @@ llvm::Error MockSerializationFormat::writeTUSummary(const 
TUSummary &Summary,
   return llvm::Error::success();
 }
 
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFMockSerializationFormatAnchorSource = 0;
 static SerializationFormatRegistry::Add<MockSerializationFormat>
     RegisterFormat("MockSerializationFormat",
                    "A serialization format for testing");
-// This anchor is used to force the linker to link in the generated object file
-// and thus register JSONFormat in the SerializationFormatRegistry.
-// NOLINTNEXTLINE(misc-use-internal-linkage)
-volatile int SSAFMockSerializationFormatAnchorSource = 0;
 
 llvm::Expected<TUSummaryEncoding>
 MockSerializationFormat::readTUSummaryEncoding(llvm::StringRef Path) {
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp 
b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp
index 2153f49c20dfa..785c2dd438a15 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp
@@ -38,12 +38,9 @@ class MockSummaryExtractor1 : public TUSummaryExtractor {
   }
 };
 
-static TUSummaryExtractorRegistry::Add<MockSummaryExtractor1>
-    RegisterExtractor("MockSummaryExtractor1", "Mock summary extractor 1");
-
 } // namespace
 
-// This anchor is used to force the linker to link in the generated object file
-// and thus register MockSummaryExtractor1.
 // NOLINTNEXTLINE(misc-use-internal-linkage)
 volatile int SSAFMockSummaryExtractor1AnchorSource = 0;
+static TUSummaryExtractorRegistry::Add<MockSummaryExtractor1>
+    RegisterExtractor("MockSummaryExtractor1", "Mock summary extractor 1");
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp 
b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp
index 61270c953ddd9..2439cccdc39ba 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp
@@ -38,12 +38,9 @@ class MockSummaryExtractor2 : public TUSummaryExtractor {
   }
 };
 
-static TUSummaryExtractorRegistry::Add<MockSummaryExtractor2>
-    RegisterExtractor("MockSummaryExtractor2", "Mock summary extractor 2");
-
 } // namespace
 
-// This anchor is used to force the linker to link in the generated object file
-// and thus register MockSummaryExtractor2.
 // NOLINTNEXTLINE(misc-use-internal-linkage)
 volatile int SSAFMockSummaryExtractor2AnchorSource = 0;
+static TUSummaryExtractorRegistry::Add<MockSummaryExtractor2>
+    RegisterExtractor("MockSummaryExtractor2", "Mock summary extractor 2");
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
 
b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
index 791a1fe0501e5..f8b104ce35840 100644
--- 
a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
+++ 
b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
@@ -58,7 +58,8 @@ TEST(SerializationFormatRegistryTest, 
EnumeratingRegistryEntries) {
   auto NamesOf = [](const auto &Entry) { return Entry.getName(); };
   auto Names = llvm::map_range(SerializationFormatRegistry::entries(), 
NamesOf);
   using testing::UnorderedElementsAre;
-  EXPECT_THAT(Names, UnorderedElementsAre("MockSerializationFormat", "json"));
+  EXPECT_THAT(Names, UnorderedElementsAre("MockSerializationFormat", "json",
+                                          "FailingSerializationFormat"));
 }
 
 TEST(SerializationFormatRegistryTest, Roundtrip) {
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp 
b/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp
index a17a4f145b038..ab51f2c3fc530 100644
--- 
a/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp
+++ 
b/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp
@@ -41,6 +41,7 @@ TEST(SummaryExtractorRegistryTest, 
EnumeratingRegistryEntries) {
   EXPECT_EQ(ActualNames, (std::set<llvm::StringRef>{
                              "MockSummaryExtractor1",
                              "MockSummaryExtractor2",
+                             "NoOpExtractor",
                          }));
 }
 
diff --git a/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h 
b/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h
index 5169eb504be48..81766a1196761 100644
--- a/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h
+++ b/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h
@@ -18,6 +18,11 @@
 #ifndef LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFBUILTINTESTFORCELINKER_H
 #define LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFBUILTINTESTFORCELINKER_H
 
+// Force the linker to link NoOpExtractor registration.
+extern volatile int SSAFNoOpExtractorAnchorSource;
+[[maybe_unused]] static int SSAFNoOpExtractorAnchorDestination =
+    SSAFNoOpExtractorAnchorSource;
+
 // Force the linker to link MockSummaryExtractor1 registration.
 extern volatile int SSAFMockSummaryExtractor1AnchorSource;
 [[maybe_unused]] static int SSAFMockSummaryExtractor1AnchorDestination =
@@ -28,6 +33,11 @@ extern volatile int SSAFMockSummaryExtractor2AnchorSource;
 [[maybe_unused]] static int SSAFMockSummaryExtractor2AnchorDestination =
     SSAFMockSummaryExtractor2AnchorSource;
 
+// Force the linker to link FailingSerializationFormat registration.
+extern volatile int SSAFFailingSerializationFormatAnchorSource;
+[[maybe_unused]] static int SSAFFailingSerializationFormatAnchorDestination =
+    SSAFFailingSerializationFormatAnchorSource;
+
 // Force the linker to link MockSerializationFormat registration.
 extern volatile int SSAFMockSerializationFormatAnchorSource;
 [[maybe_unused]] static int SSAFMockSerializationFormatAnchorDestination =

From b139a1abddd1c49a87cecb3b929ca60bf5b804d1 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Wed, 4 Mar 2026 17:58:09 +0000
Subject: [PATCH 3/3] Drop work-in-progress dev docs - it's not ready yet

---
 .../developer-docs/Extensibility.rst          | 146 ------------------
 1 file changed, 146 deletions(-)
 delete mode 100644 
clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst

diff --git 
a/clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst 
b/clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst
deleted file mode 100644
index 6c8ec7d73add4..0000000000000
--- 
a/clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst
+++ /dev/null
@@ -1,146 +0,0 @@
-=============
-Extensibility
-=============
-
-.. WARNING:: The framework is rapidly evolving.
-  The documentation might be out-of-sync of the implementation.
-  The purpose of this documentation to give context for upcoming reviews.
-
-This document describes how to extend the framework with new formats and 
extractors in different contexts.
-Downstream users can extend the framework - let it be in-tree or out-of-tree.
-
-There are two 
-
-Static extensibility
-********************
-
-When building SSAF, new formats and extractors are registered via 
``llvm::Registry`` translation unit local 
-
-
-Two flags control summary extraction:
-
-- ``--ssaf-extract-summaries=<name1>,<name2>,...``: Comma-separated list of 
summary extractor names to enable.
-- ``--ssaf-tu-summary-file=<path>.<format>``: Output file for the extracted 
summaries. The file extension selects the serialization format (e.g. ``.json``).
-
-Both flags are forwarded from the driver to ``-cc1``.
-
-Example invocation:
-
-.. code-block:: bash
-
-  clang --ssaf-extract-summaries=MyAwesomeAnalysis \
-        --ssaf-tu-summary-file=/tmp/out.json \
-        -fsyntax-only input.cpp
-
-Activation
-==========
-
-When ``--ssaf-tu-summary-file=`` is non-empty, ``CreateFrontendAction()`` (in 
``ExecuteCompilerInvocation.cpp``)
-wraps the original ``FrontendAction`` inside a 
``TUSummaryExtractorFrontendAction``.
-This is a ``WrapperFrontendAction``, so the wrapped action (e.g. 
``-fsyntax-only``) still runs normally.
-It could have been also the codegen action (``-c``).
-
-Lifetime of a summary extraction
-*********************************
-
-The ``TUSummaryExtractorFrontendAction`` just wraps the original frontend 
action and triggers the extraction.
-If the ``TUSummaryExtractorFrontendAction`` fails to fulfill - in other words, 
create the extractors or fails to serialize and write the extracted summaries, 
then it issues a fatal error.
-
-Details of ``CreateASTConsumer()``
-==================================
-
-#. Create the wrapped ``FrontendAction`` consumers by calling 
``CreateASTConsumer()`` on it.
-#. Create a ``TUSummaryRunner`` (via ``TUSummaryRunner::create()``). This step 
validates the command-line arguments:
-
-   #. Parse the output file path and format extension from 
``--ssaf-tu-summary-file=``. Diagnose if the value doesn't follow the 
``<path>.<format>`` pattern, or if the format is not registered.
-   #. For each requested extractor name (from ``--ssaf-extract-summaries=``), 
check if it is registered in the ``TUSummaryExtractorRegistry``, and diagnose 
if not.
-   #. Instantiate the ``SerializationFormat`` for the requested format.
-
-#. If validation fails, the ``TUSummaryRunner`` is not created, and only the 
wrapped consumer is returned (the compilation proceeds without summary 
extraction, but the error diagnostics are emitted).
-#. Otherwise, both the wrapped consumer and the ``TUSummaryRunner`` are added 
to a ``MultiplexConsumer`` and returned. This ensures the wrapped action's 
consumer runs first.
-
-Details of ``TUSummaryRunner``
-==============================
-
-``TUSummaryRunner`` derives from ``MultiplexConsumer`` so every 
``ASTConsumer`` callback is automatically forwarded to each extractor.
-
-Construction:
-
-#. Create the ``TUSummary`` and ``TUSummaryBuilder``.
-#. Call ``makeTUSummaryExtractor()`` for each requested analysis name.
-
-   #. Look up in the *summary registry* the relevant *Info* object and call 
the ``Factory`` function pointer to create the relevant ``ASTConsumer``.
-   #. A mutable ``TUSummaryBuilder`` reference is passed to the constructor, 
so the analysis can create ``EntityID`` objects and map them to 
``TUSummaryData`` objects in their implementation. Their custom metadata needs 
to inherit from ``TUSummaryData`` to achieve this.
-
-``HandleTranslationUnit()``:
-
-#. First, invoke the summary extractors (via 
``MultiplexConsumer::HandleTranslationUnit``).
-#. Then call the virtual ``writeTUSummary()`` on the serialization format, 
leading to the desired format handler (such as JSON or binary or something 
custom - provided by a plugin).
-
-   #. Create the directory structure for the enabled analyses.
-   #. Serialize ``entities``, ``entity_linkage``, etc. by calling the matching 
virtual functions, dispatching to the concrete implementation.
-   #. The same goes for each enabled analysis: serialize the ``EntityID`` to 
``TUSummaryData`` mapping using the analysis-provided ``Serialize`` function 
pointer.
-
-Implementation details
-**********************
-
-Global Registries
-=================
-
-The framework uses `llvm::Registry\<\> 
<https://llvm.org/doxygen/classllvm_1_1Registry.html>`_
-as an extension point for adding new summary analyses or serialization formats.
-Each entry in the *registry* holds a name, a description and a pointer to a 
constructor.
-
-**Pros**:
-
-  - Decentralizes the registration. There is not a single place in the source 
code where we spell out all of the analyses/formats.
-  - Plays nicely with downstream extensibility, as downstream users can add 
their own analyses/formats without touching the source code of the framework; 
while still benefiting from the upstream-provided analyses/formats.
-  - Works with static and dynamic linking. In other words, plugins as shared 
objects compose naturally.
-
-**Cons**:
-
-  - Registration slows down all ``clang`` users by a tiny amount, even if they 
don't invoke the summary extraction framework.
-  - As the registration is now decoupled, it's now a global program property; 
and potentially more difficult to reason about.
-  - Complicates testing.
-
-Force-linker headers
---------------------
-
-Because ``llvm::Registry`` entries live in separate translation units, the 
static linker may discard the
-object files containing them if nothing else references those files.
-To prevent this, each registration file defines a ``volatile int`` anchor 
symbol (e.g. ``SSAFJSONFormatAnchorSource``),
-and a corresponding *force-linker header* references it:
-
-- ``clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h`` — for 
upstream (built-in) registrations.
-- ``clang/include/clang/Analysis/Scalable/SSAFForceLinker.h`` — umbrella 
header that includes the above (and is meant for downstream extension).
-
-Include the appropriate force-linker header (with ``// IWYU pragma: keep``) in 
any translation unit that
-must guarantee these registrations are active — typically the entry point of a 
binary that uses
-``clangAnalysisScalable``.
-
-Downstream users should have their own ``ForceLinker`` header and include it 
from the
-``clang/include/clang/Analysis/Scalable/SSAFForceLinker.h`` umbrella header to 
avoid merge-conflicts.
-
-Example for adding a custom summary extraction
------------------------------------------------
-
-.. code-block:: c++
-
-  //--- MyAnalysis.cpp
-  class MyAnalysis : public TUSummaryExtractor {
-    using TUSummaryExtractor::TUSummaryExtractor;
-    // Implementation...
-  };
-
-  // NOLINTNEXTLINE(misc-use-internal-linkage)
-  volatile int SSAFMyAnalysisAnchorSource = 0;
-  static TUSummaryExtractorRegistry::Add<MyAnalysis>
-    RegisterExtractor("MyAwesomeAnalysis", "The analysis produces some awesome 
results");
-
-Then add the anchor reference to the force-linker header 
(``SSAFBuiltinForceLinker.h``):
-
-.. code-block:: c++
-
-  extern volatile int SSAFMyAnalysisAnchorSource;
-  [[maybe_unused]] static int SSAFMyAnalysisAnchorDestination =
-      SSAFMyAnalysisAnchorSource;

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to