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
51namespace clang {
52namespace dataflow {
53
54// Requires a `<<` operator for the `Lattice` type.
55// FIXME: move to a non-test utility library.
56template <typename Lattice>
57std::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
63namespace 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.
68constexpr 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`.
76template <typename LatticeT>
77const 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.
86struct 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/// Arguments for building the dataflow analysis.
114template <typename AnalysisT> struct AnalysisInputs {
115 /// Required fields are set in constructor.
116 AnalysisInputs(
117 llvm::StringRef CodeArg,
118 ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcherArg,
119 std::function<AnalysisT(ASTContext &, Environment &)> MakeAnalysisArg)
120 : Code(CodeArg), TargetFuncMatcher(std::move(TargetFuncMatcherArg)),
121 MakeAnalysis(std::move(MakeAnalysisArg)) {}
122
123 /// Optional fields can be set with methods of the form `withFieldName(...)`.
124 AnalysisInputs<AnalysisT> &&
125 withSetupTest(std::function<llvm::Error(AnalysisOutputs &)> Arg) && {
126 SetupTest = std::move(Arg);
127 return std::move(*this);
128 }
129 AnalysisInputs<AnalysisT> &&withPostVisitCFG(
130 std::function<void(
131 ASTContext &, const CFGElement &,
132 const TransferStateForDiagnostics<typename AnalysisT::Lattice> &)>
133 Arg) && {
134 PostVisitCFG = std::move(Arg);
135 return std::move(*this);
136 }
137 AnalysisInputs<AnalysisT> &&withASTBuildArgs(ArrayRef<std::string> Arg) && {
138 ASTBuildArgs = std::move(Arg);
139 return std::move(*this);
140 }
141 AnalysisInputs<AnalysisT> &&
142 withASTBuildVirtualMappedFiles(tooling::FileContentMappings Arg) && {
143 ASTBuildVirtualMappedFiles = std::move(Arg);
144 return std::move(*this);
145 }
146 AnalysisInputs<AnalysisT> &&
147 withBuiltinOptions(DataflowAnalysisContext::Options Options) && {
148 BuiltinOptions = std::move(Options);
149 return std::move(*this);
150 }
151 AnalysisInputs<AnalysisT> &&
152 withSolverFactory(std::function<std::unique_ptr<Solver>()> Factory) && {
153 assert(Factory);
154 SolverFactory = std::move(Factory);
155 return std::move(*this);
156 }
157
158 /// Required. Input code that is analyzed.
159 llvm::StringRef Code;
160 /// Required. All functions that match this matcher are analyzed.
161 ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcher;
162 /// Required. The analysis to be run is constructed with this function that
163 /// takes as argument the AST generated from the code being analyzed and the
164 /// initial state from which the analysis starts with.
165 std::function<AnalysisT(ASTContext &, Environment &)> MakeAnalysis;
166 /// Optional. If provided, this function is executed immediately before
167 /// running the dataflow analysis to allow for additional setup. All fields in
168 /// the `AnalysisOutputs` argument will be initialized except for the
169 /// `BlockStates` field which is only computed later during the analysis.
170 std::function<llvm::Error(AnalysisOutputs &)> SetupTest = nullptr;
171 /// Optional. If provided, this function is applied on each CFG element after
172 /// the analysis has been run.
173 std::function<void(
174 ASTContext &, const CFGElement &,
175 const TransferStateForDiagnostics<typename AnalysisT::Lattice> &)>
176 PostVisitCFG = nullptr;
177
178 /// Optional. Options for building the AST context.
179 ArrayRef<std::string> ASTBuildArgs = {};
180 /// Optional. Options for building the AST context.
181 tooling::FileContentMappings ASTBuildVirtualMappedFiles = {};
182 /// Configuration options for the built-in model.
183 DataflowAnalysisContext::Options BuiltinOptions;
184 /// SAT solver factory.
185 std::function<std::unique_ptr<Solver>()> SolverFactory = [] {
186 return std::make_unique<WatchedLiteralsSolver>();
187 };
188};
189
190/// Returns assertions based on annotations that are present after statements in
191/// `AnnotatedCode`.
192llvm::Expected<llvm::DenseMap<const Stmt *, std::string>>
193buildStatementToAnnotationMapping(const FunctionDecl *Func,
194 llvm::Annotations AnnotatedCode);
195
196/// Returns line numbers and content of the annotations in `AnnotatedCode`
197/// within the token range `BoundingRange`.
198llvm::DenseMap<unsigned, std::string> buildLineToAnnotationMapping(
199 const SourceManager &SM, const LangOptions &LangOpts,
200 SourceRange BoundingRange, llvm::Annotations AnnotatedCode);
201
202/// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on all
203/// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the
204/// analysis outputs, `VerifyResults` checks that the results from the analysis
205/// are correct.
206///
207/// Requirements:
208///
209/// `AnalysisT` contains a type `Lattice`.
210///
211/// `Code`, `TargetFuncMatcher` and `MakeAnalysis` must be provided in `AI`.
212///
213/// `VerifyResults` must be provided.
214template <typename AnalysisT>
215llvm::Error
216checkDataflow(AnalysisInputs<AnalysisT> AI,
217 std::function<void(const AnalysisOutputs &)> VerifyResults) {
218 // Build AST context from code.
219 llvm::Annotations AnnotatedCode(AI.Code);
220 auto Unit = tooling::buildASTFromCodeWithArgs(
221 Code: AnnotatedCode.code(), Args: AI.ASTBuildArgs, FileName: "input.cc", ToolName: "clang-dataflow-test",
222 PCHContainerOps: std::make_shared<PCHContainerOperations>(),
223 Adjuster: tooling::getClangStripDependencyFileAdjuster(),
224 VirtualMappedFiles: AI.ASTBuildVirtualMappedFiles);
225 auto &Context = Unit->getASTContext();
226
227 if (Context.getDiagnostics().getClient()->getNumErrors() != 0) {
228 return llvm::make_error<llvm::StringError>(
229 Args: llvm::errc::invalid_argument, Args: "Source file has syntax or type errors, "
230 "they were printed to the test log");
231 }
232
233 std::function<void(const CFGElement &,
234 const TypeErasedDataflowAnalysisState &)>
235 TypeErasedPostVisitCFG = nullptr;
236 if (AI.PostVisitCFG) {
237 TypeErasedPostVisitCFG = [&AI, &Context](
238 const CFGElement &Element,
239 const TypeErasedDataflowAnalysisState &State) {
240 AI.PostVisitCFG(Context, Element,
241 TransferStateForDiagnostics<typename AnalysisT::Lattice>(
242 llvm::any_cast<const typename AnalysisT::Lattice &>(
243 State.Lattice.Value),
244 State.Env));
245 };
246 }
247
248 SmallVector<ast_matchers::BoundNodes, 1> MatchResult = ast_matchers::match(
249 ast_matchers::functionDecl(ast_matchers::hasBody(InnerMatcher: ast_matchers::stmt()),
250 AI.TargetFuncMatcher)
251 .bind("target"),
252 Context);
253 if (MatchResult.empty())
254 return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),
255 Msg: "didn't find any matching target functions");
256 for (const ast_matchers::BoundNodes &BN : MatchResult) {
257 // Get the AST node of the target function.
258 const FunctionDecl *Target = BN.getNodeAs<FunctionDecl>(ID: "target");
259 if (Target == nullptr)
260 return llvm::make_error<llvm::StringError>(
261 Args: llvm::errc::invalid_argument, Args: "Could not find the target function.");
262
263 // Build the control flow graph for the target function.
264 auto MaybeACFG = AdornedCFG::build(Func: *Target);
265 if (!MaybeACFG)
266 return MaybeACFG.takeError();
267 auto &ACFG = *MaybeACFG;
268
269 // Initialize states for running dataflow analysis.
270 DataflowAnalysisContext DACtx(AI.SolverFactory(),
271 {/*Opts=*/AI.BuiltinOptions});
272 Environment InitEnv(DACtx, *Target);
273 auto Analysis = AI.MakeAnalysis(Context, InitEnv);
274
275 AnalysisOutputs AO{AnnotatedCode, Context, Target, ACFG,
276 Analysis, InitEnv, {}};
277
278 // Additional test setup.
279 if (AI.SetupTest) {
280 if (auto Error = AI.SetupTest(AO)) return Error;
281 }
282
283 // If successful, the dataflow analysis returns a mapping from block IDs to
284 // the post-analysis states for the CFG blocks that have been evaluated.
285 llvm::Expected<std::vector<std::optional<TypeErasedDataflowAnalysisState>>>
286 MaybeBlockStates =
287 runTypeErasedDataflowAnalysis(ACFG, Analysis, InitEnv,
288 TypeErasedPostVisitCFG,
289 MaxBlockVisitsInAnalysis);
290 if (!MaybeBlockStates) return MaybeBlockStates.takeError();
291 AO.BlockStates = *MaybeBlockStates;
292
293 // Verify dataflow analysis outputs.
294 VerifyResults(AO);
295 }
296
297 return llvm::Error::success();
298}
299
300/// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on all
301/// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the
302/// annotation line numbers and analysis outputs, `VerifyResults` checks that
303/// the results from the analysis are correct.
304///
305/// Requirements:
306///
307/// `AnalysisT` contains a type `Lattice`.
308///
309/// `Code`, `TargetFuncMatcher` and `MakeAnalysis` must be provided in `AI`.
310///
311/// `VerifyResults` must be provided.
312template <typename AnalysisT>
313llvm::Error
314checkDataflow(AnalysisInputs<AnalysisT> AI,
315 std::function<void(const llvm::DenseMap<unsigned, std::string> &,
316 const AnalysisOutputs &)>
317 VerifyResults) {
318 return checkDataflow<AnalysisT>(
319 std::move(AI), [&VerifyResults](const AnalysisOutputs &AO) {
320 auto AnnotationLinesAndContent = buildLineToAnnotationMapping(
321 SM: AO.ASTCtx.getSourceManager(), LangOpts: AO.ASTCtx.getLangOpts(),
322 BoundingRange: AO.Target->getSourceRange(), AnnotatedCode: AO.Code);
323 VerifyResults(AnnotationLinesAndContent, AO);
324 });
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 state
329/// computed at each annotated statement and analysis outputs, `VerifyResults`
330/// checks that 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///
340/// Any annotations appearing in `Code` must come after a statement.
341///
342/// There can be at most one annotation attached per statement.
343///
344/// Annotations must not be repeated.
345template <typename AnalysisT>
346llvm::Error
347checkDataflow(AnalysisInputs<AnalysisT> AI,
348 std::function<void(const llvm::StringMap<DataflowAnalysisState<
349 typename AnalysisT::Lattice>> &,
350 const AnalysisOutputs &)>
351 VerifyResults) {
352 // Compute mapping from nodes of annotated statements to the content in the
353 // annotation.
354 llvm::DenseMap<const Stmt *, std::string> StmtToAnnotations;
355 auto SetupTest = [&StmtToAnnotations,
356 PrevSetupTest = std::move(AI.SetupTest)](
357 AnalysisOutputs &AO) -> llvm::Error {
358 auto MaybeStmtToAnnotations = buildStatementToAnnotationMapping(
359 Func: cast<FunctionDecl>(Val: AO.InitEnv.getDeclCtx()), AnnotatedCode: AO.Code);
360 if (!MaybeStmtToAnnotations) {
361 return MaybeStmtToAnnotations.takeError();
362 }
363 StmtToAnnotations = std::move(*MaybeStmtToAnnotations);
364 return PrevSetupTest ? PrevSetupTest(AO) : llvm::Error::success();
365 };
366
367 using StateT = DataflowAnalysisState<typename AnalysisT::Lattice>;
368
369 // Save the states computed for program points immediately following annotated
370 // statements. The saved states are keyed by the content of the annotation.
371 llvm::StringMap<StateT> AnnotationStates;
372 auto PostVisitCFG =
373 [&StmtToAnnotations, &AnnotationStates,
374 PrevPostVisitCFG = std::move(AI.PostVisitCFG)](
375 ASTContext &Ctx, const CFGElement &Elt,
376 const TransferStateForDiagnostics<typename AnalysisT::Lattice>
377 &State) {
378 if (PrevPostVisitCFG) {
379 PrevPostVisitCFG(Ctx, Elt, State);
380 }
381 // FIXME: Extend retrieval of state for non statement constructs.
382 auto Stmt = Elt.getAs<CFGStmt>();
383 if (!Stmt)
384 return;
385 auto It = StmtToAnnotations.find(Val: Stmt->getStmt());
386 if (It == StmtToAnnotations.end())
387 return;
388 auto [_, InsertSuccess] = AnnotationStates.insert(
389 {It->second, StateT{State.Lattice, State.Env.fork()}});
390 (void)_;
391 (void)InsertSuccess;
392 assert(InsertSuccess);
393 };
394 return checkDataflow<AnalysisT>(
395 std::move(AI)
396 .withSetupTest(std::move(SetupTest))
397 .withPostVisitCFG(std::move(PostVisitCFG)),
398 [&VerifyResults, &AnnotationStates](const AnalysisOutputs &AO) {
399 VerifyResults(AnnotationStates, AO);
400
401 // `checkDataflow()` can analyze more than one function. Reset the
402 // variables to prepare for analyzing the next function.
403 AnnotationStates.clear();
404 });
405}
406
407using BuiltinOptions = DataflowAnalysisContext::Options;
408
409/// Runs dataflow on function named `TargetFun` in `Code` with a `NoopAnalysis`
410/// and calls `VerifyResults` to verify the results.
411llvm::Error checkDataflowWithNoopAnalysis(
412 llvm::StringRef Code,
413 std::function<
414 void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
415 ASTContext &)>
416 VerifyResults = [](const auto &, auto &) {},
417 DataflowAnalysisOptions Options = {.BuiltinOpts: BuiltinOptions()},
418 LangStandard::Kind Std = LangStandard::lang_cxx17,
419 llvm::StringRef TargetFun = "target");
420
421/// Runs dataflow on function matched by `TargetFuncMatcher` in `Code` with a
422/// `NoopAnalysis` and calls `VerifyResults` to verify the results.
423llvm::Error checkDataflowWithNoopAnalysis(
424 llvm::StringRef Code,
425 ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcher,
426 std::function<
427 void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
428 ASTContext &)>
429 VerifyResults = [](const auto &, auto &) {},
430 DataflowAnalysisOptions Options = {.BuiltinOpts: BuiltinOptions()},
431 LangStandard::Kind Std = LangStandard::lang_cxx17,
432 std::function<llvm::StringMap<QualType>(QualType)> SyntheticFieldCallback =
433 {});
434
435/// Returns the `ValueDecl` for the given identifier.
436/// The returned pointer is guaranteed to be non-null; the function asserts if
437/// no `ValueDecl` with the given name is found.
438///
439/// Requirements:
440///
441/// `Name` must be unique in `ASTCtx`.
442const ValueDecl *findValueDecl(ASTContext &ASTCtx, llvm::StringRef Name);
443
444/// Returns the `IndirectFieldDecl` for the given identifier.
445///
446/// Requirements:
447///
448/// `Name` must be unique in `ASTCtx`.
449const IndirectFieldDecl *findIndirectFieldDecl(ASTContext &ASTCtx,
450 llvm::StringRef Name);
451
452/// Returns the storage location (of type `LocT`) for the given identifier.
453/// `LocT` must be a subclass of `StorageLocation` and must be of the
454/// appropriate type.
455///
456/// Requirements:
457///
458/// `Name` must be unique in `ASTCtx`.
459template <class LocT = StorageLocation>
460LocT &getLocForDecl(ASTContext &ASTCtx, const Environment &Env,
461 llvm::StringRef Name) {
462 const ValueDecl *VD = findValueDecl(ASTCtx, Name);
463 assert(VD != nullptr);
464 return *cast<LocT>(Env.getStorageLocation(D: *VD));
465}
466
467/// Returns the value (of type `ValueT`) for the given identifier.
468/// `ValueT` must be a subclass of `Value` and must be of the appropriate type.
469///
470/// Requirements:
471///
472/// `Name` must be unique in `ASTCtx`.
473template <class ValueT = Value>
474ValueT &getValueForDecl(ASTContext &ASTCtx, const Environment &Env,
475 llvm::StringRef Name) {
476 const ValueDecl *VD = findValueDecl(ASTCtx, Name);
477 assert(VD != nullptr);
478 return *cast<ValueT>(Env.getValue(D: *VD));
479}
480
481/// Returns the storage location for the field called `Name` of `Loc`.
482/// Optionally casts the field storage location to `T`.
483template <typename T = StorageLocation>
484std::enable_if_t<std::is_base_of_v<StorageLocation, T>, T &>
485getFieldLoc(const RecordStorageLocation &Loc, llvm::StringRef Name,
486 ASTContext &ASTCtx) {
487 return *cast<T>(Loc.getChild(D: *findValueDecl(ASTCtx, Name)));
488}
489
490/// Returns the value of a `Field` on the record referenced by `Loc.`
491/// Returns null if `Loc` is null.
492inline Value *getFieldValue(const RecordStorageLocation *Loc,
493 const ValueDecl &Field, const Environment &Env) {
494 if (Loc == nullptr)
495 return nullptr;
496 StorageLocation *FieldLoc = Loc->getChild(D: Field);
497 if (FieldLoc == nullptr)
498 return nullptr;
499 return Env.getValue(Loc: *FieldLoc);
500}
501
502/// Returns the value of a `Field` on the record referenced by `Loc.`
503/// Returns null if `Loc` is null.
504inline Value *getFieldValue(const RecordStorageLocation *Loc,
505 llvm::StringRef Name, ASTContext &ASTCtx,
506 const Environment &Env) {
507 return getFieldValue(Loc, Field: *findValueDecl(ASTCtx, Name), Env);
508}
509
510/// Creates and owns constraints which are boolean values.
511class ConstraintContext {
512 unsigned NextAtom = 0;
513 llvm::BumpPtrAllocator A;
514
515 const Formula *make(Formula::Kind K,
516 llvm::ArrayRef<const Formula *> Operands) {
517 return &Formula::create(Alloc&: A, K, Operands);
518 }
519
520public:
521 // Returns a reference to a fresh atomic variable.
522 const Formula *atom() {
523 return &Formula::create(Alloc&: A, K: Formula::AtomRef, Operands: {}, Value: NextAtom++);
524 }
525
526 // Returns a reference to a literal boolean value.
527 const Formula *literal(bool B) {
528 return &Formula::create(Alloc&: A, K: Formula::Literal, Operands: {}, Value: B);
529 }
530
531 // Creates a boolean conjunction.
532 const Formula *conj(const Formula *LHS, const Formula *RHS) {
533 return make(K: Formula::And, Operands: {LHS, RHS});
534 }
535
536 // Creates a boolean disjunction.
537 const Formula *disj(const Formula *LHS, const Formula *RHS) {
538 return make(K: Formula::Or, Operands: {LHS, RHS});
539 }
540
541 // Creates a boolean negation.
542 const Formula *neg(const Formula *Operand) {
543 return make(K: Formula::Not, Operands: {Operand});
544 }
545
546 // Creates a boolean implication.
547 const Formula *impl(const Formula *LHS, const Formula *RHS) {
548 return make(K: Formula::Implies, Operands: {LHS, RHS});
549 }
550
551 // Creates a boolean biconditional.
552 const Formula *iff(const Formula *LHS, const Formula *RHS) {
553 return make(K: Formula::Equal, Operands: {LHS, RHS});
554 }
555};
556
557/// Parses a list of formulas, separated by newlines, and returns them.
558/// On parse errors, calls `ADD_FAILURE()` to fail the current test.
559std::vector<const Formula *> parseFormulas(Arena &A, StringRef Lines);
560
561} // namespace test
562} // namespace dataflow
563} // namespace clang
564
565#endif // LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_
566

source code of clang/unittests/Analysis/FlowSensitive/TestingSupport.h