[clang] [clang][ssaf] Add JSONFormat support for WPASuite (PR #187403)
Aviral Goel via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 25 11:54:09 PDT 2026
https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/187403
>From 397d5649770f59907fb910c6be127d322b9fdd6a Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Sat, 14 Mar 2026 09:23:07 -0700
Subject: [PATCH 01/19] Analysis Tree
---
.../Core/Analysis/AnalysisBase.h | 55 +++
.../Core/Analysis/AnalysisDriver.h | 91 ++++
.../Core/Analysis/AnalysisRegistry.h | 95 +++++
.../Core/Analysis/AnalysisResult.h | 30 ++
.../Core/Analysis/DerivedAnalysis.h | 133 ++++++
.../Core/Analysis/SummaryAnalysis.h | 132 ++++++
.../Core/Analysis/WPASuite.h | 92 +++++
.../Core/EntityLinker/LUSummary.h | 1 +
.../Core/Model/AnalysisName.h | 49 +++
.../Core/Model/AnalysisTraits.h | 33 ++
.../Core/Support/FormatProviders.h | 8 +
.../Core/Analysis/AnalysisDriver.cpp | 196 +++++++++
.../Core/Analysis/AnalysisRegistry.cpp | 37 ++
.../Core/CMakeLists.txt | 3 +
.../Core/Model/AnalysisName.cpp | 16 +
.../Analysis/AnalysisDriverTest.cpp | 390 ++++++++++++++++++
.../CMakeLists.txt | 1 +
17 files changed, 1362 insertions(+)
create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h
create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h
create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.cpp
create mode 100644 clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
new file mode 100644
index 0000000000000..478a6fa85d4a8
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
@@ -0,0 +1,55 @@
+//===- AnalysisBase.h -----------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Minimal common base for SummaryAnalysisBase and DerivedAnalysisBase.
+// Carries the identity (analysisName()) and dependency list
+// (dependencyNames()) shared by every analysis regardless of kind.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISBASE_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISBASE_H
+
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
+#include <vector>
+
+namespace clang::ssaf {
+
+class AnalysisDriver;
+class SummaryAnalysisBase;
+class DerivedAnalysisBase;
+
+/// Minimal common base for both analysis kinds.
+///
+/// Not subclassed directly — use SummaryAnalysis<...> or
+/// DerivedAnalysis<...> instead.
+class AnalysisBase {
+ friend class AnalysisDriver;
+ friend class SummaryAnalysisBase;
+ friend class DerivedAnalysisBase;
+
+ enum class Kind { Summary, Derived };
+ Kind TheKind;
+
+protected:
+ explicit AnalysisBase(Kind K) : TheKind(K) {}
+
+public:
+ virtual ~AnalysisBase() = default;
+
+ /// Name of this analysis. Equal to ResultT::analysisName() in both typed
+ /// intermediates.
+ virtual AnalysisName analysisName() const = 0;
+
+ /// AnalysisNames of all AnalysisResult dependencies.
+ virtual const std::vector<AnalysisName> &dependencyNames() const = 0;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISBASE_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
new file mode 100644
index 0000000000000..1cc5c18d348b9
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
@@ -0,0 +1,91 @@
+//===- AnalysisDriver.h ---------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Central orchestrator for whole-program analysis. Takes ownership of an
+// LUSummary, drives all registered analyses in topological dependency order,
+// and returns a WPASuite.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISDRIVER_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISDRIVER_H
+
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Support/Error.h"
+#include <memory>
+#include <vector>
+
+namespace clang::ssaf {
+
+/// Orchestrates whole-program analysis over an LUSummary.
+///
+/// Three run() patterns are supported:
+/// - run() && — all registered analyses; silently skips any whose
+/// entity data is absent or whose dependency was skipped.
+/// Requires an rvalue driver because this exhausts the
+/// LUSummary.
+/// - run(names) — named subset plus transitive dependencies; returns
+/// Expected and fails if any listed name has no
+/// registered analysis or missing entity data.
+/// - run<ResultTs..> — type-safe variant of run(names).
+class AnalysisDriver final {
+public:
+ explicit AnalysisDriver(std::unique_ptr<LUSummary> LU);
+
+ /// Runs all registered analyses in topological dependency order.
+ /// Silently skips analyses with absent entity data or skipped dependencies.
+ ///
+ /// Requires an rvalue driver (std::move(Driver).run()) because this
+ /// exhausts all remaining LUSummary data.
+ [[nodiscard]] llvm::Expected<WPASuite> run() &&;
+
+ /// Runs only the named analyses (plus their transitive dependencies).
+ ///
+ /// Returns an error if any listed AnalysisName has no registered analysis
+ /// or if a required SummaryAnalysis has no matching entity data in the
+ /// LUSummary. The EntityIdTable is copied (not moved) so the driver remains
+ /// usable for subsequent calls.
+ [[nodiscard]] llvm::Expected<WPASuite>
+ run(llvm::ArrayRef<AnalysisName> Names);
+
+ /// Type-safe variant of run(names). Derives names from
+ /// ResultTs::analysisName().
+ template <typename... ResultTs> [[nodiscard]] llvm::Expected<WPASuite> run() {
+ return run({ResultTs::analysisName()...});
+ }
+
+private:
+ std::unique_ptr<LUSummary> LU;
+
+ /// Instantiates all analyses reachable from \p Roots (plus transitive
+ /// dependencies) and returns them in topological order via a single DFS.
+ /// Reports an error on unregistered names or cycles.
+ static llvm::Expected<std::vector<std::unique_ptr<AnalysisBase>>>
+ sortTopologically(llvm::ArrayRef<AnalysisName> Roots);
+
+ /// Executes a topologically-sorted analysis list and returns a WPASuite.
+ /// \p IdTable is moved into the returned WPASuite.
+ llvm::Expected<WPASuite>
+ execute(EntityIdTable IdTable,
+ std::vector<std::unique_ptr<AnalysisBase>> Sorted);
+
+ llvm::Error
+ executeSummaryAnalysis(std::unique_ptr<SummaryAnalysisBase> Summary,
+ WPASuite &Suite);
+
+ llvm::Error
+ executeDerivedAnalysis(std::unique_ptr<DerivedAnalysisBase> Derived,
+ WPASuite &Suite);
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISDRIVER_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
new file mode 100644
index 0000000000000..e4faba62c070e
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
@@ -0,0 +1,95 @@
+//===- AnalysisRegistry.h -------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Unified registry for both SummaryAnalysis and DerivedAnalysis subclasses.
+//
+// To register an analysis, add a static Add<AnalysisT> in its translation
+// unit:
+//
+// static AnalysisRegistry::Add<MyAnalysis>
+// Registered("One-line description of MyAnalysis");
+//
+// The registry entry name is derived automatically from
+// MyAnalysis::analysisName(), so name-mismatch bugs are impossible.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISREGISTRY_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISREGISTRY_H
+
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "llvm/Support/Registry.h"
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace clang::ssaf {
+
+/// Unified registry for SummaryAnalysis and DerivedAnalysis implementations.
+///
+/// Internally uses a single llvm::Registry<AnalysisBase>. The correct kind
+/// is carried by the AnalysisBase::TheKind tag set in each subclass
+/// constructor.
+class AnalysisRegistry {
+ using RegistryT = llvm::Registry<AnalysisBase>;
+
+ AnalysisRegistry() = delete;
+
+public:
+ /// Registers AnalysisT with the unified 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 <typename AnalysisT> struct Add {
+ static_assert(std::is_base_of_v<SummaryAnalysisBase, AnalysisT> ||
+ std::is_base_of_v<DerivedAnalysisBase, AnalysisT>,
+ "AnalysisT must derive from SummaryAnalysis<...> or "
+ "DerivedAnalysis<...>");
+
+ explicit Add(llvm::StringRef Desc)
+ : Name(AnalysisT::ResultType::analysisName().str().str()),
+ Node(Name, Desc) {
+ if (contains(Name)) {
+ ErrorBuilder::fatal("duplicate analysis registration for '{0}'", Name);
+ }
+ analysisNames.push_back(AnalysisT::ResultType::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(llvm::StringRef Name);
+
+ /// Returns the names of all registered analyses.
+ static const std::vector<AnalysisName> &names();
+
+ /// Instantiates the analysis registered under \p Name, or returns
+ /// std::nullopt if no such analysis is registered.
+ static std::optional<std::unique_ptr<AnalysisBase>>
+ instantiate(llvm::StringRef Name);
+
+private:
+ static std::vector<AnalysisName> analysisNames;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISREGISTRY_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
new file mode 100644
index 0000000000000..87d781cff30a8
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
@@ -0,0 +1,30 @@
+//===- AnalysisResult.h ---------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Base class for all whole-program analysis results produced by AnalysisDriver.
+// Replaces SummaryData. Concrete subclasses carry a static analysisName().
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISRESULT_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISRESULT_H
+
+namespace clang::ssaf {
+
+/// Base class for whole-program analysis results.
+///
+/// Concrete subclasses must provide:
+/// static AnalysisName analysisName();
+class AnalysisResult {
+public:
+ virtual ~AnalysisResult() = default;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISRESULT_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
new file mode 100644
index 0000000000000..7b5695dab7c72
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
@@ -0,0 +1,133 @@
+//===- DerivedAnalysis.h --------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines DerivedAnalysisBase (type-erased base known to AnalysisDriver) and
+// the typed intermediate DerivedAnalysis<ResultT, DepResultTs...> that
+// concrete analyses inherit from.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_DERIVEDANALYSIS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_DERIVEDANALYSIS_H
+
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h"
+#include "llvm/Support/Error.h"
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace clang::ssaf {
+
+class AnalysisDriver;
+
+/// Type-erased base for derived analyses. Known to AnalysisDriver.
+///
+/// Not subclassed directly — use DerivedAnalysis<ResultT, DepResultTs...>.
+/// A derived analysis consumes previously produced AnalysisResult objects
+/// and computes a new one via an initialize/step/finalize lifecycle.
+class DerivedAnalysisBase : public AnalysisBase {
+ friend class AnalysisDriver;
+
+protected:
+ DerivedAnalysisBase() : AnalysisBase(AnalysisBase::Kind::Derived) {}
+
+private:
+ /// Called once with the dependency results before the step() loop.
+ ///
+ /// \param DepResults Immutable results of all declared dependencies, keyed
+ /// by AnalysisName. Guaranteed to contain every name
+ /// returned by dependencyNames().
+ virtual llvm::Error initialize(
+ const std::map<AnalysisName, const AnalysisResult *> &DepResults) = 0;
+
+ /// Performs one pass. Returns true if another pass is needed; false when
+ /// converged.
+ virtual llvm::Expected<bool> step() = 0;
+
+ /// Called after the step() loop converges. Default is a no-op.
+ virtual llvm::Error finalize() { return llvm::Error::success(); }
+
+ /// Transfers ownership of the computed result. Called once after finalize().
+ virtual std::unique_ptr<AnalysisResult> result() && = 0;
+};
+
+/// Typed intermediate that concrete derived analyses inherit from.
+///
+/// Concrete analyses must implement:
+/// llvm::Error initialize(const DepResultTs &...) override;
+/// llvm::Expected<bool> step() override;
+/// and may override finalize().
+///
+/// Dependencies are fixed for the lifetime of the analysis — initialize()
+/// binds them once, step() is called until it returns false, and
+/// finalize() post-processes after convergence.
+template <typename ResultT, typename... DepResultTs>
+class DerivedAnalysis : public DerivedAnalysisBase {
+ static_assert(std::is_base_of_v<AnalysisResult, ResultT>,
+ "ResultT must derive from AnalysisResult");
+ static_assert(HasAnalysisName<ResultT>::value,
+ "ResultT must have a static analysisName() method");
+ static_assert((std::is_base_of_v<AnalysisResult, DepResultTs> && ...),
+ "Every DepResultT must derive from AnalysisResult");
+ static_assert((HasAnalysisName<DepResultTs>::value && ...),
+ "Every DepResultT must have a static analysisName() method");
+
+ std::unique_ptr<ResultT> Result;
+
+public:
+ DerivedAnalysis() : Result(std::make_unique<ResultT>()) {}
+
+ using ResultType = ResultT;
+
+ /// Used by AnalysisRegistry::Add to derive the registry entry name.
+ AnalysisName analysisName() const final { return ResultT::analysisName(); }
+
+ const std::vector<AnalysisName> &dependencyNames() const final {
+ static const std::vector<AnalysisName> Names = {
+ DepResultTs::analysisName()...};
+ return Names;
+ }
+
+ /// Called once with the fixed dependency results before the step() loop.
+ virtual llvm::Error initialize(const DepResultTs &...) = 0;
+
+ /// Performs one step. Returns true if another step is needed; false when
+ /// converged. Single-step analyses always return false.
+ virtual llvm::Expected<bool> step() = 0;
+
+ /// Called after the step() loop converges. Override for post-processing.
+ virtual llvm::Error finalize() { return llvm::Error::success(); }
+
+protected:
+ /// Read-only access to the result being built.
+ const ResultT &result() const & { return *Result; }
+
+ /// Mutable access to the result being built.
+ ResultT &result() & { return *Result; }
+
+private:
+ /// Seals the type-erased base overload, downcasts, and dispatches to the
+ /// typed initialize().
+ llvm::Error
+ initialize(const std::map<AnalysisName, const AnalysisResult *> &Map) final {
+ return initialize(*static_cast<const DepResultTs *>(
+ Map.at(DepResultTs::analysisName()))...);
+ }
+
+ /// Type-erased result extraction for the driver.
+ std::unique_ptr<AnalysisResult> result() && final {
+ return std::move(Result);
+ }
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_DERIVEDANALYSIS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
new file mode 100644
index 0000000000000..3a53ec147db4f
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
@@ -0,0 +1,132 @@
+//===- SummaryAnalysis.h --------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines SummaryAnalysisBase (type-erased base known to AnalysisDriver) and
+// the typed intermediate SummaryAnalysis<ResultT, EntitySummaryT> that
+// concrete analyses inherit from.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_SUMMARYANALYSIS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_SUMMARYANALYSIS_H
+
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/EntitySummary.h"
+#include "llvm/Support/Error.h"
+#include <memory>
+
+namespace clang::ssaf {
+
+class AnalysisDriver;
+
+/// Type-erased base for summary analyses. Known to AnalysisDriver.
+///
+/// Not subclassed directly — use SummaryAnalysis<ResultT, EntitySummaryT>.
+/// A summary analysis processes per-entity EntitySummary objects from the
+/// LUSummary one at a time, accumulating whole-program data into an
+/// AnalysisResult.
+class SummaryAnalysisBase : public AnalysisBase {
+ friend class AnalysisDriver;
+
+protected:
+ SummaryAnalysisBase() : AnalysisBase(AnalysisBase::Kind::Summary) {}
+
+public:
+ /// SummaryName of the EntitySummary type this analysis consumes.
+ /// Used by the driver to route entities from the LUSummary.
+ virtual SummaryName summaryName() const = 0;
+
+private:
+ /// Called once before any add() calls. Default is a no-op.
+ virtual llvm::Error initialize() { return llvm::Error::success(); }
+
+ /// Called once per matching entity. The driver retains ownership of the
+ /// summary; multiple SummaryAnalysis instances may receive the same entity.
+ virtual llvm::Error add(EntityId Id, const EntitySummary &Summary) = 0;
+
+ /// Called after all entities have been processed. Default is a no-op.
+ virtual llvm::Error finalize() { return llvm::Error::success(); }
+
+ /// Transfers ownership of the built result. Called once after finalize().
+ /// The rvalue ref-qualifier enforces single use.
+ virtual std::unique_ptr<AnalysisResult> result() && = 0;
+};
+
+/// Typed intermediate that concrete summary analyses inherit from.
+///
+/// Concrete analyses must implement:
+/// llvm::Error add(EntityId Id, const EntitySummaryT &Summary) override;
+/// and may override initialize() and finalize().
+///
+/// The result being built is accessible via result() const & (read-only) and
+/// result() & (mutable) within the analysis implementation.
+template <typename ResultT, typename EntitySummaryT>
+class SummaryAnalysis : public SummaryAnalysisBase {
+ static_assert(std::is_base_of_v<AnalysisResult, ResultT>,
+ "ResultT must derive from AnalysisResult");
+ static_assert(HasAnalysisName<ResultT>::value,
+ "ResultT must have a static analysisName() method");
+ static_assert(std::is_base_of_v<EntitySummary, EntitySummaryT>,
+ "EntitySummaryT must derive from EntitySummary");
+
+ std::unique_ptr<ResultT> Result;
+
+public:
+ SummaryAnalysis() : Result(std::make_unique<ResultT>()) {}
+
+ using ResultType = ResultT;
+
+ /// Used by AnalysisRegistry::Add to derive the registry entry name.
+ AnalysisName analysisName() const final { return ResultT::analysisName(); }
+
+ SummaryName summaryName() const final {
+ return EntitySummaryT::summaryName();
+ }
+
+ const std::vector<AnalysisName> &dependencyNames() const final {
+ static const std::vector<AnalysisName> Empty;
+ return Empty;
+ }
+
+ /// Called once before the first add() call. Override for initialization.
+ virtual llvm::Error initialize() override { return llvm::Error::success(); }
+
+ /// Called once per matching entity. Implement to accumulate data.
+ virtual llvm::Error add(EntityId Id, const EntitySummaryT &Summary) = 0;
+
+ /// Called after all entities have been processed. Override for
+ /// post-processing.
+ virtual llvm::Error finalize() override { return llvm::Error::success(); }
+
+protected:
+ /// Read-only access to the result being built.
+ const ResultT &result() const & { return *Result; }
+
+ /// Mutable access to the result being built.
+ ResultT &result() & { return *Result; }
+
+private:
+ /// Seals the type-erased base overload, downcasts, and dispatches to the
+ /// typed add().
+ llvm::Error add(EntityId Id, const EntitySummary &Summary) final {
+ return add(Id, static_cast<const EntitySummaryT &>(Summary));
+ }
+
+ /// Type-erased result extraction for the driver.
+ std::unique_ptr<AnalysisResult> result() && final {
+ return std::move(Result);
+ }
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_SUMMARYANALYSIS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
new file mode 100644
index 0000000000000..040f8ea79c0ec
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
@@ -0,0 +1,92 @@
+//===- WPASuite.h ---------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// The value returned by AnalysisDriver::run(). Bundles the EntityIdTable
+// (moved from the LUSummary) with the analysis results keyed by AnalysisName.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_WPASUITE_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_WPASUITE_H
+
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityIdTable.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "llvm/Support/Error.h"
+#include <map>
+#include <memory>
+
+namespace clang::ssaf {
+
+class AnalysisDriver;
+
+/// Bundles the EntityIdTable (moved from the LUSummary) and the analysis
+/// results produced by one AnalysisDriver::run() call, keyed by AnalysisName.
+///
+/// This is the natural unit of persistence: entity names and analysis results
+/// are self-contained in one object.
+class WPASuite {
+ friend class AnalysisDriver;
+
+ EntityIdTable IdTable;
+ std::map<AnalysisName, std::unique_ptr<AnalysisResult>> Data;
+
+ WPASuite() = default;
+
+public:
+ /// Returns the EntityIdTable that maps EntityId values to their symbolic
+ /// names. Moved from the LUSummary during AnalysisDriver::run().
+ const EntityIdTable &idTable() const { return IdTable; }
+
+ /// Returns true if a result for \p ResultT is present.
+ template <typename ResultT> [[nodiscard]] bool contains() const {
+ static_assert(std::is_base_of_v<AnalysisResult, ResultT>,
+ "ResultT must derive from AnalysisResult");
+ static_assert(HasAnalysisName<ResultT>::value,
+ "ResultT must have a static analysisName() method");
+
+ return contains(ResultT::analysisName());
+ }
+
+ /// Returns true if a result for \p Name is present.
+ [[nodiscard]] bool contains(AnalysisName Name) const {
+ return Data.find(Name) != Data.end();
+ }
+
+ /// Returns a reference to the result for \p ResultT, or an error if absent.
+ template <typename ResultT> [[nodiscard]] llvm::Expected<ResultT &> get() {
+ static_assert(std::is_base_of_v<AnalysisResult, ResultT>,
+ "ResultT must derive from AnalysisResult");
+ static_assert(HasAnalysisName<ResultT>::value,
+ "ResultT must have a static analysisName() method");
+
+ auto Result = get(ResultT::analysisName());
+ if (!Result) {
+ return Result.takeError();
+ }
+ return static_cast<ResultT &>(*Result);
+ }
+
+ /// Returns a reference to the result for \p Name, or an error if absent.
+ [[nodiscard]] llvm::Expected<AnalysisResult &> get(AnalysisName Name) {
+ auto It = Data.find(Name);
+ if (It == Data.end()) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ "no result for analysis '{0}' in WPASuite",
+ Name.str())
+ .build();
+ }
+ return *It->second;
+ }
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_WPASUITE_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h
index 552fff04a4c01..44e7504009bee 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h
@@ -34,6 +34,7 @@ class LUSummary {
friend class LUSummaryConsumer;
friend class SerializationFormat;
friend class TestFixture;
+ friend class AnalysisDriver;
NestedBuildNamespace LUNamespace;
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h
new file mode 100644
index 0000000000000..167d8a8b0485e
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h
@@ -0,0 +1,49 @@
+//===- AnalysisName.h -----------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Strong typedef identifying a whole-program analysis and its result type.
+// Distinct from SummaryName, which identifies per-entity EntitySummary types.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISNAME_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISNAME_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+
+namespace clang::ssaf {
+
+/// Uniquely identifies a whole-program analysis and the AnalysisResult it
+/// produces. Used as the key in WPASuite and AnalysisRegistry.
+///
+/// Distinct from SummaryName, which is used by EntitySummary types for routing
+/// through the LUSummary.
+class AnalysisName {
+public:
+ explicit AnalysisName(std::string Name) : Name(std::move(Name)) {}
+
+ bool operator==(const AnalysisName &Other) const {
+ return Name == Other.Name;
+ }
+ bool operator!=(const AnalysisName &Other) const { return !(*this == Other); }
+ bool operator<(const AnalysisName &Other) const { return Name < Other.Name; }
+
+ /// Explicit conversion to the underlying string representation.
+ llvm::StringRef str() const { return Name; }
+
+private:
+ std::string Name;
+};
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const AnalysisName &AN);
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISNAME_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h
new file mode 100644
index 0000000000000..888f4a8e6be4a
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h
@@ -0,0 +1,33 @@
+//===- AnalysisTraits.h ---------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Type traits for AnalysisResult subclasses.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISTRAITS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISTRAITS_H
+
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
+#include <type_traits>
+
+namespace clang::ssaf {
+
+/// Type trait that checks whether \p T has a static \c analysisName() method
+/// returning \c AnalysisName. Used to enforce the convention on AnalysisResult
+/// subclasses and analysis classes at instantiation time.
+template <typename T, typename = void>
+struct HasAnalysisName : std::false_type {};
+
+template <typename T>
+struct HasAnalysisName<T, std::void_t<decltype(T::analysisName())>>
+ : std::is_same<decltype(T::analysisName()), AnalysisName> {};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISTRAITS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
index d49fd6cb4a1dc..f50d17d7f035a 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
@@ -14,6 +14,7 @@
#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SUPPORT_FORMATPROVIDERS_H
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SUPPORT_FORMATPROVIDERS_H
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityLinkage.h"
@@ -24,6 +25,13 @@
namespace llvm {
+template <> struct format_provider<clang::ssaf::AnalysisName> {
+ static void format(const clang::ssaf::AnalysisName &Val, raw_ostream &OS,
+ StringRef Style) {
+ OS << Val;
+ }
+};
+
template <> struct format_provider<clang::ssaf::EntityId> {
static void format(const clang::ssaf::EntityId &Val, raw_ostream &OS,
StringRef Style) {
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
new file mode 100644
index 0000000000000..64b496142cd47
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
@@ -0,0 +1,196 @@
+//===- AnalysisDriver.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/Analysis/AnalysisDriver.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <map>
+#include <vector>
+
+using namespace clang;
+using namespace ssaf;
+
+AnalysisDriver::AnalysisDriver(std::unique_ptr<LUSummary> LU)
+ : LU(std::move(LU)) {}
+
+llvm::Expected<std::vector<std::unique_ptr<AnalysisBase>>>
+AnalysisDriver::sortTopologically(llvm::ArrayRef<AnalysisName> Roots) {
+ struct Visitor {
+ enum class State { Unvisited, Visiting, Visited };
+
+ std::map<AnalysisName, State> Marks;
+ std::map<AnalysisName, std::unique_ptr<AnalysisBase>> Analyses;
+ std::vector<std::unique_ptr<AnalysisBase>> Result;
+
+ State getState(const AnalysisName &Name) {
+ auto MarkIt = Marks.find(Name);
+ return MarkIt != Marks.end() ? MarkIt->second : State::Unvisited;
+ }
+
+ void setState(const AnalysisName &Name, State S) { Marks[Name] = S; }
+
+ llvm::Error visit(const AnalysisName &Name) {
+ State S = getState(Name);
+
+ if (S == State::Visited) {
+ return llvm::Error::success();
+ }
+
+ if (S == State::Visiting) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ "cycle detected involving analysis '{0}'",
+ Name)
+ .build();
+ }
+
+ if (S == State::Unvisited) {
+ setState(Name, State::Visiting);
+
+ auto V = AnalysisRegistry::instantiate(Name.str());
+ if (!V) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ "no analysis registered for '{0}'", Name)
+ .build();
+ }
+
+ const auto &Deps = (*V)->dependencyNames();
+ Analyses[Name] = std::move(*V);
+
+ for (const auto &Dep : Deps) {
+ if (auto Err = visit(Dep)) {
+ return Err;
+ }
+ }
+
+ setState(Name, State::Visited);
+ Result.push_back(std::move(Analyses[Name]));
+ Analyses.erase(Name);
+ return llvm::Error::success();
+ }
+ llvm_unreachable("unhandled State");
+ }
+ };
+
+ Visitor V;
+ for (const auto &Root : Roots) {
+ if (auto Err = V.visit(Root)) {
+ return std::move(Err);
+ }
+ }
+ return std::move(V.Result);
+}
+
+llvm::Error AnalysisDriver::executeSummaryAnalysis(
+ std::unique_ptr<SummaryAnalysisBase> Summary, WPASuite &Suite) {
+ SummaryName SN = Summary->summaryName();
+ auto DataIt = LU->Data.find(SN);
+ if (DataIt == LU->Data.end()) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ "no data for analysis '{0}' in LUSummary",
+ Summary->analysisName().str())
+ .build();
+ }
+
+ if (auto Err = Summary->initialize()) {
+ return Err;
+ }
+
+ for (auto &[Id, EntitySummary] : DataIt->second) {
+ if (auto Err = Summary->add(Id, *EntitySummary)) {
+ return Err;
+ }
+ }
+
+ if (auto Err = Summary->finalize()) {
+ return Err;
+ }
+
+ Suite.Data.emplace(Summary->analysisName(), std::move(*Summary).result());
+
+ return llvm::Error::success();
+}
+
+llvm::Error AnalysisDriver::executeDerivedAnalysis(
+ std::unique_ptr<DerivedAnalysisBase> Derived, WPASuite &Suite) {
+ std::map<AnalysisName, const AnalysisResult *> DepMap;
+
+ for (const auto &DepName : Derived->dependencyNames()) {
+ auto It = Suite.Data.find(DepName);
+ if (It == Suite.Data.end()) {
+ ErrorBuilder::fatal("missing dependency '{0}' for analysis '{1}': "
+ "dependency graph is not topologically sorted",
+ DepName.str(), Derived->analysisName().str());
+ }
+ DepMap[DepName] = It->second.get();
+ }
+
+ if (auto Err = Derived->initialize(DepMap)) {
+ return Err;
+ }
+
+ while (true) {
+ auto StepOrErr = Derived->step();
+ if (!StepOrErr) {
+ return StepOrErr.takeError();
+ }
+ if (!*StepOrErr) {
+ break;
+ }
+ }
+
+ if (auto Err = Derived->finalize()) {
+ return Err;
+ }
+
+ Suite.Data.emplace(Derived->analysisName(), std::move(*Derived).result());
+
+ return llvm::Error::success();
+}
+
+llvm::Expected<WPASuite>
+AnalysisDriver::execute(EntityIdTable IdTable,
+ std::vector<std::unique_ptr<AnalysisBase>> Sorted) {
+ WPASuite Suite;
+ Suite.IdTable = std::move(IdTable);
+
+ for (auto &V : Sorted) {
+ if (V->TheKind == AnalysisBase::Kind::Summary) {
+ auto SA = std::unique_ptr<SummaryAnalysisBase>(
+ static_cast<SummaryAnalysisBase *>(V.release()));
+ if (auto Err = executeSummaryAnalysis(std::move(SA), Suite)) {
+ return std::move(Err);
+ }
+ } else {
+ auto DA = std::unique_ptr<DerivedAnalysisBase>(
+ static_cast<DerivedAnalysisBase *>(V.release()));
+ if (auto Err = executeDerivedAnalysis(std::move(DA), Suite)) {
+ return std::move(Err);
+ }
+ }
+ }
+
+ return Suite;
+}
+
+llvm::Expected<WPASuite> AnalysisDriver::run() && {
+ return run(AnalysisRegistry::names());
+}
+
+llvm::Expected<WPASuite>
+AnalysisDriver::run(llvm::ArrayRef<AnalysisName> Names) {
+ auto ExpectedSorted = sortTopologically(Names);
+ if (!ExpectedSorted) {
+ return ExpectedSorted.takeError();
+ }
+
+ return execute(LU->IdTable, std::move(*ExpectedSorted));
+}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
new file mode 100644
index 0000000000000..aac05fdb08453
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
@@ -0,0 +1,37 @@
+//===- AnalysisRegistry.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/Analysis/AnalysisRegistry.h"
+#include "llvm/ADT/STLExtras.h"
+
+using namespace clang;
+using namespace ssaf;
+
+using RegistryT = llvm::Registry<AnalysisBase>;
+
+LLVM_INSTANTIATE_REGISTRY(RegistryT)
+
+std::vector<AnalysisName> AnalysisRegistry::analysisNames;
+
+bool AnalysisRegistry::contains(llvm::StringRef Name) {
+ return llvm::is_contained(analysisNames, AnalysisName(std::string(Name)));
+}
+
+const std::vector<AnalysisName> &AnalysisRegistry::names() {
+ return analysisNames;
+}
+
+std::optional<std::unique_ptr<AnalysisBase>>
+AnalysisRegistry::instantiate(llvm::StringRef Name) {
+ for (const auto &Entry : RegistryT::entries()) {
+ if (Entry.getName() == Name) {
+ return std::unique_ptr<AnalysisBase>(Entry.instantiate());
+ }
+ }
+ return std::nullopt;
+}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
index 190b9fe400a64..9e9786dae5a07 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
@@ -4,7 +4,10 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangScalableStaticAnalysisFrameworkCore
ASTEntityMapping.cpp
+ Analysis/AnalysisDriver.cpp
+ Analysis/AnalysisRegistry.cpp
EntityLinker/EntityLinker.cpp
+ Model/AnalysisName.cpp
Model/BuildNamespace.cpp
Model/EntityId.cpp
Model/EntityIdTable.cpp
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.cpp
new file mode 100644
index 0000000000000..95e0c02f9a92f
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.cpp
@@ -0,0 +1,16 @@
+//===- AnalysisName.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/Model/AnalysisName.h"
+
+using namespace clang::ssaf;
+
+llvm::raw_ostream &clang::ssaf::operator<<(llvm::raw_ostream &OS,
+ const AnalysisName &AN) {
+ return OS << "AnalysisName(" << AN.str() << ")";
+}
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
new file mode 100644
index 0000000000000..436e0da580910
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
@@ -0,0 +1,390 @@
+//===- AnalysisDriverTest.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/Analysis/AnalysisDriver.h"
+#include "../TestFixture.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityLinkage.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <utility>
+#include <vector>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace {
+
+// ---------------------------------------------------------------------------
+// Instance counter
+// ---------------------------------------------------------------------------
+
+static int NextSummaryInstanceId = 0;
+
+// ---------------------------------------------------------------------------
+// Entity summaries
+// ---------------------------------------------------------------------------
+
+class Analysis1EntitySummary final : public EntitySummary {
+public:
+ int InstanceId = NextSummaryInstanceId++;
+ static SummaryName summaryName() { return SummaryName("Analysis1"); }
+ SummaryName getSummaryName() const override {
+ return SummaryName("Analysis1");
+ }
+};
+
+class Analysis2EntitySummary final : public EntitySummary {
+public:
+ int InstanceId = NextSummaryInstanceId++;
+ static SummaryName summaryName() { return SummaryName("Analysis2"); }
+ SummaryName getSummaryName() const override {
+ return SummaryName("Analysis2");
+ }
+};
+
+class Analysis3EntitySummary final : public EntitySummary {
+public:
+ int InstanceId = NextSummaryInstanceId++;
+ static SummaryName summaryName() { return SummaryName("Analysis3"); }
+ SummaryName getSummaryName() const override {
+ return SummaryName("Analysis3");
+ }
+};
+
+class Analysis4EntitySummary final : public EntitySummary {
+public:
+ int InstanceId = NextSummaryInstanceId++;
+ static SummaryName summaryName() { return SummaryName("Analysis4"); }
+ SummaryName getSummaryName() const override {
+ return SummaryName("Analysis4");
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Results
+// ---------------------------------------------------------------------------
+
+class Analysis1Result final : public AnalysisResult {
+public:
+ static AnalysisName analysisName() { return AnalysisName("Analysis1"); }
+ std::vector<std::pair<EntityId, int>> Entries;
+ bool WasFinalized = false;
+};
+
+class Analysis2Result final : public AnalysisResult {
+public:
+ static AnalysisName analysisName() { return AnalysisName("Analysis2"); }
+ std::vector<std::pair<EntityId, int>> Entries;
+ bool WasFinalized = false;
+};
+
+// No analysis or registration for Analysis3. Data for Analysis3 is inserted
+// into the LUSummary to verify the driver silently skips it.
+class Analysis3Result final : public AnalysisResult {
+public:
+ static AnalysisName analysisName() { return AnalysisName("Analysis3"); }
+};
+
+// Analysis4 has a registered analysis but no data is inserted into the
+// LUSummary, so it is skipped and get() returns nullptr.
+class Analysis4Result final : public AnalysisResult {
+public:
+ static AnalysisName analysisName() { return AnalysisName("Analysis4"); }
+ std::vector<std::pair<EntityId, int>> Entries;
+ bool WasFinalized = false;
+};
+
+// ---------------------------------------------------------------------------
+// Analysis destruction flags (reset in SetUp)
+// ---------------------------------------------------------------------------
+
+static bool Analysis1WasDestroyed = false;
+static bool Analysis2WasDestroyed = false;
+static bool Analysis4WasDestroyed = false;
+
+// ---------------------------------------------------------------------------
+// Analyses
+// ---------------------------------------------------------------------------
+
+class Analysis1 final
+ : public SummaryAnalysis<Analysis1Result, Analysis1EntitySummary> {
+public:
+ ~Analysis1() { Analysis1WasDestroyed = true; }
+
+ llvm::Error add(EntityId Id, const Analysis1EntitySummary &S) override {
+ result().Entries.push_back({Id, S.InstanceId});
+ return llvm::Error::success();
+ }
+
+ llvm::Error finalize() override {
+ result().WasFinalized = true;
+ return llvm::Error::success();
+ }
+};
+
+static AnalysisRegistry::Add<Analysis1> RegAnalysis1("Analysis for Analysis1");
+
+class Analysis2 final
+ : public SummaryAnalysis<Analysis2Result, Analysis2EntitySummary> {
+public:
+ ~Analysis2() { Analysis2WasDestroyed = true; }
+
+ llvm::Error add(EntityId Id, const Analysis2EntitySummary &S) override {
+ result().Entries.push_back({Id, S.InstanceId});
+ return llvm::Error::success();
+ }
+
+ llvm::Error finalize() override {
+ result().WasFinalized = true;
+ return llvm::Error::success();
+ }
+};
+
+static AnalysisRegistry::Add<Analysis2> RegAnalysis2("Analysis for Analysis2");
+
+class Analysis4 final
+ : public SummaryAnalysis<Analysis4Result, Analysis4EntitySummary> {
+public:
+ ~Analysis4() { Analysis4WasDestroyed = true; }
+
+ llvm::Error add(EntityId Id, const Analysis4EntitySummary &S) override {
+ result().Entries.push_back({Id, S.InstanceId});
+ return llvm::Error::success();
+ }
+
+ llvm::Error finalize() override {
+ result().WasFinalized = true;
+ return llvm::Error::success();
+ }
+};
+
+static AnalysisRegistry::Add<Analysis4> RegAnalysis4("Analysis for Analysis4");
+
+// ---------------------------------------------------------------------------
+// Fixture
+// ---------------------------------------------------------------------------
+
+class AnalysisDriverTest : public TestFixture {
+protected:
+ static constexpr EntityLinkage ExternalLinkage =
+ EntityLinkage(EntityLinkageType::External);
+
+ void SetUp() override {
+ NextSummaryInstanceId = 0;
+ Analysis1WasDestroyed = false;
+ Analysis2WasDestroyed = false;
+ Analysis4WasDestroyed = false;
+ }
+
+ std::unique_ptr<LUSummary> makeLUSummary() {
+ NestedBuildNamespace NS(
+ {BuildNamespace(BuildNamespaceKind::LinkUnit, "TestLU")});
+ return std::make_unique<LUSummary>(std::move(NS));
+ }
+
+ EntityId addEntity(LUSummary &LU, llvm::StringRef USR) {
+ NestedBuildNamespace NS(
+ {BuildNamespace(BuildNamespaceKind::LinkUnit, "TestLU")});
+ EntityName Name(USR.str(), "", NS);
+ EntityId Id = getIdTable(LU).getId(Name);
+ getLinkageTable(LU).insert({Id, ExternalLinkage});
+ return Id;
+ }
+
+ static bool hasEntry(const std::vector<std::pair<EntityId, int>> &Entries,
+ EntityId Id, int InstanceId) {
+ return llvm::is_contained(Entries, std::make_pair(Id, InstanceId));
+ }
+
+ template <typename SummaryT>
+ int insertSummary(LUSummary &LU, llvm::StringRef SN, EntityId Id) {
+ auto S = std::make_unique<SummaryT>();
+ int InstanceId = S->InstanceId;
+ getData(LU)[SummaryName(SN.str())][Id] = std::move(S);
+ return InstanceId;
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Tests
+// ---------------------------------------------------------------------------
+
+TEST(AnalysisRegistryTest, AnalysisIsRegistered) {
+ EXPECT_FALSE(AnalysisRegistry::contains("AnalysisNonExisting"));
+ EXPECT_TRUE(AnalysisRegistry::contains("Analysis1"));
+ EXPECT_TRUE(AnalysisRegistry::contains("Analysis2"));
+ EXPECT_TRUE(AnalysisRegistry::contains("Analysis4"));
+}
+
+TEST(AnalysisRegistryTest, AnalysisCanBeInstantiated) {
+ EXPECT_FALSE(
+ AnalysisRegistry::instantiate("AnalysisNonExisting").has_value());
+ EXPECT_TRUE(AnalysisRegistry::instantiate("Analysis1").has_value());
+ EXPECT_TRUE(AnalysisRegistry::instantiate("Analysis2").has_value());
+ EXPECT_TRUE(AnalysisRegistry::instantiate("Analysis4").has_value());
+}
+
+// run() — processes all registered analyses present in the LUSummary.
+// Silently skips data whose analysis is unregistered (Analysis3).
+TEST_F(AnalysisDriverTest, RunAll) {
+ auto LU = makeLUSummary();
+ const auto E1 = addEntity(*LU, "Entity1");
+ const auto E2 = addEntity(*LU, "Entity2");
+ const auto E3 = addEntity(*LU, "Entity3");
+ const auto E4 = addEntity(*LU, "Entity4");
+
+ int s1a = insertSummary<Analysis1EntitySummary>(*LU, "Analysis1", E1);
+ int s1b = insertSummary<Analysis1EntitySummary>(*LU, "Analysis1", E2);
+ int s2a = insertSummary<Analysis2EntitySummary>(*LU, "Analysis2", E2);
+ int s2b = insertSummary<Analysis2EntitySummary>(*LU, "Analysis2", E3);
+ int s4a = insertSummary<Analysis4EntitySummary>(*LU, "Analysis4", E4);
+
+ // No registered analysis — Analysis3 data silently skipped.
+ (void)insertSummary<Analysis3EntitySummary>(*LU, "Analysis3", E1);
+
+ AnalysisDriver Driver(std::move(LU));
+ auto WPAOrErr = std::move(Driver).run();
+ ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
+
+ {
+ auto R1OrErr = WPAOrErr->get<Analysis1Result>();
+ ASSERT_THAT_EXPECTED(R1OrErr, llvm::Succeeded());
+ EXPECT_EQ(R1OrErr->Entries.size(), 2u);
+ EXPECT_TRUE(hasEntry(R1OrErr->Entries, E1, s1a));
+ EXPECT_TRUE(hasEntry(R1OrErr->Entries, E2, s1b));
+ EXPECT_TRUE(R1OrErr->WasFinalized);
+ EXPECT_TRUE(Analysis1WasDestroyed);
+ }
+
+ {
+ auto R2OrErr = WPAOrErr->get<Analysis2Result>();
+ ASSERT_THAT_EXPECTED(R2OrErr, llvm::Succeeded());
+ EXPECT_EQ(R2OrErr->Entries.size(), 2u);
+ EXPECT_TRUE(hasEntry(R2OrErr->Entries, E2, s2a));
+ EXPECT_TRUE(hasEntry(R2OrErr->Entries, E3, s2b));
+ EXPECT_TRUE(R2OrErr->WasFinalized);
+ EXPECT_TRUE(Analysis2WasDestroyed);
+ }
+
+ {
+ auto R4OrErr = WPAOrErr->get<Analysis4Result>();
+ ASSERT_THAT_EXPECTED(R4OrErr, llvm::Succeeded());
+ EXPECT_EQ(R4OrErr->Entries.size(), 1u);
+ EXPECT_TRUE(hasEntry(R4OrErr->Entries, E4, s4a));
+ EXPECT_TRUE(R4OrErr->WasFinalized);
+ EXPECT_TRUE(Analysis4WasDestroyed);
+ }
+
+ // Unregistered analysis — not present in WPA.
+ EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis3Result>(), llvm::Failed());
+}
+
+// run(names) — processes only the analyses for the given names.
+TEST_F(AnalysisDriverTest, RunByName) {
+ auto LU = makeLUSummary();
+ const auto E1 = addEntity(*LU, "Entity1");
+ const auto E2 = addEntity(*LU, "Entity2");
+
+ int s1a = insertSummary<Analysis1EntitySummary>(*LU, "Analysis1", E1);
+ insertSummary<Analysis2EntitySummary>(*LU, "Analysis2", E2);
+
+ AnalysisDriver Driver(std::move(LU));
+ auto WPAOrErr = Driver.run({AnalysisName("Analysis1")});
+ ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
+
+ // Analysis1 was requested and has data — present.
+ auto R1OrErr = WPAOrErr->get<Analysis1Result>();
+ ASSERT_THAT_EXPECTED(R1OrErr, llvm::Succeeded());
+ EXPECT_EQ(R1OrErr->Entries.size(), 1u);
+ EXPECT_TRUE(hasEntry(R1OrErr->Entries, E1, s1a));
+ EXPECT_TRUE(R1OrErr->WasFinalized);
+
+ // Analysis2 was not requested — not present even though data exists.
+ EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis2Result>(), llvm::Failed());
+}
+
+// run(names) — error when a requested name has no data in LUSummary.
+TEST_F(AnalysisDriverTest, RunByNameErrorMissingData) {
+ auto LU = makeLUSummary();
+ AnalysisDriver Driver(std::move(LU));
+
+ EXPECT_THAT_EXPECTED(Driver.run({AnalysisName("Analysis1")}), llvm::Failed());
+}
+
+// run(names) — error when a requested name has no registered analysis.
+TEST_F(AnalysisDriverTest, RunByNameErrorMissingAnalysis) {
+ auto LU = makeLUSummary();
+ const auto E1 = addEntity(*LU, "Entity1");
+ insertSummary<Analysis3EntitySummary>(*LU, "Analysis3", E1);
+
+ AnalysisDriver Driver(std::move(LU));
+
+ // Analysis3 has data but no registered analysis.
+ EXPECT_THAT_EXPECTED(Driver.run({AnalysisName("Analysis3")}), llvm::Failed());
+}
+
+// run<ResultTs...>() — type-safe subset.
+TEST_F(AnalysisDriverTest, RunByType) {
+ auto LU = makeLUSummary();
+ const auto E1 = addEntity(*LU, "Entity1");
+ const auto E2 = addEntity(*LU, "Entity2");
+
+ int s1a = insertSummary<Analysis1EntitySummary>(*LU, "Analysis1", E1);
+ insertSummary<Analysis2EntitySummary>(*LU, "Analysis2", E2);
+
+ AnalysisDriver Driver(std::move(LU));
+ auto WPAOrErr = Driver.run<Analysis1Result>();
+ ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
+
+ // Analysis1 was requested — present.
+ auto R1OrErr = WPAOrErr->get<Analysis1Result>();
+ ASSERT_THAT_EXPECTED(R1OrErr, llvm::Succeeded());
+ EXPECT_EQ(R1OrErr->Entries.size(), 1u);
+ EXPECT_TRUE(hasEntry(R1OrErr->Entries, E1, s1a));
+ EXPECT_TRUE(R1OrErr->WasFinalized);
+
+ // Analysis2 was not requested — not present even though data exists.
+ EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis2Result>(), llvm::Failed());
+}
+
+// run<ResultTs...>() — error when a requested type has no data in LUSummary.
+TEST_F(AnalysisDriverTest, RunByTypeErrorMissingData) {
+ auto LU = makeLUSummary();
+ AnalysisDriver Driver(std::move(LU));
+
+ EXPECT_THAT_EXPECTED(Driver.run<Analysis1Result>(), llvm::Failed());
+}
+
+// contains() — present entries return true; absent entries return false.
+TEST_F(AnalysisDriverTest, Contains) {
+ auto LU = makeLUSummary();
+ const auto E1 = addEntity(*LU, "Entity1");
+ insertSummary<Analysis1EntitySummary>(*LU, "Analysis1", E1);
+ insertSummary<Analysis4EntitySummary>(*LU, "Analysis4", E1);
+
+ AnalysisDriver Driver(std::move(LU));
+ auto WPAOrErr = std::move(Driver).run();
+ ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
+
+ EXPECT_TRUE(WPAOrErr->contains<Analysis1Result>());
+ EXPECT_FALSE(WPAOrErr->contains<Analysis2Result>());
+}
+
+} // namespace
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
index 7652ebb390f86..2fec611718475 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
@@ -1,5 +1,6 @@
add_distinct_clang_unittest(ClangScalableAnalysisTests
Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
+ Analysis/AnalysisDriverTest.cpp
ASTEntityMappingTest.cpp
BuildNamespaceTest.cpp
EntityIdTableTest.cpp
>From 6478cc09e7ff9fe17eeabd45fa2edb4ed09f89a6 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 08:04:19 -0700
Subject: [PATCH 02/19] Move
---
.../Core/Analysis/AnalysisBase.h | 2 +-
.../Core/{Model => Analysis}/AnalysisName.h | 6 +++---
.../Core/Analysis/AnalysisRegistry.h | 2 +-
.../Core/{Model => Analysis}/AnalysisTraits.h | 8 ++++----
.../Core/Analysis/DerivedAnalysis.h | 4 ++--
.../Core/Analysis/SummaryAnalysis.h | 2 +-
.../Core/Analysis/WPASuite.h | 4 ++--
.../Core/Support/FormatProviders.h | 2 +-
.../Core/{Model => Analysis}/AnalysisName.cpp | 2 +-
.../ScalableStaticAnalysisFramework/Core/CMakeLists.txt | 2 +-
.../Analysis/AnalysisDriverTest.cpp | 2 +-
11 files changed, 18 insertions(+), 18 deletions(-)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Model => Analysis}/AnalysisName.h (86%)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Model => Analysis}/AnalysisTraits.h (76%)
rename clang/lib/ScalableStaticAnalysisFramework/Core/{Model => Analysis}/AnalysisName.cpp (88%)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
index 478a6fa85d4a8..873d21150ce87 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
@@ -15,7 +15,7 @@
#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISBASE_H
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISBASE_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
#include <vector>
namespace clang::ssaf {
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h
similarity index 86%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h
index 167d8a8b0485e..73ba96ccc594e 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h
@@ -11,8 +11,8 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISNAME_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISNAME_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISNAME_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISNAME_H
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
@@ -46,4 +46,4 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const AnalysisName &AN);
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISNAME_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISNAME_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
index e4faba62c070e..f51d7845b36e9 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
@@ -22,9 +22,9 @@
#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISREGISTRY_H
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISREGISTRY_H
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
#include "llvm/Support/Registry.h"
#include <memory>
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
similarity index 76%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
index 888f4a8e6be4a..ef6a5a56d990a 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
@@ -10,10 +10,10 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISTRAITS_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISTRAITS_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISTRAITS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISTRAITS_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
#include <type_traits>
namespace clang::ssaf {
@@ -30,4 +30,4 @@ struct HasAnalysisName<T, std::void_t<decltype(T::analysisName())>>
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_MODEL_ANALYSISTRAITS_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISTRAITS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
index 7b5695dab7c72..a4de8b186fc76 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
@@ -16,9 +16,9 @@
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_DERIVEDANALYSIS_H
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h"
#include "llvm/Support/Error.h"
#include <map>
#include <memory>
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
index 3a53ec147db4f..2c0f6e7821771 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
@@ -17,7 +17,7 @@
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/EntitySummary.h"
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
index 040f8ea79c0ec..c1a48bbdd3e2b 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
@@ -14,9 +14,9 @@
#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_WPASUITE_H
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_WPASUITE_H
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisTraits.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityIdTable.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
#include "llvm/Support/Error.h"
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
index f50d17d7f035a..1f8e34868c1d7 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
@@ -14,7 +14,7 @@
#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SUPPORT_FORMATPROVIDERS_H
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SUPPORT_FORMATPROVIDERS_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityLinkage.h"
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.cpp
similarity index 88%
rename from clang/lib/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.cpp
rename to clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.cpp
index 95e0c02f9a92f..d49f41ab24eb8 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
using namespace clang::ssaf;
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
index 9e9786dae5a07..7951e77e7c10b 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
@@ -5,9 +5,9 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangScalableStaticAnalysisFrameworkCore
ASTEntityMapping.cpp
Analysis/AnalysisDriver.cpp
+ Analysis/AnalysisName.cpp
Analysis/AnalysisRegistry.cpp
EntityLinker/EntityLinker.cpp
- Model/AnalysisName.cpp
Model/BuildNamespace.cpp
Model/EntityId.cpp
Model/EntityIdTable.cpp
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
index 436e0da580910..22554764adf80 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
@@ -8,12 +8,12 @@
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h"
#include "../TestFixture.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h"
#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Model/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityLinkage.h"
>From 949626d8550924d8987ef836fcf507da2fd9e169 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 08:12:38 -0700
Subject: [PATCH 03/19] Fixes
---
.../Core/Analysis/AnalysisBase.h | 2 +-
.../Core/Analysis/AnalysisDriver.h | 16 +++++-----
.../Core/Analysis/AnalysisResult.h | 2 +-
.../Core/Analysis/DerivedAnalysis.h | 16 +++++++---
.../Core/Analysis/SummaryAnalysis.h | 2 +-
.../Core/Analysis/WPASuite.h | 30 +++++++++++++++++++
.../Core/Analysis/AnalysisDriver.cpp | 6 ++--
7 files changed, 57 insertions(+), 17 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
index 873d21150ce87..29c46f3c2e544 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
@@ -26,7 +26,7 @@ class DerivedAnalysisBase;
/// Minimal common base for both analysis kinds.
///
-/// Not subclassed directly — use SummaryAnalysis<...> or
+/// Not subclassed directly -- use SummaryAnalysis<...> or
/// DerivedAnalysis<...> instead.
class AnalysisBase {
friend class AnalysisDriver;
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
index 1cc5c18d348b9..3ac0d64e7de46 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
@@ -28,14 +28,14 @@ namespace clang::ssaf {
/// Orchestrates whole-program analysis over an LUSummary.
///
/// Three run() patterns are supported:
-/// - run() && — all registered analyses; silently skips any whose
-/// entity data is absent or whose dependency was skipped.
-/// Requires an rvalue driver because this exhausts the
-/// LUSummary.
-/// - run(names) — named subset plus transitive dependencies; returns
-/// Expected and fails if any listed name has no
-/// registered analysis or missing entity data.
-/// - run<ResultTs..> — type-safe variant of run(names).
+/// - run() && -- all registered analyses; silently skips any whose
+/// entity data is absent or whose dependency was
+/// skipped. Requires an rvalue driver because this
+/// exhausts the LUSummary.
+/// - run(names) -- named subset plus transitive dependencies; returns
+/// Expected and fails if any listed name has no
+/// registered analysis or missing entity data.
+/// - run<ResultTs..> -- type-safe variant of run(names).
class AnalysisDriver final {
public:
explicit AnalysisDriver(std::unique_ptr<LUSummary> LU);
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
index 87d781cff30a8..7ac2a9ad7db6a 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
//
// Base class for all whole-program analysis results produced by AnalysisDriver.
-// Replaces SummaryData. Concrete subclasses carry a static analysisName().
+// Concrete subclasses carry a static analysisName().
//
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
index a4de8b186fc76..ecbf31c70c5fe 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
@@ -20,6 +20,7 @@
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h"
#include "llvm/Support/Error.h"
+#include "llvm/Support/ErrorHandling.h"
#include <map>
#include <memory>
#include <vector>
@@ -30,7 +31,7 @@ class AnalysisDriver;
/// Type-erased base for derived analyses. Known to AnalysisDriver.
///
-/// Not subclassed directly — use DerivedAnalysis<ResultT, DepResultTs...>.
+/// Not subclassed directly -- use DerivedAnalysis<ResultT, DepResultTs...>.
/// A derived analysis consumes previously produced AnalysisResult objects
/// and computes a new one via an initialize/step/finalize lifecycle.
class DerivedAnalysisBase : public AnalysisBase {
@@ -66,7 +67,7 @@ class DerivedAnalysisBase : public AnalysisBase {
/// llvm::Expected<bool> step() override;
/// and may override finalize().
///
-/// Dependencies are fixed for the lifetime of the analysis — initialize()
+/// Dependencies are fixed for the lifetime of the analysis: initialize()
/// binds them once, step() is called until it returns false, and
/// finalize() post-processes after convergence.
template <typename ResultT, typename... DepResultTs>
@@ -115,11 +116,18 @@ class DerivedAnalysis : public DerivedAnalysisBase {
private:
/// Seals the type-erased base overload, downcasts, and dispatches to the
- /// typed initialize().
+ /// typed initialize(). All dependencies are guaranteed present by the driver.
llvm::Error
initialize(const std::map<AnalysisName, const AnalysisResult *> &Map) final {
+ auto lookup = [&Map](const AnalysisName &Name) -> const AnalysisResult * {
+ auto It = Map.find(Name);
+ if (It == Map.end())
+ llvm_unreachable("dependency missing from DepResults map; "
+ "dependency graph is not topologically sorted");
+ return It->second;
+ };
return initialize(*static_cast<const DepResultTs *>(
- Map.at(DepResultTs::analysisName()))...);
+ lookup(DepResultTs::analysisName()))...);
}
/// Type-erased result extraction for the driver.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
index 2c0f6e7821771..138e0e4754b5e 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
@@ -30,7 +30,7 @@ class AnalysisDriver;
/// Type-erased base for summary analyses. Known to AnalysisDriver.
///
-/// Not subclassed directly — use SummaryAnalysis<ResultT, EntitySummaryT>.
+/// Not subclassed directly -- use SummaryAnalysis<ResultT, EntitySummaryT>.
/// A summary analysis processes per-entity EntitySummary objects from the
/// LUSummary one at a time, accumulating whole-program data into an
/// AnalysisResult.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
index c1a48bbdd3e2b..33a7124fbacf8 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
@@ -74,6 +74,22 @@ class WPASuite {
return static_cast<ResultT &>(*Result);
}
+ /// Returns a const reference to the result for \p ResultT, or an error if
+ /// absent.
+ template <typename ResultT>
+ [[nodiscard]] llvm::Expected<const ResultT &> get() const {
+ static_assert(std::is_base_of_v<AnalysisResult, ResultT>,
+ "ResultT must derive from AnalysisResult");
+ static_assert(HasAnalysisName<ResultT>::value,
+ "ResultT must have a static analysisName() method");
+
+ auto Result = get(ResultT::analysisName());
+ if (!Result) {
+ return Result.takeError();
+ }
+ return static_cast<const ResultT &>(*Result);
+ }
+
/// Returns a reference to the result for \p Name, or an error if absent.
[[nodiscard]] llvm::Expected<AnalysisResult &> get(AnalysisName Name) {
auto It = Data.find(Name);
@@ -85,6 +101,20 @@ class WPASuite {
}
return *It->second;
}
+
+ /// Returns a const reference to the result for \p Name, or an error if
+ /// absent.
+ [[nodiscard]] llvm::Expected<const AnalysisResult &>
+ get(AnalysisName Name) const {
+ auto It = Data.find(Name);
+ if (It == Data.end()) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ "no result for analysis '{0}' in WPASuite",
+ Name.str())
+ .build();
+ }
+ return *It->second;
+ }
};
} // namespace clang::ssaf
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
index 64b496142cd47..41fd55ce88c6b 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
@@ -114,7 +114,8 @@ llvm::Error AnalysisDriver::executeSummaryAnalysis(
return Err;
}
- Suite.Data.emplace(Summary->analysisName(), std::move(*Summary).result());
+ AnalysisName Name = Summary->analysisName();
+ Suite.Data.emplace(Name, std::move(*Summary).result());
return llvm::Error::success();
}
@@ -151,7 +152,8 @@ llvm::Error AnalysisDriver::executeDerivedAnalysis(
return Err;
}
- Suite.Data.emplace(Derived->analysisName(), std::move(*Derived).result());
+ AnalysisName Name = Derived->analysisName();
+ Suite.Data.emplace(Name, std::move(*Derived).result());
return llvm::Error::success();
}
>From 53f52415c6c7a848b689731558a530a0b673a449 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 08:14:26 -0700
Subject: [PATCH 04/19] More Fix
---
.../Core/Support/FormatProviders.h | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
index 1f8e34868c1d7..6cc816edfd967 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
@@ -25,13 +25,6 @@
namespace llvm {
-template <> struct format_provider<clang::ssaf::AnalysisName> {
- static void format(const clang::ssaf::AnalysisName &Val, raw_ostream &OS,
- StringRef Style) {
- OS << Val;
- }
-};
-
template <> struct format_provider<clang::ssaf::EntityId> {
static void format(const clang::ssaf::EntityId &Val, raw_ostream &OS,
StringRef Style) {
@@ -88,6 +81,13 @@ template <> struct format_provider<clang::ssaf::SummaryName> {
}
};
+template <> struct format_provider<clang::ssaf::AnalysisName> {
+ static void format(const clang::ssaf::AnalysisName &Val, raw_ostream &OS,
+ StringRef Style) {
+ OS << Val;
+ }
+};
+
} // namespace llvm
#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SUPPORT_FORMATPROVIDERS_H
>From 5963996bb6734384145a702d5415430e9a761516 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 08:36:36 -0700
Subject: [PATCH 05/19] Remove non-const methods
---
.../Core/Analysis/DerivedAnalysis.h | 3 ++-
.../Core/Analysis/WPASuite.h | 26 -------------------
2 files changed, 2 insertions(+), 27 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
index ecbf31c70c5fe..e8c2f2fb8c188 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
@@ -121,9 +121,10 @@ class DerivedAnalysis : public DerivedAnalysisBase {
initialize(const std::map<AnalysisName, const AnalysisResult *> &Map) final {
auto lookup = [&Map](const AnalysisName &Name) -> const AnalysisResult * {
auto It = Map.find(Name);
- if (It == Map.end())
+ if (It == Map.end()) {
llvm_unreachable("dependency missing from DepResults map; "
"dependency graph is not topologically sorted");
+ }
return It->second;
};
return initialize(*static_cast<const DepResultTs *>(
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
index 33a7124fbacf8..0e5470495ec23 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
@@ -60,20 +60,6 @@ class WPASuite {
return Data.find(Name) != Data.end();
}
- /// Returns a reference to the result for \p ResultT, or an error if absent.
- template <typename ResultT> [[nodiscard]] llvm::Expected<ResultT &> get() {
- static_assert(std::is_base_of_v<AnalysisResult, ResultT>,
- "ResultT must derive from AnalysisResult");
- static_assert(HasAnalysisName<ResultT>::value,
- "ResultT must have a static analysisName() method");
-
- auto Result = get(ResultT::analysisName());
- if (!Result) {
- return Result.takeError();
- }
- return static_cast<ResultT &>(*Result);
- }
-
/// Returns a const reference to the result for \p ResultT, or an error if
/// absent.
template <typename ResultT>
@@ -90,18 +76,6 @@ class WPASuite {
return static_cast<const ResultT &>(*Result);
}
- /// Returns a reference to the result for \p Name, or an error if absent.
- [[nodiscard]] llvm::Expected<AnalysisResult &> get(AnalysisName Name) {
- auto It = Data.find(Name);
- if (It == Data.end()) {
- return ErrorBuilder::create(std::errc::invalid_argument,
- "no result for analysis '{0}' in WPASuite",
- Name.str())
- .build();
- }
- return *It->second;
- }
-
/// Returns a const reference to the result for \p Name, or an error if
/// absent.
[[nodiscard]] llvm::Expected<const AnalysisResult &>
>From 01e8017f8d57812c38db1f7ce09e1b85b57e0d7c Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 18:41:34 -0700
Subject: [PATCH 06/19] Fix
---
.../Core/Analysis/AnalysisDriver.h | 19 ++++---
.../Core/Analysis/AnalysisRegistry.h | 8 +--
.../Core/Analysis/AnalysisDriver.cpp | 57 +++++++++----------
.../Core/Analysis/AnalysisRegistry.cpp | 7 ++-
.../Analysis/AnalysisDriverTest.cpp | 13 +++--
5 files changed, 55 insertions(+), 49 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
index 3ac0d64e7de46..c17cd584f2434 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
@@ -15,7 +15,6 @@
#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISDRIVER_H
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISDRIVER_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h"
#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h"
#include "llvm/ADT/ArrayRef.h"
@@ -25,13 +24,18 @@
namespace clang::ssaf {
+class AnalysisBase;
+class SummaryAnalysisBase;
+class DerivedAnalysisBase;
+
/// Orchestrates whole-program analysis over an LUSummary.
///
/// Three run() patterns are supported:
-/// - run() && -- all registered analyses; silently skips any whose
-/// entity data is absent or whose dependency was
-/// skipped. Requires an rvalue driver because this
-/// exhausts the LUSummary.
+/// - run() && -- all registered analyses in topological dependency
+/// order. Returns an error if any registered analysis
+/// has no matching entity data in the LUSummary.
+/// Requires an rvalue driver because this exhausts the
+/// LUSummary.
/// - run(names) -- named subset plus transitive dependencies; returns
/// Expected and fails if any listed name has no
/// registered analysis or missing entity data.
@@ -41,7 +45,8 @@ class AnalysisDriver final {
explicit AnalysisDriver(std::unique_ptr<LUSummary> LU);
/// Runs all registered analyses in topological dependency order.
- /// Silently skips analyses with absent entity data or skipped dependencies.
+ /// Returns an error if any registered analysis has no matching entity data
+ /// in the LUSummary.
///
/// Requires an rvalue driver (std::move(Driver).run()) because this
/// exhausts all remaining LUSummary data.
@@ -69,7 +74,7 @@ class AnalysisDriver final {
/// dependencies) and returns them in topological order via a single DFS.
/// Reports an error on unregistered names or cycles.
static llvm::Expected<std::vector<std::unique_ptr<AnalysisBase>>>
- sortTopologically(llvm::ArrayRef<AnalysisName> Roots);
+ sort(llvm::ArrayRef<AnalysisName> Roots);
/// Executes a topologically-sorted analysis list and returns a WPASuite.
/// \p IdTable is moved into the returned WPASuite.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
index f51d7845b36e9..33734870218b0 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
@@ -26,9 +26,9 @@
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/Registry.h"
#include <memory>
-#include <optional>
#include <string>
#include <vector>
@@ -81,9 +81,9 @@ class AnalysisRegistry {
/// Returns the names of all registered analyses.
static const std::vector<AnalysisName> &names();
- /// Instantiates the analysis registered under \p Name, or returns
- /// std::nullopt if no such analysis is registered.
- static std::optional<std::unique_ptr<AnalysisBase>>
+ /// Instantiates the analysis registered under \p Name, or returns an error
+ /// if no such analysis is registered.
+ static llvm::Expected<std::unique_ptr<AnalysisBase>>
instantiate(llvm::StringRef Name);
private:
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
index 41fd55ce88c6b..b010583a03d4e 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
@@ -23,59 +23,45 @@ AnalysisDriver::AnalysisDriver(std::unique_ptr<LUSummary> LU)
: LU(std::move(LU)) {}
llvm::Expected<std::vector<std::unique_ptr<AnalysisBase>>>
-AnalysisDriver::sortTopologically(llvm::ArrayRef<AnalysisName> Roots) {
+AnalysisDriver::sort(llvm::ArrayRef<AnalysisName> Roots) {
struct Visitor {
enum class State { Unvisited, Visiting, Visited };
std::map<AnalysisName, State> Marks;
- std::map<AnalysisName, std::unique_ptr<AnalysisBase>> Analyses;
std::vector<std::unique_ptr<AnalysisBase>> Result;
- State getState(const AnalysisName &Name) {
- auto MarkIt = Marks.find(Name);
- return MarkIt != Marks.end() ? MarkIt->second : State::Unvisited;
- }
-
- void setState(const AnalysisName &Name, State S) { Marks[Name] = S; }
-
llvm::Error visit(const AnalysisName &Name) {
- State S = getState(Name);
-
- if (S == State::Visited) {
+ auto It = Marks.find(Name);
+ switch (It != Marks.end() ? It->second : State::Unvisited) {
+ case State::Visited:
return llvm::Error::success();
- }
- if (S == State::Visiting) {
+ case State::Visiting:
return ErrorBuilder::create(std::errc::invalid_argument,
"cycle detected involving analysis '{0}'",
Name)
.build();
- }
- if (S == State::Unvisited) {
- setState(Name, State::Visiting);
+ case State::Unvisited: {
+ Marks[Name] = State::Visiting;
auto V = AnalysisRegistry::instantiate(Name.str());
if (!V) {
- return ErrorBuilder::create(std::errc::invalid_argument,
- "no analysis registered for '{0}'", Name)
- .build();
+ return V.takeError();
}
- const auto &Deps = (*V)->dependencyNames();
- Analyses[Name] = std::move(*V);
-
- for (const auto &Dep : Deps) {
+ auto Analysis = std::move(*V);
+ for (const auto &Dep : Analysis->dependencyNames()) {
if (auto Err = visit(Dep)) {
return Err;
}
}
- setState(Name, State::Visited);
- Result.push_back(std::move(Analyses[Name]));
- Analyses.erase(Name);
+ Marks[Name] = State::Visited;
+ Result.push_back(std::move(Analysis));
return llvm::Error::success();
}
+ }
llvm_unreachable("unhandled State");
}
};
@@ -165,18 +151,23 @@ AnalysisDriver::execute(EntityIdTable IdTable,
Suite.IdTable = std::move(IdTable);
for (auto &V : Sorted) {
- if (V->TheKind == AnalysisBase::Kind::Summary) {
+ switch (V->TheKind) {
+ case AnalysisBase::Kind::Summary: {
auto SA = std::unique_ptr<SummaryAnalysisBase>(
static_cast<SummaryAnalysisBase *>(V.release()));
if (auto Err = executeSummaryAnalysis(std::move(SA), Suite)) {
return std::move(Err);
}
- } else {
+ break;
+ }
+ case AnalysisBase::Kind::Derived: {
auto DA = std::unique_ptr<DerivedAnalysisBase>(
static_cast<DerivedAnalysisBase *>(V.release()));
if (auto Err = executeDerivedAnalysis(std::move(DA), Suite)) {
return std::move(Err);
}
+ break;
+ }
}
}
@@ -184,12 +175,16 @@ AnalysisDriver::execute(EntityIdTable IdTable,
}
llvm::Expected<WPASuite> AnalysisDriver::run() && {
- return run(AnalysisRegistry::names());
+ auto ExpectedSorted = sort(AnalysisRegistry::names());
+ if (!ExpectedSorted) {
+ return ExpectedSorted.takeError();
+ }
+ return execute(std::move(LU->IdTable), std::move(*ExpectedSorted));
}
llvm::Expected<WPASuite>
AnalysisDriver::run(llvm::ArrayRef<AnalysisName> Names) {
- auto ExpectedSorted = sortTopologically(Names);
+ auto ExpectedSorted = sort(Names);
if (!ExpectedSorted) {
return ExpectedSorted.takeError();
}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
index aac05fdb08453..e9c5208aa7be6 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
#include "llvm/ADT/STLExtras.h"
using namespace clang;
@@ -26,12 +27,14 @@ const std::vector<AnalysisName> &AnalysisRegistry::names() {
return analysisNames;
}
-std::optional<std::unique_ptr<AnalysisBase>>
+llvm::Expected<std::unique_ptr<AnalysisBase>>
AnalysisRegistry::instantiate(llvm::StringRef Name) {
for (const auto &Entry : RegistryT::entries()) {
if (Entry.getName() == Name) {
return std::unique_ptr<AnalysisBase>(Entry.instantiate());
}
}
- return std::nullopt;
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ "no analysis registered for '{0}'", Name)
+ .build();
}
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
index 22554764adf80..8e9c34e081403 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
@@ -234,11 +234,14 @@ TEST(AnalysisRegistryTest, AnalysisIsRegistered) {
}
TEST(AnalysisRegistryTest, AnalysisCanBeInstantiated) {
- EXPECT_FALSE(
- AnalysisRegistry::instantiate("AnalysisNonExisting").has_value());
- EXPECT_TRUE(AnalysisRegistry::instantiate("Analysis1").has_value());
- EXPECT_TRUE(AnalysisRegistry::instantiate("Analysis2").has_value());
- EXPECT_TRUE(AnalysisRegistry::instantiate("Analysis4").has_value());
+ EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("AnalysisNonExisting"),
+ llvm::Failed());
+ EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis1"),
+ llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis2"),
+ llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis4"),
+ llvm::Succeeded());
}
// run() — processes all registered analyses present in the LUSummary.
>From 84d650f704fbb0860e95e82540ef90939ab5ee24 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 18:52:28 -0700
Subject: [PATCH 07/19] Fix
---
clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
index 7951e77e7c10b..5ab085c0ef07e 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
@@ -3,10 +3,10 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangScalableStaticAnalysisFrameworkCore
- ASTEntityMapping.cpp
Analysis/AnalysisDriver.cpp
Analysis/AnalysisName.cpp
Analysis/AnalysisRegistry.cpp
+ ASTEntityMapping.cpp
EntityLinker/EntityLinker.cpp
Model/BuildNamespace.cpp
Model/EntityId.cpp
>From 3b3ed414e272ded7e807f4a3f10aa0dbbd2dd17e Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 19:06:36 -0700
Subject: [PATCH 08/19] Fix
---
.../Core/Analysis/DerivedAnalysis.h | 6 ++++--
.../Core/Analysis/SummaryAnalysis.h | 6 ++++--
.../Core/Analysis/WPASuite.h | 4 ++--
3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
index e8c2f2fb8c188..446778f2fd513 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
@@ -28,6 +28,7 @@
namespace clang::ssaf {
class AnalysisDriver;
+class AnalysisRegistry;
/// Type-erased base for derived analyses. Known to AnalysisDriver.
///
@@ -81,13 +82,14 @@ class DerivedAnalysis : public DerivedAnalysisBase {
static_assert((HasAnalysisName<DepResultTs>::value && ...),
"Every DepResultT must have a static analysisName() method");
+ friend class AnalysisRegistry;
+ using ResultType = ResultT;
+
std::unique_ptr<ResultT> Result;
public:
DerivedAnalysis() : Result(std::make_unique<ResultT>()) {}
- using ResultType = ResultT;
-
/// Used by AnalysisRegistry::Add to derive the registry entry name.
AnalysisName analysisName() const final { return ResultT::analysisName(); }
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
index 138e0e4754b5e..6d6892305d217 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
@@ -27,6 +27,7 @@
namespace clang::ssaf {
class AnalysisDriver;
+class AnalysisRegistry;
/// Type-erased base for summary analyses. Known to AnalysisDriver.
///
@@ -78,13 +79,14 @@ class SummaryAnalysis : public SummaryAnalysisBase {
static_assert(std::is_base_of_v<EntitySummary, EntitySummaryT>,
"EntitySummaryT must derive from EntitySummary");
+ friend class AnalysisRegistry;
+ using ResultType = ResultT;
+
std::unique_ptr<ResultT> Result;
public:
SummaryAnalysis() : Result(std::make_unique<ResultT>()) {}
- using ResultType = ResultT;
-
/// Used by AnalysisRegistry::Add to derive the registry entry name.
AnalysisName analysisName() const final { return ResultT::analysisName(); }
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
index 0e5470495ec23..ce2d334a15071 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
//
// The value returned by AnalysisDriver::run(). Bundles the EntityIdTable
-// (moved from the LUSummary) with the analysis results keyed by AnalysisName.
+// with the analysis results keyed by AnalysisName.
//
//===----------------------------------------------------------------------===//
@@ -42,7 +42,7 @@ class WPASuite {
public:
/// Returns the EntityIdTable that maps EntityId values to their symbolic
- /// names. Moved from the LUSummary during AnalysisDriver::run().
+ /// names.
const EntityIdTable &idTable() const { return IdTable; }
/// Returns true if a result for \p ResultT is present.
>From e440a667154c8226c81a0d112c17020676f53d67 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 20:44:39 -0700
Subject: [PATCH 09/19] Fix
---
.../Core/Analysis/AnalysisDriver.cpp | 18 +-
.../Analysis/AnalysisDriverTest.cpp | 186 ++++++++++++++++--
2 files changed, 188 insertions(+), 16 deletions(-)
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
index b010583a03d4e..d510632cf4278 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
@@ -11,6 +11,7 @@
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include <map>
@@ -28,8 +29,18 @@ AnalysisDriver::sort(llvm::ArrayRef<AnalysisName> Roots) {
enum class State { Unvisited, Visiting, Visited };
std::map<AnalysisName, State> Marks;
+ std::vector<AnalysisName> Path;
std::vector<std::unique_ptr<AnalysisBase>> Result;
+ std::string formatCycle(const AnalysisName &CycleEntry) const {
+ auto CycleBegin = llvm::find(Path, CycleEntry);
+ std::string Cycle;
+ llvm::raw_string_ostream OS(Cycle);
+ llvm::interleave(llvm::make_range(CycleBegin, Path.end()), OS, " -> ");
+ OS << " -> " << CycleEntry;
+ return Cycle;
+ }
+
llvm::Error visit(const AnalysisName &Name) {
auto It = Marks.find(Name);
switch (It != Marks.end() ? It->second : State::Unvisited) {
@@ -38,26 +49,29 @@ AnalysisDriver::sort(llvm::ArrayRef<AnalysisName> Roots) {
case State::Visiting:
return ErrorBuilder::create(std::errc::invalid_argument,
- "cycle detected involving analysis '{0}'",
- Name)
+ "cycle detected: {0}", formatCycle(Name))
.build();
case State::Unvisited: {
Marks[Name] = State::Visiting;
+ Path.push_back(Name);
auto V = AnalysisRegistry::instantiate(Name.str());
if (!V) {
+ Path.pop_back();
return V.takeError();
}
auto Analysis = std::move(*V);
for (const auto &Dep : Analysis->dependencyNames()) {
if (auto Err = visit(Dep)) {
+ Path.pop_back();
return Err;
}
}
Marks[Name] = State::Visited;
+ Path.pop_back();
Result.push_back(std::move(Analysis));
return llvm::Error::success();
}
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
index 8e9c34e081403..f2bab5b40d914 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
@@ -11,6 +11,7 @@
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h"
#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h"
@@ -23,6 +24,7 @@
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
#include <memory>
+#include <string>
#include <utility>
#include <vector>
@@ -85,6 +87,7 @@ class Analysis1Result final : public AnalysisResult {
public:
static AnalysisName analysisName() { return AnalysisName("Analysis1"); }
std::vector<std::pair<EntityId, int>> Entries;
+ bool WasInitialized = false;
bool WasFinalized = false;
};
@@ -92,6 +95,7 @@ class Analysis2Result final : public AnalysisResult {
public:
static AnalysisName analysisName() { return AnalysisName("Analysis2"); }
std::vector<std::pair<EntityId, int>> Entries;
+ bool WasInitialized = false;
bool WasFinalized = false;
};
@@ -108,9 +112,36 @@ class Analysis4Result final : public AnalysisResult {
public:
static AnalysisName analysisName() { return AnalysisName("Analysis4"); }
std::vector<std::pair<EntityId, int>> Entries;
+ bool WasInitialized = false;
bool WasFinalized = false;
};
+// Analysis5 is a derived analysis that depends on Analysis1, Analysis2, and
+// Analysis4. It verifies that the driver passes dependency results to
+// initialize() and that the initialize/step/finalize lifecycle is respected.
+class Analysis5Result final : public AnalysisResult {
+public:
+ static AnalysisName analysisName() { return AnalysisName("Analysis5"); }
+ std::vector<std::string> CallSequence;
+ std::vector<std::pair<EntityId, int>> Analysis1Entries;
+ std::vector<std::pair<EntityId, int>> Analysis2Entries;
+ std::vector<std::pair<EntityId, int>> Analysis4Entries;
+};
+
+// CycleA and CycleB form a dependency cycle (CycleA → CycleB → CycleA).
+// Registered solely to exercise cycle detection in AnalysisDriver::sort().
+// initialize() and step() are unreachable stubs — the cycle is caught before
+// any analysis executes.
+class CycleAResult final : public AnalysisResult {
+public:
+ static AnalysisName analysisName() { return AnalysisName("CycleA"); }
+};
+
+class CycleBResult final : public AnalysisResult {
+public:
+ static AnalysisName analysisName() { return AnalysisName("CycleB"); }
+};
+
// ---------------------------------------------------------------------------
// Analysis destruction flags (reset in SetUp)
// ---------------------------------------------------------------------------
@@ -118,6 +149,7 @@ class Analysis4Result final : public AnalysisResult {
static bool Analysis1WasDestroyed = false;
static bool Analysis2WasDestroyed = false;
static bool Analysis4WasDestroyed = false;
+static bool Analysis5WasDestroyed = false;
// ---------------------------------------------------------------------------
// Analyses
@@ -128,6 +160,11 @@ class Analysis1 final
public:
~Analysis1() { Analysis1WasDestroyed = true; }
+ llvm::Error initialize() override {
+ result().WasInitialized = true;
+ return llvm::Error::success();
+ }
+
llvm::Error add(EntityId Id, const Analysis1EntitySummary &S) override {
result().Entries.push_back({Id, S.InstanceId});
return llvm::Error::success();
@@ -146,6 +183,11 @@ class Analysis2 final
public:
~Analysis2() { Analysis2WasDestroyed = true; }
+ llvm::Error initialize() override {
+ result().WasInitialized = true;
+ return llvm::Error::success();
+ }
+
llvm::Error add(EntityId Id, const Analysis2EntitySummary &S) override {
result().Entries.push_back({Id, S.InstanceId});
return llvm::Error::success();
@@ -159,11 +201,18 @@ class Analysis2 final
static AnalysisRegistry::Add<Analysis2> RegAnalysis2("Analysis for Analysis2");
+// No Analysis3 or registration for Analysis3.
+
class Analysis4 final
: public SummaryAnalysis<Analysis4Result, Analysis4EntitySummary> {
public:
~Analysis4() { Analysis4WasDestroyed = true; }
+ llvm::Error initialize() override {
+ result().WasInitialized = true;
+ return llvm::Error::success();
+ }
+
llvm::Error add(EntityId Id, const Analysis4EntitySummary &S) override {
result().Entries.push_back({Id, S.InstanceId});
return llvm::Error::success();
@@ -177,6 +226,56 @@ class Analysis4 final
static AnalysisRegistry::Add<Analysis4> RegAnalysis4("Analysis for Analysis4");
+class Analysis5 final
+ : public DerivedAnalysis<Analysis5Result, Analysis1Result, Analysis2Result,
+ Analysis4Result> {
+ int StepCount = 0;
+
+public:
+ ~Analysis5() { Analysis5WasDestroyed = true; }
+
+ llvm::Error initialize(const Analysis1Result &R1, const Analysis2Result &R2,
+ const Analysis4Result &R4) override {
+ result().CallSequence.push_back("initialize");
+ result().Analysis1Entries = R1.Entries;
+ result().Analysis2Entries = R2.Entries;
+ result().Analysis4Entries = R4.Entries;
+ return llvm::Error::success();
+ }
+
+ llvm::Expected<bool> step() override {
+ result().CallSequence.push_back("step");
+ return ++StepCount < 2;
+ }
+
+ llvm::Error finalize() override {
+ result().CallSequence.push_back("finalize");
+ return llvm::Error::success();
+ }
+};
+
+static AnalysisRegistry::Add<Analysis5> RegAnalysis5("Analysis for Analysis5");
+
+class CycleA final : public DerivedAnalysis<CycleAResult, CycleBResult> {
+public:
+ llvm::Error initialize(const CycleBResult &) override {
+ return llvm::Error::success();
+ }
+ llvm::Expected<bool> step() override { return false; }
+};
+
+static AnalysisRegistry::Add<CycleA> RegCycleA("Cyclic analysis A (test only)");
+
+class CycleB final : public DerivedAnalysis<CycleBResult, CycleAResult> {
+public:
+ llvm::Error initialize(const CycleAResult &) override {
+ return llvm::Error::success();
+ }
+ llvm::Expected<bool> step() override { return false; }
+};
+
+static AnalysisRegistry::Add<CycleB> RegCycleB("Cyclic analysis B (test only)");
+
// ---------------------------------------------------------------------------
// Fixture
// ---------------------------------------------------------------------------
@@ -191,6 +290,7 @@ class AnalysisDriverTest : public TestFixture {
Analysis1WasDestroyed = false;
Analysis2WasDestroyed = false;
Analysis4WasDestroyed = false;
+ Analysis5WasDestroyed = false;
}
std::unique_ptr<LUSummary> makeLUSummary() {
@@ -227,25 +327,36 @@ class AnalysisDriverTest : public TestFixture {
// ---------------------------------------------------------------------------
TEST(AnalysisRegistryTest, AnalysisIsRegistered) {
- EXPECT_FALSE(AnalysisRegistry::contains("AnalysisNonExisting"));
EXPECT_TRUE(AnalysisRegistry::contains("Analysis1"));
EXPECT_TRUE(AnalysisRegistry::contains("Analysis2"));
+ EXPECT_FALSE(AnalysisRegistry::contains("Analysis3"));
EXPECT_TRUE(AnalysisRegistry::contains("Analysis4"));
+ EXPECT_TRUE(AnalysisRegistry::contains("Analysis5"));
+ EXPECT_TRUE(AnalysisRegistry::contains("CycleA"));
+ EXPECT_TRUE(AnalysisRegistry::contains("CycleB"));
}
TEST(AnalysisRegistryTest, AnalysisCanBeInstantiated) {
EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("AnalysisNonExisting"),
- llvm::Failed());
+ llvm::FailedWithMessage(
+ "no analysis registered for 'AnalysisNonExisting'"));
EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis1"),
llvm::Succeeded());
EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis2"),
llvm::Succeeded());
EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis4"),
llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis5"),
+ llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("CycleA"),
+ llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("CycleB"),
+ llvm::Succeeded());
}
-// run() — processes all registered analyses present in the LUSummary.
-// Silently skips data whose analysis is unregistered (Analysis3).
+// run<T...>() — processes the non-cyclic analyses in topological order.
+// CycleA and CycleB are excluded because they form a cycle; run() && would
+// error on them, so the type-safe subset overload is used here instead.
TEST_F(AnalysisDriverTest, RunAll) {
auto LU = makeLUSummary();
const auto E1 = addEntity(*LU, "Entity1");
@@ -263,7 +374,8 @@ TEST_F(AnalysisDriverTest, RunAll) {
(void)insertSummary<Analysis3EntitySummary>(*LU, "Analysis3", E1);
AnalysisDriver Driver(std::move(LU));
- auto WPAOrErr = std::move(Driver).run();
+ auto WPAOrErr = Driver.run<Analysis1Result, Analysis2Result, Analysis4Result,
+ Analysis5Result>();
ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
{
@@ -272,6 +384,7 @@ TEST_F(AnalysisDriverTest, RunAll) {
EXPECT_EQ(R1OrErr->Entries.size(), 2u);
EXPECT_TRUE(hasEntry(R1OrErr->Entries, E1, s1a));
EXPECT_TRUE(hasEntry(R1OrErr->Entries, E2, s1b));
+ EXPECT_TRUE(R1OrErr->WasInitialized);
EXPECT_TRUE(R1OrErr->WasFinalized);
EXPECT_TRUE(Analysis1WasDestroyed);
}
@@ -282,6 +395,7 @@ TEST_F(AnalysisDriverTest, RunAll) {
EXPECT_EQ(R2OrErr->Entries.size(), 2u);
EXPECT_TRUE(hasEntry(R2OrErr->Entries, E2, s2a));
EXPECT_TRUE(hasEntry(R2OrErr->Entries, E3, s2b));
+ EXPECT_TRUE(R2OrErr->WasInitialized);
EXPECT_TRUE(R2OrErr->WasFinalized);
EXPECT_TRUE(Analysis2WasDestroyed);
}
@@ -291,12 +405,32 @@ TEST_F(AnalysisDriverTest, RunAll) {
ASSERT_THAT_EXPECTED(R4OrErr, llvm::Succeeded());
EXPECT_EQ(R4OrErr->Entries.size(), 1u);
EXPECT_TRUE(hasEntry(R4OrErr->Entries, E4, s4a));
+ EXPECT_TRUE(R4OrErr->WasInitialized);
EXPECT_TRUE(R4OrErr->WasFinalized);
EXPECT_TRUE(Analysis4WasDestroyed);
}
+ {
+ auto R5OrErr = WPAOrErr->get<Analysis5Result>();
+ ASSERT_THAT_EXPECTED(R5OrErr, llvm::Succeeded());
+ EXPECT_EQ(
+ R5OrErr->CallSequence,
+ (std::vector<std::string>{"initialize", "step", "step", "finalize"}));
+ EXPECT_EQ(R5OrErr->Analysis1Entries.size(), 2u);
+ EXPECT_TRUE(hasEntry(R5OrErr->Analysis1Entries, E1, s1a));
+ EXPECT_TRUE(hasEntry(R5OrErr->Analysis1Entries, E2, s1b));
+ EXPECT_EQ(R5OrErr->Analysis2Entries.size(), 2u);
+ EXPECT_TRUE(hasEntry(R5OrErr->Analysis2Entries, E2, s2a));
+ EXPECT_TRUE(hasEntry(R5OrErr->Analysis2Entries, E3, s2b));
+ EXPECT_EQ(R5OrErr->Analysis4Entries.size(), 1u);
+ EXPECT_TRUE(hasEntry(R5OrErr->Analysis4Entries, E4, s4a));
+ EXPECT_TRUE(Analysis5WasDestroyed);
+ }
+
// Unregistered analysis — not present in WPA.
- EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis3Result>(), llvm::Failed());
+ EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis3Result>(),
+ llvm::FailedWithMessage(
+ "no result for analysis 'Analysis3' in WPASuite"));
}
// run(names) — processes only the analyses for the given names.
@@ -317,10 +451,13 @@ TEST_F(AnalysisDriverTest, RunByName) {
ASSERT_THAT_EXPECTED(R1OrErr, llvm::Succeeded());
EXPECT_EQ(R1OrErr->Entries.size(), 1u);
EXPECT_TRUE(hasEntry(R1OrErr->Entries, E1, s1a));
+ EXPECT_TRUE(R1OrErr->WasInitialized);
EXPECT_TRUE(R1OrErr->WasFinalized);
// Analysis2 was not requested — not present even though data exists.
- EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis2Result>(), llvm::Failed());
+ EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis2Result>(),
+ llvm::FailedWithMessage(
+ "no result for analysis 'Analysis2' in WPASuite"));
}
// run(names) — error when a requested name has no data in LUSummary.
@@ -328,7 +465,9 @@ TEST_F(AnalysisDriverTest, RunByNameErrorMissingData) {
auto LU = makeLUSummary();
AnalysisDriver Driver(std::move(LU));
- EXPECT_THAT_EXPECTED(Driver.run({AnalysisName("Analysis1")}), llvm::Failed());
+ EXPECT_THAT_EXPECTED(
+ Driver.run({AnalysisName("Analysis1")}),
+ llvm::FailedWithMessage("no data for analysis 'Analysis1' in LUSummary"));
}
// run(names) — error when a requested name has no registered analysis.
@@ -340,7 +479,9 @@ TEST_F(AnalysisDriverTest, RunByNameErrorMissingAnalysis) {
AnalysisDriver Driver(std::move(LU));
// Analysis3 has data but no registered analysis.
- EXPECT_THAT_EXPECTED(Driver.run({AnalysisName("Analysis3")}), llvm::Failed());
+ EXPECT_THAT_EXPECTED(
+ Driver.run({AnalysisName("Analysis3")}),
+ llvm::FailedWithMessage("no analysis registered for 'Analysis3'"));
}
// run<ResultTs...>() — type-safe subset.
@@ -361,10 +502,13 @@ TEST_F(AnalysisDriverTest, RunByType) {
ASSERT_THAT_EXPECTED(R1OrErr, llvm::Succeeded());
EXPECT_EQ(R1OrErr->Entries.size(), 1u);
EXPECT_TRUE(hasEntry(R1OrErr->Entries, E1, s1a));
+ EXPECT_TRUE(R1OrErr->WasInitialized);
EXPECT_TRUE(R1OrErr->WasFinalized);
// Analysis2 was not requested — not present even though data exists.
- EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis2Result>(), llvm::Failed());
+ EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis2Result>(),
+ llvm::FailedWithMessage(
+ "no result for analysis 'Analysis2' in WPASuite"));
}
// run<ResultTs...>() — error when a requested type has no data in LUSummary.
@@ -372,7 +516,9 @@ TEST_F(AnalysisDriverTest, RunByTypeErrorMissingData) {
auto LU = makeLUSummary();
AnalysisDriver Driver(std::move(LU));
- EXPECT_THAT_EXPECTED(Driver.run<Analysis1Result>(), llvm::Failed());
+ EXPECT_THAT_EXPECTED(
+ Driver.run<Analysis1Result>(),
+ llvm::FailedWithMessage("no data for analysis 'Analysis1' in LUSummary"));
}
// contains() — present entries return true; absent entries return false.
@@ -380,14 +526,26 @@ TEST_F(AnalysisDriverTest, Contains) {
auto LU = makeLUSummary();
const auto E1 = addEntity(*LU, "Entity1");
insertSummary<Analysis1EntitySummary>(*LU, "Analysis1", E1);
+ insertSummary<Analysis2EntitySummary>(*LU, "Analysis2", E1);
insertSummary<Analysis4EntitySummary>(*LU, "Analysis4", E1);
AnalysisDriver Driver(std::move(LU));
- auto WPAOrErr = std::move(Driver).run();
+ auto WPAOrErr = Driver.run<Analysis1Result, Analysis2Result, Analysis4Result,
+ Analysis5Result>();
ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
-
EXPECT_TRUE(WPAOrErr->contains<Analysis1Result>());
- EXPECT_FALSE(WPAOrErr->contains<Analysis2Result>());
+ // Analysis3 has no registered analysis — never present in WPA.
+ EXPECT_FALSE(WPAOrErr->contains<Analysis3Result>());
+}
+
+// run() && — errors when the registry contains a dependency cycle.
+TEST_F(AnalysisDriverTest, CycleDetected) {
+ auto LU = makeLUSummary();
+ AnalysisDriver Driver(std::move(LU));
+ EXPECT_THAT_EXPECTED(
+ std::move(Driver).run(),
+ llvm::FailedWithMessage("cycle detected: AnalysisName(CycleA) -> "
+ "AnalysisName(CycleB) -> AnalysisName(CycleA)"));
}
} // namespace
>From 0d8c983591d11f32bdff9bfe8a8713dcc02e352f Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 21:23:40 -0700
Subject: [PATCH 10/19] Fix
---
.../Core/Analysis/AnalysisRegistry.h | 9 +++++++--
.../Core/Analysis/AnalysisRegistry.cpp | 10 +++++++---
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
index 33734870218b0..4ed5999bda477 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
@@ -64,7 +64,7 @@ class AnalysisRegistry {
if (contains(Name)) {
ErrorBuilder::fatal("duplicate analysis registration for '{0}'", Name);
}
- analysisNames.push_back(AnalysisT::ResultType::analysisName());
+ getAnalysisNames().push_back(AnalysisT::ResultType::analysisName());
}
Add(const Add &) = delete;
@@ -87,7 +87,12 @@ class AnalysisRegistry {
instantiate(llvm::StringRef Name);
private:
- static std::vector<AnalysisName> analysisNames;
+ /// 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
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
index e9c5208aa7be6..91e95fa8b2379 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
@@ -17,14 +17,18 @@ using RegistryT = llvm::Registry<AnalysisBase>;
LLVM_INSTANTIATE_REGISTRY(RegistryT)
-std::vector<AnalysisName> AnalysisRegistry::analysisNames;
+std::vector<AnalysisName> &AnalysisRegistry::getAnalysisNames() {
+ static std::vector<AnalysisName> Names;
+ return Names;
+}
bool AnalysisRegistry::contains(llvm::StringRef Name) {
- return llvm::is_contained(analysisNames, AnalysisName(std::string(Name)));
+ return llvm::is_contained(getAnalysisNames(),
+ AnalysisName(std::string(Name)));
}
const std::vector<AnalysisName> &AnalysisRegistry::names() {
- return analysisNames;
+ return getAnalysisNames();
}
llvm::Expected<std::unique_ptr<AnalysisBase>>
>From 5e2fd99672ccac872a924bd60e765d91ab290881 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 21:50:01 -0700
Subject: [PATCH 11/19] More fixes
---
.../Core/Analysis/DerivedAnalysis.h | 7 ++++---
.../Core/EntityLinker/LUSummary.h | 2 +-
.../Core/Analysis/AnalysisDriver.cpp | 4 ++--
3 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
index 446778f2fd513..7066f3451a3d0 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
@@ -19,8 +19,8 @@
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
#include "llvm/Support/Error.h"
-#include "llvm/Support/ErrorHandling.h"
#include <map>
#include <memory>
#include <vector>
@@ -124,8 +124,9 @@ class DerivedAnalysis : public DerivedAnalysisBase {
auto lookup = [&Map](const AnalysisName &Name) -> const AnalysisResult * {
auto It = Map.find(Name);
if (It == Map.end()) {
- llvm_unreachable("dependency missing from DepResults map; "
- "dependency graph is not topologically sorted");
+ ErrorBuilder::fatal("dependency '{0}' missing from DepResults map; "
+ "dependency graph is not topologically sorted",
+ Name);
}
return It->second;
};
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h
index 44e7504009bee..a36002006430c 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h
@@ -31,10 +31,10 @@ namespace clang::ssaf {
/// together. It contains deduplicated entities with their linkage information
/// and the merged entity summaries.
class LUSummary {
+ friend class AnalysisDriver;
friend class LUSummaryConsumer;
friend class SerializationFormat;
friend class TestFixture;
- friend class AnalysisDriver;
NestedBuildNamespace LUNamespace;
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
index d510632cf4278..9572304d8d8c5 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
@@ -96,7 +96,7 @@ llvm::Error AnalysisDriver::executeSummaryAnalysis(
if (DataIt == LU->Data.end()) {
return ErrorBuilder::create(std::errc::invalid_argument,
"no data for analysis '{0}' in LUSummary",
- Summary->analysisName().str())
+ Summary->analysisName())
.build();
}
@@ -129,7 +129,7 @@ llvm::Error AnalysisDriver::executeDerivedAnalysis(
if (It == Suite.Data.end()) {
ErrorBuilder::fatal("missing dependency '{0}' for analysis '{1}': "
"dependency graph is not topologically sorted",
- DepName.str(), Derived->analysisName().str());
+ DepName, Derived->analysisName());
}
DepMap[DepName] = It->second.get();
}
>From ede4e3fee0ee5a401b2cf3c8fba78d04890d624e Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Mar 2026 22:14:29 -0700
Subject: [PATCH 12/19] Even more fixes
---
.../Analysis/AnalysisDriverTest.cpp | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
index f2bab5b40d914..2fdc752df1760 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
@@ -467,7 +467,8 @@ TEST_F(AnalysisDriverTest, RunByNameErrorMissingData) {
EXPECT_THAT_EXPECTED(
Driver.run({AnalysisName("Analysis1")}),
- llvm::FailedWithMessage("no data for analysis 'Analysis1' in LUSummary"));
+ llvm::FailedWithMessage(
+ "no data for analysis 'AnalysisName(Analysis1)' in LUSummary"));
}
// run(names) — error when a requested name has no registered analysis.
@@ -518,7 +519,8 @@ TEST_F(AnalysisDriverTest, RunByTypeErrorMissingData) {
EXPECT_THAT_EXPECTED(
Driver.run<Analysis1Result>(),
- llvm::FailedWithMessage("no data for analysis 'Analysis1' in LUSummary"));
+ llvm::FailedWithMessage(
+ "no data for analysis 'AnalysisName(Analysis1)' in LUSummary"));
}
// contains() — present entries return true; absent entries return false.
>From 0ab38581538844bad825681c997341a574e35591 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Tue, 17 Mar 2026 15:58:14 -0700
Subject: [PATCH 13/19] Balazs
---
.../Core/Analysis/AnalysisBase.h | 11 ++-
.../Core/Analysis/AnalysisDriver.h | 23 +++---
.../Core/Analysis/AnalysisName.h | 2 +-
.../Core/Analysis/AnalysisRegistry.h | 2 +-
.../Core/Analysis/AnalysisResult.h | 2 +-
.../Core/Analysis/AnalysisTraits.h | 2 +-
.../Core/Analysis/DerivedAnalysis.h | 9 +--
.../Core/Analysis/SummaryAnalysis.h | 10 +--
.../Core/Analysis/WPASuite.h | 2 +-
.../SSAFBuiltinForceLinker.h | 5 ++
.../Core/Analysis/AnalysisDriver.cpp | 80 +++++++++----------
.../Core/Analysis/AnalysisRegistry.cpp | 2 +
12 files changed, 72 insertions(+), 78 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
index 29c46f3c2e544..595aa1c9286b5 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
@@ -1,4 +1,4 @@
-//===- AnalysisBase.h -----------------------------------------------------===//
+//===- AnalysisBase.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.
@@ -21,8 +21,9 @@
namespace clang::ssaf {
class AnalysisDriver;
-class SummaryAnalysisBase;
+class AnalysisResult;
class DerivedAnalysisBase;
+class SummaryAnalysisBase;
/// Minimal common base for both analysis kinds.
///
@@ -30,8 +31,8 @@ class DerivedAnalysisBase;
/// DerivedAnalysis<...> instead.
class AnalysisBase {
friend class AnalysisDriver;
- friend class SummaryAnalysisBase;
friend class DerivedAnalysisBase;
+ friend class SummaryAnalysisBase;
enum class Kind { Summary, Derived };
Kind TheKind;
@@ -48,6 +49,10 @@ class AnalysisBase {
/// AnalysisNames of all AnalysisResult dependencies.
virtual const std::vector<AnalysisName> &dependencyNames() const = 0;
+
+ /// Transfers ownership of the built result. Called once after finalize().
+ /// The rvalue ref-qualifier enforces single use.
+ virtual std::unique_ptr<AnalysisResult> result() && = 0;
};
} // namespace clang::ssaf
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
index c17cd584f2434..cb26051d1f106 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
@@ -1,4 +1,4 @@
-//===- AnalysisDriver.h ---------------------------------------------------===//
+//===- AnalysisDriver.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.
@@ -25,8 +25,8 @@
namespace clang::ssaf {
class AnalysisBase;
-class SummaryAnalysisBase;
class DerivedAnalysisBase;
+class SummaryAnalysisBase;
/// Orchestrates whole-program analysis over an LUSummary.
///
@@ -59,11 +59,12 @@ class AnalysisDriver final {
/// LUSummary. The EntityIdTable is copied (not moved) so the driver remains
/// usable for subsequent calls.
[[nodiscard]] llvm::Expected<WPASuite>
- run(llvm::ArrayRef<AnalysisName> Names);
+ run(llvm::ArrayRef<AnalysisName> Names) const;
/// Type-safe variant of run(names). Derives names from
/// ResultTs::analysisName().
- template <typename... ResultTs> [[nodiscard]] llvm::Expected<WPASuite> run() {
+ template <typename... ResultTs>
+ [[nodiscard]] llvm::Expected<WPASuite> run() const {
return run({ResultTs::analysisName()...});
}
@@ -74,21 +75,19 @@ class AnalysisDriver final {
/// dependencies) and returns them in topological order via a single DFS.
/// Reports an error on unregistered names or cycles.
static llvm::Expected<std::vector<std::unique_ptr<AnalysisBase>>>
- sort(llvm::ArrayRef<AnalysisName> Roots);
+ toposort(llvm::ArrayRef<AnalysisName> Roots);
/// Executes a topologically-sorted analysis list and returns a WPASuite.
/// \p IdTable is moved into the returned WPASuite.
llvm::Expected<WPASuite>
execute(EntityIdTable IdTable,
- std::vector<std::unique_ptr<AnalysisBase>> Sorted);
+ llvm::ArrayRef<std::unique_ptr<AnalysisBase>> Sorted) const;
- llvm::Error
- executeSummaryAnalysis(std::unique_ptr<SummaryAnalysisBase> Summary,
- WPASuite &Suite);
+ llvm::Error executeSummaryAnalysis(SummaryAnalysisBase &Summary,
+ WPASuite &Suite) const;
- llvm::Error
- executeDerivedAnalysis(std::unique_ptr<DerivedAnalysisBase> Derived,
- WPASuite &Suite);
+ llvm::Error executeDerivedAnalysis(DerivedAnalysisBase &Derived,
+ WPASuite &Suite) const;
};
} // namespace clang::ssaf
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h
index 73ba96ccc594e..7c074fa5695fe 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h
@@ -1,4 +1,4 @@
-//===- AnalysisName.h -----------------------------------------------------===//
+//===- AnalysisName.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.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
index 4ed5999bda477..cf275597afc1a 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
@@ -1,4 +1,4 @@
-//===- AnalysisRegistry.h -------------------------------------------------===//
+//===- AnalysisRegistry.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.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
index 7ac2a9ad7db6a..97ac3760bfbfa 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
@@ -1,4 +1,4 @@
-//===- AnalysisResult.h ---------------------------------------------------===//
+//===- AnalysisResult.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.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
index ef6a5a56d990a..d7fb37e03115f 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
@@ -1,4 +1,4 @@
-//===- AnalysisTraits.h ---------------------------------------------------===//
+//===- AnalysisTraits.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.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
index 7066f3451a3d0..fe2ce9368dae4 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
@@ -1,4 +1,4 @@
-//===- DerivedAnalysis.h --------------------------------------------------===//
+//===- DerivedAnalysis.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.
@@ -56,9 +56,6 @@ class DerivedAnalysisBase : public AnalysisBase {
/// Called after the step() loop converges. Default is a no-op.
virtual llvm::Error finalize() { return llvm::Error::success(); }
-
- /// Transfers ownership of the computed result. Called once after finalize().
- virtual std::unique_ptr<AnalysisResult> result() && = 0;
};
/// Typed intermediate that concrete derived analyses inherit from.
@@ -85,11 +82,9 @@ class DerivedAnalysis : public DerivedAnalysisBase {
friend class AnalysisRegistry;
using ResultType = ResultT;
- std::unique_ptr<ResultT> Result;
+ std::unique_ptr<ResultT> Result = std::make_unique<ResultT>();
public:
- DerivedAnalysis() : Result(std::make_unique<ResultT>()) {}
-
/// Used by AnalysisRegistry::Add to derive the registry entry name.
AnalysisName analysisName() const final { return ResultT::analysisName(); }
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
index 6d6892305d217..226427a0091da 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
@@ -1,4 +1,4 @@
-//===- SummaryAnalysis.h --------------------------------------------------===//
+//===- SummaryAnalysis.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.
@@ -56,10 +56,6 @@ class SummaryAnalysisBase : public AnalysisBase {
/// Called after all entities have been processed. Default is a no-op.
virtual llvm::Error finalize() { return llvm::Error::success(); }
-
- /// Transfers ownership of the built result. Called once after finalize().
- /// The rvalue ref-qualifier enforces single use.
- virtual std::unique_ptr<AnalysisResult> result() && = 0;
};
/// Typed intermediate that concrete summary analyses inherit from.
@@ -82,11 +78,9 @@ class SummaryAnalysis : public SummaryAnalysisBase {
friend class AnalysisRegistry;
using ResultType = ResultT;
- std::unique_ptr<ResultT> Result;
+ std::unique_ptr<ResultT> Result = std::make_unique<ResultT>();
public:
- SummaryAnalysis() : Result(std::make_unique<ResultT>()) {}
-
/// Used by AnalysisRegistry::Add to derive the registry entry name.
AnalysisName analysisName() const final { return ResultT::analysisName(); }
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
index ce2d334a15071..a4c6be09e08f0 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
@@ -1,4 +1,4 @@
-//===- WPASuite.h ---------------------------------------------------------===//
+//===- WPASuite.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.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
index 5f201487ca1fe..2f144b92a1a94 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
@@ -25,4 +25,9 @@ extern volatile int SSAFJSONFormatAnchorSource;
[[maybe_unused]] static int SSAFJSONFormatAnchorDestination =
SSAFJSONFormatAnchorSource;
+// This anchor is used to force the linker to link the AnalysisRegistry.
+extern volatile int SSAFAnalysisRegistryAnchorSource;
+[[maybe_unused]] static int SSAFAnalysisRegistryAnchorDestination =
+ SSAFAnalysisRegistryAnchorSource;
+
#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINFORCELINKER_H
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
index 9572304d8d8c5..d04b8e640e515 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
@@ -24,7 +24,7 @@ AnalysisDriver::AnalysisDriver(std::unique_ptr<LUSummary> LU)
: LU(std::move(LU)) {}
llvm::Expected<std::vector<std::unique_ptr<AnalysisBase>>>
-AnalysisDriver::sort(llvm::ArrayRef<AnalysisName> Roots) {
+AnalysisDriver::toposort(llvm::ArrayRef<AnalysisName> Roots) {
struct Visitor {
enum class State { Unvisited, Visiting, Visited };
@@ -42,8 +42,9 @@ AnalysisDriver::sort(llvm::ArrayRef<AnalysisName> Roots) {
}
llvm::Error visit(const AnalysisName &Name) {
- auto It = Marks.find(Name);
- switch (It != Marks.end() ? It->second : State::Unvisited) {
+ auto [It, _] = Marks.emplace(Name, State::Unvisited);
+
+ switch (It->second) {
case State::Visited:
return llvm::Error::success();
@@ -53,26 +54,25 @@ AnalysisDriver::sort(llvm::ArrayRef<AnalysisName> Roots) {
.build();
case State::Unvisited: {
- Marks[Name] = State::Visiting;
+ It->second = State::Visiting;
Path.push_back(Name);
auto V = AnalysisRegistry::instantiate(Name.str());
if (!V) {
- Path.pop_back();
return V.takeError();
}
auto Analysis = std::move(*V);
for (const auto &Dep : Analysis->dependencyNames()) {
if (auto Err = visit(Dep)) {
- Path.pop_back();
return Err;
}
}
- Marks[Name] = State::Visited;
+ It->second = State::Visited;
Path.pop_back();
Result.push_back(std::move(Analysis));
+
return llvm::Error::success();
}
}
@@ -89,57 +89,54 @@ AnalysisDriver::sort(llvm::ArrayRef<AnalysisName> Roots) {
return std::move(V.Result);
}
-llvm::Error AnalysisDriver::executeSummaryAnalysis(
- std::unique_ptr<SummaryAnalysisBase> Summary, WPASuite &Suite) {
- SummaryName SN = Summary->summaryName();
+llvm::Error AnalysisDriver::executeSummaryAnalysis(SummaryAnalysisBase &Summary,
+ WPASuite &Suite) const {
+ SummaryName SN = Summary.summaryName();
auto DataIt = LU->Data.find(SN);
if (DataIt == LU->Data.end()) {
return ErrorBuilder::create(std::errc::invalid_argument,
"no data for analysis '{0}' in LUSummary",
- Summary->analysisName())
+ Summary.analysisName())
.build();
}
- if (auto Err = Summary->initialize()) {
+ if (auto Err = Summary.initialize()) {
return Err;
}
for (auto &[Id, EntitySummary] : DataIt->second) {
- if (auto Err = Summary->add(Id, *EntitySummary)) {
+ if (auto Err = Summary.add(Id, *EntitySummary)) {
return Err;
}
}
- if (auto Err = Summary->finalize()) {
+ if (auto Err = Summary.finalize()) {
return Err;
}
- AnalysisName Name = Summary->analysisName();
- Suite.Data.emplace(Name, std::move(*Summary).result());
-
return llvm::Error::success();
}
-llvm::Error AnalysisDriver::executeDerivedAnalysis(
- std::unique_ptr<DerivedAnalysisBase> Derived, WPASuite &Suite) {
+llvm::Error AnalysisDriver::executeDerivedAnalysis(DerivedAnalysisBase &Derived,
+ WPASuite &Suite) const {
std::map<AnalysisName, const AnalysisResult *> DepMap;
- for (const auto &DepName : Derived->dependencyNames()) {
+ for (const auto &DepName : Derived.dependencyNames()) {
auto It = Suite.Data.find(DepName);
if (It == Suite.Data.end()) {
ErrorBuilder::fatal("missing dependency '{0}' for analysis '{1}': "
"dependency graph is not topologically sorted",
- DepName, Derived->analysisName());
+ DepName, Derived.analysisName());
}
DepMap[DepName] = It->second.get();
}
- if (auto Err = Derived->initialize(DepMap)) {
+ if (auto Err = Derived.initialize(DepMap)) {
return Err;
}
while (true) {
- auto StepOrErr = Derived->step();
+ auto StepOrErr = Derived.step();
if (!StepOrErr) {
return StepOrErr.takeError();
}
@@ -148,60 +145,57 @@ llvm::Error AnalysisDriver::executeDerivedAnalysis(
}
}
- if (auto Err = Derived->finalize()) {
+ if (auto Err = Derived.finalize()) {
return Err;
}
- AnalysisName Name = Derived->analysisName();
- Suite.Data.emplace(Name, std::move(*Derived).result());
-
return llvm::Error::success();
}
-llvm::Expected<WPASuite>
-AnalysisDriver::execute(EntityIdTable IdTable,
- std::vector<std::unique_ptr<AnalysisBase>> Sorted) {
+llvm::Expected<WPASuite> AnalysisDriver::execute(
+ EntityIdTable IdTable,
+ llvm::ArrayRef<std::unique_ptr<AnalysisBase>> Sorted) const {
WPASuite Suite;
Suite.IdTable = std::move(IdTable);
- for (auto &V : Sorted) {
- switch (V->TheKind) {
+ for (auto &Analysis : Sorted) {
+ switch (Analysis->TheKind) {
case AnalysisBase::Kind::Summary: {
- auto SA = std::unique_ptr<SummaryAnalysisBase>(
- static_cast<SummaryAnalysisBase *>(V.release()));
- if (auto Err = executeSummaryAnalysis(std::move(SA), Suite)) {
+ SummaryAnalysisBase &SA = static_cast<SummaryAnalysisBase &>(*Analysis);
+ if (auto Err = executeSummaryAnalysis(SA, Suite)) {
return std::move(Err);
}
break;
}
case AnalysisBase::Kind::Derived: {
- auto DA = std::unique_ptr<DerivedAnalysisBase>(
- static_cast<DerivedAnalysisBase *>(V.release()));
- if (auto Err = executeDerivedAnalysis(std::move(DA), Suite)) {
+ DerivedAnalysisBase &DA = static_cast<DerivedAnalysisBase &>(*Analysis);
+ if (auto Err = executeDerivedAnalysis(DA, Suite)) {
return std::move(Err);
}
break;
}
}
+ AnalysisName Name = Analysis->analysisName();
+ Suite.Data.emplace(Name, std::move(*Analysis).result());
}
return Suite;
}
llvm::Expected<WPASuite> AnalysisDriver::run() && {
- auto ExpectedSorted = sort(AnalysisRegistry::names());
+ auto ExpectedSorted = toposort(AnalysisRegistry::names());
if (!ExpectedSorted) {
return ExpectedSorted.takeError();
}
- return execute(std::move(LU->IdTable), std::move(*ExpectedSorted));
+ return execute(std::move(LU->IdTable), *ExpectedSorted);
}
llvm::Expected<WPASuite>
-AnalysisDriver::run(llvm::ArrayRef<AnalysisName> Names) {
- auto ExpectedSorted = sort(Names);
+AnalysisDriver::run(llvm::ArrayRef<AnalysisName> Names) const {
+ auto ExpectedSorted = toposort(Names);
if (!ExpectedSorted) {
return ExpectedSorted.takeError();
}
- return execute(LU->IdTable, std::move(*ExpectedSorted));
+ return execute(LU->IdTable, *ExpectedSorted);
}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
index 91e95fa8b2379..d39c47335f1bb 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
@@ -15,6 +15,8 @@ using namespace ssaf;
using RegistryT = llvm::Registry<AnalysisBase>;
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int SSAFAnalysisRegistryAnchorSource = 0;
LLVM_INSTANTIATE_REGISTRY(RegistryT)
std::vector<AnalysisName> &AnalysisRegistry::getAnalysisNames() {
>From fb389e30508c30cb8c829ad01cc77a0ed24a8e16 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 19 Mar 2026 11:54:23 -0700
Subject: [PATCH 14/19] Balazs 2
---
.../Core/Analysis/AnalysisRegistry.h | 13 +++-
.../Core/Analysis/AnalysisTraits.h | 3 +
.../Core/Analysis/DerivedAnalysis.h | 8 +--
.../Core/Analysis/SummaryAnalysis.h | 6 +-
.../Core/Analysis/WPASuite.h | 4 +-
.../Core/Analysis/AnalysisDriver.cpp | 19 ++++--
.../Analysis/AnalysisDriverTest.cpp | 68 +++++++++----------
7 files changed, 70 insertions(+), 51 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
index cf275597afc1a..2199d2213d7ac 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
@@ -8,12 +8,21 @@
//
// Unified registry for both SummaryAnalysis and DerivedAnalysis subclasses.
//
-// To register an analysis, add a static Add<AnalysisT> in its translation
-// unit:
+// 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:
//
+// // MyAnalysis.cpp
// static AnalysisRegistry::Add<MyAnalysis>
// Registered("One-line description of MyAnalysis");
//
+// volatile int SSAFMyAnalysisAnchorSource = 0;
+//
+// // SSAFBuiltinForceLinker.h (or the relevant force-linker header)
+// extern volatile int SSAFMyAnalysisAnchorSource;
+// [[maybe_unused]] static int SSAFMyAnalysisAnchorDestination =
+// SSAFMyAnalysisAnchorSource;
+//
// The registry entry name is derived automatically from
// MyAnalysis::analysisName(), so name-mismatch bugs are impossible.
//
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
index d7fb37e03115f..c7e0a77c85785 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
@@ -28,6 +28,9 @@ template <typename T>
struct HasAnalysisName<T, std::void_t<decltype(T::analysisName())>>
: std::is_same<decltype(T::analysisName()), AnalysisName> {};
+template <typename T>
+inline constexpr bool HasAnalysisName_v = HasAnalysisName<T>::value;
+
} // namespace clang::ssaf
#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISTRAITS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
index fe2ce9368dae4..edc5429e3b49c 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
@@ -50,8 +50,8 @@ class DerivedAnalysisBase : public AnalysisBase {
virtual llvm::Error initialize(
const std::map<AnalysisName, const AnalysisResult *> &DepResults) = 0;
- /// Performs one pass. Returns true if another pass is needed; false when
- /// converged.
+ /// Performs one pass.
+ /// Returns true if another pass is needed; false when converged.
virtual llvm::Expected<bool> step() = 0;
/// Called after the step() loop converges. Default is a no-op.
@@ -72,11 +72,11 @@ template <typename ResultT, typename... DepResultTs>
class DerivedAnalysis : public DerivedAnalysisBase {
static_assert(std::is_base_of_v<AnalysisResult, ResultT>,
"ResultT must derive from AnalysisResult");
- static_assert(HasAnalysisName<ResultT>::value,
+ static_assert(HasAnalysisName_v<ResultT>,
"ResultT must have a static analysisName() method");
static_assert((std::is_base_of_v<AnalysisResult, DepResultTs> && ...),
"Every DepResultT must derive from AnalysisResult");
- static_assert((HasAnalysisName<DepResultTs>::value && ...),
+ static_assert((HasAnalysisName_v<DepResultTs> && ...),
"Every DepResultT must have a static analysisName() method");
friend class AnalysisRegistry;
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
index 226427a0091da..ca9123c1d1d61 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
@@ -70,7 +70,7 @@ template <typename ResultT, typename EntitySummaryT>
class SummaryAnalysis : public SummaryAnalysisBase {
static_assert(std::is_base_of_v<AnalysisResult, ResultT>,
"ResultT must derive from AnalysisResult");
- static_assert(HasAnalysisName<ResultT>::value,
+ static_assert(HasAnalysisName_v<ResultT>,
"ResultT must have a static analysisName() method");
static_assert(std::is_base_of_v<EntitySummary, EntitySummaryT>,
"EntitySummaryT must derive from EntitySummary");
@@ -99,8 +99,8 @@ class SummaryAnalysis : public SummaryAnalysisBase {
/// Called once per matching entity. Implement to accumulate data.
virtual llvm::Error add(EntityId Id, const EntitySummaryT &Summary) = 0;
- /// Called after all entities have been processed. Override for
- /// post-processing.
+ /// Called after all entities have been processed.
+ /// Override for post-processing.
virtual llvm::Error finalize() override { return llvm::Error::success(); }
protected:
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
index a4c6be09e08f0..4fc23826fe200 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
@@ -49,7 +49,7 @@ class WPASuite {
template <typename ResultT> [[nodiscard]] bool contains() const {
static_assert(std::is_base_of_v<AnalysisResult, ResultT>,
"ResultT must derive from AnalysisResult");
- static_assert(HasAnalysisName<ResultT>::value,
+ static_assert(HasAnalysisName_v<ResultT>,
"ResultT must have a static analysisName() method");
return contains(ResultT::analysisName());
@@ -66,7 +66,7 @@ class WPASuite {
[[nodiscard]] llvm::Expected<const ResultT &> get() const {
static_assert(std::is_base_of_v<AnalysisResult, ResultT>,
"ResultT must derive from AnalysisResult");
- static_assert(HasAnalysisName<ResultT>::value,
+ static_assert(HasAnalysisName_v<ResultT>,
"ResultT must have a static analysisName() method");
auto Result = get(ResultT::analysisName());
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
index d04b8e640e515..e3b358ef2c432 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
@@ -32,6 +32,11 @@ AnalysisDriver::toposort(llvm::ArrayRef<AnalysisName> Roots) {
std::vector<AnalysisName> Path;
std::vector<std::unique_ptr<AnalysisBase>> Result;
+ explicit Visitor(size_t N) {
+ Path.reserve(N);
+ Result.reserve(N);
+ }
+
std::string formatCycle(const AnalysisName &CycleEntry) const {
auto CycleBegin = llvm::find(Path, CycleEntry);
std::string Cycle;
@@ -57,18 +62,24 @@ AnalysisDriver::toposort(llvm::ArrayRef<AnalysisName> Roots) {
It->second = State::Visiting;
Path.push_back(Name);
- auto V = AnalysisRegistry::instantiate(Name.str());
+ llvm::Expected<std::unique_ptr<AnalysisBase>> V =
+ AnalysisRegistry::instantiate(Name.str());
if (!V) {
return V.takeError();
}
- auto Analysis = std::move(*V);
+ // Unwrap for convenience to avoid the noise of dereferencing an
+ // Expected on every subsequent access.
+ std::unique_ptr<AnalysisBase> Analysis = std::move(*V);
+
for (const auto &Dep : Analysis->dependencyNames()) {
if (auto Err = visit(Dep)) {
return Err;
}
}
+ // std::map iterators are not invalidated by insertions, so It remains
+ // valid after recursive visit() calls that insert new entries.
It->second = State::Visited;
Path.pop_back();
Result.push_back(std::move(Analysis));
@@ -80,7 +91,7 @@ AnalysisDriver::toposort(llvm::ArrayRef<AnalysisName> Roots) {
}
};
- Visitor V;
+ Visitor V(Roots.size());
for (const auto &Root : Roots) {
if (auto Err = V.visit(Root)) {
return std::move(Err);
@@ -176,7 +187,7 @@ llvm::Expected<WPASuite> AnalysisDriver::execute(
}
}
AnalysisName Name = Analysis->analysisName();
- Suite.Data.emplace(Name, std::move(*Analysis).result());
+ Suite.Data.emplace(std::move(Name), std::move(*Analysis).result());
}
return Suite;
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
index 2fdc752df1760..3f603715c984e 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
@@ -129,8 +129,8 @@ class Analysis5Result final : public AnalysisResult {
};
// CycleA and CycleB form a dependency cycle (CycleA → CycleB → CycleA).
-// Registered solely to exercise cycle detection in AnalysisDriver::sort().
-// initialize() and step() are unreachable stubs — the cycle is caught before
+// Registered solely to exercise cycle detection in AnalysisDriver::toposort().
+// initialize() and step() are unreachable stubs - the cycle is caught before
// any analysis executes.
class CycleAResult final : public AnalysisResult {
public:
@@ -142,15 +142,6 @@ class CycleBResult final : public AnalysisResult {
static AnalysisName analysisName() { return AnalysisName("CycleB"); }
};
-// ---------------------------------------------------------------------------
-// Analysis destruction flags (reset in SetUp)
-// ---------------------------------------------------------------------------
-
-static bool Analysis1WasDestroyed = false;
-static bool Analysis2WasDestroyed = false;
-static bool Analysis4WasDestroyed = false;
-static bool Analysis5WasDestroyed = false;
-
// ---------------------------------------------------------------------------
// Analyses
// ---------------------------------------------------------------------------
@@ -158,7 +149,8 @@ static bool Analysis5WasDestroyed = false;
class Analysis1 final
: public SummaryAnalysis<Analysis1Result, Analysis1EntitySummary> {
public:
- ~Analysis1() { Analysis1WasDestroyed = true; }
+ inline static bool WasDestroyed = false;
+ ~Analysis1() { WasDestroyed = true; }
llvm::Error initialize() override {
result().WasInitialized = true;
@@ -176,12 +168,17 @@ class Analysis1 final
}
};
+// These static registrations are safe without SSAFBuiltinTestForceLinker.h
+// because this translation unit is compiled directly into the test binary -
+// the linker cannot dead-strip it, so all static initializers are guaranteed
+// to run.
static AnalysisRegistry::Add<Analysis1> RegAnalysis1("Analysis for Analysis1");
class Analysis2 final
: public SummaryAnalysis<Analysis2Result, Analysis2EntitySummary> {
public:
- ~Analysis2() { Analysis2WasDestroyed = true; }
+ inline static bool WasDestroyed = false;
+ ~Analysis2() { WasDestroyed = true; }
llvm::Error initialize() override {
result().WasInitialized = true;
@@ -206,7 +203,8 @@ static AnalysisRegistry::Add<Analysis2> RegAnalysis2("Analysis for Analysis2");
class Analysis4 final
: public SummaryAnalysis<Analysis4Result, Analysis4EntitySummary> {
public:
- ~Analysis4() { Analysis4WasDestroyed = true; }
+ inline static bool WasDestroyed = false;
+ ~Analysis4() { WasDestroyed = true; }
llvm::Error initialize() override {
result().WasInitialized = true;
@@ -232,7 +230,8 @@ class Analysis5 final
int StepCount = 0;
public:
- ~Analysis5() { Analysis5WasDestroyed = true; }
+ inline static bool WasDestroyed = false;
+ ~Analysis5() { WasDestroyed = true; }
llvm::Error initialize(const Analysis1Result &R1, const Analysis2Result &R2,
const Analysis4Result &R4) override {
@@ -287,10 +286,11 @@ class AnalysisDriverTest : public TestFixture {
void SetUp() override {
NextSummaryInstanceId = 0;
- Analysis1WasDestroyed = false;
- Analysis2WasDestroyed = false;
- Analysis4WasDestroyed = false;
- Analysis5WasDestroyed = false;
+ Analysis1::WasDestroyed = false;
+ Analysis2::WasDestroyed = false;
+ // No Analysis3 - not registered, so no WasDestroyed flag.
+ Analysis4::WasDestroyed = false;
+ Analysis5::WasDestroyed = false;
}
std::unique_ptr<LUSummary> makeLUSummary() {
@@ -337,21 +337,17 @@ TEST(AnalysisRegistryTest, AnalysisIsRegistered) {
}
TEST(AnalysisRegistryTest, AnalysisCanBeInstantiated) {
- EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("AnalysisNonExisting"),
+ constexpr auto instantiate = AnalysisRegistry::instantiate;
+ EXPECT_THAT_EXPECTED(instantiate("AnalysisNonExisting"),
llvm::FailedWithMessage(
"no analysis registered for 'AnalysisNonExisting'"));
- EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis1"),
- llvm::Succeeded());
- EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis2"),
- llvm::Succeeded());
- EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis4"),
- llvm::Succeeded());
- EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("Analysis5"),
- llvm::Succeeded());
- EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("CycleA"),
- llvm::Succeeded());
- EXPECT_THAT_EXPECTED(AnalysisRegistry::instantiate("CycleB"),
- llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(instantiate("Analysis1"), llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(instantiate("Analysis2"), llvm::Succeeded());
+ // No Analysis3 - not registered, so instantiate() would fail.
+ EXPECT_THAT_EXPECTED(instantiate("Analysis4"), llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(instantiate("Analysis5"), llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(instantiate("CycleA"), llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(instantiate("CycleB"), llvm::Succeeded());
}
// run<T...>() — processes the non-cyclic analyses in topological order.
@@ -386,7 +382,7 @@ TEST_F(AnalysisDriverTest, RunAll) {
EXPECT_TRUE(hasEntry(R1OrErr->Entries, E2, s1b));
EXPECT_TRUE(R1OrErr->WasInitialized);
EXPECT_TRUE(R1OrErr->WasFinalized);
- EXPECT_TRUE(Analysis1WasDestroyed);
+ EXPECT_TRUE(Analysis1::WasDestroyed);
}
{
@@ -397,7 +393,7 @@ TEST_F(AnalysisDriverTest, RunAll) {
EXPECT_TRUE(hasEntry(R2OrErr->Entries, E3, s2b));
EXPECT_TRUE(R2OrErr->WasInitialized);
EXPECT_TRUE(R2OrErr->WasFinalized);
- EXPECT_TRUE(Analysis2WasDestroyed);
+ EXPECT_TRUE(Analysis2::WasDestroyed);
}
{
@@ -407,7 +403,7 @@ TEST_F(AnalysisDriverTest, RunAll) {
EXPECT_TRUE(hasEntry(R4OrErr->Entries, E4, s4a));
EXPECT_TRUE(R4OrErr->WasInitialized);
EXPECT_TRUE(R4OrErr->WasFinalized);
- EXPECT_TRUE(Analysis4WasDestroyed);
+ EXPECT_TRUE(Analysis4::WasDestroyed);
}
{
@@ -424,7 +420,7 @@ TEST_F(AnalysisDriverTest, RunAll) {
EXPECT_TRUE(hasEntry(R5OrErr->Analysis2Entries, E3, s2b));
EXPECT_EQ(R5OrErr->Analysis4Entries.size(), 1u);
EXPECT_TRUE(hasEntry(R5OrErr->Analysis4Entries, E4, s4a));
- EXPECT_TRUE(Analysis5WasDestroyed);
+ EXPECT_TRUE(Analysis5::WasDestroyed);
}
// Unregistered analysis — not present in WPA.
>From ebc9ece064fe416256b53e3cab718f438dde24c3 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 19 Mar 2026 12:09:13 -0700
Subject: [PATCH 15/19] I like to move it, move it!
---
.../Core/Support/FormatProviders.h | 2 +-
.../AnalysisBase.h | 8 ++++----
.../AnalysisDriver.h | 8 ++++----
.../AnalysisName.h | 6 +++---
.../AnalysisRegistry.h | 12 ++++++------
.../AnalysisResult.h | 6 +++---
.../AnalysisTraits.h | 8 ++++----
.../DerivedAnalysis.h | 14 +++++++-------
.../SummaryAnalysis.h | 12 ++++++------
.../{Analysis => WholeProgramAnalysis}/WPASuite.h | 12 ++++++------
.../Core/CMakeLists.txt | 6 +++---
.../AnalysisDriver.cpp | 8 ++++----
.../AnalysisName.cpp | 2 +-
.../AnalysisRegistry.cpp | 2 +-
.../ScalableStaticAnalysisFramework/CMakeLists.txt | 2 +-
.../AnalysisDriverTest.cpp | 14 +++++++-------
16 files changed, 61 insertions(+), 61 deletions(-)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/AnalysisBase.h (82%)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/AnalysisDriver.h (90%)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/AnalysisName.h (85%)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/AnalysisRegistry.h (86%)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/AnalysisResult.h (75%)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/AnalysisTraits.h (75%)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/DerivedAnalysis.h (88%)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/SummaryAnalysis.h (89%)
rename clang/include/clang/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/WPASuite.h (85%)
rename clang/lib/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/AnalysisDriver.cpp (94%)
rename clang/lib/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/AnalysisName.cpp (86%)
rename clang/lib/ScalableStaticAnalysisFramework/Core/{Analysis => WholeProgramAnalysis}/AnalysisRegistry.cpp (94%)
rename clang/unittests/ScalableStaticAnalysisFramework/{Analysis => WholeProgramAnalysis}/AnalysisDriverTest.cpp (96%)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
index 6cc816edfd967..437152d43f425 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h
@@ -14,12 +14,12 @@
#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SUPPORT_FORMATPROVIDERS_H
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SUPPORT_FORMATPROVIDERS_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityLinkage.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
#include "llvm/Support/FormatProviders.h"
#include "llvm/Support/raw_ostream.h"
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisBase.h
similarity index 82%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisBase.h
index 595aa1c9286b5..b86a9c5828700 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisBase.h
@@ -12,10 +12,10 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISBASE_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISBASE_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISBASE_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISBASE_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
#include <vector>
namespace clang::ssaf {
@@ -57,4 +57,4 @@ class AnalysisBase {
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISBASE_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISBASE_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h
similarity index 90%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h
index cb26051d1f106..156d8e806bd0f 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h
@@ -12,11 +12,11 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISDRIVER_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISDRIVER_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISDRIVER_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISDRIVER_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h"
#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/Error.h"
#include <memory>
@@ -92,4 +92,4 @@ class AnalysisDriver final {
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISDRIVER_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISDRIVER_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h
similarity index 85%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h
index 7c074fa5695fe..32f76e73b14e0 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h
@@ -11,8 +11,8 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISNAME_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISNAME_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISNAME_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISNAME_H
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
@@ -46,4 +46,4 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const AnalysisName &AN);
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISNAME_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISNAME_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h
similarity index 86%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h
index 2199d2213d7ac..7ea55e4b3af91 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h
@@ -28,13 +28,13 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISREGISTRY_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISREGISTRY_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISREGISTRY_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISREGISTRY_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Registry.h"
#include <memory>
@@ -106,4 +106,4 @@ class AnalysisRegistry {
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISREGISTRY_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISREGISTRY_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h
similarity index 75%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h
index 97ac3760bfbfa..07d1f0549a9ee 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h
@@ -11,8 +11,8 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISRESULT_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISRESULT_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISRESULT_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISRESULT_H
namespace clang::ssaf {
@@ -27,4 +27,4 @@ class AnalysisResult {
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISRESULT_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISRESULT_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisTraits.h
similarity index 75%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisTraits.h
index c7e0a77c85785..78df3b35648c2 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisTraits.h
@@ -10,10 +10,10 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISTRAITS_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISTRAITS_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISTRAITS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISTRAITS_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
#include <type_traits>
namespace clang::ssaf {
@@ -33,4 +33,4 @@ inline constexpr bool HasAnalysisName_v = HasAnalysisName<T>::value;
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_ANALYSISTRAITS_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISTRAITS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h
similarity index 88%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h
index edc5429e3b49c..4eb35262d4625 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h
@@ -12,14 +12,14 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_DERIVEDANALYSIS_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_DERIVEDANALYSIS_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_DERIVEDANALYSIS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_DERIVEDANALYSIS_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisBase.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisTraits.h"
#include "llvm/Support/Error.h"
#include <map>
#include <memory>
@@ -137,4 +137,4 @@ class DerivedAnalysis : public DerivedAnalysisBase {
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_DERIVEDANALYSIS_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_DERIVEDANALYSIS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h
similarity index 89%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h
index ca9123c1d1d61..31b9e6ae4a6c3 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h
@@ -12,15 +12,15 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_SUMMARYANALYSIS_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_SUMMARYANALYSIS_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_SUMMARYANALYSIS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_SUMMARYANALYSIS_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisBase.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/EntitySummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisBase.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisTraits.h"
#include "llvm/Support/Error.h"
#include <memory>
@@ -125,4 +125,4 @@ class SummaryAnalysis : public SummaryAnalysisBase {
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_SUMMARYANALYSIS_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_SUMMARYANALYSIS_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h
similarity index 85%
rename from clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
rename to clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h
index 4fc23826fe200..b2e9c36d94bd9 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h
@@ -11,14 +11,14 @@
//
//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_WPASUITE_H
-#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_WPASUITE_H
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_WPASUITE_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_WPASUITE_H
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisTraits.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityIdTable.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisTraits.h"
#include "llvm/Support/Error.h"
#include <map>
#include <memory>
@@ -93,4 +93,4 @@ class WPASuite {
} // namespace clang::ssaf
-#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_ANALYSIS_WPASUITE_H
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_WPASUITE_H
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
index 5ab085c0ef07e..8c306163df1a7 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
@@ -3,9 +3,6 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangScalableStaticAnalysisFrameworkCore
- Analysis/AnalysisDriver.cpp
- Analysis/AnalysisName.cpp
- Analysis/AnalysisRegistry.cpp
ASTEntityMapping.cpp
EntityLinker/EntityLinker.cpp
Model/BuildNamespace.cpp
@@ -26,6 +23,9 @@ add_clang_library(clangScalableStaticAnalysisFrameworkCore
Support/ErrorBuilder.cpp
TUSummary/ExtractorRegistry.cpp
TUSummary/TUSummaryBuilder.cpp
+ WholeProgramAnalysis/AnalysisDriver.cpp
+ WholeProgramAnalysis/AnalysisName.cpp
+ WholeProgramAnalysis/AnalysisRegistry.cpp
LINK_LIBS
clangAST
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.cpp
similarity index 94%
rename from clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
rename to clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.cpp
index e3b358ef2c432..8d6791f47cfd4 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.cpp
@@ -6,11 +6,11 @@
//
//===----------------------------------------------------------------------===//
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.cpp
similarity index 86%
rename from clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.cpp
rename to clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.cpp
index d49f41ab24eb8..9719196ed4d6d 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
using namespace clang::ssaf;
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.cpp
similarity index 94%
rename from clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
rename to clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.cpp
index d39c47335f1bb..ba91d27f9f168 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
#include "llvm/ADT/STLExtras.h"
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
index 147131db4fb12..345eed6c5279b 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
@@ -1,6 +1,5 @@
add_distinct_clang_unittest(ClangScalableAnalysisTests
Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
- Analysis/AnalysisDriverTest.cpp
ASTEntityMappingTest.cpp
BuildNamespaceTest.cpp
EntityIdTableTest.cpp
@@ -24,6 +23,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
SummaryNameTest.cpp
TestFixture.cpp
TUSummaryBuilderTest.cpp
+ WholeProgramAnalysis/AnalysisDriverTest.cpp
CLANG_LIBS
clangAST
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/WholeProgramAnalysis/AnalysisDriverTest.cpp
similarity index 96%
rename from clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
rename to clang/unittests/ScalableStaticAnalysisFramework/WholeProgramAnalysis/AnalysisDriverTest.cpp
index 3f603715c984e..ee6cc31408c29 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Analysis/AnalysisDriverTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/WholeProgramAnalysis/AnalysisDriverTest.cpp
@@ -6,20 +6,20 @@
//
//===----------------------------------------------------------------------===//
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisDriver.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h"
#include "../TestFixture.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisName.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisRegistry.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/AnalysisResult.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/DerivedAnalysis.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/SummaryAnalysis.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Analysis/WPASuite.h"
#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityLinkage.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
>From 6756efc56cec40a79da9689eba5cfd9de080da96 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 19 Mar 2026 15:20:39 -0700
Subject: [PATCH 16/19] AnalysisRegistry APIs and error messages should accept
AnalysisName as arguments
---
.../WholeProgramAnalysis/AnalysisRegistry.h | 6 +-
.../Core/WholeProgramAnalysis/WPASuite.h | 3 +-
.../WholeProgramAnalysis/AnalysisDriver.cpp | 2 +-
.../WholeProgramAnalysis/AnalysisRegistry.cpp | 9 +-
.../AnalysisDriverTest.cpp | 83 +++++++++++--------
5 files changed, 59 insertions(+), 44 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h
index 7ea55e4b3af91..44eabce6c809c 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h
@@ -70,7 +70,7 @@ class AnalysisRegistry {
explicit Add(llvm::StringRef Desc)
: Name(AnalysisT::ResultType::analysisName().str().str()),
Node(Name, Desc) {
- if (contains(Name)) {
+ if (contains(AnalysisT::ResultType::analysisName())) {
ErrorBuilder::fatal("duplicate analysis registration for '{0}'", Name);
}
getAnalysisNames().push_back(AnalysisT::ResultType::analysisName());
@@ -85,7 +85,7 @@ class AnalysisRegistry {
};
/// Returns true if an analysis is registered under \p Name.
- static bool contains(llvm::StringRef Name);
+ static bool contains(const AnalysisName &Name);
/// Returns the names of all registered analyses.
static const std::vector<AnalysisName> &names();
@@ -93,7 +93,7 @@ class AnalysisRegistry {
/// Instantiates the analysis registered under \p Name, or returns an error
/// if no such analysis is registered.
static llvm::Expected<std::unique_ptr<AnalysisBase>>
- instantiate(llvm::StringRef Name);
+ instantiate(const AnalysisName &Name);
private:
/// Returns the global list of registered analysis names.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h
index b2e9c36d94bd9..5a0105fc1f4d9 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h
@@ -83,8 +83,7 @@ class WPASuite {
auto It = Data.find(Name);
if (It == Data.end()) {
return ErrorBuilder::create(std::errc::invalid_argument,
- "no result for analysis '{0}' in WPASuite",
- Name.str())
+ "no result for '{0}' in WPASuite", Name)
.build();
}
return *It->second;
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.cpp
index 8d6791f47cfd4..47eb3f6d4e7e7 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.cpp
@@ -63,7 +63,7 @@ AnalysisDriver::toposort(llvm::ArrayRef<AnalysisName> Roots) {
Path.push_back(Name);
llvm::Expected<std::unique_ptr<AnalysisBase>> V =
- AnalysisRegistry::instantiate(Name.str());
+ AnalysisRegistry::instantiate(Name);
if (!V) {
return V.takeError();
}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.cpp
index ba91d27f9f168..8e1ea954d9afd 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.cpp
@@ -24,9 +24,8 @@ std::vector<AnalysisName> &AnalysisRegistry::getAnalysisNames() {
return Names;
}
-bool AnalysisRegistry::contains(llvm::StringRef Name) {
- return llvm::is_contained(getAnalysisNames(),
- AnalysisName(std::string(Name)));
+bool AnalysisRegistry::contains(const AnalysisName &Name) {
+ return llvm::is_contained(getAnalysisNames(), Name);
}
const std::vector<AnalysisName> &AnalysisRegistry::names() {
@@ -34,9 +33,9 @@ const std::vector<AnalysisName> &AnalysisRegistry::names() {
}
llvm::Expected<std::unique_ptr<AnalysisBase>>
-AnalysisRegistry::instantiate(llvm::StringRef Name) {
+AnalysisRegistry::instantiate(const AnalysisName &Name) {
for (const auto &Entry : RegistryT::entries()) {
- if (Entry.getName() == Name) {
+ if (Entry.getName() == Name.str()) {
return std::unique_ptr<AnalysisBase>(Entry.instantiate());
}
}
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/WholeProgramAnalysis/AnalysisDriverTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/WholeProgramAnalysis/AnalysisDriverTest.cpp
index ee6cc31408c29..e206b33d80295 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/WholeProgramAnalysis/AnalysisDriverTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/WholeProgramAnalysis/AnalysisDriverTest.cpp
@@ -79,13 +79,25 @@ class Analysis4EntitySummary final : public EntitySummary {
}
};
+// ---------------------------------------------------------------------------
+// Analysis names
+// ---------------------------------------------------------------------------
+
+const AnalysisName Analysis1Name("Analysis1");
+const AnalysisName Analysis2Name("Analysis2");
+const AnalysisName Analysis3Name("Analysis3");
+const AnalysisName Analysis4Name("Analysis4");
+const AnalysisName Analysis5Name("Analysis5");
+const AnalysisName CycleAName("CycleA");
+const AnalysisName CycleBName("CycleB");
+
// ---------------------------------------------------------------------------
// Results
// ---------------------------------------------------------------------------
class Analysis1Result final : public AnalysisResult {
public:
- static AnalysisName analysisName() { return AnalysisName("Analysis1"); }
+ static AnalysisName analysisName() { return Analysis1Name; }
std::vector<std::pair<EntityId, int>> Entries;
bool WasInitialized = false;
bool WasFinalized = false;
@@ -93,7 +105,7 @@ class Analysis1Result final : public AnalysisResult {
class Analysis2Result final : public AnalysisResult {
public:
- static AnalysisName analysisName() { return AnalysisName("Analysis2"); }
+ static AnalysisName analysisName() { return Analysis2Name; }
std::vector<std::pair<EntityId, int>> Entries;
bool WasInitialized = false;
bool WasFinalized = false;
@@ -103,14 +115,14 @@ class Analysis2Result final : public AnalysisResult {
// into the LUSummary to verify the driver silently skips it.
class Analysis3Result final : public AnalysisResult {
public:
- static AnalysisName analysisName() { return AnalysisName("Analysis3"); }
+ static AnalysisName analysisName() { return Analysis3Name; }
};
// Analysis4 has a registered analysis but no data is inserted into the
// LUSummary, so it is skipped and get() returns nullptr.
class Analysis4Result final : public AnalysisResult {
public:
- static AnalysisName analysisName() { return AnalysisName("Analysis4"); }
+ static AnalysisName analysisName() { return Analysis4Name; }
std::vector<std::pair<EntityId, int>> Entries;
bool WasInitialized = false;
bool WasFinalized = false;
@@ -121,7 +133,7 @@ class Analysis4Result final : public AnalysisResult {
// initialize() and that the initialize/step/finalize lifecycle is respected.
class Analysis5Result final : public AnalysisResult {
public:
- static AnalysisName analysisName() { return AnalysisName("Analysis5"); }
+ static AnalysisName analysisName() { return Analysis5Name; }
std::vector<std::string> CallSequence;
std::vector<std::pair<EntityId, int>> Analysis1Entries;
std::vector<std::pair<EntityId, int>> Analysis2Entries;
@@ -134,12 +146,12 @@ class Analysis5Result final : public AnalysisResult {
// any analysis executes.
class CycleAResult final : public AnalysisResult {
public:
- static AnalysisName analysisName() { return AnalysisName("CycleA"); }
+ static AnalysisName analysisName() { return CycleAName; }
};
class CycleBResult final : public AnalysisResult {
public:
- static AnalysisName analysisName() { return AnalysisName("CycleB"); }
+ static AnalysisName analysisName() { return CycleBName; }
};
// ---------------------------------------------------------------------------
@@ -327,27 +339,28 @@ class AnalysisDriverTest : public TestFixture {
// ---------------------------------------------------------------------------
TEST(AnalysisRegistryTest, AnalysisIsRegistered) {
- EXPECT_TRUE(AnalysisRegistry::contains("Analysis1"));
- EXPECT_TRUE(AnalysisRegistry::contains("Analysis2"));
- EXPECT_FALSE(AnalysisRegistry::contains("Analysis3"));
- EXPECT_TRUE(AnalysisRegistry::contains("Analysis4"));
- EXPECT_TRUE(AnalysisRegistry::contains("Analysis5"));
- EXPECT_TRUE(AnalysisRegistry::contains("CycleA"));
- EXPECT_TRUE(AnalysisRegistry::contains("CycleB"));
+ EXPECT_TRUE(AnalysisRegistry::contains(Analysis1Name));
+ EXPECT_TRUE(AnalysisRegistry::contains(Analysis2Name));
+ EXPECT_FALSE(AnalysisRegistry::contains(Analysis3Name));
+ EXPECT_TRUE(AnalysisRegistry::contains(Analysis4Name));
+ EXPECT_TRUE(AnalysisRegistry::contains(Analysis5Name));
+ EXPECT_TRUE(AnalysisRegistry::contains(CycleAName));
+ EXPECT_TRUE(AnalysisRegistry::contains(CycleBName));
}
TEST(AnalysisRegistryTest, AnalysisCanBeInstantiated) {
constexpr auto instantiate = AnalysisRegistry::instantiate;
- EXPECT_THAT_EXPECTED(instantiate("AnalysisNonExisting"),
- llvm::FailedWithMessage(
- "no analysis registered for 'AnalysisNonExisting'"));
- EXPECT_THAT_EXPECTED(instantiate("Analysis1"), llvm::Succeeded());
- EXPECT_THAT_EXPECTED(instantiate("Analysis2"), llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(
+ instantiate(AnalysisName("AnalysisNonExisting")),
+ llvm::FailedWithMessage(
+ "no analysis registered for 'AnalysisName(AnalysisNonExisting)'"));
+ EXPECT_THAT_EXPECTED(instantiate(Analysis1Name), llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(instantiate(Analysis2Name), llvm::Succeeded());
// No Analysis3 - not registered, so instantiate() would fail.
- EXPECT_THAT_EXPECTED(instantiate("Analysis4"), llvm::Succeeded());
- EXPECT_THAT_EXPECTED(instantiate("Analysis5"), llvm::Succeeded());
- EXPECT_THAT_EXPECTED(instantiate("CycleA"), llvm::Succeeded());
- EXPECT_THAT_EXPECTED(instantiate("CycleB"), llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(instantiate(Analysis4Name), llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(instantiate(Analysis5Name), llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(instantiate(CycleAName), llvm::Succeeded());
+ EXPECT_THAT_EXPECTED(instantiate(CycleBName), llvm::Succeeded());
}
// run<T...>() — processes the non-cyclic analyses in topological order.
@@ -424,9 +437,10 @@ TEST_F(AnalysisDriverTest, RunAll) {
}
// Unregistered analysis — not present in WPA.
- EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis3Result>(),
- llvm::FailedWithMessage(
- "no result for analysis 'Analysis3' in WPASuite"));
+ EXPECT_THAT_EXPECTED(
+ WPAOrErr->get<Analysis3Result>(),
+ llvm::FailedWithMessage(
+ "no result for 'AnalysisName(Analysis3)' in WPASuite"));
}
// run(names) — processes only the analyses for the given names.
@@ -451,9 +465,10 @@ TEST_F(AnalysisDriverTest, RunByName) {
EXPECT_TRUE(R1OrErr->WasFinalized);
// Analysis2 was not requested — not present even though data exists.
- EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis2Result>(),
- llvm::FailedWithMessage(
- "no result for analysis 'Analysis2' in WPASuite"));
+ EXPECT_THAT_EXPECTED(
+ WPAOrErr->get<Analysis2Result>(),
+ llvm::FailedWithMessage(
+ "no result for 'AnalysisName(Analysis2)' in WPASuite"));
}
// run(names) — error when a requested name has no data in LUSummary.
@@ -478,7 +493,8 @@ TEST_F(AnalysisDriverTest, RunByNameErrorMissingAnalysis) {
// Analysis3 has data but no registered analysis.
EXPECT_THAT_EXPECTED(
Driver.run({AnalysisName("Analysis3")}),
- llvm::FailedWithMessage("no analysis registered for 'Analysis3'"));
+ llvm::FailedWithMessage(
+ "no analysis registered for 'AnalysisName(Analysis3)'"));
}
// run<ResultTs...>() — type-safe subset.
@@ -503,9 +519,10 @@ TEST_F(AnalysisDriverTest, RunByType) {
EXPECT_TRUE(R1OrErr->WasFinalized);
// Analysis2 was not requested — not present even though data exists.
- EXPECT_THAT_EXPECTED(WPAOrErr->get<Analysis2Result>(),
- llvm::FailedWithMessage(
- "no result for analysis 'Analysis2' in WPASuite"));
+ EXPECT_THAT_EXPECTED(
+ WPAOrErr->get<Analysis2Result>(),
+ llvm::FailedWithMessage(
+ "no result for 'AnalysisName(Analysis2)' in WPASuite"));
}
// run<ResultTs...>() — error when a requested type has no data in LUSummary.
>From 004991a57c1ae62d64b2a04d3a9439405e2cd025 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 18 Mar 2026 16:28:39 -0700
Subject: [PATCH 17/19] Add JSONFormat support for WPASuite
---
.../Core/Model/PrivateFieldNames.def | 2 +
.../Core/Serialization/JSONFormat.h | 29 +
.../Core/Serialization/SerializationFormat.h | 124 +++
.../Core/WholeProgramAnalysis/WPASuite.h | 4 +
.../Core/CMakeLists.txt | 1 +
.../JSONFormat/JSONFormatImpl.cpp | 14 +
.../Serialization/JSONFormat/JSONFormatImpl.h | 8 +
.../Serialization/JSONFormat/WPASuite.cpp | 209 +++++
.../CMakeLists.txt | 1 +
.../TUSummaryExtractorFrontendActionTest.cpp | 9 +
.../Registries/MockSerializationFormat.cpp | 10 +
.../Registries/MockSerializationFormat.h | 5 +
.../JSONFormatTest/WPASuiteTest.cpp | 768 ++++++++++++++++++
.../TestFixture.h | 3 +
14 files changed, 1187 insertions(+)
create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/WPASuite.cpp
create mode 100644 clang/unittests/ScalableStaticAnalysisFramework/Serialization/JSONFormatTest/WPASuiteTest.cpp
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/PrivateFieldNames.def b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/PrivateFieldNames.def
index 1ee2a809430f4..1c75543062cbc 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/PrivateFieldNames.def
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Model/PrivateFieldNames.def
@@ -42,5 +42,7 @@ FIELD(TUSummaryEncoding, Data)
FIELD(TUSummaryEncoding, IdTable)
FIELD(TUSummaryEncoding, LinkageTable)
FIELD(TUSummaryEncoding, TUNamespace)
+FIELD(WPASuite, Data)
+FIELD(WPASuite, IdTable)
#undef FIELD
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h
index 47b46cbe42698..18dc71585b0fd 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h
@@ -58,6 +58,11 @@ class JSONFormat final : public SerializationFormat {
llvm::Error writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
llvm::StringRef Path) override;
+ llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) override;
+
+ llvm::Error writeWPASuite(const WPASuite &Suite,
+ llvm::StringRef Path) override;
+
void forEachRegisteredAnalysis(
llvm::function_ref<void(llvm::StringRef Name, llvm::StringRef Desc)>
Callback) const override;
@@ -74,6 +79,16 @@ class JSONFormat final : public SerializationFormat {
using FormatInfo = FormatInfoEntry<SerializerFn, DeserializerFn>;
+ using AnalysisResultSerializerFn =
+ llvm::function_ref<Object(const AnalysisResult &, EntityIdToJSONFn)>;
+ using AnalysisResultDeserializerFn =
+ llvm::function_ref<llvm::Expected<std::unique_ptr<AnalysisResult>>(
+ const Object &, EntityIdFromJSONFn)>;
+
+ using AnalysisResultRegistry =
+ SerializationFormat::AnalysisResultRegistryGenerator<
+ JSONFormat, AnalysisResultSerializerFn, AnalysisResultDeserializerFn>;
+
private:
static std::map<SummaryName, FormatInfo> initFormatInfos();
const std::map<SummaryName, FormatInfo> FormatInfos = initFormatInfos();
@@ -186,6 +201,18 @@ class JSONFormat final : public SerializationFormat {
const std::map<SummaryName,
std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
&EncodingSummaryDataMap) const;
+
+ llvm::Expected<std::pair<AnalysisName, std::unique_ptr<AnalysisResult>>>
+ analysisResultMapEntryFromJSON(const Object &Entry) const;
+ llvm::Expected<Object> analysisResultMapEntryToJSON(
+ const AnalysisName &Name,
+ const std::unique_ptr<AnalysisResult> &Result) const;
+
+ llvm::Expected<std::map<AnalysisName, std::unique_ptr<AnalysisResult>>>
+ analysisResultMapFromJSON(const Array &ResultsArray) const;
+ llvm::Expected<Array> analysisResultMapToJSON(
+ const std::map<AnalysisName, std::unique_ptr<AnalysisResult>> &Data)
+ const;
};
} // namespace clang::ssaf
@@ -193,6 +220,8 @@ class JSONFormat final : public SerializationFormat {
namespace llvm {
extern template class CLANG_TEMPLATE_ABI
Registry<clang::ssaf::JSONFormat::FormatInfo>;
+extern template class CLANG_TEMPLATE_ABI
+ Registry<clang::ssaf::JSONFormat::AnalysisResultRegistry::Entry>;
} // namespace llvm
#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SERIALIZATION_JSONFORMAT_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h
index 7aebf06a6368e..113e8ca9b1497 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h
@@ -19,10 +19,13 @@
#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/TUSummaryEncoding.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
+#include "llvm/Support/Registry.h"
namespace clang::ssaf {
@@ -55,6 +58,11 @@ class SerializationFormat {
writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
llvm::StringRef Path) = 0;
+ virtual llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) = 0;
+
+ virtual llvm::Error writeWPASuite(const WPASuite &Suite,
+ llvm::StringRef Path) = 0;
+
/// Invokes \p Callback once for each analysis that has registered
/// serialization support for this format.
virtual void forEachRegisteredAnalysis(
@@ -67,10 +75,126 @@ class SerializationFormat {
static EntityId makeEntityId(const size_t Index) { return EntityId(Index); }
+ /// Constructs an empty WPASuite. Bypasses the private default constructor
+ /// so that deserialization code can build a WPASuite incrementally.
+ static WPASuite makeWPASuite() { return WPASuite(); }
+
#define FIELD(CLASS, FIELD_NAME) \
static const auto &get##FIELD_NAME(const CLASS &X) { return X.FIELD_NAME; } \
static auto &get##FIELD_NAME(CLASS &X) { return X.FIELD_NAME; }
#include "clang/ScalableStaticAnalysisFramework/Core/Model/PrivateFieldNames.def"
+
+ /// Generates a per-format plugin registry for analysis result
+ /// serializers and deserializers.
+ ///
+ /// Each concrete format (e.g. JSONFormat) instantiates this template once
+ /// via a \c using alias, then exposes that alias publicly so that analysis
+ /// authors can register (de)serialization support with a single declaration:
+ ///
+ /// static MyFormat::AnalysisResultRegistryGenerator::Add<MyAnalysisResult>
+ /// Reg(serializeFn, deserializeFn);
+ ///
+ /// ---
+ /// Design overview
+ /// ---
+ ///
+ /// **Registry isolation via \p FormatT.**
+ /// The underlying store is \c llvm::Registry<Entry>, which is a global
+ /// linked list keyed on the \c Entry type. Because \c Entry is a member of
+ /// this template, each \c (FormatT, SerializerFn, DeserializerFn)
+ /// instantiation produces a distinct \c Entry type and therefore a distinct
+ /// \c llvm::Registry — even if two formats happen to share the same
+ /// serializer/deserializer function signatures. The \p FormatT parameter
+ /// exists solely to provide this isolation; it is otherwise unused inside
+ /// the template body.
+ ///
+ /// **Bridging \c function_ref into \c llvm::Registry.**
+ /// \c llvm::function_ref is a non-owning view of a callable — it cannot be
+ /// stored inside the registry because the registry only keeps nullary
+ /// factories of the form <tt>[]{ return make_unique<ConcreteEntry>(); }</tt>
+ /// that capture no state. Two mechanisms bridge this gap:
+ ///
+ /// 1. *Function-local statics as per-analysis storage.*
+ /// Inside \c Add<AnalysisResultT>::Add(...), two function-local statics
+ /// — \c SavedSerialize and \c SavedDeserialize — are initialized from
+ /// the constructor arguments on the first (and only) call. Because
+ /// \c Add<T>::Add(...) is a distinct function for each \c T, each
+ /// analysis type gets its own pair of statics with program lifetime,
+ /// giving the \c function_ref values a stable home.
+ ///
+ /// 2. *\c ConcreteEntry as a local struct.*
+ /// \c ConcreteEntry is defined inside the \c Add constructor body so
+ /// that its default constructor can read \c SavedSerialize and
+ /// \c SavedDeserialize from the enclosing function scope. When
+ /// \c llvm::Registry later calls \c E.instantiate() during \c lookup,
+ /// it invokes \c ConcreteEntry(), which re-wraps those stored values
+ /// into a fresh \c Entry — reconstructing the \c function_ref from the
+ /// same stable underlying callables.
+ ///
+ /// **One-time registration via a function-local static \c Reg.**
+ /// \c static typename RegistryT::template Add<ConcreteEntry> Reg(NameStr,"")
+ /// is also a function-local static. C++ guarantees it is initialized exactly
+ /// once, on the first call to \c Add<T>::Add(...). Its constructor appends a
+ /// node to the \c llvm::Registry linked list, associating \c NameStr with
+ /// the \c ConcreteEntry factory. All subsequent calls to \c Add<T>::Add(...)
+ /// hit the \c Registered guard first and abort with a fatal error, making
+ /// duplicate registrations a detectable programmer mistake rather than a
+ /// silent no-op.
+ ///
+ /// **Lookup.**
+ /// \c lookup iterates \c RegistryT::entries() and compares each node's name
+ /// (available directly on the entry node without instantiation) against the
+ /// requested \c AnalysisName. Only the matching node is instantiated via
+ /// \c E.instantiate(), which invokes \c ConcreteEntry() and returns the
+ /// stored \c function_ref pair.
+ template <class FormatT, class SerializerFn, class DeserializerFn>
+ class AnalysisResultRegistryGenerator {
+ public:
+ struct Entry {
+ explicit Entry(SerializerFn Serialize, DeserializerFn Deserialize)
+ : Serialize(Serialize), Deserialize(Deserialize) {}
+ virtual ~Entry() = default;
+ SerializerFn Serialize;
+ DeserializerFn Deserialize;
+ };
+
+ using RegistryT = llvm::Registry<Entry>;
+
+ template <class AnalysisResultT> struct Add {
+ Add(SerializerFn Serialize, DeserializerFn Deserialize) {
+ static bool Registered = false;
+ if (Registered) {
+ ErrorBuilder::fatal("support is already registered for analysis: {0}",
+ AnalysisResultT::analysisName());
+ }
+ Registered = true;
+ static SerializerFn SavedSerialize = Serialize;
+ static DeserializerFn SavedDeserialize = Deserialize;
+
+ struct ConcreteEntry : Entry {
+ ConcreteEntry() : Entry(SavedSerialize, SavedDeserialize) {}
+ };
+
+ static std::string NameStr =
+ AnalysisResultT::analysisName().str().str();
+ static typename RegistryT::template Add<ConcreteEntry> Reg(NameStr, "");
+ }
+ };
+
+ static llvm::Expected<std::pair<SerializerFn, DeserializerFn>>
+ lookup(const AnalysisName &Name) {
+ for (const auto &E : RegistryT::entries()) {
+ if (E.getName() == Name.str()) {
+ auto Entry = E.instantiate();
+ return std::make_pair(Entry->Serialize, Entry->Deserialize);
+ }
+ }
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ "no support registered for analysis: {0}",
+ Name)
+ .build();
+ }
+ };
};
template <class SerializerFn, class DeserializerFn> struct FormatInfoEntry {
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h
index 5a0105fc1f4d9..52c6f9e46297d 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h
@@ -26,6 +26,8 @@
namespace clang::ssaf {
class AnalysisDriver;
+class SerializationFormat;
+class TestFixture;
/// Bundles the EntityIdTable (moved from the LUSummary) and the analysis
/// results produced by one AnalysisDriver::run() call, keyed by AnalysisName.
@@ -34,6 +36,8 @@ class AnalysisDriver;
/// are self-contained in one object.
class WPASuite {
friend class AnalysisDriver;
+ friend class SerializationFormat;
+ friend class TestFixture;
EntityIdTable IdTable;
std::map<AnalysisName, std::unique_ptr<AnalysisResult>> Data;
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
index 8c306163df1a7..83772ceff58bf 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt
@@ -17,6 +17,7 @@ add_clang_library(clangScalableStaticAnalysisFrameworkCore
Serialization/JSONFormat/LUSummaryEncoding.cpp
Serialization/JSONFormat/TUSummary.cpp
Serialization/JSONFormat/TUSummaryEncoding.cpp
+ Serialization/JSONFormat/WPASuite.cpp
Serialization/SerializationFormatRegistry.cpp
SummaryData/LUSummaryConsumer.cpp
SummaryData/SummaryDataBuilderRegistry.cpp
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.cpp
index 4072532d4972c..230fed5bac20d 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.cpp
@@ -13,8 +13,12 @@
// NOLINTNEXTLINE(misc-use-internal-linkage)
volatile int SSAFJSONFormatAnchorSource = 0;
+
LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::ssaf::JSONFormat::FormatInfo>)
+LLVM_INSTANTIATE_REGISTRY(
+ llvm::Registry<clang::ssaf::JSONFormat::AnalysisResultRegistry::Entry>)
+
static clang::ssaf::SerializationFormatRegistry::Add<clang::ssaf::JSONFormat>
RegisterJSONFormat("json", "JSON serialization format");
@@ -141,6 +145,16 @@ SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
+//----------------------------------------------------------------------------
+// AnalysisName
+//----------------------------------------------------------------------------
+
+AnalysisName analysisNameFromJSON(llvm::StringRef AnalysisNameStr) {
+ return AnalysisName(AnalysisNameStr.str());
+}
+
+llvm::StringRef analysisNameToJSON(const AnalysisName &AN) { return AN.str(); }
+
//----------------------------------------------------------------------------
// EntityId
//----------------------------------------------------------------------------
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.h b/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.h
index 2ff1f2ae16192..97154b19bed31 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.h
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.h
@@ -16,6 +16,7 @@
#include "../../ModelStringConversions.h"
#include "JSONEntitySummaryEncoding.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/EntitySummaryEncoding.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityLinkage.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
@@ -139,6 +140,13 @@ llvm::Error writeJSON(Value &&V, llvm::StringRef Path);
SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr);
llvm::StringRef summaryNameToJSON(const SummaryName &SN);
+//----------------------------------------------------------------------------
+// AnalysisName helpers
+//----------------------------------------------------------------------------
+
+AnalysisName analysisNameFromJSON(llvm::StringRef AnalysisNameStr);
+llvm::StringRef analysisNameToJSON(const AnalysisName &AN);
+
//----------------------------------------------------------------------------
// BuildNamespaceKind helpers
//----------------------------------------------------------------------------
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/WPASuite.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/WPASuite.cpp
new file mode 100644
index 0000000000000..f6673d9ef4742
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/WPASuite.cpp
@@ -0,0 +1,209 @@
+//===- WPASuite.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 "JSONFormatImpl.h"
+
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
+
+namespace clang::ssaf {
+
+//----------------------------------------------------------------------------
+// AnalysisResultMapEntry
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<AnalysisName, std::unique_ptr<AnalysisResult>>>
+JSONFormat::analysisResultMapEntryFromJSON(const Object &Entry) const {
+ auto OptName = Entry.getString("analysis_name");
+ if (!OptName) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "AnalysisName", "analysis_name", "string")
+ .build();
+ }
+
+ AnalysisName Name = analysisNameFromJSON(*OptName);
+
+ auto ExpectedFns = AnalysisResultRegistryGenerator::lookup(Name);
+ if (!ExpectedFns) {
+ return ExpectedFns.takeError();
+ }
+
+ const Object *ResultObj = Entry.getObject("result");
+ if (!ResultObj) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "AnalysisResult", "result", "object")
+ .build();
+ }
+
+ auto ExpectedResult =
+ ExpectedFns->second(*ResultObj, &entityIdFromJSONObject);
+ if (!ExpectedResult) {
+ return ExpectedResult.takeError();
+ }
+
+ return std::make_pair(std::move(Name), std::move(*ExpectedResult));
+}
+
+llvm::Expected<Object> JSONFormat::analysisResultMapEntryToJSON(
+ const AnalysisName &Name,
+ const std::unique_ptr<AnalysisResult> &Result) const {
+ auto ExpectedFns = AnalysisResultRegistryGenerator::lookup(Name);
+ if (!ExpectedFns) {
+ return ExpectedFns.takeError();
+ }
+
+ Object Entry;
+ Entry["analysis_name"] = analysisNameToJSON(Name);
+ Entry["result"] = ExpectedFns->first(*Result, &entityIdToJSONObject);
+ return Entry;
+}
+
+//----------------------------------------------------------------------------
+// AnalysisResultMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::map<AnalysisName, std::unique_ptr<AnalysisResult>>>
+JSONFormat::analysisResultMapFromJSON(const Array &ResultsArray) const {
+ std::map<AnalysisName, std::unique_ptr<AnalysisResult>> Results;
+ for (size_t I = 0; I < ResultsArray.size(); ++I) {
+ const Object *Entry = ResultsArray[I].getAsObject();
+ if (!Entry) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtIndex,
+ "WPA result entry", I, "object")
+ .build();
+ }
+
+ auto ExpectedPair = analysisResultMapEntryFromJSON(*Entry);
+ if (!ExpectedPair) {
+ return ErrorBuilder::wrap(ExpectedPair.takeError())
+ .context(ErrorMessages::ReadingFromIndex, "WPA result entry", I)
+ .build();
+ }
+
+ auto [Name, Result] = std::move(*ExpectedPair);
+ bool Inserted = Results.try_emplace(Name, std::move(Result)).second;
+ if (!Inserted) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedInsertionOnDuplication,
+ "WPA result", I, Name)
+ .build();
+ }
+ }
+ return Results;
+}
+
+llvm::Expected<Array> JSONFormat::analysisResultMapToJSON(
+ const std::map<AnalysisName, std::unique_ptr<AnalysisResult>> &Data) const {
+ Array Results;
+ for (const auto &[Name, Result] : Data) {
+ auto ExpectedEntry = analysisResultMapEntryToJSON(Name, Result);
+ if (!ExpectedEntry) {
+ return ExpectedEntry.takeError();
+ }
+ Results.push_back(std::move(*ExpectedEntry));
+ }
+ return Results;
+}
+
+//----------------------------------------------------------------------------
+// WPASuite
+//----------------------------------------------------------------------------
+
+llvm::Expected<WPASuite> JSONFormat::readWPASuite(llvm::StringRef Path) {
+ auto ExpectedJSON = readJSON(Path);
+ if (!ExpectedJSON) {
+ return ErrorBuilder::wrap(ExpectedJSON.takeError())
+ .context(ErrorMessages::ReadingFromFile, "WPASuite", Path)
+ .build();
+ }
+
+ Object *RootObjectPtr = ExpectedJSON->getAsObject();
+ if (!RootObjectPtr) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObject, "WPASuite",
+ "object")
+ .context(ErrorMessages::ReadingFromFile, "WPASuite", Path)
+ .build();
+ }
+
+ const Object &RootObject = *RootObjectPtr;
+
+ WPASuite Suite = makeWPASuite();
+
+ {
+ const Array *IdTableArray = RootObject.getArray("id_table");
+ if (!IdTableArray) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "IdTable", "id_table", "array")
+ .context(ErrorMessages::ReadingFromFile, "WPASuite", Path)
+ .build();
+ }
+
+ auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
+ if (!ExpectedIdTable) {
+ return ErrorBuilder::wrap(ExpectedIdTable.takeError())
+ .context(ErrorMessages::ReadingFromField, "IdTable", "id_table")
+ .context(ErrorMessages::ReadingFromFile, "WPASuite", Path)
+ .build();
+ }
+
+ getIdTable(Suite) = std::move(*ExpectedIdTable);
+ }
+
+ {
+ const Array *ResultsArray = RootObject.getArray("results");
+ if (!ResultsArray) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "WPA results", "results", "array")
+ .context(ErrorMessages::ReadingFromFile, "WPASuite", Path)
+ .build();
+ }
+
+ auto ExpectedResultsMap = analysisResultMapFromJSON(*ResultsArray);
+ if (!ExpectedResultsMap) {
+ return ErrorBuilder::wrap(ExpectedResultsMap.takeError())
+ .context(ErrorMessages::ReadingFromField, "WPA results", "results")
+ .context(ErrorMessages::ReadingFromFile, "WPASuite", Path)
+ .build();
+ }
+
+ getData(Suite) = std::move(*ExpectedResultsMap);
+ }
+
+ return Suite;
+}
+
+llvm::Error JSONFormat::writeWPASuite(const WPASuite &Suite,
+ llvm::StringRef Path) {
+ Object RootObject;
+
+ RootObject["id_table"] = entityIdTableToJSON(getIdTable(Suite));
+
+ auto ExpectedResults = analysisResultMapToJSON(getData(Suite));
+ if (!ExpectedResults) {
+ return ErrorBuilder::wrap(ExpectedResults.takeError())
+ .context(ErrorMessages::WritingToFile, "WPASuite", Path)
+ .build();
+ }
+
+ RootObject["results"] = std::move(*ExpectedResults);
+
+ if (auto Error = writeJSON(std::move(RootObject), Path)) {
+ return ErrorBuilder::wrap(std::move(Error))
+ .context(ErrorMessages::WritingToFile, "WPASuite", Path)
+ .build();
+ }
+
+ return llvm::Error::success();
+}
+
+} // namespace clang::ssaf
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
index 345eed6c5279b..f24dd837f1274 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
@@ -19,6 +19,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
Serialization/JSONFormatTest/JSONFormatTest.cpp
Serialization/JSONFormatTest/LUSummaryTest.cpp
Serialization/JSONFormatTest/TUSummaryTest.cpp
+ Serialization/JSONFormatTest/WPASuiteTest.cpp
SummaryData/SummaryDataTest.cpp
SummaryNameTest.cpp
TestFixture.cpp
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendActionTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendActionTest.cpp
index d684366ed53ce..5d2392c9236a9 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendActionTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendActionTest.cpp
@@ -99,6 +99,15 @@ class FailingSerializationFormat final : public SerializationFormat {
return failing("writeLUSummaryEncoding");
}
+ llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) override {
+ return failing("readWPASuite");
+ }
+
+ llvm::Error writeWPASuite(const WPASuite &Suite,
+ llvm::StringRef Path) override {
+ return failing("writeWPASuite");
+ }
+
void forEachRegisteredAnalysis(
llvm::function_ref<void(llvm::StringRef Name, llvm::StringRef Desc)>
Callback) const override {}
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.cpp
index 535b5fced0da6..684249620e869 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.cpp
@@ -199,3 +199,13 @@ llvm::Error MockSerializationFormat::writeLUSummaryEncoding(
llvm_unreachable(
"MockSerializationFormat does not support LUSummaryEncoding");
}
+
+llvm::Expected<WPASuite>
+MockSerializationFormat::readWPASuite(llvm::StringRef Path) {
+ llvm_unreachable("MockSerializationFormat does not support WPASuite");
+}
+
+llvm::Error MockSerializationFormat::writeWPASuite(const WPASuite &Suite,
+ llvm::StringRef Path) {
+ llvm_unreachable("MockSerializationFormat does not support WPASuite");
+}
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.h b/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.h
index 87d84c3d06c7a..71acfeffca1c9 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.h
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.h
@@ -44,6 +44,11 @@ class MockSerializationFormat final : public SerializationFormat {
llvm::Error writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
llvm::StringRef Path) override;
+ llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) override;
+
+ llvm::Error writeWPASuite(const WPASuite &Suite,
+ llvm::StringRef Path) override;
+
/// Lists what analyses implement this particular serialisation format.
void forEachRegisteredAnalysis(
llvm::function_ref<void(llvm::StringRef Name, llvm::StringRef Desc)>
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Serialization/JSONFormatTest/WPASuiteTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Serialization/JSONFormatTest/WPASuiteTest.cpp
new file mode 100644
index 0000000000000..3972604a36f49
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Serialization/JSONFormatTest/WPASuiteTest.cpp
@@ -0,0 +1,768 @@
+//===- WPASuiteTest.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for SSAF JSON serialization format reading and writing of
+// WPASuite.
+//
+//===----------------------------------------------------------------------===//
+
+#include "JSONFormatTest.h"
+
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+
+#include <memory>
+#include <vector>
+
+using namespace clang::ssaf;
+using namespace llvm;
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+
+namespace {
+
+// ============================================================================
+// First Test AnalysisResult - Tags (no entity ID references)
+// ============================================================================
+
+struct TagsAnalysisResultForJSONFormatTest final : AnalysisResult {
+ static AnalysisName analysisName() {
+ return AnalysisName("TagsAnalysisResultForJSONFormatTest");
+ }
+
+ std::vector<std::string> Tags;
+};
+
+json::Object serializeTagsAnalysisResult(const AnalysisResult &Result,
+ JSONFormat::EntityIdToJSONFn) {
+ const auto &R =
+ static_cast<const TagsAnalysisResultForJSONFormatTest &>(Result);
+ json::Array TagsArray;
+ for (const auto &Tag : R.Tags)
+ TagsArray.push_back(Tag);
+ return json::Object{{"tags", std::move(TagsArray)}};
+}
+
+Expected<std::unique_ptr<AnalysisResult>>
+deserializeTagsAnalysisResult(const json::Object &Obj,
+ JSONFormat::EntityIdFromJSONFn) {
+ const json::Array *TagsArray = Obj.getArray("tags");
+ if (!TagsArray)
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'tags'");
+
+ auto R = std::make_unique<TagsAnalysisResultForJSONFormatTest>();
+ for (const auto &[Index, Val] : llvm::enumerate(*TagsArray)) {
+ auto S = Val.getAsString();
+ if (!S)
+ return createStringError(inconvertibleErrorCode(),
+ "tags element at index %zu is not a string",
+ Index);
+ R->Tags.push_back(S->str());
+ }
+ return std::move(R);
+}
+
+JSONFormat::AnalysisResultRegistryGenerator::Add<
+ TagsAnalysisResultForJSONFormatTest>
+ RegisterTagsAnalysisFormatInfo(serializeTagsAnalysisResult,
+ deserializeTagsAnalysisResult);
+
+// ============================================================================
+// Second Test AnalysisResult - Counts (with entity ID references)
+// ============================================================================
+
+struct CountsAnalysisResultForJSONFormatTest final : AnalysisResult {
+ static AnalysisName analysisName() {
+ return AnalysisName("CountsAnalysisResultForJSONFormatTest");
+ }
+
+ std::vector<std::pair<EntityId, int>> Counts;
+};
+
+json::Object
+serializeCountsAnalysisResult(const AnalysisResult &Result,
+ JSONFormat::EntityIdToJSONFn ToJSON) {
+ const auto &R =
+ static_cast<const CountsAnalysisResultForJSONFormatTest &>(Result);
+ json::Array CountsArray;
+ for (const auto &[EI, Count] : R.Counts) {
+ CountsArray.push_back(
+ json::Object{{"entity_id", ToJSON(EI)}, {"count", Count}});
+ }
+ return json::Object{{"counts", std::move(CountsArray)}};
+}
+
+Expected<std::unique_ptr<AnalysisResult>>
+deserializeCountsAnalysisResult(const json::Object &Obj,
+ JSONFormat::EntityIdFromJSONFn FromJSON) {
+ const json::Array *CountsArray = Obj.getArray("counts");
+ if (!CountsArray)
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'counts'");
+
+ auto R = std::make_unique<CountsAnalysisResultForJSONFormatTest>();
+ for (const auto &[Index, Val] : llvm::enumerate(*CountsArray)) {
+ const json::Object *Entry = Val.getAsObject();
+ if (!Entry)
+ return createStringError(inconvertibleErrorCode(),
+ "counts element at index %zu is not an object",
+ Index);
+ const json::Object *EIObj = Entry->getObject("entity_id");
+ if (!EIObj)
+ return createStringError(
+ inconvertibleErrorCode(),
+ "missing or invalid 'entity_id' field at index %zu", Index);
+ auto ExpectedEI = FromJSON(*EIObj);
+ if (!ExpectedEI)
+ return ExpectedEI.takeError();
+
+ auto CountVal = Entry->getInteger("count");
+ if (!CountVal)
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid 'count' field at index %zu",
+ Index);
+ R->Counts.emplace_back(*ExpectedEI, static_cast<int>(*CountVal));
+ }
+ return std::move(R);
+}
+
+JSONFormat::AnalysisResultRegistryGenerator::Add<
+ CountsAnalysisResultForJSONFormatTest>
+ RegisterCountsAnalysisFormatInfo(serializeCountsAnalysisResult,
+ deserializeCountsAnalysisResult);
+
+// ============================================================================
+// FailingDeserializerAnalysisResult - always returns an error on deserialize
+// ============================================================================
+
+struct FailingDeserializerAnalysisResultForJSONFormatTest final
+ : AnalysisResult {
+ static AnalysisName analysisName() {
+ return AnalysisName("FailingDeserializerAnalysisResultForJSONFormatTest");
+ }
+};
+
+json::Object
+serializeFailingDeserializerAnalysisResult(const AnalysisResult &,
+ JSONFormat::EntityIdToJSONFn) {
+ return json::Object{};
+}
+
+Expected<std::unique_ptr<AnalysisResult>>
+deserializeFailingDeserializerAnalysisResult(const json::Object &,
+ JSONFormat::EntityIdFromJSONFn) {
+ return createStringError(inconvertibleErrorCode(),
+ "intentional deserializer failure");
+}
+
+JSONFormat::AnalysisResultRegistryGenerator::Add<
+ FailingDeserializerAnalysisResultForJSONFormatTest>
+ RegisterFailingDeserializerAnalysisFormatInfo(
+ serializeFailingDeserializerAnalysisResult,
+ deserializeFailingDeserializerAnalysisResult);
+
+// ============================================================================
+// JSONFormatWPASuiteTest Fixture
+// ============================================================================
+
+class JSONFormatWPASuiteTest : public JSONFormatTest {
+protected:
+ llvm::Expected<WPASuite>
+ readWPASuiteFromString(StringRef JSON,
+ StringRef FileName = "test.json") const {
+ auto ExpectedFilePath = writeJSON(JSON, FileName);
+ if (!ExpectedFilePath)
+ return ExpectedFilePath.takeError();
+ return JSONFormat().readWPASuite(makePath(FileName));
+ }
+
+ llvm::Expected<WPASuite> readWPASuiteFromFile(StringRef FileName) const {
+ return JSONFormat().readWPASuite(makePath(FileName));
+ }
+
+ llvm::Error writeWPASuite(const WPASuite &Suite, StringRef FileName) const {
+ return JSONFormat().writeWPASuite(Suite, makePath(FileName));
+ }
+
+ // Builds an empty WPASuite (no id_table entries, no results) and writes it.
+ llvm::Error writeEmptyWPASuite(StringRef FileName) const {
+ WPASuite Suite = makeWPASuite();
+ return writeWPASuite(Suite, FileName);
+ }
+
+ void readWriteCompare(StringRef JSON) const {
+ const PathString InputFileName("input.json");
+ const PathString OutputFileName("output.json");
+
+ auto ExpectedInputFilePath = writeJSON(JSON, InputFileName);
+ ASSERT_THAT_EXPECTED(ExpectedInputFilePath, Succeeded());
+
+ auto ExpectedSuite = readWPASuiteFromFile(InputFileName);
+ ASSERT_THAT_EXPECTED(ExpectedSuite, Succeeded());
+
+ ASSERT_THAT_ERROR(writeWPASuite(*ExpectedSuite, OutputFileName),
+ Succeeded());
+
+ auto ExpectedInputJSON = readJSONFromFile(InputFileName);
+ ASSERT_THAT_EXPECTED(ExpectedInputJSON, Succeeded());
+
+ auto ExpectedOutputJSON = readJSONFromFile(OutputFileName);
+ ASSERT_THAT_EXPECTED(ExpectedOutputJSON, Succeeded());
+
+ auto ExpectedNormalizedInput =
+ normalizeWPASuiteJSON(std::move(*ExpectedInputJSON));
+ ASSERT_THAT_EXPECTED(ExpectedNormalizedInput, Succeeded());
+
+ auto ExpectedNormalizedOutput =
+ normalizeWPASuiteJSON(std::move(*ExpectedOutputJSON));
+ ASSERT_THAT_EXPECTED(ExpectedNormalizedOutput, Succeeded());
+
+ ASSERT_EQ(*ExpectedNormalizedInput, *ExpectedNormalizedOutput)
+ << "Serialization is broken: input is different from output\n"
+ << "Input: " << llvm::formatv("{0:2}", *ExpectedNormalizedInput).str()
+ << "\n"
+ << "Output: "
+ << llvm::formatv("{0:2}", *ExpectedNormalizedOutput).str();
+ }
+
+private:
+ static llvm::Expected<json::Value> normalizeWPASuiteJSON(json::Value Val) {
+ auto *Obj = Val.getAsObject();
+ if (!Obj)
+ return createStringError(
+ inconvertibleErrorCode(),
+ "Cannot normalize WPASuite JSON: expected object");
+
+ auto *IdTable = Obj->getArray("id_table");
+ if (!IdTable)
+ return createStringError(
+ inconvertibleErrorCode(),
+ "Cannot normalize WPASuite JSON: missing 'id_table' array");
+
+ llvm::sort(*IdTable, [](const json::Value &A, const json::Value &B) {
+ const auto *OA = A.getAsObject();
+ const auto *OB = B.getAsObject();
+ if (!OA || !OB)
+ return false;
+ auto IA = OA->getInteger("id");
+ auto IB = OB->getInteger("id");
+ return IA && IB && *IA < *IB;
+ });
+
+ auto *Results = Obj->getArray("results");
+ if (!Results)
+ return createStringError(
+ inconvertibleErrorCode(),
+ "Cannot normalize WPASuite JSON: missing 'results' array");
+
+ llvm::sort(*Results, [](const json::Value &A, const json::Value &B) {
+ const auto *OA = A.getAsObject();
+ const auto *OB = B.getAsObject();
+ if (!OA || !OB)
+ return false;
+ auto NA = OA->getString("analysis_name");
+ auto NB = OB->getString("analysis_name");
+ return NA && NB && *NA < *NB;
+ });
+
+ return Val;
+ }
+};
+
+// ============================================================================
+// readJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatWPASuiteTest, NonexistentFile) {
+ auto Result = readWPASuiteFromFile("nonexistent.json");
+
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(HasSubstr("reading WPASuite from"),
+ HasSubstr("file does not exist"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, PathIsDirectory) {
+ auto ExpectedDirPath = makeDirectory("test_directory.json");
+ ASSERT_THAT_EXPECTED(ExpectedDirPath, Succeeded());
+
+ auto Result = readWPASuiteFromFile("test_directory.json");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from"),
+ HasSubstr("path is a directory, not a file"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, NotJsonExtension) {
+ auto ExpectedFilePath = writeJSON("{}", "test.txt");
+ ASSERT_THAT_EXPECTED(ExpectedFilePath, Succeeded());
+
+ auto Result = JSONFormat().readWPASuite(makePath("test.txt"));
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("failed to read file"),
+ HasSubstr("file does not end with '.json'"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, InvalidSyntax) {
+ auto Result = readWPASuiteFromString("{ invalid json }");
+
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("Expected object key"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, NotObject) {
+ auto Result = readWPASuiteFromString("[]");
+
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("failed to read WPASuite"),
+ HasSubstr("expected JSON object"))));
+}
+
+// ============================================================================
+// Structural Error Tests - id_table
+// ============================================================================
+
+TEST_F(JSONFormatWPASuiteTest, MissingIdTable) {
+ auto Result = readWPASuiteFromString(R"({"results": []})");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("failed to read IdTable from field "
+ "'id_table'"),
+ HasSubstr("expected JSON array"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, IdTableNotArray) {
+ auto Result = readWPASuiteFromString(R"({
+ "id_table": {},
+ "results": []
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("failed to read IdTable from field "
+ "'id_table'"),
+ HasSubstr("expected JSON array"))));
+}
+
+// ============================================================================
+// Structural Error Tests - results
+// ============================================================================
+
+TEST_F(JSONFormatWPASuiteTest, MissingResults) {
+ auto Result = readWPASuiteFromString(R"({"id_table": []})");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("failed to read WPA results from field "
+ "'results'"),
+ HasSubstr("expected JSON array"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, ResultsNotArray) {
+ auto Result = readWPASuiteFromString(R"({
+ "id_table": [],
+ "results": {}
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("failed to read WPA results from field "
+ "'results'"),
+ HasSubstr("expected JSON array"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, ResultEntryNotObject) {
+ auto Result = readWPASuiteFromString(R"({
+ "id_table": [],
+ "results": ["invalid"]
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("reading WPA results from field "
+ "'results'"),
+ HasSubstr("failed to read WPA result entry from "
+ "index '0'"),
+ HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, ResultEntryMissingAnalysisName) {
+ auto Result = readWPASuiteFromString(R"({
+ "id_table": [],
+ "results": [{"result": {}}]
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("reading WPA results from field "
+ "'results'"),
+ HasSubstr("reading WPA result entry from index "
+ "'0'"),
+ HasSubstr("failed to read AnalysisName from "
+ "field 'analysis_name'"),
+ HasSubstr("expected JSON string"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, ResultEntryAnalysisNameNotString) {
+ auto Result = readWPASuiteFromString(R"({
+ "id_table": [],
+ "results": [{"analysis_name": 42, "result": {}}]
+ })");
+
+ EXPECT_THAT_EXPECTED(Result, FailedWithMessage(AllOf(
+ HasSubstr("reading WPASuite from file"),
+ HasSubstr("failed to read AnalysisName from "
+ "field 'analysis_name'"),
+ HasSubstr("expected JSON string"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, ResultEntryNoFormatInfo) {
+ auto Result = readWPASuiteFromString(R"({
+ "id_table": [],
+ "results": [
+ {"analysis_name": "UnregisteredAnalysis", "result": {}}
+ ]
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result, FailedWithMessage(
+ AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("reading WPA results from field 'results'"),
+ HasSubstr("reading WPA result entry from index '0'"),
+ HasSubstr("no support registered for analysis: "
+ "AnalysisName(UnregisteredAnalysis)"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, ResultEntryMissingResultField) {
+ auto Result = readWPASuiteFromString(R"({
+ "id_table": [],
+ "results": [
+ {"analysis_name": "TagsAnalysisResultForJSONFormatTest"}
+ ]
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("reading WPA results from field "
+ "'results'"),
+ HasSubstr("reading WPA result entry from index "
+ "'0'"),
+ HasSubstr("failed to read AnalysisResult from "
+ "field 'result'"),
+ HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, ResultEntryResultNotObject) {
+ auto Result = readWPASuiteFromString(R"({
+ "id_table": [],
+ "results": [
+ {"analysis_name": "TagsAnalysisResultForJSONFormatTest",
+ "result": "not_an_object"}
+ ]
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("failed to read AnalysisResult from "
+ "field 'result'"),
+ HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, ResultEntryDeserializerError) {
+ auto Result = readWPASuiteFromString(R"({
+ "id_table": [],
+ "results": [
+ {"analysis_name": "FailingDeserializerAnalysisResultForJSONFormatTest",
+ "result": {}}
+ ]
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(HasSubstr("reading WPASuite from file"),
+ HasSubstr("reading WPA results from field "
+ "'results'"),
+ HasSubstr("reading WPA result entry from index "
+ "'0'"),
+ HasSubstr("intentional deserializer failure"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, DuplicateAnalysisName) {
+ auto Result = readWPASuiteFromString(R"({
+ "id_table": [],
+ "results": [
+ {"analysis_name": "TagsAnalysisResultForJSONFormatTest",
+ "result": {"tags": []}},
+ {"analysis_name": "TagsAnalysisResultForJSONFormatTest",
+ "result": {"tags": []}}
+ ]
+ })");
+
+ EXPECT_THAT_EXPECTED(
+ Result,
+ FailedWithMessage(AllOf(
+ HasSubstr("reading WPASuite from file"),
+ HasSubstr("reading WPA results from field 'results'"),
+ HasSubstr("failed to insert WPA result at index '1'"),
+ HasSubstr("encountered duplicate "
+ "'AnalysisName(TagsAnalysisResultForJSONFormatTest)'"))));
+}
+
+// ============================================================================
+// Write Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatWPASuiteTest, WriteFileAlreadyExists) {
+ auto ExpectedFilePath = writeJSON("{}", "existing.json");
+ ASSERT_THAT_EXPECTED(ExpectedFilePath, Succeeded());
+
+ auto Result = writeEmptyWPASuite("existing.json");
+
+ EXPECT_THAT_ERROR(
+ std::move(Result),
+ FailedWithMessage(AllOf(HasSubstr("writing WPASuite to file"),
+ HasSubstr("failed to write file"),
+ HasSubstr("file already exists"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, WriteParentDirectoryNotFound) {
+ PathString FilePath = makePath("nonexistent-dir", "test.json");
+
+ auto Result = JSONFormat().writeWPASuite(makeWPASuite(), FilePath);
+
+ EXPECT_THAT_ERROR(
+ std::move(Result),
+ FailedWithMessage(AllOf(HasSubstr("writing WPASuite to file"),
+ HasSubstr("failed to write file"),
+ HasSubstr("parent directory does not exist"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, WriteNotJsonExtension) {
+ auto Result =
+ JSONFormat().writeWPASuite(makeWPASuite(), makePath("test.txt"));
+
+ EXPECT_THAT_ERROR(
+ std::move(Result),
+ FailedWithMessage(AllOf(HasSubstr("writing WPASuite to file"),
+ HasSubstr("failed to write file"),
+ HasSubstr("file does not end with '.json'"))));
+}
+
+TEST_F(JSONFormatWPASuiteTest, WriteNoFormatInfo) {
+ WPASuite Suite = makeWPASuite();
+ getData(Suite).emplace(
+ AnalysisName("UnregisteredAnalysisForJSONFormatTest"),
+ std::make_unique<TagsAnalysisResultForJSONFormatTest>());
+
+ auto Result = writeWPASuite(Suite, "output.json");
+
+ EXPECT_THAT_ERROR(
+ std::move(Result),
+ FailedWithMessage(AllOf(
+ HasSubstr("writing WPASuite to file"),
+ HasSubstr("no support registered for analysis: "
+ "AnalysisName(UnregisteredAnalysisForJSONFormatTest)"))));
+}
+
+// ============================================================================
+// Round-Trip Tests
+// ============================================================================
+
+TEST_F(JSONFormatWPASuiteTest, RoundTripEmpty) {
+ readWriteCompare(R"({
+ "id_table": [],
+ "results": []
+ })");
+}
+
+TEST_F(JSONFormatWPASuiteTest, RoundTripSingleResultNoEntities) {
+ readWriteCompare(R"({
+ "id_table": [],
+ "results": [
+ {
+ "analysis_name": "TagsAnalysisResultForJSONFormatTest",
+ "result": {"tags": ["alpha", "beta", "gamma"]}
+ }
+ ]
+ })");
+}
+
+TEST_F(JSONFormatWPASuiteTest, RoundTripSingleResultWithEntityRefs) {
+ readWriteCompare(R"({
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {"kind": "CompilationUnit", "name": "a.cpp"},
+ {"kind": "LinkUnit", "name": "a.exe"}
+ ]
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "usr": "c:@F at bar",
+ "suffix": "",
+ "namespace": [
+ {"kind": "CompilationUnit", "name": "a.cpp"},
+ {"kind": "LinkUnit", "name": "a.exe"}
+ ]
+ }
+ }
+ ],
+ "results": [
+ {
+ "analysis_name": "CountsAnalysisResultForJSONFormatTest",
+ "result": {
+ "counts": [
+ {"entity_id": {"@": 0}, "count": 42},
+ {"entity_id": {"@": 1}, "count": 7}
+ ]
+ }
+ }
+ ]
+ })");
+}
+
+TEST_F(JSONFormatWPASuiteTest, RoundTripMultipleResults) {
+ readWriteCompare(R"({
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {"kind": "LinkUnit", "name": "test.exe"}
+ ]
+ }
+ }
+ ],
+ "results": [
+ {
+ "analysis_name": "CountsAnalysisResultForJSONFormatTest",
+ "result": {
+ "counts": [
+ {"entity_id": {"@": 0}, "count": 100}
+ ]
+ }
+ },
+ {
+ "analysis_name": "TagsAnalysisResultForJSONFormatTest",
+ "result": {"tags": ["important"]}
+ }
+ ]
+ })");
+}
+
+TEST_F(JSONFormatWPASuiteTest, RoundTripEmptyResultPayload) {
+ readWriteCompare(R"({
+ "id_table": [],
+ "results": [
+ {
+ "analysis_name": "TagsAnalysisResultForJSONFormatTest",
+ "result": {"tags": []}
+ }
+ ]
+ })");
+}
+
+// ============================================================================
+// Content Verification Tests
+// ============================================================================
+
+TEST_F(JSONFormatWPASuiteTest, ReadVerifyTagsResult) {
+ auto ExpectedSuite = readWPASuiteFromString(R"({
+ "id_table": [],
+ "results": [
+ {
+ "analysis_name": "TagsAnalysisResultForJSONFormatTest",
+ "result": {"tags": ["foo", "bar"]}
+ }
+ ]
+ })");
+
+ ASSERT_THAT_EXPECTED(ExpectedSuite, Succeeded());
+ const WPASuite &Suite = *ExpectedSuite;
+
+ ASSERT_TRUE(
+ Suite.contains(TagsAnalysisResultForJSONFormatTest::analysisName()));
+ auto ExpectedResult = Suite.get<TagsAnalysisResultForJSONFormatTest>();
+ ASSERT_THAT_EXPECTED(ExpectedResult, Succeeded());
+
+ const auto &R = *ExpectedResult;
+ ASSERT_EQ(R.Tags.size(), 2u);
+ EXPECT_EQ(R.Tags[0], "foo");
+ EXPECT_EQ(R.Tags[1], "bar");
+}
+
+TEST_F(JSONFormatWPASuiteTest, ReadVerifyCountsResultWithEntityId) {
+ auto ExpectedSuite = readWPASuiteFromString(R"({
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {"kind": "CompilationUnit", "name": "test.cpp"},
+ {"kind": "LinkUnit", "name": "test.exe"}
+ ]
+ }
+ }
+ ],
+ "results": [
+ {
+ "analysis_name": "CountsAnalysisResultForJSONFormatTest",
+ "result": {
+ "counts": [
+ {"entity_id": {"@": 0}, "count": 99}
+ ]
+ }
+ }
+ ]
+ })");
+
+ ASSERT_THAT_EXPECTED(ExpectedSuite, Succeeded());
+ const WPASuite &Suite = *ExpectedSuite;
+
+ ASSERT_TRUE(
+ Suite.contains(CountsAnalysisResultForJSONFormatTest::analysisName()));
+ auto ExpectedResult = Suite.get<CountsAnalysisResultForJSONFormatTest>();
+ ASSERT_THAT_EXPECTED(ExpectedResult, Succeeded());
+
+ const auto &R = *ExpectedResult;
+ ASSERT_EQ(R.Counts.size(), 1u);
+ EXPECT_EQ(R.Counts[0].second, 99);
+}
+
+} // namespace
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/TestFixture.h b/clang/unittests/ScalableStaticAnalysisFramework/TestFixture.h
index 74bc3a09dd0d8..2116c2b4a664c 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/TestFixture.h
+++ b/clang/unittests/ScalableStaticAnalysisFramework/TestFixture.h
@@ -19,6 +19,7 @@
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
#include "gtest/gtest.h"
#include <iosfwd>
@@ -26,6 +27,8 @@ namespace clang::ssaf {
class TestFixture : public ::testing::Test {
protected:
+ static WPASuite makeWPASuite() { return WPASuite(); }
+
#define FIELD(CLASS, FIELD_NAME) \
static const auto &get##FIELD_NAME(const CLASS &X) { return X.FIELD_NAME; } \
static auto &get##FIELD_NAME(CLASS &X) { return X.FIELD_NAME; }
>From 0e7637f722021731753c390e1d37dd18aa6ab22a Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Fri, 20 Mar 2026 11:51:03 -0700
Subject: [PATCH 18/19] Move testing to plugins
---
.../CMakeLists.txt | 1 +
.../plugins/CMakeLists.txt | 12 +
.../ssaf-wpa-suite-test-plugin/CMakeLists.txt | 21 ++
.../SSAFWPASuiteTestPlugin.cpp | 225 ++++++++++++++++
.../ssaf-analyzer/Inputs/lu-empty.json | 11 +
.../Analysis/Scalable/ssaf-analyzer/cli.test | 10 +
.../Analysis/Scalable/ssaf-analyzer/io.test | 21 ++
.../Scalable/ssaf-analyzer/plugin.test | 80 ++++++
.../Inputs/duplicate-analysis-name.json | 7 +
.../wpa-suite/Inputs/id-table-not-array.json | 1 +
.../wpa-suite/Inputs/malformed.json | 1 +
.../wpa-suite/Inputs/missing-id-table.json | 1 +
.../wpa-suite/Inputs/missing-results.json | 1 +
.../wpa-suite/Inputs/not-object.json | 1 +
...result-entry-analysis-name-not-string.json | 1 +
.../result-entry-deserializer-error.json | 1 +
.../result-entry-missing-analysis-name.json | 1 +
.../result-entry-missing-result-field.json | 1 +
.../Inputs/result-entry-no-format-info.json | 1 +
.../Inputs/result-entry-not-object.json | 1 +
.../result-entry-result-not-object.json | 1 +
.../wpa-suite/Inputs/results-not-array.json | 1 +
.../Inputs/rt-counts-with-entities.json | 25 ++
.../wpa-suite/Inputs/rt-empty-payload.json | 9 +
.../wpa-suite/Inputs/rt-empty.json | 1 +
.../wpa-suite/Inputs/rt-multiple-results.json | 28 ++
.../wpa-suite/Inputs/rt-single-tags.json | 9 +
.../ssaf-format/wpa-suite/errors.test | 129 +++++++++
.../ssaf-format/wpa-suite/plugin.test | 121 +++++++++
clang/tools/CMakeLists.txt | 1 +
.../tools/clang-ssaf-analyzer/CMakeLists.txt | 15 ++
.../clang-ssaf-analyzer/SSAFAnalyzer.cpp | 253 ++++++++++++++++++
clang/tools/clang-ssaf-format/SSAFFormat.cpp | 10 +-
33 files changed, 1000 insertions(+), 2 deletions(-)
create mode 100644 clang/lib/ScalableStaticAnalysisFramework/plugins/CMakeLists.txt
create mode 100644 clang/lib/ScalableStaticAnalysisFramework/plugins/ssaf-wpa-suite-test-plugin/CMakeLists.txt
create mode 100644 clang/lib/ScalableStaticAnalysisFramework/plugins/ssaf-wpa-suite-test-plugin/SSAFWPASuiteTestPlugin.cpp
create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-empty.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/cli.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/io.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/plugin.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/duplicate-analysis-name.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/id-table-not-array.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/malformed.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/missing-id-table.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/missing-results.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/not-object.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-analysis-name-not-string.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-deserializer-error.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-missing-analysis-name.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-missing-result-field.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-no-format-info.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-not-object.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-result-not-object.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/results-not-array.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-counts-with-entities.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-empty-payload.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-empty.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-multiple-results.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-single-tags.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/errors.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-format/wpa-suite/plugin.test
create mode 100644 clang/tools/clang-ssaf-analyzer/CMakeLists.txt
create mode 100644 clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp
diff --git a/clang/lib/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/CMakeLists.txt
index 3da5c8899ddb6..a803d88cd978a 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/CMakeLists.txt
@@ -1,4 +1,5 @@
add_subdirectory(Analyses)
add_subdirectory(Core)
add_subdirectory(Frontend)
+add_subdirectory(plugins)
add_subdirectory(Tool)
diff --git a/clang/lib/ScalableStaticAnalysisFramework/plugins/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/plugins/CMakeLists.txt
new file mode 100644
index 0000000000000..7dba9a4b8d9a0
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/plugins/CMakeLists.txt
@@ -0,0 +1,12 @@
+if(LLVM_ENABLE_PLUGINS)
+ # Plugins must never bring LLVM or Clang libraries in statically.
+ # clang-ssaf-analyzer already loads clangScalableStaticAnalysisFrameworkCore
+ # and LLVM into the process; a second static copy would produce duplicate
+ # llvm::Registry instances with separate global state, breaking registration.
+ #
+ # Instead: build each plugin as a MODULE with PLUGIN_TOOL pointing to the
+ # tool that will load it, and pull only include paths (not link deps) from
+ # the libraries whose headers are needed. All symbols are resolved from the
+ # tool's address space when the plugin is loaded via --load.
+ add_subdirectory(ssaf-wpa-suite-test-plugin)
+endif()
diff --git a/clang/lib/ScalableStaticAnalysisFramework/plugins/ssaf-wpa-suite-test-plugin/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/plugins/ssaf-wpa-suite-test-plugin/CMakeLists.txt
new file mode 100644
index 0000000000000..b78297b324253
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/plugins/ssaf-wpa-suite-test-plugin/CMakeLists.txt
@@ -0,0 +1,21 @@
+# Do NOT set LLVM_LINK_COMPONENTS here. Setting it would cause LLVM libraries
+# (e.g. Support) to be linked statically into this MODULE, producing a second
+# copy of LLVM in the process alongside the copy already loaded by
+# clang-ssaf-analyzer. Two copies means two separate llvm::Registry instances,
+# so registrations made here would not be visible to the host tool.
+#
+# The same applies to clang static libraries: do not use
+# clang_target_link_libraries here. Instead, expose only their include paths
+# via $<TARGET_PROPERTY:...,INTERFACE_INCLUDE_DIRECTORIES> and let the
+# dynamic loader resolve every SSAF and LLVM symbol from the tool's address
+# space at load time.
+
+add_llvm_library(SSAFWPASuiteTestPlugin MODULE BUILDTREE_ONLY
+ SSAFWPASuiteTestPlugin.cpp
+ PLUGIN_TOOL clang-ssaf-analyzer
+ )
+
+# Pull in SSAF and LLVM include paths without any static link dependency.
+target_include_directories(SSAFWPASuiteTestPlugin PRIVATE
+ $<TARGET_PROPERTY:clangScalableStaticAnalysisFrameworkCore,INTERFACE_INCLUDE_DIRECTORIES>
+ )
diff --git a/clang/lib/ScalableStaticAnalysisFramework/plugins/ssaf-wpa-suite-test-plugin/SSAFWPASuiteTestPlugin.cpp b/clang/lib/ScalableStaticAnalysisFramework/plugins/ssaf-wpa-suite-test-plugin/SSAFWPASuiteTestPlugin.cpp
new file mode 100644
index 0000000000000..465e6f1488908
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/plugins/ssaf-wpa-suite-test-plugin/SSAFWPASuiteTestPlugin.cpp
@@ -0,0 +1,225 @@
+//===- SSAFWPASuiteTestPlugin.cpp - WPASuite serialization test plugin ----===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// A loadable plugin for clang-ssaf-analyzer lit tests that exercises WPASuite
+// JSON serialization. It registers two analysis result types — Tags and Counts
+// — together with their JSON serializers/deserializers and trivial analysis
+// implementations, enabling end-to-end lit tests for the WPASuite round-trip
+// without depending on any real analysis logic.
+//
+// Usage:
+// clang-ssaf-analyzer --load <path/to/SSAFWPASuiteTestPlugin.so> \
+// --analysis TagsAnalysisResult \
+// --analysis CountsAnalysisResult \
+// -o output.json input.json
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace clang::ssaf;
+using namespace llvm;
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// TagsAnalysisResult
+//
+// Holds a flat list of string tags. Serialized as:
+// { "tags": ["tag1", "tag2", ...] }
+//===----------------------------------------------------------------------===//
+
+struct TagsAnalysisResult final : AnalysisResult {
+ static AnalysisName analysisName() {
+ return AnalysisName("TagsAnalysisResult");
+ }
+
+ std::vector<std::string> Tags;
+};
+
+json::Object serializeTagsAnalysisResult(const AnalysisResult &Result,
+ JSONFormat::EntityIdToJSONFn) {
+ const auto &R = static_cast<const TagsAnalysisResult &>(Result);
+ json::Array TagsArray;
+ for (const auto &Tag : R.Tags)
+ TagsArray.push_back(Tag);
+ return json::Object{{"tags", std::move(TagsArray)}};
+}
+
+Expected<std::unique_ptr<AnalysisResult>>
+deserializeTagsAnalysisResult(const json::Object &Obj,
+ JSONFormat::EntityIdFromJSONFn) {
+ const json::Array *TagsArray = Obj.getArray("tags");
+ if (!TagsArray)
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'tags'");
+
+ auto R = std::make_unique<TagsAnalysisResult>();
+ for (const auto &[Index, Val] : llvm::enumerate(*TagsArray)) {
+ auto S = Val.getAsString();
+ if (!S)
+ return createStringError(inconvertibleErrorCode(),
+ "tags element at index %zu is not a string",
+ Index);
+ R->Tags.push_back(S->str());
+ }
+ return std::move(R);
+}
+
+JSONFormat::AnalysisResultRegistry::Add<TagsAnalysisResult>
+ RegisterTagsForJSON(serializeTagsAnalysisResult,
+ deserializeTagsAnalysisResult);
+
+//===----------------------------------------------------------------------===//
+// TagsAnalysis
+//
+// A trivial DerivedAnalysis that produces a fixed set of tags, independent
+// of the input LUSummary. Used so that lit tests have predictable output.
+//===----------------------------------------------------------------------===//
+
+class TagsAnalysis final : public DerivedAnalysis<TagsAnalysisResult> {
+public:
+ llvm::Error initialize() override { return llvm::Error::success(); }
+
+ llvm::Expected<bool> step() override {
+ result().Tags = {"alpha", "beta", "gamma"};
+ return false; // converged after one step
+ }
+};
+
+AnalysisRegistry::Add<TagsAnalysis> RegisterTagsAnalysis(
+ "Produces a fixed list of string tags for testing WPASuite serialization");
+
+//===----------------------------------------------------------------------===//
+// CountsAnalysisResult
+//
+// Holds a list of (EntityId, int) count pairs. Serialized as:
+// { "counts": [{"entity_id": {...}, "count": N}, ...] }
+//===----------------------------------------------------------------------===//
+
+struct CountsAnalysisResult final : AnalysisResult {
+ static AnalysisName analysisName() {
+ return AnalysisName("CountsAnalysisResult");
+ }
+
+ std::vector<std::pair<EntityId, int>> Counts;
+};
+
+json::Object
+serializeCountsAnalysisResult(const AnalysisResult &Result,
+ JSONFormat::EntityIdToJSONFn ToJSON) {
+ const auto &R = static_cast<const CountsAnalysisResult &>(Result);
+ json::Array CountsArray;
+ for (const auto &[EI, Count] : R.Counts) {
+ CountsArray.push_back(
+ json::Object{{"entity_id", ToJSON(EI)}, {"count", Count}});
+ }
+ return json::Object{{"counts", std::move(CountsArray)}};
+}
+
+Expected<std::unique_ptr<AnalysisResult>>
+deserializeCountsAnalysisResult(const json::Object &Obj,
+ JSONFormat::EntityIdFromJSONFn FromJSON) {
+ const json::Array *CountsArray = Obj.getArray("counts");
+ if (!CountsArray)
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'counts'");
+
+ auto R = std::make_unique<CountsAnalysisResult>();
+ for (const auto &[Index, Val] : llvm::enumerate(*CountsArray)) {
+ const json::Object *Entry = Val.getAsObject();
+ if (!Entry)
+ return createStringError(inconvertibleErrorCode(),
+ "counts element at index %zu is not an object",
+ Index);
+ const json::Object *EIObj = Entry->getObject("entity_id");
+ if (!EIObj)
+ return createStringError(
+ inconvertibleErrorCode(),
+ "missing or invalid 'entity_id' field at index %zu", Index);
+ auto ExpectedEI = FromJSON(*EIObj);
+ if (!ExpectedEI)
+ return ExpectedEI.takeError();
+
+ auto CountVal = Entry->getInteger("count");
+ if (!CountVal)
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid 'count' field at index %zu",
+ Index);
+ R->Counts.emplace_back(*ExpectedEI, static_cast<int>(*CountVal));
+ }
+ return std::move(R);
+}
+
+JSONFormat::AnalysisResultRegistry::Add<CountsAnalysisResult>
+ RegisterCountsForJSON(serializeCountsAnalysisResult,
+ deserializeCountsAnalysisResult);
+
+//===----------------------------------------------------------------------===//
+// CountsAnalysis
+//
+// A trivial DerivedAnalysis that produces an empty counts result. Entity ID
+// serialization is exercised by round-trip tests that supply pre-built JSON.
+//===----------------------------------------------------------------------===//
+
+class CountsAnalysis final : public DerivedAnalysis<CountsAnalysisResult> {
+public:
+ llvm::Error initialize() override { return llvm::Error::success(); }
+
+ llvm::Expected<bool> step() override {
+ // Produces no counts; entity-ID round-trip is covered by direct JSON tests.
+ return false;
+ }
+};
+
+AnalysisRegistry::Add<CountsAnalysis> RegisterCountsAnalysis(
+ "Produces an empty counts result for testing WPASuite serialization");
+
+//===----------------------------------------------------------------------===//
+// FailingDeserializerAnalysisResult
+//
+// An analysis result whose deserializer always returns an error. Used to
+// exercise error propagation in the WPASuite read path without needing a
+// malformed payload.
+//===----------------------------------------------------------------------===//
+
+struct FailingDeserializerAnalysisResult final : AnalysisResult {
+ static AnalysisName analysisName() {
+ return AnalysisName("FailingDeserializerAnalysisResult");
+ }
+};
+
+json::Object
+serializeFailingDeserializerAnalysisResult(const AnalysisResult &,
+ JSONFormat::EntityIdToJSONFn) {
+ return json::Object{};
+}
+
+Expected<std::unique_ptr<AnalysisResult>>
+deserializeFailingDeserializerAnalysisResult(const json::Object &,
+ JSONFormat::EntityIdFromJSONFn) {
+ return createStringError(inconvertibleErrorCode(),
+ "intentional deserializer failure");
+}
+
+JSONFormat::AnalysisResultRegistry::Add<FailingDeserializerAnalysisResult>
+ RegisterFailingDeserializerForJSON(
+ serializeFailingDeserializerAnalysisResult,
+ deserializeFailingDeserializerAnalysisResult);
+
+} // namespace
diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-empty.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-empty.json
new file mode 100644
index 0000000000000..2255b4eb1ae38
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-empty.json
@@ -0,0 +1,11 @@
+{
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "test"
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/cli.test b/clang/test/Analysis/Scalable/ssaf-analyzer/cli.test
new file mode 100644
index 0000000000000..4f1b24f34247c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-analyzer/cli.test
@@ -0,0 +1,10 @@
+// Tests for clang-ssaf-analyzer command-line option validation.
+
+// RUN: not clang-ssaf-analyzer 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-ARGS
+// NO-ARGS: clang-ssaf-analyzer{{(\.exe)?}}: error: no LUSummary file specified
+// NO-ARGS-NOT: {{.+}}
+
+// RUN: not clang-ssaf-analyzer %S/Inputs/lu-empty.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-OUTPUT
+// NO-OUTPUT: clang-ssaf-analyzer{{(\.exe)?}}: for the -o option: must be specified at least once!
diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/io.test b/clang/test/Analysis/Scalable/ssaf-analyzer/io.test
new file mode 100644
index 0000000000000..e5a7d7a06bd91
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-analyzer/io.test
@@ -0,0 +1,21 @@
+// Tests for clang-ssaf-analyzer file I/O error handling.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// Nonexistent input file.
+// RUN: not clang-ssaf-analyzer %t/nonexistent.json -o %t/out.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-INPUT
+// NO-INPUT: clang-ssaf-analyzer{{(\.exe)?}}: error: cannot validate summary '{{.*}}nonexistent.json'{{.*}}
+
+// Input path with no extension.
+// RUN: cp %S/Inputs/lu-empty.json %t/lu-noext
+// RUN: not clang-ssaf-analyzer %t/lu-noext -o %t/out.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-EXT
+// NO-EXT: clang-ssaf-analyzer{{(\.exe)?}}: error: cannot validate summary '{{.*}}lu-noext'{{.*}}
+
+// Output parent directory does not exist.
+// RUN: not clang-ssaf-analyzer %S/Inputs/lu-empty.json \
+// RUN: -o %t/nosuchdir/out.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-OUTPUT-DIR
+// NO-OUTPUT-DIR: clang-ssaf-analyzer{{(\.exe)?}}: error: cannot validate summary '{{.*}}out.json'{{.*}}
diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/plugin.test b/clang/test/Analysis/Scalable/ssaf-analyzer/plugin.test
new file mode 100644
index 0000000000000..b56446f510190
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-analyzer/plugin.test
@@ -0,0 +1,80 @@
+// Tests for clang-ssaf-analyzer WPASuite serialization via the test plugin.
+// The SSAFWPASuiteTestPlugin registers TagsAnalysisResult and
+// CountsAnalysisResult together with their JSON serializers so that the
+// round-trip through clang-ssaf-analyzer can be verified end-to-end.
+
+// REQUIRES: plugins
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// ============================================================================
+// Run TagsAnalysis only -- verify the output JSON structure.
+// ============================================================================
+
+// RUN: clang-ssaf-analyzer \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: --analysis TagsAnalysisResult \
+// RUN: -o %t/tags.json \
+// RUN: %S/Inputs/lu-empty.json
+// RUN: FileCheck %s --input-file %t/tags.json --check-prefix=TAGS-OUT
+
+// TAGS-OUT: "analysis_name": "TagsAnalysisResult"
+// TAGS-OUT: "tags":
+// TAGS-OUT-DAG: "alpha"
+// TAGS-OUT-DAG: "beta"
+// TAGS-OUT-DAG: "gamma"
+
+// ============================================================================
+// Run CountsAnalysis only -- verify the output JSON structure.
+// ============================================================================
+
+// RUN: clang-ssaf-analyzer \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: --analysis CountsAnalysisResult \
+// RUN: -o %t/counts.json \
+// RUN: %S/Inputs/lu-empty.json
+// RUN: FileCheck %s --input-file %t/counts.json --check-prefix=COUNTS-OUT
+
+// COUNTS-OUT: "analysis_name": "CountsAnalysisResult"
+// COUNTS-OUT: "counts": []
+
+// ============================================================================
+// Run both analyses -- verify both results appear in the output.
+// ============================================================================
+
+// RUN: clang-ssaf-analyzer \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: --analysis TagsAnalysisResult \
+// RUN: --analysis CountsAnalysisResult \
+// RUN: -o %t/both.json \
+// RUN: %S/Inputs/lu-empty.json
+// RUN: FileCheck %s --input-file %t/both.json --check-prefix=BOTH-OUT
+
+// BOTH-OUT-DAG: "analysis_name": "TagsAnalysisResult"
+// BOTH-OUT-DAG: "analysis_name": "CountsAnalysisResult"
+
+// ============================================================================
+// Run all registered analyses (no --analysis flags).
+// ============================================================================
+
+// RUN: clang-ssaf-analyzer \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: -o %t/all.json \
+// RUN: %S/Inputs/lu-empty.json
+// RUN: FileCheck %s --input-file %t/all.json --check-prefix=ALL-OUT
+
+// ALL-OUT-DAG: "analysis_name": "TagsAnalysisResult"
+// ALL-OUT-DAG: "analysis_name": "CountsAnalysisResult"
+
+// ============================================================================
+// Unknown analysis name produces an error.
+// ============================================================================
+
+// RUN: not clang-ssaf-analyzer \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: --analysis UnknownAnalysis \
+// RUN: -o %t/unknown.json \
+// RUN: %S/Inputs/lu-empty.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=UNKNOWN-ANALYSIS
+// UNKNOWN-ANALYSIS: error: {{.*}}UnknownAnalysis{{.*}}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/duplicate-analysis-name.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/duplicate-analysis-name.json
new file mode 100644
index 0000000000000..975dbfb7c6b41
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/duplicate-analysis-name.json
@@ -0,0 +1,7 @@
+{
+ "id_table": [],
+ "results": [
+ {"analysis_name": "TagsAnalysisResult", "result": {"tags": []}},
+ {"analysis_name": "TagsAnalysisResult", "result": {"tags": []}}
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/id-table-not-array.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/id-table-not-array.json
new file mode 100644
index 0000000000000..8754744da6aae
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/id-table-not-array.json
@@ -0,0 +1 @@
+{"id_table": {}, "results": []}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/malformed.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/malformed.json
new file mode 100644
index 0000000000000..b0e13f61aa06e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/malformed.json
@@ -0,0 +1 @@
+{ invalid json }
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/missing-id-table.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/missing-id-table.json
new file mode 100644
index 0000000000000..330e8fe3131fd
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/missing-id-table.json
@@ -0,0 +1 @@
+{"results": []}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/missing-results.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/missing-results.json
new file mode 100644
index 0000000000000..ad430aa5b4d53
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/missing-results.json
@@ -0,0 +1 @@
+{"id_table": []}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/not-object.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/not-object.json
new file mode 100644
index 0000000000000..fe51488c7066f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/not-object.json
@@ -0,0 +1 @@
+[]
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-analysis-name-not-string.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-analysis-name-not-string.json
new file mode 100644
index 0000000000000..e9208de987f1b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-analysis-name-not-string.json
@@ -0,0 +1 @@
+{"id_table": [], "results": [{"analysis_name": 42, "result": {}}]}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-deserializer-error.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-deserializer-error.json
new file mode 100644
index 0000000000000..7f1200e06201f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-deserializer-error.json
@@ -0,0 +1 @@
+{"id_table": [], "results": [{"analysis_name": "FailingDeserializerAnalysisResult", "result": {}}]}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-missing-analysis-name.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-missing-analysis-name.json
new file mode 100644
index 0000000000000..35452aec3604d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-missing-analysis-name.json
@@ -0,0 +1 @@
+{"id_table": [], "results": [{"result": {}}]}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-missing-result-field.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-missing-result-field.json
new file mode 100644
index 0000000000000..f989e9a22cff2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-missing-result-field.json
@@ -0,0 +1 @@
+{"id_table": [], "results": [{"analysis_name": "TagsAnalysisResult"}]}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-no-format-info.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-no-format-info.json
new file mode 100644
index 0000000000000..7dec19ffb2d4a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-no-format-info.json
@@ -0,0 +1 @@
+{"id_table": [], "results": [{"analysis_name": "UnregisteredAnalysis", "result": {}}]}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-not-object.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-not-object.json
new file mode 100644
index 0000000000000..5fc110b069dd8
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-not-object.json
@@ -0,0 +1 @@
+{"id_table": [], "results": ["invalid"]}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-result-not-object.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-result-not-object.json
new file mode 100644
index 0000000000000..3ee24d8d09065
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/result-entry-result-not-object.json
@@ -0,0 +1 @@
+{"id_table": [], "results": [{"analysis_name": "TagsAnalysisResult", "result": "not_an_object"}]}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/results-not-array.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/results-not-array.json
new file mode 100644
index 0000000000000..52e44a5fb7c2e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/results-not-array.json
@@ -0,0 +1 @@
+{"id_table": [], "results": {}}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-counts-with-entities.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-counts-with-entities.json
new file mode 100644
index 0000000000000..f5bc930231606
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-counts-with-entities.json
@@ -0,0 +1,25 @@
+{
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {"kind": "CompilationUnit", "name": "a.cpp"},
+ {"kind": "LinkUnit", "name": "a.exe"}
+ ]
+ }
+ }
+ ],
+ "results": [
+ {
+ "analysis_name": "CountsAnalysisResult",
+ "result": {
+ "counts": [
+ {"entity_id": {"@": 0}, "count": 42}
+ ]
+ }
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-empty-payload.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-empty-payload.json
new file mode 100644
index 0000000000000..590b413e530c3
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-empty-payload.json
@@ -0,0 +1,9 @@
+{
+ "id_table": [],
+ "results": [
+ {
+ "analysis_name": "TagsAnalysisResult",
+ "result": {"tags": []}
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-empty.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-empty.json
new file mode 100644
index 0000000000000..761ca67c45e9f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-empty.json
@@ -0,0 +1 @@
+{"id_table": [], "results": []}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-multiple-results.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-multiple-results.json
new file mode 100644
index 0000000000000..5098e0eaa787a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-multiple-results.json
@@ -0,0 +1,28 @@
+{
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "usr": "c:@F at foo",
+ "suffix": "",
+ "namespace": [
+ {"kind": "LinkUnit", "name": "test.exe"}
+ ]
+ }
+ }
+ ],
+ "results": [
+ {
+ "analysis_name": "CountsAnalysisResult",
+ "result": {
+ "counts": [
+ {"entity_id": {"@": 0}, "count": 100}
+ ]
+ }
+ },
+ {
+ "analysis_name": "TagsAnalysisResult",
+ "result": {"tags": ["important"]}
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-single-tags.json b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-single-tags.json
new file mode 100644
index 0000000000000..fcd010f124407
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/Inputs/rt-single-tags.json
@@ -0,0 +1,9 @@
+{
+ "id_table": [],
+ "results": [
+ {
+ "analysis_name": "TagsAnalysisResult",
+ "result": {"tags": ["alpha", "beta", "gamma"]}
+ }
+ ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/errors.test b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/errors.test
new file mode 100644
index 0000000000000..81b2733e5b882
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/errors.test
@@ -0,0 +1,129 @@
+// Tests for clang-ssaf-format --type wpa error handling.
+// None of these tests require a plugin.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// ============================================================================
+// Read errors: file system
+// ============================================================================
+
+// Nonexistent file.
+// RUN: not clang-ssaf-format --type wpa %t/nonexistent.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-FILE
+// NO-FILE: clang-ssaf-format{{(\.exe)?}}: error: cannot validate summary '{{.*}}nonexistent.json'{{.*}}
+
+// Input path is a directory.
+// RUN: mkdir %t/test_directory.json
+// RUN: not clang-ssaf-format --type wpa %t/test_directory.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=IS-DIR
+// IS-DIR: clang-ssaf-format{{(\.exe)?}}: error: {{.*}}test_directory.json{{.*}}
+
+// Input has an unrecognised extension (no format registered for '.txt').
+// RUN: cp %S/Inputs/rt-empty.json %t/test.txt
+// RUN: not clang-ssaf-format --type wpa %t/test.txt 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-EXT
+// BAD-EXT: clang-ssaf-format{{(\.exe)?}}: error: cannot validate summary '{{.*}}test.txt'{{.*}}
+
+// ============================================================================
+// Read errors: JSON structure
+// ============================================================================
+
+// Invalid JSON syntax.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/malformed.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-JSON
+// BAD-JSON: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}malformed.json'
+// BAD-JSON-NEXT: {{.*}}Expected object key{{.*}}
+
+// Top-level value is not a JSON object.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/not-object.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NOT-OBJ
+// NOT-OBJ: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}not-object.json'
+// NOT-OBJ-NEXT: failed to read WPASuite: expected JSON object
+
+// ============================================================================
+// Read errors: id_table field
+// ============================================================================
+
+// 'id_table' field is absent.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/missing-id-table.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-ID-TABLE
+// NO-ID-TABLE: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}missing-id-table.json'
+// NO-ID-TABLE-NEXT: failed to read IdTable from field 'id_table': expected JSON array
+
+// 'id_table' is an object, not an array.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/id-table-not-array.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=ID-TABLE-OBJ
+// ID-TABLE-OBJ: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}id-table-not-array.json'
+// ID-TABLE-OBJ-NEXT: failed to read IdTable from field 'id_table': expected JSON array
+
+// ============================================================================
+// Read errors: results field
+// ============================================================================
+
+// 'results' field is absent.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/missing-results.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-RESULTS
+// NO-RESULTS: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}missing-results.json'
+// NO-RESULTS-NEXT: failed to read WPA results from field 'results': expected JSON array
+
+// 'results' is an object, not an array.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/results-not-array.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=RESULTS-OBJ
+// RESULTS-OBJ: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}results-not-array.json'
+// RESULTS-OBJ-NEXT: failed to read WPA results from field 'results': expected JSON array
+
+// A result entry is a string, not an object.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/result-entry-not-object.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=ENTRY-NOT-OBJ
+// ENTRY-NOT-OBJ: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}result-entry-not-object.json'
+// ENTRY-NOT-OBJ-NEXT: reading WPA results from field 'results'
+// ENTRY-NOT-OBJ-NEXT: failed to read WPA result entry from index '0': expected JSON object
+
+// 'analysis_name' field is absent.
+// RUN: not clang-ssaf-format --type wpa \
+// RUN: %S/Inputs/result-entry-missing-analysis-name.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-NAME
+// NO-NAME: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}'
+// NO-NAME: reading WPA result entry from index '0'
+// NO-NAME-NEXT: failed to read AnalysisName from field 'analysis_name': expected JSON string
+
+// 'analysis_name' is an integer, not a string.
+// RUN: not clang-ssaf-format --type wpa \
+// RUN: %S/Inputs/result-entry-analysis-name-not-string.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NAME-NOT-STR
+// NAME-NOT-STR: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}'
+// NAME-NOT-STR-NEXT: reading WPA results from field 'results'
+// NAME-NOT-STR-NEXT: reading WPA result entry from index '0'
+// NAME-NOT-STR-NEXT: failed to read AnalysisName from field 'analysis_name': expected JSON string
+
+// Analysis name is not registered (no plugin loaded).
+// RUN: not clang-ssaf-format --type wpa \
+// RUN: %S/Inputs/result-entry-no-format-info.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-FORMAT-INFO
+// NO-FORMAT-INFO: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}'
+// NO-FORMAT-INFO: reading WPA result entry from index '0'
+// NO-FORMAT-INFO-NEXT: no support registered for analysis: AnalysisName(UnregisteredAnalysis)
+
+// ============================================================================
+// Write errors
+// ============================================================================
+
+// Output file already exists.
+// RUN: touch %t/existing.json
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/rt-empty.json \
+// RUN: -o %t/existing.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=OUT-EXISTS
+// OUT-EXISTS: clang-ssaf-format{{(\.exe)?}}: error: cannot validate summary '{{.*}}existing.json'{{.*}}Output file already exists
+
+// Output parent directory does not exist.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/rt-empty.json \
+// RUN: -o %t/nosuchdir/out.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-OUT-DIR
+// NO-OUT-DIR: clang-ssaf-format{{(\.exe)?}}: error: cannot validate summary '{{.*}}out.json'{{.*}}
+
+// Output path has an unrecognised extension.
+// RUN: not clang-ssaf-format --type wpa %S/Inputs/rt-empty.json \
+// RUN: -o %t/out.txt 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-OUT-EXT
+// BAD-OUT-EXT: clang-ssaf-format{{(\.exe)?}}: error: cannot validate summary '{{.*}}out.txt'{{.*}}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/plugin.test b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/plugin.test
new file mode 100644
index 0000000000000..45258aaf6f6d0
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/wpa-suite/plugin.test
@@ -0,0 +1,121 @@
+// Tests for clang-ssaf-format --type wpa that require the WPASuite test
+// plugin. Covers:
+// - Read errors that need a registered deserializer to reach
+// - Round-trip correctness (read → write → verify)
+// - Content verification (field values in the output)
+
+// REQUIRES: plugins
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// ============================================================================
+// Read errors requiring a registered deserializer
+// ============================================================================
+
+// 'result' field is absent; the deserializer lookup succeeds but the field
+// check fires before the deserializer is called.
+// RUN: not clang-ssaf-format --type wpa \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: %S/Inputs/result-entry-missing-result-field.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-RESULT-FIELD
+// NO-RESULT-FIELD: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}'
+// NO-RESULT-FIELD: reading WPA result entry from index '0'
+// NO-RESULT-FIELD-NEXT: failed to read AnalysisResult from field 'result': expected JSON object
+
+// 'result' is a string, not an object.
+// RUN: not clang-ssaf-format --type wpa \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: %S/Inputs/result-entry-result-not-object.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=RESULT-NOT-OBJ
+// RESULT-NOT-OBJ: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}'
+// RESULT-NOT-OBJ-NEXT: failed to read AnalysisResult from field 'result': expected JSON object
+
+// Deserializer returns an error.
+// RUN: not clang-ssaf-format --type wpa \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: %S/Inputs/result-entry-deserializer-error.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DESER-ERR
+// DESER-ERR: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}'
+// DESER-ERR: reading WPA result entry from index '0'
+// DESER-ERR-NEXT: intentional deserializer failure
+
+// Duplicate analysis name in results array.
+// RUN: not clang-ssaf-format --type wpa \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: %S/Inputs/duplicate-analysis-name.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DUPLICATE
+// DUPLICATE: clang-ssaf-format{{(\.exe)?}}: error: reading WPASuite from file '{{.*}}'
+// DUPLICATE: reading WPA results from field 'results'
+// DUPLICATE-NEXT: failed to insert WPA result at index '1': encountered duplicate 'AnalysisName(TagsAnalysisResult)'
+
+// ============================================================================
+// Round-trip: RoundTripEmpty
+// ============================================================================
+
+// RUN: clang-ssaf-format --type wpa \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: %S/Inputs/rt-empty.json \
+// RUN: -o %t/rt-empty.json
+// RUN: FileCheck %s --input-file %t/rt-empty.json --check-prefix=RT-EMPTY
+// RT-EMPTY: "id_table": []
+// RT-EMPTY: "results": []
+
+// ============================================================================
+// Round-trip + content verification: RoundTripSingleResultNoEntities /
+// ReadVerifyTagsResult
+// ============================================================================
+
+// RUN: clang-ssaf-format --type wpa \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: %S/Inputs/rt-single-tags.json \
+// RUN: -o %t/rt-single-tags.json
+// RUN: FileCheck %s --input-file %t/rt-single-tags.json --check-prefix=RT-TAGS
+// RT-TAGS: "analysis_name": "TagsAnalysisResult"
+// RT-TAGS: "tags":
+// RT-TAGS-DAG: "alpha"
+// RT-TAGS-DAG: "beta"
+// RT-TAGS-DAG: "gamma"
+
+// ============================================================================
+// Round-trip + content verification: RoundTripEmptyResultPayload
+// ============================================================================
+
+// RUN: clang-ssaf-format --type wpa \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: %S/Inputs/rt-empty-payload.json \
+// RUN: -o %t/rt-empty-payload.json
+// RUN: FileCheck %s --input-file %t/rt-empty-payload.json --check-prefix=RT-EMPTY-PAYLOAD
+// RT-EMPTY-PAYLOAD: "analysis_name": "TagsAnalysisResult"
+// RT-EMPTY-PAYLOAD: "tags": []
+
+// ============================================================================
+// Round-trip + content verification: RoundTripSingleResultWithEntityRefs /
+// ReadVerifyCountsResultWithEntityId
+// ============================================================================
+
+// RUN: clang-ssaf-format --type wpa \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: %S/Inputs/rt-counts-with-entities.json \
+// RUN: -o %t/rt-counts-with-entities.json
+// RUN: FileCheck %s --input-file %t/rt-counts-with-entities.json --check-prefix=RT-COUNTS
+// RT-COUNTS: "analysis_name": "CountsAnalysisResult"
+// RT-COUNTS: "counts":
+// RT-COUNTS: "entity_id":
+// RT-COUNTS: "@":
+// RT-COUNTS: "count": 42
+// The entity appears in the id_table with its original name.
+// RT-COUNTS: "usr": "c:@F at foo"
+
+// ============================================================================
+// Round-trip: RoundTripMultipleResults
+// ============================================================================
+
+// RUN: clang-ssaf-format --type wpa \
+// RUN: --load %llvmshlibdir/SSAFWPASuiteTestPlugin%pluginext \
+// RUN: %S/Inputs/rt-multiple-results.json \
+// RUN: -o %t/rt-multiple-results.json
+// RUN: FileCheck %s --input-file %t/rt-multiple-results.json --check-prefix=RT-MULTI
+// RT-MULTI-DAG: "analysis_name": "CountsAnalysisResult"
+// RT-MULTI-DAG: "analysis_name": "TagsAnalysisResult"
+// RT-MULTI: "important"
diff --git a/clang/tools/CMakeLists.txt b/clang/tools/CMakeLists.txt
index 891043ec31f77..a16089be041bf 100644
--- a/clang/tools/CMakeLists.txt
+++ b/clang/tools/CMakeLists.txt
@@ -15,6 +15,7 @@ add_clang_subdirectory(clang-linker-wrapper)
add_clang_subdirectory(clang-nvlink-wrapper)
add_clang_subdirectory(clang-offload-bundler)
add_clang_subdirectory(clang-scan-deps)
+add_clang_subdirectory(clang-ssaf-analyzer)
add_clang_subdirectory(clang-ssaf-format)
add_clang_subdirectory(clang-ssaf-linker)
add_clang_subdirectory(clang-sycl-linker)
diff --git a/clang/tools/clang-ssaf-analyzer/CMakeLists.txt b/clang/tools/clang-ssaf-analyzer/CMakeLists.txt
new file mode 100644
index 0000000000000..022032beb262d
--- /dev/null
+++ b/clang/tools/clang-ssaf-analyzer/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(LLVM_LINK_COMPONENTS
+ Option
+ Support
+ )
+
+add_clang_tool(clang-ssaf-analyzer
+ SSAFAnalyzer.cpp
+ )
+
+clang_target_link_libraries(clang-ssaf-analyzer
+ PRIVATE
+ clangBasic
+ clangScalableStaticAnalysisFrameworkCore
+ clangScalableStaticAnalysisFrameworkTool
+ )
diff --git a/clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp b/clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp
new file mode 100644
index 0000000000000..d4f0c5a8c4145
--- /dev/null
+++ b/clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp
@@ -0,0 +1,253 @@
+//===- SSAFAnalyzer.cpp - SSAF Analyzer Tool ------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the SSAF analyzer tool that runs whole-program analyses
+// over an LUSummary and writes the resulting WPASuite.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
+#include "clang/ScalableStaticAnalysisFramework/SSAFForceLinker.h" // IWYU pragma: keep
+#include "clang/ScalableStaticAnalysisFramework/Tool/Utils.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Timer.h"
+#include "llvm/Support/WithColor.h"
+#include "llvm/Support/raw_ostream.h"
+#include <memory>
+#include <string>
+#include <system_error>
+#include <vector>
+
+using namespace llvm;
+using namespace clang::ssaf;
+
+namespace fs = llvm::sys::fs;
+namespace path = llvm::sys::path;
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// Command-Line Options
+//===----------------------------------------------------------------------===//
+
+cl::OptionCategory SsafAnalyzerCategory("clang-ssaf-analyzer options");
+
+cl::list<std::string> LoadPlugins("load",
+ cl::desc("Load a plugin shared library"),
+ cl::value_desc("path"),
+ cl::cat(SsafAnalyzerCategory));
+
+cl::opt<std::string> LUSummaryPath(cl::Positional, cl::desc("<lu-summary>"),
+ cl::cat(SsafAnalyzerCategory));
+
+cl::opt<std::string> OutputPath("o", cl::desc("Output WPASuite path"),
+ cl::value_desc("path"), cl::Required,
+ cl::cat(SsafAnalyzerCategory));
+
+cl::list<std::string>
+ AnalysisNameStrs("analysis",
+ cl::desc("Name of an analysis to run (may be repeated; "
+ "default: run all registered analyses)"),
+ cl::value_desc("name"), cl::cat(SsafAnalyzerCategory));
+
+cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"),
+ cl::init(false), cl::cat(SsafAnalyzerCategory));
+
+cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(false),
+ cl::cat(SsafAnalyzerCategory));
+
+//===----------------------------------------------------------------------===//
+// Error Messages
+//===----------------------------------------------------------------------===//
+
+namespace LocalErrorMessages {
+
+constexpr const char *RunningAnalysis = "Running analysis '{0}'";
+
+} // namespace LocalErrorMessages
+
+//===----------------------------------------------------------------------===//
+// Diagnostic Utilities
+//===----------------------------------------------------------------------===//
+
+constexpr unsigned IndentationWidth = 2;
+
+template <typename... Ts>
+void info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
+ if (Verbose) {
+ llvm::WithColor::note()
+ << std::string(IndentationLevel * IndentationWidth, ' ') << "- "
+ << llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
+ }
+}
+
+//===----------------------------------------------------------------------===//
+// Data Structures
+//===----------------------------------------------------------------------===//
+
+struct AnalyzerInput {
+ SummaryFile LUSummaryFile;
+ SummaryFile WPAOutputFile;
+ std::vector<AnalysisName> Names; // Empty means run all registered analyses.
+};
+
+//===----------------------------------------------------------------------===//
+// Pipeline
+//===----------------------------------------------------------------------===//
+
+AnalyzerInput validate(llvm::TimerGroup &TG) {
+ llvm::Timer TValidate("validate", "Validate Input", TG);
+ llvm::TimeRegion _(Time ? &TValidate : nullptr);
+
+ AnalyzerInput AI;
+
+ // Validate the LUSummary input path.
+ {
+ if (LUSummaryPath.empty()) {
+ fail("no LUSummary file specified");
+ }
+
+ llvm::SmallString<256> RealInputPath;
+ if (std::error_code EC =
+ fs::real_path(LUSummaryPath, RealInputPath, /*expand_tilde=*/true))
+ fail(ErrorMessages::CannotValidateSummary, LUSummaryPath, EC.message());
+
+ AI.LUSummaryFile = SummaryFile::fromPath(RealInputPath);
+ }
+
+ info(2, "Validated LUSummary input path '{0}'.", AI.LUSummaryFile.Path);
+
+ // Validate the WPASuite output path.
+ {
+ llvm::StringRef ParentDir = path::parent_path(OutputPath);
+ llvm::StringRef DirToCheck = ParentDir.empty() ? "." : ParentDir;
+
+ if (!fs::exists(DirToCheck)) {
+ fail(ErrorMessages::CannotValidateSummary, OutputPath,
+ ErrorMessages::OutputDirectoryMissing);
+ }
+
+ // The output file does not exist yet, so real_path cannot be called on it
+ // directly. Resolve the parent directory first, then append the filename.
+ llvm::SmallString<256> RealParentDir;
+ if (std::error_code EC = fs::real_path(DirToCheck, RealParentDir))
+ fail(ErrorMessages::CannotValidateSummary, OutputPath, EC.message());
+
+ llvm::SmallString<256> RealOutputPath = RealParentDir;
+ path::append(RealOutputPath, path::filename(OutputPath));
+
+ AI.WPAOutputFile = SummaryFile::fromPath(RealOutputPath);
+ }
+
+ info(2, "Validated WPASuite output path '{0}'.", AI.WPAOutputFile.Path);
+
+ // Convert analysis name strings to AnalysisName objects.
+ for (const auto &Name : AnalysisNameStrs)
+ AI.Names.emplace_back(Name);
+
+ if (AI.Names.empty())
+ info(2, "No analyses specified; all registered analyses will be run.");
+ else
+ info(2, "Running {0} named {1}.", AI.Names.size(),
+ AI.Names.size() == 1 ? "analysis" : "analyses");
+
+ return AI;
+}
+
+void analyze(const AnalyzerInput &AI, llvm::TimerGroup &TG) {
+ llvm::Timer TRead("read", "Read LUSummary", TG);
+ llvm::Timer TRun("run", "Run Analyses", TG);
+ llvm::Timer TWrite("write", "Write WPASuite", TG);
+
+ // Read the LUSummary.
+ std::unique_ptr<LUSummary> LU;
+ {
+ info(2, "Reading LUSummary from '{0}'.", AI.LUSummaryFile.Path);
+ llvm::TimeRegion _(Time ? &TRead : nullptr);
+
+ auto ExpectedLU =
+ AI.LUSummaryFile.Format->readLUSummary(AI.LUSummaryFile.Path);
+ if (!ExpectedLU)
+ fail(ExpectedLU.takeError());
+
+ LU = std::make_unique<LUSummary>(std::move(*ExpectedLU));
+ }
+
+ // Run analyses.
+ WPASuite Suite;
+ {
+ info(2, "Running analyses.");
+ llvm::TimeRegion _(Time ? &TRun : nullptr);
+
+ AnalysisDriver Driver(std::move(LU));
+
+ llvm::Expected<WPASuite> ExpectedSuite =
+ AI.Names.empty() ? std::move(Driver).run() : Driver.run(AI.Names);
+ if (!ExpectedSuite)
+ fail(ExpectedSuite.takeError());
+
+ Suite = std::move(*ExpectedSuite);
+ }
+
+ // Write the WPASuite.
+ {
+ info(2, "Writing WPASuite to '{0}'.", AI.WPAOutputFile.Path);
+ llvm::TimeRegion _(Time ? &TWrite : nullptr);
+
+ if (auto Err = AI.WPAOutputFile.Format->writeWPASuite(
+ Suite, AI.WPAOutputFile.Path))
+ fail(std::move(Err));
+ }
+}
+
+} // namespace
+
+//===----------------------------------------------------------------------===//
+// Driver
+//===----------------------------------------------------------------------===//
+
+int main(int argc, const char **argv) {
+ llvm::StringRef ToolHeading = "SSAF Analyzer";
+
+ InitLLVM X(argc, argv);
+ initTool(argc, argv, "0.1", SsafAnalyzerCategory, ToolHeading);
+
+ loadPlugins(LoadPlugins);
+
+ llvm::TimerGroup AnalyzerTimers(getToolName(), ToolHeading);
+
+ {
+ info(0, "Analysis started.");
+
+ AnalyzerInput AI;
+
+ {
+ info(1, "Validating input.");
+ AI = validate(AnalyzerTimers);
+ }
+
+ {
+ info(1, "Running analyses.");
+ analyze(AI, AnalyzerTimers);
+ }
+
+ info(0, "Analysis finished.");
+ }
+
+ return 0;
+}
diff --git a/clang/tools/clang-ssaf-format/SSAFFormat.cpp b/clang/tools/clang-ssaf-format/SSAFFormat.cpp
index 4f337bff89ee1..16e87a67969ff 100644
--- a/clang/tools/clang-ssaf-format/SSAFFormat.cpp
+++ b/clang/tools/clang-ssaf-format/SSAFFormat.cpp
@@ -44,7 +44,7 @@ namespace path = llvm::sys::path;
// Summary Type
//===----------------------------------------------------------------------===//
-enum class SummaryType { TU, LU };
+enum class SummaryType { TU, LU, WPA };
//===----------------------------------------------------------------------===//
// Command-Line Options
@@ -62,7 +62,9 @@ cl::list<std::string> LoadPlugins("load",
cl::opt<SummaryType> Type(
"type", cl::desc("Summary type (required unless --list is given)"),
cl::values(clEnumValN(SummaryType::TU, "tu", "Translation unit summary"),
- clEnumValN(SummaryType::LU, "lu", "Link unit summary")),
+ clEnumValN(SummaryType::LU, "lu", "Link unit summary"),
+ clEnumValN(SummaryType::WPA, "wpa",
+ "Whole-program analysis suite")),
cl::cat(SsafFormatCategory));
cl::opt<std::string> InputPath(cl::Positional, cl::desc("<input file>"),
@@ -338,6 +340,10 @@ void convert(const FormatInput &FI) {
&SerializationFormat::writeLUSummary);
}
return;
+ case SummaryType::WPA:
+ run(FI, &SerializationFormat::readWPASuite,
+ &SerializationFormat::writeWPASuite);
+ return;
}
llvm_unreachable("Unhandled SummaryType variant");
>From e9dd7b65806b13742f1d8c7232ce098c0ce7d87f Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 25 Mar 2026 11:53:32 -0700
Subject: [PATCH 19/19] Remove analyzer from this PR
---
clang/tools/CMakeLists.txt | 1 -
.../tools/clang-ssaf-analyzer/CMakeLists.txt | 15 --
.../clang-ssaf-analyzer/SSAFAnalyzer.cpp | 253 ------------------
3 files changed, 269 deletions(-)
delete mode 100644 clang/tools/clang-ssaf-analyzer/CMakeLists.txt
delete mode 100644 clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp
diff --git a/clang/tools/CMakeLists.txt b/clang/tools/CMakeLists.txt
index a16089be041bf..891043ec31f77 100644
--- a/clang/tools/CMakeLists.txt
+++ b/clang/tools/CMakeLists.txt
@@ -15,7 +15,6 @@ add_clang_subdirectory(clang-linker-wrapper)
add_clang_subdirectory(clang-nvlink-wrapper)
add_clang_subdirectory(clang-offload-bundler)
add_clang_subdirectory(clang-scan-deps)
-add_clang_subdirectory(clang-ssaf-analyzer)
add_clang_subdirectory(clang-ssaf-format)
add_clang_subdirectory(clang-ssaf-linker)
add_clang_subdirectory(clang-sycl-linker)
diff --git a/clang/tools/clang-ssaf-analyzer/CMakeLists.txt b/clang/tools/clang-ssaf-analyzer/CMakeLists.txt
deleted file mode 100644
index 022032beb262d..0000000000000
--- a/clang/tools/clang-ssaf-analyzer/CMakeLists.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-set(LLVM_LINK_COMPONENTS
- Option
- Support
- )
-
-add_clang_tool(clang-ssaf-analyzer
- SSAFAnalyzer.cpp
- )
-
-clang_target_link_libraries(clang-ssaf-analyzer
- PRIVATE
- clangBasic
- clangScalableStaticAnalysisFrameworkCore
- clangScalableStaticAnalysisFrameworkTool
- )
diff --git a/clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp b/clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp
deleted file mode 100644
index d4f0c5a8c4145..0000000000000
--- a/clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp
+++ /dev/null
@@ -1,253 +0,0 @@
-//===- SSAFAnalyzer.cpp - SSAF Analyzer Tool ------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements the SSAF analyzer tool that runs whole-program analyses
-// over an LUSummary and writes the resulting WPASuite.
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h"
-#include "clang/ScalableStaticAnalysisFramework/SSAFForceLinker.h" // IWYU pragma: keep
-#include "clang/ScalableStaticAnalysisFramework/Tool/Utils.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/Support/CommandLine.h"
-#include "llvm/Support/FileSystem.h"
-#include "llvm/Support/FormatVariadic.h"
-#include "llvm/Support/InitLLVM.h"
-#include "llvm/Support/Path.h"
-#include "llvm/Support/Timer.h"
-#include "llvm/Support/WithColor.h"
-#include "llvm/Support/raw_ostream.h"
-#include <memory>
-#include <string>
-#include <system_error>
-#include <vector>
-
-using namespace llvm;
-using namespace clang::ssaf;
-
-namespace fs = llvm::sys::fs;
-namespace path = llvm::sys::path;
-
-namespace {
-
-//===----------------------------------------------------------------------===//
-// Command-Line Options
-//===----------------------------------------------------------------------===//
-
-cl::OptionCategory SsafAnalyzerCategory("clang-ssaf-analyzer options");
-
-cl::list<std::string> LoadPlugins("load",
- cl::desc("Load a plugin shared library"),
- cl::value_desc("path"),
- cl::cat(SsafAnalyzerCategory));
-
-cl::opt<std::string> LUSummaryPath(cl::Positional, cl::desc("<lu-summary>"),
- cl::cat(SsafAnalyzerCategory));
-
-cl::opt<std::string> OutputPath("o", cl::desc("Output WPASuite path"),
- cl::value_desc("path"), cl::Required,
- cl::cat(SsafAnalyzerCategory));
-
-cl::list<std::string>
- AnalysisNameStrs("analysis",
- cl::desc("Name of an analysis to run (may be repeated; "
- "default: run all registered analyses)"),
- cl::value_desc("name"), cl::cat(SsafAnalyzerCategory));
-
-cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"),
- cl::init(false), cl::cat(SsafAnalyzerCategory));
-
-cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(false),
- cl::cat(SsafAnalyzerCategory));
-
-//===----------------------------------------------------------------------===//
-// Error Messages
-//===----------------------------------------------------------------------===//
-
-namespace LocalErrorMessages {
-
-constexpr const char *RunningAnalysis = "Running analysis '{0}'";
-
-} // namespace LocalErrorMessages
-
-//===----------------------------------------------------------------------===//
-// Diagnostic Utilities
-//===----------------------------------------------------------------------===//
-
-constexpr unsigned IndentationWidth = 2;
-
-template <typename... Ts>
-void info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
- if (Verbose) {
- llvm::WithColor::note()
- << std::string(IndentationLevel * IndentationWidth, ' ') << "- "
- << llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
- }
-}
-
-//===----------------------------------------------------------------------===//
-// Data Structures
-//===----------------------------------------------------------------------===//
-
-struct AnalyzerInput {
- SummaryFile LUSummaryFile;
- SummaryFile WPAOutputFile;
- std::vector<AnalysisName> Names; // Empty means run all registered analyses.
-};
-
-//===----------------------------------------------------------------------===//
-// Pipeline
-//===----------------------------------------------------------------------===//
-
-AnalyzerInput validate(llvm::TimerGroup &TG) {
- llvm::Timer TValidate("validate", "Validate Input", TG);
- llvm::TimeRegion _(Time ? &TValidate : nullptr);
-
- AnalyzerInput AI;
-
- // Validate the LUSummary input path.
- {
- if (LUSummaryPath.empty()) {
- fail("no LUSummary file specified");
- }
-
- llvm::SmallString<256> RealInputPath;
- if (std::error_code EC =
- fs::real_path(LUSummaryPath, RealInputPath, /*expand_tilde=*/true))
- fail(ErrorMessages::CannotValidateSummary, LUSummaryPath, EC.message());
-
- AI.LUSummaryFile = SummaryFile::fromPath(RealInputPath);
- }
-
- info(2, "Validated LUSummary input path '{0}'.", AI.LUSummaryFile.Path);
-
- // Validate the WPASuite output path.
- {
- llvm::StringRef ParentDir = path::parent_path(OutputPath);
- llvm::StringRef DirToCheck = ParentDir.empty() ? "." : ParentDir;
-
- if (!fs::exists(DirToCheck)) {
- fail(ErrorMessages::CannotValidateSummary, OutputPath,
- ErrorMessages::OutputDirectoryMissing);
- }
-
- // The output file does not exist yet, so real_path cannot be called on it
- // directly. Resolve the parent directory first, then append the filename.
- llvm::SmallString<256> RealParentDir;
- if (std::error_code EC = fs::real_path(DirToCheck, RealParentDir))
- fail(ErrorMessages::CannotValidateSummary, OutputPath, EC.message());
-
- llvm::SmallString<256> RealOutputPath = RealParentDir;
- path::append(RealOutputPath, path::filename(OutputPath));
-
- AI.WPAOutputFile = SummaryFile::fromPath(RealOutputPath);
- }
-
- info(2, "Validated WPASuite output path '{0}'.", AI.WPAOutputFile.Path);
-
- // Convert analysis name strings to AnalysisName objects.
- for (const auto &Name : AnalysisNameStrs)
- AI.Names.emplace_back(Name);
-
- if (AI.Names.empty())
- info(2, "No analyses specified; all registered analyses will be run.");
- else
- info(2, "Running {0} named {1}.", AI.Names.size(),
- AI.Names.size() == 1 ? "analysis" : "analyses");
-
- return AI;
-}
-
-void analyze(const AnalyzerInput &AI, llvm::TimerGroup &TG) {
- llvm::Timer TRead("read", "Read LUSummary", TG);
- llvm::Timer TRun("run", "Run Analyses", TG);
- llvm::Timer TWrite("write", "Write WPASuite", TG);
-
- // Read the LUSummary.
- std::unique_ptr<LUSummary> LU;
- {
- info(2, "Reading LUSummary from '{0}'.", AI.LUSummaryFile.Path);
- llvm::TimeRegion _(Time ? &TRead : nullptr);
-
- auto ExpectedLU =
- AI.LUSummaryFile.Format->readLUSummary(AI.LUSummaryFile.Path);
- if (!ExpectedLU)
- fail(ExpectedLU.takeError());
-
- LU = std::make_unique<LUSummary>(std::move(*ExpectedLU));
- }
-
- // Run analyses.
- WPASuite Suite;
- {
- info(2, "Running analyses.");
- llvm::TimeRegion _(Time ? &TRun : nullptr);
-
- AnalysisDriver Driver(std::move(LU));
-
- llvm::Expected<WPASuite> ExpectedSuite =
- AI.Names.empty() ? std::move(Driver).run() : Driver.run(AI.Names);
- if (!ExpectedSuite)
- fail(ExpectedSuite.takeError());
-
- Suite = std::move(*ExpectedSuite);
- }
-
- // Write the WPASuite.
- {
- info(2, "Writing WPASuite to '{0}'.", AI.WPAOutputFile.Path);
- llvm::TimeRegion _(Time ? &TWrite : nullptr);
-
- if (auto Err = AI.WPAOutputFile.Format->writeWPASuite(
- Suite, AI.WPAOutputFile.Path))
- fail(std::move(Err));
- }
-}
-
-} // namespace
-
-//===----------------------------------------------------------------------===//
-// Driver
-//===----------------------------------------------------------------------===//
-
-int main(int argc, const char **argv) {
- llvm::StringRef ToolHeading = "SSAF Analyzer";
-
- InitLLVM X(argc, argv);
- initTool(argc, argv, "0.1", SsafAnalyzerCategory, ToolHeading);
-
- loadPlugins(LoadPlugins);
-
- llvm::TimerGroup AnalyzerTimers(getToolName(), ToolHeading);
-
- {
- info(0, "Analysis started.");
-
- AnalyzerInput AI;
-
- {
- info(1, "Validating input.");
- AI = validate(AnalyzerTimers);
- }
-
- {
- info(1, "Running analyses.");
- analyze(AI, AnalyzerTimers);
- }
-
- info(0, "Analysis finished.");
- }
-
- return 0;
-}
More information about the cfe-commits
mailing list