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 |
Definitions
- operator<<
- MaxBlockVisitsInAnalysis
- getEnvironmentAtAnnotation
- AnalysisOutputs
- DiagnosisCallbacksForTesting
- AnalysisInputs
- AnalysisInputs
- withSetupTest
- withDiagnosisCallbacks
- withPostVisitCFG
- withASTBuildArgs
- withASTBuildVirtualMappedFiles
- withBuiltinOptions
- withSolverFactory
- checkDataflow
- checkDataflow
- checkDataflow
- getLocForDecl
- getValueForDecl
- getFieldLoc
- getFieldValue
- getFieldValue
- ConstraintContext
- make
- atom
- literal
- conj
- disj
- neg
- impl
Learn to use CMake with our Intro Training
Find out more