| 1 | //===--- TestingSupport.h - Testing utils for dataflow analyses -*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file defines utilities to simplify testing of dataflow analyses. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #ifndef LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_ |
| 14 | #define LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_ |
| 15 | |
| 16 | #include <functional> |
| 17 | #include <memory> |
| 18 | #include <optional> |
| 19 | #include <ostream> |
| 20 | #include <string> |
| 21 | #include <utility> |
| 22 | #include <vector> |
| 23 | |
| 24 | #include "clang/AST/ASTContext.h" |
| 25 | #include "clang/AST/Decl.h" |
| 26 | #include "clang/AST/Stmt.h" |
| 27 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 28 | #include "clang/ASTMatchers/ASTMatchers.h" |
| 29 | #include "clang/ASTMatchers/ASTMatchersInternal.h" |
| 30 | #include "clang/Analysis/CFG.h" |
| 31 | #include "clang/Analysis/FlowSensitive/AdornedCFG.h" |
| 32 | #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" |
| 33 | #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" |
| 34 | #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" |
| 35 | #include "clang/Analysis/FlowSensitive/MatchSwitch.h" |
| 36 | #include "clang/Analysis/FlowSensitive/NoopLattice.h" |
| 37 | #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" |
| 38 | #include "clang/Basic/LLVM.h" |
| 39 | #include "clang/Serialization/PCHContainerOperations.h" |
| 40 | #include "clang/Tooling/ArgumentsAdjusters.h" |
| 41 | #include "clang/Tooling/Tooling.h" |
| 42 | #include "llvm/ADT/ArrayRef.h" |
| 43 | #include "llvm/ADT/DenseMap.h" |
| 44 | #include "llvm/ADT/StringMap.h" |
| 45 | #include "llvm/ADT/StringRef.h" |
| 46 | #include "llvm/Support/Allocator.h" |
| 47 | #include "llvm/Support/Errc.h" |
| 48 | #include "llvm/Support/Error.h" |
| 49 | #include "llvm/Testing/Annotations/Annotations.h" |
| 50 | |
| 51 | namespace clang { |
| 52 | namespace dataflow { |
| 53 | |
| 54 | // Requires a `<<` operator for the `Lattice` type. |
| 55 | // FIXME: move to a non-test utility library. |
| 56 | template <typename Lattice> |
| 57 | std::ostream &operator<<(std::ostream &OS, |
| 58 | const DataflowAnalysisState<Lattice> &S) { |
| 59 | // FIXME: add printing support for the environment. |
| 60 | return OS << "{lattice=" << S.Lattice << ", environment=...}" ; |
| 61 | } |
| 62 | |
| 63 | namespace test { |
| 64 | |
| 65 | // Caps the number of block visits in any individual analysis. Given that test |
| 66 | // code is typically quite small, we set a low number to help catch any problems |
| 67 | // early. But, the choice is arbitrary. |
| 68 | constexpr std::int32_t MaxBlockVisitsInAnalysis = 2'000; |
| 69 | |
| 70 | /// Returns the environment at the program point marked with `Annotation` from |
| 71 | /// the mapping of annotated program points to analysis state. |
| 72 | /// |
| 73 | /// Requirements: |
| 74 | /// |
| 75 | /// `Annotation` must be present as a key in `AnnotationStates`. |
| 76 | template <typename LatticeT> |
| 77 | const Environment &getEnvironmentAtAnnotation( |
| 78 | const llvm::StringMap<DataflowAnalysisState<LatticeT>> &AnnotationStates, |
| 79 | llvm::StringRef Annotation) { |
| 80 | auto It = AnnotationStates.find(Annotation); |
| 81 | assert(It != AnnotationStates.end()); |
| 82 | return It->getValue().Env; |
| 83 | } |
| 84 | |
| 85 | /// Contains data structures required and produced by a dataflow analysis run. |
| 86 | struct AnalysisOutputs { |
| 87 | /// Input code that is analyzed. Points within the code may be marked with |
| 88 | /// annotations to facilitate testing. |
| 89 | /// |
| 90 | /// Example: |
| 91 | /// void target(int *x) { |
| 92 | /// *x; // [[p]] |
| 93 | /// } |
| 94 | /// From the annotation `p`, the line number and analysis state immediately |
| 95 | /// after the statement `*x` can be retrieved and verified. |
| 96 | llvm::Annotations Code; |
| 97 | /// AST context generated from `Code`. |
| 98 | ASTContext &ASTCtx; |
| 99 | /// The function whose body is analyzed. |
| 100 | const FunctionDecl *Target; |
| 101 | /// Contains the control flow graph built from the body of the `Target` |
| 102 | /// function and is analyzed. |
| 103 | const AdornedCFG &ACFG; |
| 104 | /// The analysis to be run. |
| 105 | TypeErasedDataflowAnalysis &Analysis; |
| 106 | /// Initial state to start the analysis. |
| 107 | const Environment &InitEnv; |
| 108 | // Stores the state of a CFG block if it has been evaluated by the analysis. |
| 109 | // The indices correspond to the block IDs. |
| 110 | llvm::ArrayRef<std::optional<TypeErasedDataflowAnalysisState>> BlockStates; |
| 111 | }; |
| 112 | |
| 113 | /// A callback to be called with the state before or after visiting a CFG |
| 114 | /// element. |
| 115 | /// This differs from `DiagnosisCallback` in that the return type is void. |
| 116 | template <typename AnalysisT> |
| 117 | using DiagnosisCallbackForTesting = std::function<void( |
| 118 | ASTContext &, const CFGElement &, |
| 119 | const TransferStateForDiagnostics<typename AnalysisT::Lattice> &)>; |
| 120 | |
| 121 | /// A pair of callbacks to be called with the state before and after visiting a |
| 122 | /// CFG element. |
| 123 | /// Either or both of the callbacks may be null. |
| 124 | template <typename AnalysisT> struct DiagnosisCallbacksForTesting { |
| 125 | DiagnosisCallbackForTesting<AnalysisT> Before; |
| 126 | DiagnosisCallbackForTesting<AnalysisT> After; |
| 127 | }; |
| 128 | |
| 129 | /// Arguments for building the dataflow analysis. |
| 130 | template <typename AnalysisT> struct AnalysisInputs { |
| 131 | /// Required fields are set in constructor. |
| 132 | AnalysisInputs( |
| 133 | llvm::StringRef CodeArg, |
| 134 | ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcherArg, |
| 135 | std::function<AnalysisT(ASTContext &, Environment &)> MakeAnalysisArg) |
| 136 | : Code(CodeArg), TargetFuncMatcher(std::move(TargetFuncMatcherArg)), |
| 137 | MakeAnalysis(std::move(MakeAnalysisArg)) {} |
| 138 | |
| 139 | /// Optional fields can be set with methods of the form `withFieldName(...)`. |
| 140 | AnalysisInputs<AnalysisT> && |
| 141 | withSetupTest(std::function<llvm::Error(AnalysisOutputs &)> Arg) && { |
| 142 | SetupTest = std::move(Arg); |
| 143 | return std::move(*this); |
| 144 | } |
| 145 | AnalysisInputs<AnalysisT> && |
| 146 | withDiagnosisCallbacks(DiagnosisCallbacksForTesting<AnalysisT> Arg) && { |
| 147 | Callbacks = std::move(Arg); |
| 148 | return std::move(*this); |
| 149 | } |
| 150 | /// Provided for backwards compatibility. New callers should use |
| 151 | /// `withDiagnosisCallbacks()`. |
| 152 | AnalysisInputs<AnalysisT> && |
| 153 | withPostVisitCFG(DiagnosisCallbackForTesting<AnalysisT> Arg) && { |
| 154 | Callbacks.After = std::move(Arg); |
| 155 | return std::move(*this); |
| 156 | } |
| 157 | AnalysisInputs<AnalysisT> &&withASTBuildArgs(ArrayRef<std::string> Arg) && { |
| 158 | ASTBuildArgs = std::move(Arg); |
| 159 | return std::move(*this); |
| 160 | } |
| 161 | AnalysisInputs<AnalysisT> && |
| 162 | withASTBuildVirtualMappedFiles(tooling::FileContentMappings Arg) && { |
| 163 | ASTBuildVirtualMappedFiles = std::move(Arg); |
| 164 | return std::move(*this); |
| 165 | } |
| 166 | AnalysisInputs<AnalysisT> && |
| 167 | withBuiltinOptions(DataflowAnalysisContext::Options Options) && { |
| 168 | BuiltinOptions = std::move(Options); |
| 169 | return std::move(*this); |
| 170 | } |
| 171 | AnalysisInputs<AnalysisT> && |
| 172 | withSolverFactory(std::function<std::unique_ptr<Solver>()> Factory) && { |
| 173 | assert(Factory); |
| 174 | SolverFactory = std::move(Factory); |
| 175 | return std::move(*this); |
| 176 | } |
| 177 | |
| 178 | /// Required. Input code that is analyzed. |
| 179 | llvm::StringRef Code; |
| 180 | /// Required. All functions that match this matcher are analyzed. |
| 181 | ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcher; |
| 182 | /// Required. The analysis to be run is constructed with this function that |
| 183 | /// takes as argument the AST generated from the code being analyzed and the |
| 184 | /// initial state from which the analysis starts with. |
| 185 | std::function<AnalysisT(ASTContext &, Environment &)> MakeAnalysis; |
| 186 | /// Optional. If provided, this function is executed immediately before |
| 187 | /// running the dataflow analysis to allow for additional setup. All fields in |
| 188 | /// the `AnalysisOutputs` argument will be initialized except for the |
| 189 | /// `BlockStates` field which is only computed later during the analysis. |
| 190 | std::function<llvm::Error(AnalysisOutputs &)> SetupTest = nullptr; |
| 191 | /// Callbacks to run on each CFG element after the analysis has been run. |
| 192 | DiagnosisCallbacksForTesting<AnalysisT> Callbacks; |
| 193 | |
| 194 | /// Optional. Options for building the AST context. |
| 195 | ArrayRef<std::string> ASTBuildArgs = {}; |
| 196 | /// Optional. Options for building the AST context. |
| 197 | tooling::FileContentMappings ASTBuildVirtualMappedFiles = {}; |
| 198 | /// Configuration options for the built-in model. |
| 199 | DataflowAnalysisContext::Options BuiltinOptions; |
| 200 | /// SAT solver factory. |
| 201 | std::function<std::unique_ptr<Solver>()> SolverFactory = [] { |
| 202 | return std::make_unique<WatchedLiteralsSolver>(); |
| 203 | }; |
| 204 | }; |
| 205 | |
| 206 | /// Returns assertions based on annotations that are present after statements in |
| 207 | /// `AnnotatedCode`. |
| 208 | llvm::Expected<llvm::DenseMap<const Stmt *, std::string>> |
| 209 | buildStatementToAnnotationMapping(const FunctionDecl *Func, |
| 210 | llvm::Annotations AnnotatedCode); |
| 211 | |
| 212 | /// Returns line numbers and content of the annotations in `AnnotatedCode` |
| 213 | /// within the token range `BoundingRange`. |
| 214 | llvm::DenseMap<unsigned, std::string> buildLineToAnnotationMapping( |
| 215 | const SourceManager &SM, const LangOptions &LangOpts, |
| 216 | SourceRange BoundingRange, llvm::Annotations AnnotatedCode); |
| 217 | |
| 218 | /// Runs dataflow specified from `AI.MakeAnalysis` and `AI.Callbacks` on all |
| 219 | /// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the |
| 220 | /// analysis outputs, `VerifyResults` checks that the results from the analysis |
| 221 | /// are correct. |
| 222 | /// |
| 223 | /// Requirements: |
| 224 | /// |
| 225 | /// `AnalysisT` contains a type `Lattice`. |
| 226 | /// |
| 227 | /// `Code`, `TargetFuncMatcher` and `MakeAnalysis` must be provided in `AI`. |
| 228 | /// |
| 229 | /// `VerifyResults` must be provided. |
| 230 | template <typename AnalysisT> |
| 231 | llvm::Error |
| 232 | checkDataflow(AnalysisInputs<AnalysisT> AI, |
| 233 | std::function<void(const AnalysisOutputs &)> VerifyResults) { |
| 234 | // Build AST context from code. |
| 235 | llvm::Annotations AnnotatedCode(AI.Code); |
| 236 | auto Unit = tooling::buildASTFromCodeWithArgs( |
| 237 | Code: AnnotatedCode.code(), Args: AI.ASTBuildArgs, FileName: "input.cc" , ToolName: "clang-dataflow-test" , |
| 238 | PCHContainerOps: std::make_shared<PCHContainerOperations>(), |
| 239 | Adjuster: tooling::getClangStripDependencyFileAdjuster(), |
| 240 | VirtualMappedFiles: AI.ASTBuildVirtualMappedFiles); |
| 241 | auto &Context = Unit->getASTContext(); |
| 242 | |
| 243 | if (Context.getDiagnostics().getClient()->getNumErrors() != 0) { |
| 244 | return llvm::make_error<llvm::StringError>( |
| 245 | Args: llvm::errc::invalid_argument, Args: "Source file has syntax or type errors, " |
| 246 | "they were printed to the test log" ); |
| 247 | } |
| 248 | |
| 249 | CFGEltCallbacksTypeErased PostAnalysisCallbacks; |
| 250 | if (AI.Callbacks.Before) { |
| 251 | PostAnalysisCallbacks.Before = |
| 252 | [&AI, &Context](const CFGElement &Element, |
| 253 | const TypeErasedDataflowAnalysisState &State) { |
| 254 | AI.Callbacks.Before( |
| 255 | Context, Element, |
| 256 | TransferStateForDiagnostics<typename AnalysisT::Lattice>( |
| 257 | llvm::any_cast<const typename AnalysisT::Lattice &>( |
| 258 | State.Lattice.Value), |
| 259 | State.Env)); |
| 260 | }; |
| 261 | } |
| 262 | if (AI.Callbacks.After) { |
| 263 | PostAnalysisCallbacks.After = |
| 264 | [&AI, &Context](const CFGElement &Element, |
| 265 | const TypeErasedDataflowAnalysisState &State) { |
| 266 | AI.Callbacks.After( |
| 267 | Context, Element, |
| 268 | TransferStateForDiagnostics<typename AnalysisT::Lattice>( |
| 269 | llvm::any_cast<const typename AnalysisT::Lattice &>( |
| 270 | State.Lattice.Value), |
| 271 | State.Env)); |
| 272 | }; |
| 273 | } |
| 274 | |
| 275 | SmallVector<ast_matchers::BoundNodes, 1> MatchResult = ast_matchers::match( |
| 276 | ast_matchers::functionDecl(ast_matchers::hasBody(InnerMatcher: ast_matchers::stmt()), |
| 277 | AI.TargetFuncMatcher) |
| 278 | .bind("target" ), |
| 279 | Context); |
| 280 | if (MatchResult.empty()) |
| 281 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
| 282 | S: "didn't find any matching target functions" ); |
| 283 | for (const ast_matchers::BoundNodes &BN : MatchResult) { |
| 284 | // Get the AST node of the target function. |
| 285 | const FunctionDecl *Target = BN.getNodeAs<FunctionDecl>(ID: "target" ); |
| 286 | if (Target == nullptr) |
| 287 | return llvm::make_error<llvm::StringError>( |
| 288 | Args: llvm::errc::invalid_argument, Args: "Could not find the target function." ); |
| 289 | |
| 290 | // Build the control flow graph for the target function. |
| 291 | auto MaybeACFG = AdornedCFG::build(Func: *Target); |
| 292 | if (!MaybeACFG) |
| 293 | return MaybeACFG.takeError(); |
| 294 | auto &ACFG = *MaybeACFG; |
| 295 | |
| 296 | // Initialize states for running dataflow analysis. |
| 297 | DataflowAnalysisContext DACtx(AI.SolverFactory(), |
| 298 | {/*Opts=*/AI.BuiltinOptions}); |
| 299 | Environment InitEnv(DACtx, *Target); |
| 300 | auto Analysis = AI.MakeAnalysis(Context, InitEnv); |
| 301 | |
| 302 | AnalysisOutputs AO{AnnotatedCode, Context, Target, ACFG, |
| 303 | Analysis, InitEnv, {}}; |
| 304 | |
| 305 | // Additional test setup. |
| 306 | if (AI.SetupTest) { |
| 307 | if (auto Error = AI.SetupTest(AO)) return Error; |
| 308 | } |
| 309 | |
| 310 | // If successful, the dataflow analysis returns a mapping from block IDs to |
| 311 | // the post-analysis states for the CFG blocks that have been evaluated. |
| 312 | llvm::Expected<std::vector<std::optional<TypeErasedDataflowAnalysisState>>> |
| 313 | MaybeBlockStates = |
| 314 | runTypeErasedDataflowAnalysis(ACFG, Analysis, InitEnv, |
| 315 | PostAnalysisCallbacks, |
| 316 | MaxBlockVisitsInAnalysis); |
| 317 | if (!MaybeBlockStates) return MaybeBlockStates.takeError(); |
| 318 | AO.BlockStates = *MaybeBlockStates; |
| 319 | |
| 320 | // Verify dataflow analysis outputs. |
| 321 | VerifyResults(AO); |
| 322 | } |
| 323 | |
| 324 | return llvm::Error::success(); |
| 325 | } |
| 326 | |
| 327 | /// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on all |
| 328 | /// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the |
| 329 | /// annotation line numbers and analysis outputs, `VerifyResults` checks that |
| 330 | /// the results from the analysis are correct. |
| 331 | /// |
| 332 | /// Requirements: |
| 333 | /// |
| 334 | /// `AnalysisT` contains a type `Lattice`. |
| 335 | /// |
| 336 | /// `Code`, `TargetFuncMatcher` and `MakeAnalysis` must be provided in `AI`. |
| 337 | /// |
| 338 | /// `VerifyResults` must be provided. |
| 339 | template <typename AnalysisT> |
| 340 | llvm::Error |
| 341 | checkDataflow(AnalysisInputs<AnalysisT> AI, |
| 342 | std::function<void(const llvm::DenseMap<unsigned, std::string> &, |
| 343 | const AnalysisOutputs &)> |
| 344 | VerifyResults) { |
| 345 | return checkDataflow<AnalysisT>( |
| 346 | std::move(AI), [&VerifyResults](const AnalysisOutputs &AO) { |
| 347 | auto AnnotationLinesAndContent = buildLineToAnnotationMapping( |
| 348 | SM: AO.ASTCtx.getSourceManager(), LangOpts: AO.ASTCtx.getLangOpts(), |
| 349 | BoundingRange: AO.Target->getSourceRange(), AnnotatedCode: AO.Code); |
| 350 | VerifyResults(AnnotationLinesAndContent, AO); |
| 351 | }); |
| 352 | } |
| 353 | |
| 354 | /// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on all |
| 355 | /// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the state |
| 356 | /// computed at each annotated statement and analysis outputs, `VerifyResults` |
| 357 | /// checks that the results from the analysis are correct. |
| 358 | /// |
| 359 | /// Requirements: |
| 360 | /// |
| 361 | /// `AnalysisT` contains a type `Lattice`. |
| 362 | /// |
| 363 | /// `Code`, `TargetFuncMatcher` and `MakeAnalysis` must be provided in `AI`. |
| 364 | /// |
| 365 | /// `VerifyResults` must be provided. |
| 366 | /// |
| 367 | /// Any annotations appearing in `Code` must come after a statement. |
| 368 | /// |
| 369 | /// There can be at most one annotation attached per statement. |
| 370 | /// |
| 371 | /// Annotations must not be repeated. |
| 372 | template <typename AnalysisT> |
| 373 | llvm::Error |
| 374 | checkDataflow(AnalysisInputs<AnalysisT> AI, |
| 375 | std::function<void(const llvm::StringMap<DataflowAnalysisState< |
| 376 | typename AnalysisT::Lattice>> &, |
| 377 | const AnalysisOutputs &)> |
| 378 | VerifyResults) { |
| 379 | // Compute mapping from nodes of annotated statements to the content in the |
| 380 | // annotation. |
| 381 | llvm::DenseMap<const Stmt *, std::string> StmtToAnnotations; |
| 382 | auto SetupTest = [&StmtToAnnotations, |
| 383 | PrevSetupTest = std::move(AI.SetupTest)]( |
| 384 | AnalysisOutputs &AO) -> llvm::Error { |
| 385 | auto MaybeStmtToAnnotations = |
| 386 | buildStatementToAnnotationMapping(Func: AO.InitEnv.getCurrentFunc(), AnnotatedCode: AO.Code); |
| 387 | if (!MaybeStmtToAnnotations) { |
| 388 | return MaybeStmtToAnnotations.takeError(); |
| 389 | } |
| 390 | StmtToAnnotations = std::move(*MaybeStmtToAnnotations); |
| 391 | return PrevSetupTest ? PrevSetupTest(AO) : llvm::Error::success(); |
| 392 | }; |
| 393 | |
| 394 | using StateT = DataflowAnalysisState<typename AnalysisT::Lattice>; |
| 395 | |
| 396 | // Save the states computed for program points immediately following annotated |
| 397 | // statements. The saved states are keyed by the content of the annotation. |
| 398 | llvm::StringMap<StateT> AnnotationStates; |
| 399 | DiagnosisCallbacksForTesting<AnalysisT> Callbacks; |
| 400 | Callbacks.Before = std::move(AI.Callbacks.Before); |
| 401 | Callbacks.After = |
| 402 | [&StmtToAnnotations, &AnnotationStates, |
| 403 | PrevCallbackAfter = std::move(AI.Callbacks.After)]( |
| 404 | ASTContext &Ctx, const CFGElement &Elt, |
| 405 | const TransferStateForDiagnostics<typename AnalysisT::Lattice> |
| 406 | &State) { |
| 407 | if (PrevCallbackAfter) { |
| 408 | PrevCallbackAfter(Ctx, Elt, State); |
| 409 | } |
| 410 | // FIXME: Extend retrieval of state for non statement constructs. |
| 411 | auto Stmt = Elt.getAs<CFGStmt>(); |
| 412 | if (!Stmt) |
| 413 | return; |
| 414 | auto It = StmtToAnnotations.find(Val: Stmt->getStmt()); |
| 415 | if (It == StmtToAnnotations.end()) |
| 416 | return; |
| 417 | auto [_, InsertSuccess] = AnnotationStates.insert( |
| 418 | {It->second, StateT{State.Lattice, State.Env.fork()}}); |
| 419 | (void)_; |
| 420 | (void)InsertSuccess; |
| 421 | assert(InsertSuccess); |
| 422 | }; |
| 423 | return checkDataflow<AnalysisT>( |
| 424 | std::move(AI) |
| 425 | .withSetupTest(std::move(SetupTest)) |
| 426 | .withDiagnosisCallbacks(std::move(Callbacks)), |
| 427 | [&VerifyResults, &AnnotationStates](const AnalysisOutputs &AO) { |
| 428 | VerifyResults(AnnotationStates, AO); |
| 429 | |
| 430 | // `checkDataflow()` can analyze more than one function. Reset the |
| 431 | // variables to prepare for analyzing the next function. |
| 432 | AnnotationStates.clear(); |
| 433 | }); |
| 434 | } |
| 435 | |
| 436 | using BuiltinOptions = DataflowAnalysisContext::Options; |
| 437 | |
| 438 | /// Runs dataflow on function named `TargetFun` in `Code` with a `NoopAnalysis` |
| 439 | /// and calls `VerifyResults` to verify the results. |
| 440 | llvm::Error checkDataflowWithNoopAnalysis( |
| 441 | llvm::StringRef Code, |
| 442 | std::function< |
| 443 | void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| 444 | ASTContext &)> |
| 445 | VerifyResults = [](const auto &, auto &) {}, |
| 446 | DataflowAnalysisOptions Options = {.BuiltinOpts: BuiltinOptions()}, |
| 447 | LangStandard::Kind Std = LangStandard::lang_cxx17, |
| 448 | llvm::StringRef TargetFun = "target" ); |
| 449 | |
| 450 | /// Runs dataflow on function matched by `TargetFuncMatcher` in `Code` with a |
| 451 | /// `NoopAnalysis` and calls `VerifyResults` to verify the results. |
| 452 | llvm::Error checkDataflowWithNoopAnalysis( |
| 453 | llvm::StringRef Code, |
| 454 | ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcher, |
| 455 | std::function< |
| 456 | void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| 457 | ASTContext &)> |
| 458 | VerifyResults = [](const auto &, auto &) {}, |
| 459 | DataflowAnalysisOptions Options = {.BuiltinOpts: BuiltinOptions()}, |
| 460 | LangStandard::Kind Std = LangStandard::lang_cxx17, |
| 461 | std::function<llvm::StringMap<QualType>(QualType)> SyntheticFieldCallback = |
| 462 | {}); |
| 463 | |
| 464 | /// Returns the `ValueDecl` for the given identifier. |
| 465 | /// The returned pointer is guaranteed to be non-null; the function asserts if |
| 466 | /// no `ValueDecl` with the given name is found. |
| 467 | /// |
| 468 | /// Requirements: |
| 469 | /// |
| 470 | /// `Name` must be unique in `ASTCtx`. |
| 471 | const ValueDecl *findValueDecl(ASTContext &ASTCtx, llvm::StringRef Name); |
| 472 | |
| 473 | /// Returns the `IndirectFieldDecl` for the given identifier. |
| 474 | /// |
| 475 | /// Requirements: |
| 476 | /// |
| 477 | /// `Name` must be unique in `ASTCtx`. |
| 478 | const IndirectFieldDecl *findIndirectFieldDecl(ASTContext &ASTCtx, |
| 479 | llvm::StringRef Name); |
| 480 | |
| 481 | /// Returns the storage location (of type `LocT`) for the given identifier. |
| 482 | /// `LocT` must be a subclass of `StorageLocation` and must be of the |
| 483 | /// appropriate type. |
| 484 | /// |
| 485 | /// Requirements: |
| 486 | /// |
| 487 | /// `Name` must be unique in `ASTCtx`. |
| 488 | template <class LocT = StorageLocation> |
| 489 | LocT &getLocForDecl(ASTContext &ASTCtx, const Environment &Env, |
| 490 | llvm::StringRef Name) { |
| 491 | const ValueDecl *VD = findValueDecl(ASTCtx, Name); |
| 492 | assert(VD != nullptr); |
| 493 | return *cast<LocT>(Env.getStorageLocation(D: *VD)); |
| 494 | } |
| 495 | |
| 496 | /// Returns the value (of type `ValueT`) for the given identifier. |
| 497 | /// `ValueT` must be a subclass of `Value` and must be of the appropriate type. |
| 498 | /// |
| 499 | /// Requirements: |
| 500 | /// |
| 501 | /// `Name` must be unique in `ASTCtx`. |
| 502 | template <class ValueT = Value> |
| 503 | ValueT &getValueForDecl(ASTContext &ASTCtx, const Environment &Env, |
| 504 | llvm::StringRef Name) { |
| 505 | const ValueDecl *VD = findValueDecl(ASTCtx, Name); |
| 506 | assert(VD != nullptr); |
| 507 | return *cast<ValueT>(Env.getValue(D: *VD)); |
| 508 | } |
| 509 | |
| 510 | /// Returns the storage location for the field called `Name` of `Loc`. |
| 511 | /// Optionally casts the field storage location to `T`. |
| 512 | template <typename T = StorageLocation> |
| 513 | std::enable_if_t<std::is_base_of_v<StorageLocation, T>, T &> |
| 514 | getFieldLoc(const RecordStorageLocation &Loc, llvm::StringRef Name, |
| 515 | ASTContext &ASTCtx) { |
| 516 | return *cast<T>(Loc.getChild(D: *findValueDecl(ASTCtx, Name))); |
| 517 | } |
| 518 | |
| 519 | /// Returns the value of a `Field` on the record referenced by `Loc.` |
| 520 | /// Returns null if `Loc` is null. |
| 521 | inline Value *getFieldValue(const RecordStorageLocation *Loc, |
| 522 | const ValueDecl &Field, const Environment &Env) { |
| 523 | if (Loc == nullptr) |
| 524 | return nullptr; |
| 525 | StorageLocation *FieldLoc = Loc->getChild(D: Field); |
| 526 | if (FieldLoc == nullptr) |
| 527 | return nullptr; |
| 528 | return Env.getValue(Loc: *FieldLoc); |
| 529 | } |
| 530 | |
| 531 | /// Returns the value of a `Field` on the record referenced by `Loc.` |
| 532 | /// Returns null if `Loc` is null. |
| 533 | inline Value *getFieldValue(const RecordStorageLocation *Loc, |
| 534 | llvm::StringRef Name, ASTContext &ASTCtx, |
| 535 | const Environment &Env) { |
| 536 | return getFieldValue(Loc, Field: *findValueDecl(ASTCtx, Name), Env); |
| 537 | } |
| 538 | |
| 539 | /// Creates and owns constraints which are boolean values. |
| 540 | class ConstraintContext { |
| 541 | unsigned NextAtom = 0; |
| 542 | llvm::BumpPtrAllocator A; |
| 543 | |
| 544 | const Formula *make(Formula::Kind K, |
| 545 | llvm::ArrayRef<const Formula *> Operands) { |
| 546 | return &Formula::create(Alloc&: A, K, Operands); |
| 547 | } |
| 548 | |
| 549 | public: |
| 550 | // Returns a reference to a fresh atomic variable. |
| 551 | const Formula *atom() { |
| 552 | return &Formula::create(Alloc&: A, K: Formula::AtomRef, Operands: {}, Value: NextAtom++); |
| 553 | } |
| 554 | |
| 555 | // Returns a reference to a literal boolean value. |
| 556 | const Formula *literal(bool B) { |
| 557 | return &Formula::create(Alloc&: A, K: Formula::Literal, Operands: {}, Value: B); |
| 558 | } |
| 559 | |
| 560 | // Creates a boolean conjunction. |
| 561 | const Formula *conj(const Formula *LHS, const Formula *RHS) { |
| 562 | return make(K: Formula::And, Operands: {LHS, RHS}); |
| 563 | } |
| 564 | |
| 565 | // Creates a boolean disjunction. |
| 566 | const Formula *disj(const Formula *LHS, const Formula *RHS) { |
| 567 | return make(K: Formula::Or, Operands: {LHS, RHS}); |
| 568 | } |
| 569 | |
| 570 | // Creates a boolean negation. |
| 571 | const Formula *neg(const Formula *Operand) { |
| 572 | return make(K: Formula::Not, Operands: {Operand}); |
| 573 | } |
| 574 | |
| 575 | // Creates a boolean implication. |
| 576 | const Formula *impl(const Formula *LHS, const Formula *RHS) { |
| 577 | return make(K: Formula::Implies, Operands: {LHS, RHS}); |
| 578 | } |
| 579 | |
| 580 | // Creates a boolean biconditional. |
| 581 | const Formula *iff(const Formula *LHS, const Formula *RHS) { |
| 582 | return make(K: Formula::Equal, Operands: {LHS, RHS}); |
| 583 | } |
| 584 | }; |
| 585 | |
| 586 | /// Parses a list of formulas, separated by newlines, and returns them. |
| 587 | /// On parse errors, calls `ADD_FAILURE()` to fail the current test. |
| 588 | std::vector<const Formula *> parseFormulas(Arena &A, StringRef Lines); |
| 589 | |
| 590 | } // namespace test |
| 591 | } // namespace dataflow |
| 592 | } // namespace clang |
| 593 | |
| 594 | #endif // LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_ |
| 595 | |