[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