//===--- TestingSupport.h - Testing utils for dataflow analyses -*- 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 // //===----------------------------------------------------------------------===// // // This file defines utilities to simplify testing of dataflow analyses. // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_ #define LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_ #include <functional> #include <memory> #include <optional> #include <ostream> #include <string> #include <utility> #include <vector> #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/Stmt.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchersInternal.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/FlowSensitive/AdornedCFG.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" #include "clang/Analysis/FlowSensitive/MatchSwitch.h" #include "clang/Analysis/FlowSensitive/NoopLattice.h" #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" #include "clang/Basic/LLVM.h" #include "clang/Serialization/PCHContainerOperations.h" #include "clang/Tooling/ArgumentsAdjusters.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Testing/Annotations/Annotations.h" namespace clang { namespace dataflow { // Requires a `<<` operator for the `Lattice` type. // FIXME: move to a non-test utility library. template <typename Lattice> std::ostream &operator<<(std::ostream &OS, const DataflowAnalysisState<Lattice> &S) { … } namespace test { // Caps the number of block visits in any individual analysis. Given that test // code is typically quite small, we set a low number to help catch any problems // early. But, the choice is arbitrary. constexpr std::int32_t MaxBlockVisitsInAnalysis = …; /// Returns the environment at the program point marked with `Annotation` from /// the mapping of annotated program points to analysis state. /// /// Requirements: /// /// `Annotation` must be present as a key in `AnnotationStates`. template <typename LatticeT> const Environment &getEnvironmentAtAnnotation( const llvm::StringMap<DataflowAnalysisState<LatticeT>> &AnnotationStates, llvm::StringRef Annotation) { … } /// Contains data structures required and produced by a dataflow analysis run. struct AnalysisOutputs { … }; /// A callback to be called with the state before or after visiting a CFG /// element. /// This differs from `DiagnosisCallback` in that the return type is void. DiagnosisCallbackForTesting; /// A pair of callbacks to be called with the state before and after visiting a /// CFG element. /// Either or both of the callbacks may be null. template <typename AnalysisT> struct DiagnosisCallbacksForTesting { … }; /// Arguments for building the dataflow analysis. template <typename AnalysisT> struct AnalysisInputs { … }; /// Returns assertions based on annotations that are present after statements in /// `AnnotatedCode`. llvm::Expected<llvm::DenseMap<const Stmt *, std::string>> buildStatementToAnnotationMapping(const FunctionDecl *Func, llvm::Annotations AnnotatedCode); /// Returns line numbers and content of the annotations in `AnnotatedCode` /// within the token range `BoundingRange`. llvm::DenseMap<unsigned, std::string> buildLineToAnnotationMapping( const SourceManager &SM, const LangOptions &LangOpts, SourceRange BoundingRange, llvm::Annotations AnnotatedCode); /// Runs dataflow specified from `AI.MakeAnalysis` and `AI.Callbacks` on all /// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the /// analysis outputs, `VerifyResults` checks that the results from the analysis /// are correct. /// /// Requirements: /// /// `AnalysisT` contains a type `Lattice`. /// /// `Code`, `TargetFuncMatcher` and `MakeAnalysis` must be provided in `AI`. /// /// `VerifyResults` must be provided. template <typename AnalysisT> llvm::Error checkDataflow(AnalysisInputs<AnalysisT> AI, std::function<void(const AnalysisOutputs &)> VerifyResults) { … } /// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on all /// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the /// annotation line numbers and analysis outputs, `VerifyResults` checks that /// the results from the analysis are correct. /// /// Requirements: /// /// `AnalysisT` contains a type `Lattice`. /// /// `Code`, `TargetFuncMatcher` and `MakeAnalysis` must be provided in `AI`. /// /// `VerifyResults` must be provided. template <typename AnalysisT> llvm::Error checkDataflow(AnalysisInputs<AnalysisT> AI, std::function<void(const llvm::DenseMap<unsigned, std::string> &, const AnalysisOutputs &)> VerifyResults) { … } /// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on all /// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the state /// computed at each annotated statement and analysis outputs, `VerifyResults` /// checks that the results from the analysis are correct. /// /// Requirements: /// /// `AnalysisT` contains a type `Lattice`. /// /// `Code`, `TargetFuncMatcher` and `MakeAnalysis` must be provided in `AI`. /// /// `VerifyResults` must be provided. /// /// Any annotations appearing in `Code` must come after a statement. /// /// There can be at most one annotation attached per statement. /// /// Annotations must not be repeated. template <typename AnalysisT> llvm::Error checkDataflow(AnalysisInputs<AnalysisT> AI, std::function<void(const llvm::StringMap<DataflowAnalysisState< typename AnalysisT::Lattice>> &, const AnalysisOutputs &)> VerifyResults) { … } BuiltinOptions; /// Runs dataflow on function named `TargetFun` in `Code` with a `NoopAnalysis` /// and calls `VerifyResults` to verify the results. llvm::Error checkDataflowWithNoopAnalysis( llvm::StringRef Code, std::function< void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, ASTContext &)> VerifyResults = [](const auto &, auto &) { … }; /// Runs dataflow on function matched by `TargetFuncMatcher` in `Code` with a /// `NoopAnalysis` and calls `VerifyResults` to verify the results. llvm::Error checkDataflowWithNoopAnalysis( llvm::StringRef Code, ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcher, std::function< void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, ASTContext &)> VerifyResults = [](const auto &, auto &) { … }; /// Returns the `ValueDecl` for the given identifier. /// The returned pointer is guaranteed to be non-null; the function asserts if /// no `ValueDecl` with the given name is found. /// /// Requirements: /// /// `Name` must be unique in `ASTCtx`. const ValueDecl *findValueDecl(ASTContext &ASTCtx, llvm::StringRef Name); /// Returns the `IndirectFieldDecl` for the given identifier. /// /// Requirements: /// /// `Name` must be unique in `ASTCtx`. const IndirectFieldDecl *findIndirectFieldDecl(ASTContext &ASTCtx, llvm::StringRef Name); /// Returns the storage location (of type `LocT`) for the given identifier. /// `LocT` must be a subclass of `StorageLocation` and must be of the /// appropriate type. /// /// Requirements: /// /// `Name` must be unique in `ASTCtx`. template <class LocT = StorageLocation> LocT &getLocForDecl(ASTContext &ASTCtx, const Environment &Env, llvm::StringRef Name) { … } /// Returns the value (of type `ValueT`) for the given identifier. /// `ValueT` must be a subclass of `Value` and must be of the appropriate type. /// /// Requirements: /// /// `Name` must be unique in `ASTCtx`. template <class ValueT = Value> ValueT &getValueForDecl(ASTContext &ASTCtx, const Environment &Env, llvm::StringRef Name) { … } /// Returns the storage location for the field called `Name` of `Loc`. /// Optionally casts the field storage location to `T`. template <typename T = StorageLocation> std::enable_if_t<std::is_base_of_v<StorageLocation, T>, T &> getFieldLoc(const RecordStorageLocation &Loc, llvm::StringRef Name, ASTContext &ASTCtx) { … } /// Returns the value of a `Field` on the record referenced by `Loc.` /// Returns null if `Loc` is null. inline Value *getFieldValue(const RecordStorageLocation *Loc, const ValueDecl &Field, const Environment &Env) { … } /// Returns the value of a `Field` on the record referenced by `Loc.` /// Returns null if `Loc` is null. inline Value *getFieldValue(const RecordStorageLocation *Loc, llvm::StringRef Name, ASTContext &ASTCtx, const Environment &Env) { … } /// Creates and owns constraints which are boolean values. class ConstraintContext { … }; /// Parses a list of formulas, separated by newlines, and returns them. /// On parse errors, calls `ADD_FAILURE()` to fail the current test. std::vector<const Formula *> parseFormulas(Arena &A, StringRef Lines); } // namespace test } // namespace dataflow } // namespace clang #endif // LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_