diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h index 552fff04a4c01..a36002006430c 100644 --- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h @@ -31,6 +31,7 @@ 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; diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h index d49fd6cb4a1dc..437152d43f425 100644 --- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Support/FormatProviders.h @@ -19,6 +19,7 @@ #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" @@ -80,6 +81,13 @@ template <> struct format_provider { } }; +template <> struct format_provider { + 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 diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisBase.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisBase.h new file mode 100644 index 0000000000000..b86a9c5828700 --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisBase.h @@ -0,0 +1,60 @@ +//===- 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. +// 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_WHOLEPROGRAMANALYSIS_ANALYSISBASE_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISBASE_H + +#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h" +#include + +namespace clang::ssaf { + +class AnalysisDriver; +class AnalysisResult; +class DerivedAnalysisBase; +class SummaryAnalysisBase; + +/// Minimal common base for both analysis kinds. +/// +/// Not subclassed directly -- use SummaryAnalysis<...> or +/// DerivedAnalysis<...> instead. +class AnalysisBase { + friend class AnalysisDriver; + friend class DerivedAnalysisBase; + friend class SummaryAnalysisBase; + + 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 &dependencyNames() const = 0; + + /// Transfers ownership of the built result. Called once after finalize(). + /// The rvalue ref-qualifier enforces single use. + virtual std::unique_ptr result() && = 0; +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISBASE_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h new file mode 100644 index 0000000000000..156d8e806bd0f --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h @@ -0,0 +1,95 @@ +//===- 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. +// 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_WHOLEPROGRAMANALYSIS_ANALYSISDRIVER_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISDRIVER_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 +#include + +namespace clang::ssaf { + +class AnalysisBase; +class DerivedAnalysisBase; +class SummaryAnalysisBase; + +/// Orchestrates whole-program analysis over an LUSummary. +/// +/// Three run() patterns are supported: +/// - 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. +/// - run -- type-safe variant of run(names). +class AnalysisDriver final { +public: + explicit AnalysisDriver(std::unique_ptr LU); + + /// Runs 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 (std::move(Driver).run()) because this + /// exhausts all remaining LUSummary data. + [[nodiscard]] llvm::Expected 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 + run(llvm::ArrayRef Names) const; + + /// Type-safe variant of run(names). Derives names from + /// ResultTs::analysisName(). + template + [[nodiscard]] llvm::Expected run() const { + return run({ResultTs::analysisName()...}); + } + +private: + std::unique_ptr 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>> + toposort(llvm::ArrayRef Roots); + + /// Executes a topologically-sorted analysis list and returns a WPASuite. + /// \p IdTable is moved into the returned WPASuite. + llvm::Expected + execute(EntityIdTable IdTable, + llvm::ArrayRef> Sorted) const; + + llvm::Error executeSummaryAnalysis(SummaryAnalysisBase &Summary, + WPASuite &Suite) const; + + llvm::Error executeDerivedAnalysis(DerivedAnalysisBase &Derived, + WPASuite &Suite) const; +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISDRIVER_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h new file mode 100644 index 0000000000000..32f76e73b14e0 --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h @@ -0,0 +1,49 @@ +//===- 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. +// 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_WHOLEPROGRAMANALYSIS_ANALYSISNAME_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISNAME_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" +#include + +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_WHOLEPROGRAMANALYSIS_ANALYSISNAME_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h new file mode 100644 index 0000000000000..44eabce6c809c --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h @@ -0,0 +1,109 @@ +//===- 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. +// 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 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 +// 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. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISREGISTRY_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISREGISTRY_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 +#include +#include + +namespace clang::ssaf { + +/// Unified registry for SummaryAnalysis and DerivedAnalysis implementations. +/// +/// Internally uses a single llvm::Registry. The correct kind +/// is carried by the AnalysisBase::TheKind tag set in each subclass +/// constructor. +class AnalysisRegistry { + using RegistryT = llvm::Registry; + + 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 struct Add { + static_assert(std::is_base_of_v || + std::is_base_of_v, + "AnalysisT must derive from SummaryAnalysis<...> or " + "DerivedAnalysis<...>"); + + explicit Add(llvm::StringRef Desc) + : Name(AnalysisT::ResultType::analysisName().str().str()), + Node(Name, Desc) { + if (contains(AnalysisT::ResultType::analysisName())) { + ErrorBuilder::fatal("duplicate analysis registration for '{0}'", Name); + } + getAnalysisNames().push_back(AnalysisT::ResultType::analysisName()); + } + + Add(const Add &) = delete; + Add &operator=(const Add &) = delete; + + private: + std::string Name; + RegistryT::Add Node; + }; + + /// Returns true if an analysis is registered under \p Name. + static bool contains(const AnalysisName &Name); + + /// Returns the names of all registered analyses. + static const std::vector &names(); + + /// Instantiates the analysis registered under \p Name, or returns an error + /// if no such analysis is registered. + static llvm::Expected> + instantiate(const AnalysisName &Name); + +private: + /// Returns the global list of registered analysis names. + /// + /// Uses a function-local static to avoid static initialization order + /// fiasco: Add objects in other translation units may push names before + /// a plain static data member could be constructed. + static std::vector &getAnalysisNames(); +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISREGISTRY_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h new file mode 100644 index 0000000000000..07d1f0549a9ee --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h @@ -0,0 +1,30 @@ +//===- 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Base class for all whole-program analysis results produced by AnalysisDriver. +// Concrete subclasses carry a static analysisName(). +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISRESULT_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_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_WHOLEPROGRAMANALYSIS_ANALYSISRESULT_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisTraits.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisTraits.h new file mode 100644 index 0000000000000..78df3b35648c2 --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisTraits.h @@ -0,0 +1,36 @@ +//===- 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Type traits for AnalysisResult subclasses. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISTRAITS_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISTRAITS_H + +#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h" +#include + +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 +struct HasAnalysisName : std::false_type {}; + +template +struct HasAnalysisName> + : std::is_same {}; + +template +inline constexpr bool HasAnalysisName_v = HasAnalysisName::value; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_ANALYSISTRAITS_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h new file mode 100644 index 0000000000000..4eb35262d4625 --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h @@ -0,0 +1,140 @@ +//===- 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines DerivedAnalysisBase (type-erased base known to AnalysisDriver) and +// the typed intermediate DerivedAnalysis that +// concrete analyses inherit from. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_DERIVEDANALYSIS_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_DERIVEDANALYSIS_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 +#include +#include + +namespace clang::ssaf { + +class AnalysisDriver; +class AnalysisRegistry; + +/// Type-erased base for derived analyses. Known to AnalysisDriver. +/// +/// Not subclassed directly -- use DerivedAnalysis. +/// 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 &DepResults) = 0; + + /// Performs one pass. + /// Returns true if another pass is needed; false when converged. + virtual llvm::Expected step() = 0; + + /// Called after the step() loop converges. Default is a no-op. + virtual llvm::Error finalize() { return llvm::Error::success(); } +}; + +/// Typed intermediate that concrete derived analyses inherit from. +/// +/// Concrete analyses must implement: +/// llvm::Error initialize(const DepResultTs &...) override; +/// llvm::Expected 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 +class DerivedAnalysis : public DerivedAnalysisBase { + static_assert(std::is_base_of_v, + "ResultT must derive from AnalysisResult"); + static_assert(HasAnalysisName_v, + "ResultT must have a static analysisName() method"); + static_assert((std::is_base_of_v && ...), + "Every DepResultT must derive from AnalysisResult"); + static_assert((HasAnalysisName_v && ...), + "Every DepResultT must have a static analysisName() method"); + + friend class AnalysisRegistry; + using ResultType = ResultT; + + std::unique_ptr Result = std::make_unique(); + +public: + /// Used by AnalysisRegistry::Add to derive the registry entry name. + AnalysisName analysisName() const final { return ResultT::analysisName(); } + + const std::vector &dependencyNames() const final { + static const std::vector 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 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(). All dependencies are guaranteed present by the driver. + llvm::Error + initialize(const std::map &Map) final { + auto lookup = [&Map](const AnalysisName &Name) -> const AnalysisResult * { + auto It = Map.find(Name); + if (It == Map.end()) { + ErrorBuilder::fatal("dependency '{0}' missing from DepResults map; " + "dependency graph is not topologically sorted", + Name); + } + return It->second; + }; + return initialize(*static_cast( + lookup(DepResultTs::analysisName()))...); + } + + /// Type-erased result extraction for the driver. + std::unique_ptr result() && final { + return std::move(Result); + } +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_DERIVEDANALYSIS_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h new file mode 100644 index 0000000000000..31b9e6ae4a6c3 --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h @@ -0,0 +1,128 @@ +//===- 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines SummaryAnalysisBase (type-erased base known to AnalysisDriver) and +// the typed intermediate SummaryAnalysis that +// concrete analyses inherit from. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_SUMMARYANALYSIS_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_SUMMARYANALYSIS_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 + +namespace clang::ssaf { + +class AnalysisDriver; +class AnalysisRegistry; + +/// Type-erased base for summary analyses. Known to AnalysisDriver. +/// +/// Not subclassed directly -- use SummaryAnalysis. +/// 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(); } +}; + +/// 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 +class SummaryAnalysis : public SummaryAnalysisBase { + static_assert(std::is_base_of_v, + "ResultT must derive from AnalysisResult"); + static_assert(HasAnalysisName_v, + "ResultT must have a static analysisName() method"); + static_assert(std::is_base_of_v, + "EntitySummaryT must derive from EntitySummary"); + + friend class AnalysisRegistry; + using ResultType = ResultT; + + std::unique_ptr Result = std::make_unique(); + +public: + /// 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 &dependencyNames() const final { + static const std::vector 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(Summary)); + } + + /// Type-erased result extraction for the driver. + std::unique_ptr result() && final { + return std::move(Result); + } +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_SUMMARYANALYSIS_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h new file mode 100644 index 0000000000000..5a0105fc1f4d9 --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h @@ -0,0 +1,95 @@ +//===- 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// The value returned by AnalysisDriver::run(). Bundles the EntityIdTable +// with the analysis results keyed by AnalysisName. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_WPASUITE_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_WPASUITE_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 +#include + +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> Data; + + WPASuite() = default; + +public: + /// Returns the EntityIdTable that maps EntityId values to their symbolic + /// names. + const EntityIdTable &idTable() const { return IdTable; } + + /// Returns true if a result for \p ResultT is present. + template [[nodiscard]] bool contains() const { + static_assert(std::is_base_of_v, + "ResultT must derive from AnalysisResult"); + static_assert(HasAnalysisName_v, + "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 const reference to the result for \p ResultT, or an error if + /// absent. + template + [[nodiscard]] llvm::Expected get() const { + static_assert(std::is_base_of_v, + "ResultT must derive from AnalysisResult"); + static_assert(HasAnalysisName_v, + "ResultT must have a static analysisName() method"); + + auto Result = get(ResultT::analysisName()); + if (!Result) { + return Result.takeError(); + } + return static_cast(*Result); + } + + /// Returns a const reference to the result for \p Name, or an error if + /// absent. + [[nodiscard]] llvm::Expected + get(AnalysisName Name) const { + auto It = Data.find(Name); + if (It == Data.end()) { + return ErrorBuilder::create(std::errc::invalid_argument, + "no result for '{0}' in WPASuite", Name) + .build(); + } + return *It->second; + } +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_WHOLEPROGRAMANALYSIS_WPASUITE_H 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/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt index 190b9fe400a64..8c306163df1a7 100644 --- a/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt +++ b/clang/lib/ScalableStaticAnalysisFramework/Core/CMakeLists.txt @@ -23,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/WholeProgramAnalysis/AnalysisDriver.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.cpp new file mode 100644 index 0000000000000..430b98f3916fd --- /dev/null +++ b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.cpp @@ -0,0 +1,212 @@ +//===- 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/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" +#include +#include + +using namespace clang; +using namespace ssaf; + +AnalysisDriver::AnalysisDriver(std::unique_ptr LU) + : LU(std::move(LU)) {} + +llvm::Expected>> +AnalysisDriver::toposort(llvm::ArrayRef Roots) { + struct Visitor { + enum class State { Unvisited, Visiting, Visited }; + + std::map Marks; + std::vector Path; + std::vector> 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; + 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.emplace(Name, State::Unvisited); + + switch (It->second) { + case State::Visited: + return llvm::Error::success(); + + case State::Visiting: + return ErrorBuilder::create(std::errc::invalid_argument, + "cycle detected: {0}", formatCycle(Name)) + .build(); + + case State::Unvisited: { + It->second = State::Visiting; + Path.push_back(Name); + + llvm::Expected> V = + AnalysisRegistry::instantiate(Name); + if (!V) { + return V.takeError(); + } + + // Unwrap for convenience to avoid the noise of dereferencing an + // Expected on every subsequent access. + std::unique_ptr 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)); + + return llvm::Error::success(); + } + } + llvm_unreachable("unhandled State"); + } + }; + + Visitor V(Roots.size()); + for (const auto &Root : Roots) { + if (auto Err = V.visit(Root)) { + return std::move(Err); + } + } + return std::move(V.Result); +} + +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()) + .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; + } + + return llvm::Error::success(); +} + +llvm::Error AnalysisDriver::executeDerivedAnalysis(DerivedAnalysisBase &Derived, + WPASuite &Suite) const { + std::map 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, Derived.analysisName()); + } + 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; + } + + return llvm::Error::success(); +} + +llvm::Expected AnalysisDriver::execute( + EntityIdTable IdTable, + llvm::ArrayRef> Sorted) const { + WPASuite Suite; + Suite.IdTable = std::move(IdTable); + + for (auto &Analysis : Sorted) { + switch (Analysis->TheKind) { + case AnalysisBase::Kind::Summary: { + SummaryAnalysisBase &SA = static_cast(*Analysis); + if (auto Err = executeSummaryAnalysis(SA, Suite)) { + return std::move(Err); + } + break; + } + case AnalysisBase::Kind::Derived: { + DerivedAnalysisBase &DA = static_cast(*Analysis); + if (auto Err = executeDerivedAnalysis(DA, Suite)) { + return std::move(Err); + } + break; + } + } + AnalysisName Name = Analysis->analysisName(); + Suite.Data.emplace(std::move(Name), std::move(*Analysis).result()); + } + + return std::move(Suite); +} + +llvm::Expected AnalysisDriver::run() && { + auto ExpectedSorted = toposort(AnalysisRegistry::names()); + if (!ExpectedSorted) { + return ExpectedSorted.takeError(); + } + return execute(std::move(LU->IdTable), *ExpectedSorted); +} + +llvm::Expected +AnalysisDriver::run(llvm::ArrayRef Names) const { + auto ExpectedSorted = toposort(Names); + if (!ExpectedSorted) { + return ExpectedSorted.takeError(); + } + + return execute(LU->IdTable, *ExpectedSorted); +} diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.cpp new file mode 100644 index 0000000000000..9719196ed4d6d --- /dev/null +++ b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/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/WholeProgramAnalysis/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/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.cpp new file mode 100644 index 0000000000000..8e1ea954d9afd --- /dev/null +++ b/clang/lib/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.cpp @@ -0,0 +1,45 @@ +//===- 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/WholeProgramAnalysis/AnalysisRegistry.h" +#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h" +#include "llvm/ADT/STLExtras.h" + +using namespace clang; +using namespace ssaf; + +using RegistryT = llvm::Registry; + +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFAnalysisRegistryAnchorSource = 0; +LLVM_INSTANTIATE_REGISTRY(RegistryT) + +std::vector &AnalysisRegistry::getAnalysisNames() { + static std::vector Names; + return Names; +} + +bool AnalysisRegistry::contains(const AnalysisName &Name) { + return llvm::is_contained(getAnalysisNames(), Name); +} + +const std::vector &AnalysisRegistry::names() { + return getAnalysisNames(); +} + +llvm::Expected> +AnalysisRegistry::instantiate(const AnalysisName &Name) { + for (const auto &Entry : RegistryT::entries()) { + if (Entry.getName() == Name.str()) { + return std::unique_ptr(Entry.instantiate()); + } + } + return ErrorBuilder::create(std::errc::invalid_argument, + "no analysis registered for '{0}'", Name) + .build(); +} diff --git a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt index 871d9e6b0c02c..345eed6c5279b 100644 --- a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt +++ b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt @@ -23,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/WholeProgramAnalysis/AnalysisDriverTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/WholeProgramAnalysis/AnalysisDriverTest.cpp new file mode 100644 index 0000000000000..e206b33d80295 --- /dev/null +++ b/clang/unittests/ScalableStaticAnalysisFramework/WholeProgramAnalysis/AnalysisDriverTest.cpp @@ -0,0 +1,566 @@ +//===- 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/WholeProgramAnalysis/AnalysisDriver.h" +#include "../TestFixture.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" +#include +#include +#include +#include + +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"); + } +}; + +// --------------------------------------------------------------------------- +// 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 Analysis1Name; } + std::vector> Entries; + bool WasInitialized = false; + bool WasFinalized = false; +}; + +class Analysis2Result final : public AnalysisResult { +public: + static AnalysisName analysisName() { return Analysis2Name; } + std::vector> Entries; + bool WasInitialized = false; + 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 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 Analysis4Name; } + std::vector> 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 Analysis5Name; } + std::vector CallSequence; + std::vector> Analysis1Entries; + std::vector> Analysis2Entries; + std::vector> Analysis4Entries; +}; + +// CycleA and CycleB form a dependency cycle (CycleA → CycleB → CycleA). +// 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: + static AnalysisName analysisName() { return CycleAName; } +}; + +class CycleBResult final : public AnalysisResult { +public: + static AnalysisName analysisName() { return CycleBName; } +}; + +// --------------------------------------------------------------------------- +// Analyses +// --------------------------------------------------------------------------- + +class Analysis1 final + : public SummaryAnalysis { +public: + inline static bool WasDestroyed = false; + ~Analysis1() { WasDestroyed = 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(); + } + + llvm::Error finalize() override { + result().WasFinalized = true; + return llvm::Error::success(); + } +}; + +// 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 RegAnalysis1("Analysis for Analysis1"); + +class Analysis2 final + : public SummaryAnalysis { +public: + inline static bool WasDestroyed = false; + ~Analysis2() { WasDestroyed = 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(); + } + + llvm::Error finalize() override { + result().WasFinalized = true; + return llvm::Error::success(); + } +}; + +static AnalysisRegistry::Add RegAnalysis2("Analysis for Analysis2"); + +// No Analysis3 or registration for Analysis3. + +class Analysis4 final + : public SummaryAnalysis { +public: + inline static bool WasDestroyed = false; + ~Analysis4() { WasDestroyed = 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(); + } + + llvm::Error finalize() override { + result().WasFinalized = true; + return llvm::Error::success(); + } +}; + +static AnalysisRegistry::Add RegAnalysis4("Analysis for Analysis4"); + +class Analysis5 final + : public DerivedAnalysis { + int StepCount = 0; + +public: + inline static bool WasDestroyed = false; + ~Analysis5() { WasDestroyed = 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 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 RegAnalysis5("Analysis for Analysis5"); + +class CycleA final : public DerivedAnalysis { +public: + llvm::Error initialize(const CycleBResult &) override { + return llvm::Error::success(); + } + llvm::Expected step() override { return false; } +}; + +static AnalysisRegistry::Add RegCycleA("Cyclic analysis A (test only)"); + +class CycleB final : public DerivedAnalysis { +public: + llvm::Error initialize(const CycleAResult &) override { + return llvm::Error::success(); + } + llvm::Expected step() override { return false; } +}; + +static AnalysisRegistry::Add RegCycleB("Cyclic analysis B (test only)"); + +// --------------------------------------------------------------------------- +// Fixture +// --------------------------------------------------------------------------- + +class AnalysisDriverTest : public TestFixture { +protected: + static constexpr EntityLinkage ExternalLinkage = + EntityLinkage(EntityLinkageType::External); + + void SetUp() override { + NextSummaryInstanceId = 0; + Analysis1::WasDestroyed = false; + Analysis2::WasDestroyed = false; + // No Analysis3 - not registered, so no WasDestroyed flag. + Analysis4::WasDestroyed = false; + Analysis5::WasDestroyed = false; + } + + std::unique_ptr makeLUSummary() { + NestedBuildNamespace NS( + {BuildNamespace(BuildNamespaceKind::LinkUnit, "TestLU")}); + return std::make_unique(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> &Entries, + EntityId Id, int InstanceId) { + return llvm::is_contained(Entries, std::make_pair(Id, InstanceId)); + } + + template + int insertSummary(LUSummary &LU, llvm::StringRef SN, EntityId Id) { + auto S = std::make_unique(); + int InstanceId = S->InstanceId; + getData(LU)[SummaryName(SN.str())][Id] = std::move(S); + return InstanceId; + } +}; + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +TEST(AnalysisRegistryTest, AnalysisIsRegistered) { + 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(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(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() — 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"); + const auto E2 = addEntity(*LU, "Entity2"); + const auto E3 = addEntity(*LU, "Entity3"); + const auto E4 = addEntity(*LU, "Entity4"); + + int s1a = insertSummary(*LU, "Analysis1", E1); + int s1b = insertSummary(*LU, "Analysis1", E2); + int s2a = insertSummary(*LU, "Analysis2", E2); + int s2b = insertSummary(*LU, "Analysis2", E3); + int s4a = insertSummary(*LU, "Analysis4", E4); + + // No registered analysis — Analysis3 data silently skipped. + (void)insertSummary(*LU, "Analysis3", E1); + + AnalysisDriver Driver(std::move(LU)); + auto WPAOrErr = Driver.run(); + ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded()); + + { + auto R1OrErr = WPAOrErr->get(); + 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->WasInitialized); + EXPECT_TRUE(R1OrErr->WasFinalized); + EXPECT_TRUE(Analysis1::WasDestroyed); + } + + { + auto R2OrErr = WPAOrErr->get(); + 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->WasInitialized); + EXPECT_TRUE(R2OrErr->WasFinalized); + EXPECT_TRUE(Analysis2::WasDestroyed); + } + + { + auto R4OrErr = WPAOrErr->get(); + 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(Analysis4::WasDestroyed); + } + + { + auto R5OrErr = WPAOrErr->get(); + ASSERT_THAT_EXPECTED(R5OrErr, llvm::Succeeded()); + EXPECT_EQ( + R5OrErr->CallSequence, + (std::vector{"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(Analysis5::WasDestroyed); + } + + // Unregistered analysis — not present in WPA. + EXPECT_THAT_EXPECTED( + WPAOrErr->get(), + llvm::FailedWithMessage( + "no result for 'AnalysisName(Analysis3)' in WPASuite")); +} + +// 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(*LU, "Analysis1", E1); + insertSummary(*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(); + 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(), + llvm::FailedWithMessage( + "no result for 'AnalysisName(Analysis2)' in WPASuite")); +} + +// 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::FailedWithMessage( + "no data for analysis 'AnalysisName(Analysis1)' in LUSummary")); +} + +// 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(*LU, "Analysis3", E1); + + AnalysisDriver Driver(std::move(LU)); + + // Analysis3 has data but no registered analysis. + EXPECT_THAT_EXPECTED( + Driver.run({AnalysisName("Analysis3")}), + llvm::FailedWithMessage( + "no analysis registered for 'AnalysisName(Analysis3)'")); +} + +// run() — 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(*LU, "Analysis1", E1); + insertSummary(*LU, "Analysis2", E2); + + AnalysisDriver Driver(std::move(LU)); + auto WPAOrErr = Driver.run(); + ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded()); + + // Analysis1 was requested — present. + auto R1OrErr = WPAOrErr->get(); + 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(), + llvm::FailedWithMessage( + "no result for 'AnalysisName(Analysis2)' in WPASuite")); +} + +// run() — 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(), + llvm::FailedWithMessage( + "no data for analysis 'AnalysisName(Analysis1)' in LUSummary")); +} + +// contains() — present entries return true; absent entries return false. +TEST_F(AnalysisDriverTest, Contains) { + auto LU = makeLUSummary(); + const auto E1 = addEntity(*LU, "Entity1"); + insertSummary(*LU, "Analysis1", E1); + insertSummary(*LU, "Analysis2", E1); + insertSummary(*LU, "Analysis4", E1); + + AnalysisDriver Driver(std::move(LU)); + auto WPAOrErr = Driver.run(); + ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded()); + EXPECT_TRUE(WPAOrErr->contains()); + // Analysis3 has no registered analysis — never present in WPA. + EXPECT_FALSE(WPAOrErr->contains()); +} + +// 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