[llvm-branch-commits] [clang] [Draft][SSAF] Add SourcePassAnalysis framework (PR #195205)

Ziqing Luo via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Apr 30 18:17:05 PDT 2026


https://github.com/ziqingluo-90 created https://github.com/llvm/llvm-project/pull/195205

SourcePassAnalysis is for analyses/actions to be performed in a second pass on source code, after the SSAF whole-program analysis.

SourcePassAnalysis is defined as an ASTConsumer abstraction that depends on a whole-program analysis result.

This commit adds:
- SourcePassAnalysis base classes
- SourcePassAnalysis registry
- unit test for registry

rdar://175802731

>From c006fcee302744d22f471d7abe6e00ced3a1013a Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Tue, 28 Apr 2026 18:03:13 -0700
Subject: [PATCH] [SSAF] Add SourcePassAnalysis framework

SourcePassAnalysis is for analyses/actions to be performed in a second
pass on source code, after the SSAF whole-program analysis.

SourcePassAnalysis is defined as an ASTConsumer abstraction that
depends on a whole-program analysis result.

This commit adds:
- SourcePassAnalysis base classes
- SourcePassAnalysis registry
- unit test for registry

rdar://175802731
---
 .../SourcePassAnalysis/SourcePassAnalysis.h   |  63 +++++++++++
 .../SourcePassAnalysisRegistry.h              | 105 ++++++++++++++++++
 .../SSAFBuiltinForceLinker.h                  |   6 +
 .../Core/CMakeLists.txt                       |   1 +
 .../SourcePassAnalysisRegistry.cpp            |  46 ++++++++
 .../CMakeLists.txt                            |   1 +
 .../SourcePassAnalysisRegistryTest.cpp        |  82 ++++++++++++++
 7 files changed, 304 insertions(+)
 create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysis.h
 create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysisRegistry.h
 create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysisRegistry.cpp
 create mode 100644 clang/unittests/ScalableStaticAnalysisFramework/Registries/SourcePassAnalysisRegistryTest.cpp

diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysis.h
new file mode 100644
index 0000000000000..04213a37087a9
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysis.h
@@ -0,0 +1,63 @@
+//===- SourcePassAnalysis.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_SCALABLESTATICANALYSISFRAMEWORK_CORE_SOURCEPASSANALYSIS_SOURCEPASSANALYSIS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SOURCEPASSANALYSIS_SOURCEPASSANALYSIS_H
+
+#include "clang/AST/ASTConsumer.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityIdTable.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
+#include <memory>
+#include <utility>
+
+namespace clang::ssaf {
+
+class SourcePassAnalysisBase : public ASTConsumer {
+public:
+  SourcePassAnalysisBase(std::unique_ptr<WPASuite> WPAResult)
+      : WPAResult(std::move(WPAResult)) {}
+
+protected:
+  std::unique_ptr<WPASuite> WPAResult;
+};
+
+// FIXME: Expectations on SourcePassAnalysis results are TBD.  For a source pass
+// that associates WPA results to AST, the result type is simply void; for
+// source rewriting tools, it may be serializable CodeReplacements; for
+// diagnostic tools, ti maybe SARIF.
+
+///  A SourcePassAnalysis applies WholeProgramAnalysis results to ASTs.
+///  Therefore, it is an `ASTConsumer` that depends on a set of
+///  `clang::ssaf::AnalysisResult`s.
+template <typename ResultT, typename DepResultT>
+class SourcePassAnalysis : public SourcePassAnalysisBase {
+  static_assert((std::is_base_of_v<AnalysisResult, DepResultT>),
+                "Every DepResultT must derive from AnalysisResult");
+
+public:
+  using SourcePassAnalysisBase::SourcePassAnalysisBase;
+  static AnalysisName analysisName();
+
+protected:
+  llvm::Expected<DepResultT> getDependentWPAResult() {
+    return WPAResult->get<DepResultT>();
+  }
+
+  EntityIdTable &getEntityIdTable() {
+    // FIXME: Need to cast away `const` to lookup via getId(); probably also
+    // provide a lookupId() method:
+    return const_cast<EntityIdTable &>(WPAResult->getIdTable());
+  }
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SOURCEPASSANALYSIS_SOURCEPASSANALYSIS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysisRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysisRegistry.h
new file mode 100644
index 0000000000000..5c97935e21221
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysisRegistry.h
@@ -0,0 +1,105 @@
+//===- SourcePassAnalysisRegistry.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Registry for SourcePassAnalysis subclasses.
+//
+// To register an analysis, add a static Add<AnalysisT> and an anchor source
+// in its translation unit, then add the matching anchor destination to the
+// relevant force-linker header:
+//
+//   // MySourcePassAnalysis.cpp
+//   static SourcePassAnalysisRegistry::Add<MySourcePassAnalysis>
+//       Registered("One-line description of MySourcePassAnalysis");
+//
+//   volatile int SSAFMySourcePassAnalysisAnchorSource = 0;
+//
+//   // SSAFBuiltinForceLinker.h (or the relevant force-linker header)
+//   extern volatile int SSAFMySourcePassAnalysisAnchorSource;
+//   [[maybe_unused]] static int SSAFMySourcePassAnalysisAnchorDestination =
+//       SSAFMySourcePassAnalysisAnchorSource;
+//
+// The registry entry name is derived automatically from
+// MySourcePassAnalysis::analysisName(), so name-mismatch bugs are impossible.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SOURCEPASSANALYSIS_SOURCEPASSANALYSISREGISTRY_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SOURCEPASSANALYSIS_SOURCEPASSANALYSISREGISTRY_H
+
+#include "clang/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/Registry.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace clang::ssaf {
+/// Registry for SourcePassAnalysis implementations.
+class SourcePassAnalysisRegistry {
+  SourcePassAnalysisRegistry() = delete;
+
+public:
+  using RegistryT =
+      llvm::Registry<SourcePassAnalysisBase, std::unique_ptr<WPASuite>>;
+
+  /// Registers AnalysisT with the registry.
+  ///
+  /// The registry entry name is derived automatically from
+  /// AnalysisT::ResultType::analysisName(), so name-mismatch bugs are
+  /// impossible.
+  ///
+  /// Add objects must be declared static at namespace scope.
+  template <class AnalysisT, typename ResultT, typename DepResultT> struct Add {
+    static_assert(
+        std::is_base_of_v<SourcePassAnalysis<ResultT, DepResultT>, AnalysisT>,
+        "AnalysisT must derive from SourcePassAnalysis<...>");
+
+    explicit Add(llvm::StringRef Desc)
+        : Name(AnalysisT::analysisName().str().str()), Node(Name, Desc) {
+      if (contains(AnalysisT::analysisName())) {
+        ErrorBuilder::fatal("duplicate analysis registration for '{0}'", Name);
+      }
+      getAnalysisNames().push_back(AnalysisT::analysisName());
+    }
+
+    Add(const Add &) = delete;
+    Add &operator=(const Add &) = delete;
+
+  private:
+    std::string Name;
+    RegistryT::Add<AnalysisT> Node;
+  };
+
+  /// Returns true if an analysis is registered under \p Name.
+  static bool contains(const AnalysisName &Name);
+
+  /// Returns the names of all registered analyses.
+  static const std::vector<AnalysisName> &names();
+
+  /// Instantiates the analysis registered under \p Name, or returns an error
+  /// if no such analysis is registered.
+  static llvm::Expected<std::unique_ptr<SourcePassAnalysisBase>>
+  instantiate(const AnalysisName &Name, std::unique_ptr<WPASuite> WPAResult);
+
+private:
+  /// Returns the global list of registered analysis names.
+  ///
+  /// Uses a function-local static to avoid static initialization order
+  /// fiasco: Add<T> objects in other translation units may push names before
+  /// a plain static data member could be constructed.
+  static std::vector<AnalysisName> &getAnalysisNames();
+};
+
+} // namespace clang::ssaf
+
+LLVM_DECLARE_REGISTRY(clang::ssaf::SourcePassAnalysisRegistry::RegistryT)
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SOURCEPASSANALYSIS_SOURCEPASSANALYSISREGISTRY_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
index 26b1fe4a47840..1f3b263ecce0d 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
@@ -32,6 +32,12 @@ extern volatile int SSAFAnalysisRegistryAnchorSource;
 [[maybe_unused]] static int SSAFAnalysisRegistryAnchorDestination =
     SSAFAnalysisRegistryAnchorSource;
 
+// This anchor is used to force the linker to link the
+// SourcePassAnalysisRegistry.
+extern volatile int SSAFSourcePassAnalysisRegistryAnchorSource;
+[[maybe_unused]] static int SSAFSourcePassAnalysisRegistryAnchorDestination =
+    SSAFSourcePassAnalysisRegistryAnchorSource;
+
 // This anchor is used to force the linker to link the UnsafeBufferUsage
 // JSON format.
 extern volatile int UnsafeBufferUsageSSAFJSONFormatAnchorSource;
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
index 83772ceff58bf..260b3df98e97c 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
@@ -24,6 +24,7 @@ add_clang_library(clangScalableStaticAnalysisFrameworkCore
   Support/ErrorBuilder.cpp
   TUSummary/ExtractorRegistry.cpp
   TUSummary/TUSummaryBuilder.cpp
+  SourcePassAnalysis/SourcePassAnalysisRegistry.cpp
   WholeProgramAnalysis/AnalysisDriver.cpp
   WholeProgramAnalysis/AnalysisName.cpp
   WholeProgramAnalysis/AnalysisRegistry.cpp
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysisRegistry.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysisRegistry.cpp
new file mode 100644
index 0000000000000..bdc4371af66b4
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysisRegistry.cpp
@@ -0,0 +1,46 @@
+//===- SourcePassAnalysisRegistry.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/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "llvm/ADT/STLExtras.h"
+
+using namespace clang;
+using namespace ssaf;
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFSourcePassAnalysisRegistryAnchorSource = 0;
+LLVM_DEFINE_REGISTRY(SourcePassAnalysisRegistry::RegistryT)
+
+std::vector<AnalysisName> &SourcePassAnalysisRegistry::getAnalysisNames() {
+  static std::vector<AnalysisName> Names;
+  return Names;
+}
+
+bool SourcePassAnalysisRegistry::contains(const AnalysisName &Name) {
+  return llvm::is_contained(getAnalysisNames(), Name);
+}
+
+const std::vector<AnalysisName> &SourcePassAnalysisRegistry::names() {
+  return getAnalysisNames();
+}
+
+llvm::Expected<std::unique_ptr<SourcePassAnalysisBase>>
+SourcePassAnalysisRegistry::instantiate(const AnalysisName &Name,
+                                        std::unique_ptr<WPASuite> WPAResult) {
+  for (const auto &Entry : SourcePassAnalysisRegistry::RegistryT::entries()) {
+    if (Entry.getName() == Name.str()) {
+      auto Analysis = Entry.instantiate(std::move(WPAResult));
+      return Analysis;
+    }
+  }
+  return ErrorBuilder::create(std::errc::invalid_argument,
+                              "no source-pass analysis registered for '{0}'",
+                              Name)
+      .build();
+}
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
index e7775242a1581..40014f016aaf2 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
@@ -19,6 +19,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   Registries/MockSummaryExtractor1.cpp
   Registries/MockSummaryExtractor2.cpp
   Registries/SerializationFormatRegistryTest.cpp
+  Registries/SourcePassAnalysisRegistryTest.cpp
   Registries/SummaryExtractorRegistryTest.cpp
   Serialization/JSONFormatTest/JSONFormatTest.cpp
   Serialization/JSONFormatTest/LUSummaryTest.cpp
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Registries/SourcePassAnalysisRegistryTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Registries/SourcePassAnalysisRegistryTest.cpp
new file mode 100644
index 0000000000000..281d6e0f3780e
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Registries/SourcePassAnalysisRegistryTest.cpp
@@ -0,0 +1,82 @@
+//===- SourcePassAnalysisRegistryTest.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/ScalableStaticAnalysisFramework/Core/SourcePassAnalysis/SourcePassAnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+#include <memory>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace {
+
+// ---------------------------------------------------------------------------
+// Dummy analysis result & source-pass analysis
+// ---------------------------------------------------------------------------
+
+static const AnalysisName
+    DummyDependentAnalysisResultName("DummyDependentAnalysisResult");
+static const AnalysisName
+    DummySourcePassAnalysisName("DummySourcePassAnalysis");
+
+// Dummy AnalysisResult that the dummy source pass analysis depends on:
+class DummyWPAResult final : public AnalysisResult {
+public:
+  static AnalysisName analysisName() {
+    return DummyDependentAnalysisResultName;
+  }
+};
+
+// Dummy source pass analysis:
+class DummySourcePassAnalysis final
+    : public SourcePassAnalysis<void, DummyWPAResult> {
+public:
+  using SourcePassAnalysis::SourcePassAnalysis;
+
+  static AnalysisName analysisName() { return DummySourcePassAnalysisName; }
+};
+
+// Registration — this TU is compiled directly into the test binary, so the
+// static initializer is guaranteed to run.
+static SourcePassAnalysisRegistry::Add<DummySourcePassAnalysis, void,
+                                       DummyWPAResult>
+    RegTestSPA("Test source-pass analysis");
+
+// ---------------------------------------------------------------------------
+// Tests
+// ---------------------------------------------------------------------------
+
+TEST(SourcePassAnalysisRegistryTest, ContainsRegistered) {
+  EXPECT_TRUE(
+      SourcePassAnalysisRegistry::contains(DummySourcePassAnalysisName));
+}
+
+TEST(SourcePassAnalysisRegistryTest, DoesNotContainUnregistered) {
+  EXPECT_FALSE(
+      SourcePassAnalysisRegistry::contains(AnalysisName("NonExistent")));
+}
+
+TEST(SourcePassAnalysisRegistryTest, InstantiateRegistered) {
+  auto Result = SourcePassAnalysisRegistry::instantiate(
+      DummySourcePassAnalysis::analysisName(), nullptr);
+  EXPECT_THAT_EXPECTED(Result, llvm::Succeeded());
+}
+
+TEST(SourcePassAnalysisRegistryTest, InstantiateUnregistered) {
+  auto Result = SourcePassAnalysisRegistry::instantiate(
+      AnalysisName("NonExistent"), nullptr);
+  EXPECT_THAT_EXPECTED(
+      Result, llvm::FailedWithMessage("no source-pass analysis registered for "
+                                      "'AnalysisName(NonExistent)'"));
+}
+
+} // namespace



More information about the llvm-branch-commits mailing list