[clang] 47d526d - [clang][dataflow] Add framework for testing analyses.

Yitzhak Mandelbaum via cfe-commits cfe-commits at lists.llvm.org
Fri Dec 10 06:01:23 PST 2021


Author: Yitzhak Mandelbaum
Date: 2021-12-10T14:00:36Z
New Revision: 47d526d67e3cc66771eed1d0b607226a4fb9a5b5

URL: https://github.com/llvm/llvm-project/commit/47d526d67e3cc66771eed1d0b607226a4fb9a5b5
DIFF: https://github.com/llvm/llvm-project/commit/47d526d67e3cc66771eed1d0b607226a4fb9a5b5.diff

LOG: [clang][dataflow] Add framework for testing analyses.

Adds a general-purpose framework to support testing of dataflow analyses.

Differential Revision: https://reviews.llvm.org/D115341

Added: 
    clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp
    clang/unittests/Analysis/FlowSensitive/TestingSupport.h
    clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp

Modified: 
    clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h
    clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp
    clang/unittests/Analysis/FlowSensitive/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h b/clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h
index 55fae246da795..6193b9860d33a 100644
--- a/clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h
+++ b/clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h
@@ -78,7 +78,8 @@ struct TypeErasedDataflowAnalysisState {
 
 /// Transfers the state of a basic block by evaluating each of its statements in
 /// the context of `Analysis` and the states of its predecessors that are
-/// available in `BlockStates`.
+/// available in `BlockStates`. `HandleTransferredStmt` (if provided) will be
+/// applied to each statement in the block, after it is evaluated.
 ///
 /// Requirements:
 ///
@@ -88,7 +89,10 @@ struct TypeErasedDataflowAnalysisState {
 TypeErasedDataflowAnalysisState transferBlock(
     std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
     const CFGBlock &Block, const Environment &InitEnv,
-    TypeErasedDataflowAnalysis &Analysis);
+    TypeErasedDataflowAnalysis &Analysis,
+    std::function<void(const CFGStmt &,
+                       const TypeErasedDataflowAnalysisState &)>
+        HandleTransferredStmt = nullptr);
 
 /// Performs dataflow analysis and returns a mapping from basic block IDs to
 /// dataflow analysis states that model the respective basic blocks. Indices

diff  --git a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp
index 45afd59728e14..413e8d14bf0a9 100644
--- a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp
+++ b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp
@@ -66,7 +66,10 @@ static TypeErasedDataflowAnalysisState computeBlockInputState(
 TypeErasedDataflowAnalysisState transferBlock(
     std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
     const CFGBlock &Block, const Environment &InitEnv,
-    TypeErasedDataflowAnalysis &Analysis) {
+    TypeErasedDataflowAnalysis &Analysis,
+    std::function<void(const CFGStmt &,
+                       const TypeErasedDataflowAnalysisState &)>
+        HandleTransferredStmt) {
   TypeErasedDataflowAnalysisState State =
       computeBlockInputState(BlockStates, Block, InitEnv, Analysis);
   for (const CFGElement &Element : Block) {
@@ -79,6 +82,8 @@ TypeErasedDataflowAnalysisState transferBlock(
 
     State.Lattice = Analysis.transferTypeErased(Stmt.getValue().getStmt(),
                                                 State.Lattice, State.Env);
+    if (HandleTransferredStmt != nullptr)
+      HandleTransferredStmt(Stmt.getValue(), State);
   }
   return State;
 }

diff  --git a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
index d6f38c9404abc..1388a224b3807 100644
--- a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
+++ b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
@@ -3,6 +3,8 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_unittest(ClangAnalysisFlowSensitiveTests
+  TestingSupport.cpp
+  TestingSupportTest.cpp
   TypeErasedDataflowAnalysisTest.cpp
   )
 
@@ -14,8 +16,13 @@ clang_target_link_libraries(ClangAnalysisFlowSensitiveTests
   clangASTMatchers
   clangBasic
   clangFrontend
+  clangLex
   clangSerialization
   clangTesting
   clangTooling
   )
 
+target_link_libraries(ClangAnalysisFlowSensitiveTests
+  PRIVATE
+  LLVMTestingSupport
+  )

diff  --git a/clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp b/clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp
new file mode 100644
index 0000000000000..2b08d949c1fa6
--- /dev/null
+++ b/clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp
@@ -0,0 +1,170 @@
+#include "TestingSupport.h"
+#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/Analysis/CFG.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Basic/TokenKinds.h"
+#include "clang/Lex/Lexer.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/Optional.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Testing/Support/Annotations.h"
+#include "gtest/gtest.h"
+#include <functional>
+#include <memory>
+#include <string>
+#include <system_error>
+#include <utility>
+#include <vector>
+
+using namespace clang;
+using namespace dataflow;
+
+namespace {
+using ast_matchers::MatchFinder;
+
+class FindTranslationUnitCallback : public MatchFinder::MatchCallback {
+public:
+  explicit FindTranslationUnitCallback(
+      std::function<void(ASTContext &)> Operation)
+      : Operation{Operation} {}
+
+  void run(const MatchFinder::MatchResult &Result) override {
+    const auto *TU = Result.Nodes.getNodeAs<TranslationUnitDecl>("tu");
+    if (TU->getASTContext().getDiagnostics().getClient()->getNumErrors() != 0) {
+      FAIL() << "Source file has syntax or type errors, they were printed to "
+                "the test log";
+    }
+    Operation(TU->getASTContext());
+  }
+
+  std::function<void(ASTContext &)> Operation;
+};
+} // namespace
+
+static bool
+isAnnotationDirectlyAfterStatement(const Stmt *Stmt, unsigned AnnotationBegin,
+                                   const SourceManager &SourceManager,
+                                   const LangOptions &LangOptions) {
+  auto NextToken =
+      Lexer::findNextToken(Stmt->getEndLoc(), SourceManager, LangOptions);
+
+  while (NextToken.hasValue() &&
+         SourceManager.getFileOffset(NextToken->getLocation()) <
+             AnnotationBegin) {
+    if (NextToken->isNot(tok::semi))
+      return false;
+
+    NextToken = Lexer::findNextToken(NextToken->getEndLoc(), SourceManager,
+                                     LangOptions);
+  }
+
+  return true;
+}
+
+llvm::Expected<llvm::DenseMap<const Stmt *, std::string>>
+clang::dataflow::testing::buildStatementToAnnotationMapping(
+    const FunctionDecl *Func, llvm::Annotations AnnotatedCode) {
+  llvm::DenseMap<const Stmt *, std::string> Result;
+
+  using namespace ast_matchers; // NOLINT: Too many names
+  auto StmtMatcher =
+      findAll(stmt(unless(anyOf(hasParent(expr()), hasParent(returnStmt()))))
+                  .bind("stmt"));
+
+  // This map should stay sorted because the binding algorithm relies on the
+  // ordering of statement offsets
+  std::map<unsigned, const Stmt *> Stmts;
+  auto &Context = Func->getASTContext();
+  auto &SourceManager = Context.getSourceManager();
+
+  for (auto &Match : match(StmtMatcher, *Func->getBody(), Context)) {
+    const auto *S = Match.getNodeAs<Stmt>("stmt");
+    unsigned Offset = SourceManager.getFileOffset(S->getEndLoc());
+    Stmts[Offset] = S;
+  }
+
+  unsigned I = 0;
+  auto Annotations = AnnotatedCode.ranges();
+  std::reverse(Annotations.begin(), Annotations.end());
+  auto Code = AnnotatedCode.code();
+
+  for (auto OffsetAndStmt = Stmts.rbegin(); OffsetAndStmt != Stmts.rend();
+       OffsetAndStmt++) {
+    unsigned Offset = OffsetAndStmt->first;
+    const Stmt *Stmt = OffsetAndStmt->second;
+
+    if (I < Annotations.size() && Annotations[I].Begin >= Offset) {
+      auto Range = Annotations[I];
+
+      if (!isAnnotationDirectlyAfterStatement(Stmt, Range.Begin, SourceManager,
+                                              Context.getLangOpts())) {
+        return llvm::createStringError(
+            std::make_error_code(std::errc::invalid_argument),
+            "Annotation is not placed after a statement: %s",
+            SourceManager.getLocForStartOfFile(SourceManager.getMainFileID())
+                .getLocWithOffset(Offset)
+                .printToString(SourceManager)
+                .data());
+      }
+
+      Result[Stmt] = Code.slice(Range.Begin, Range.End).str();
+      I++;
+
+      if (I < Annotations.size() && Annotations[I].Begin >= Offset) {
+        return llvm::createStringError(
+            std::make_error_code(std::errc::invalid_argument),
+            "Multiple annotations bound to the statement at the location: %s",
+            Stmt->getBeginLoc().printToString(SourceManager).data());
+      }
+    }
+  }
+
+  if (I < Annotations.size()) {
+    return llvm::createStringError(
+        std::make_error_code(std::errc::invalid_argument),
+        "Not all annotations were bound to statements. Unbound annotation at: "
+        "%s",
+        SourceManager.getLocForStartOfFile(SourceManager.getMainFileID())
+            .getLocWithOffset(Annotations[I].Begin)
+            .printToString(SourceManager)
+            .data());
+  }
+
+  return Result;
+}
+
+std::pair<const FunctionDecl *, std::unique_ptr<CFG>>
+clang::dataflow::testing::buildCFG(
+    ASTContext &Context,
+    ast_matchers::internal::Matcher<FunctionDecl> FuncMatcher) {
+  CFG::BuildOptions Options;
+  Options.PruneTriviallyFalseEdges = false;
+  Options.AddInitializers = true;
+  Options.AddImplicitDtors = true;
+  Options.AddTemporaryDtors = true;
+  Options.setAllAlwaysAdd();
+
+  const FunctionDecl *F = ast_matchers::selectFirst<FunctionDecl>(
+      "target",
+      ast_matchers::match(
+          ast_matchers::functionDecl(ast_matchers::isDefinition(), FuncMatcher)
+              .bind("target"),
+          Context));
+  if (F == nullptr)
+    return std::make_pair(nullptr, nullptr);
+
+  return std::make_pair(
+      F, clang::CFG::buildCFG(F, F->getBody(), &Context, Options));
+}

diff  --git a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
new file mode 100644
index 0000000000000..4377a904e6c13
--- /dev/null
+++ b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
@@ -0,0 +1,174 @@
+//===--- DataflowValues.h - Data structure for dataflow values --*- 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 a skeleton data structure for encapsulating the dataflow
+// values for a CFG.  Typically this is subclassed to provide methods for
+// computing these values from a CFG.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_
+#define LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_
+
+#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/DataflowAnalysis.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Basic/LLVM.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/Error.h"
+#include "llvm/Testing/Support/Annotations.h"
+#include "gtest/gtest.h"
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace clang {
+namespace dataflow {
+
+// Requires a `<<` operator for the `Lattice` type.
+// FIXME: move to a non-test utility library.
+template <typename Lattice,
+          typename = decltype(std::declval<std::ostream &>()
+                              << std::declval<const Lattice &>())>
+std::ostream &operator<<(std::ostream &OS,
+                         const DataflowAnalysisState<Lattice> &S) {
+  std::string Separator = "";
+  OS << "{lattice=";
+  OS << S.Lattice;
+  // FIXME: add printing support for the environment.
+  OS << ", environment=...}";
+  return OS;
+}
+
+namespace testing {
+
+// 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);
+
+// Creates a CFG from the body of the function that matches `func_matcher`,
+// suitable to testing a dataflow analysis.
+std::pair<const FunctionDecl *, std::unique_ptr<CFG>>
+buildCFG(ASTContext &Context,
+         ast_matchers::internal::Matcher<FunctionDecl> FuncMatcher);
+
+// Runs dataflow on the body of the function that matches `func_matcher` in code
+// snippet `code`. Requires: `Analysis` contains a type `Lattice`.
+template <typename AnalysisT>
+void checkDataflow(
+    llvm::StringRef Code,
+    ast_matchers::internal::Matcher<FunctionDecl> FuncMatcher,
+    std::function<AnalysisT(ASTContext &, Environment &)> MakeAnalysis,
+    std::function<void(
+        llvm::ArrayRef<std::pair<
+            std::string, DataflowAnalysisState<typename AnalysisT::Lattice>>>,
+        ASTContext &)>
+        Expectations,
+    ArrayRef<std::string> Args,
+    const tooling::FileContentMappings &VirtualMappedFiles = {}) {
+  using StateT = DataflowAnalysisState<typename AnalysisT::Lattice>;
+
+  llvm::Annotations AnnotatedCode(Code);
+  auto Unit = tooling::buildASTFromCodeWithArgs(
+      AnnotatedCode.code(), {"-fsyntax-only", "-std=c++17"});
+  auto &Context = Unit->getASTContext();
+
+  if (Context.getDiagnostics().getClient()->getNumErrors() != 0) {
+    FAIL() << "Source file has syntax or type errors, they were printed to "
+              "the test log";
+  }
+
+  std::pair<const FunctionDecl *, std::unique_ptr<CFG>> CFGResult =
+      buildCFG(Context, FuncMatcher);
+  const auto *F = CFGResult.first;
+  auto Cfg = std::move(CFGResult.second);
+  ASSERT_TRUE(F != nullptr) << "Could not find target function";
+  ASSERT_TRUE(Cfg != nullptr) << "Could not build control flow graph.";
+
+  Environment Env;
+  auto Analysis = MakeAnalysis(Context, Env);
+
+  llvm::Expected<llvm::DenseMap<const clang::Stmt *, std::string>>
+      StmtToAnnotations = buildStatementToAnnotationMapping(F, AnnotatedCode);
+  if (auto E = StmtToAnnotations.takeError()) {
+    FAIL() << "Failed to build annotation map: "
+           << llvm::toString(std::move(E));
+    return;
+  }
+  auto &Annotations = *StmtToAnnotations;
+
+  std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates =
+      runTypeErasedDataflowAnalysis(*Cfg, Analysis, Env);
+
+  if (BlockStates.empty()) {
+    Expectations({}, Context);
+    return;
+  }
+
+  // Compute a map from statement annotations to the state computed for
+  // the program point immediately after the annotated statement.
+  std::vector<std::pair<std::string, StateT>> Results;
+  for (const CFGBlock *Block : *Cfg) {
+    // Skip blocks that were not evaluated.
+    if (!BlockStates[Block->getBlockID()].hasValue())
+      continue;
+
+    transferBlock(
+        BlockStates, *Block, Env, Analysis,
+        [&Results, &Annotations](const clang::CFGStmt &Stmt,
+                                 const TypeErasedDataflowAnalysisState &State) {
+          auto It = Annotations.find(Stmt.getStmt());
+          if (It == Annotations.end())
+            return;
+          if (auto *Lattice = llvm::any_cast<typename AnalysisT::Lattice>(
+                  &State.Lattice.Value)) {
+            Results.emplace_back(
+                It->second, StateT{std::move(*Lattice), std::move(State.Env)});
+          } else {
+            FAIL() << "Could not cast lattice element to expected type.";
+          }
+        });
+  }
+  Expectations(Results, Context);
+}
+
+// Runs dataflow on the body of the function named `target_fun` in code snippet
+// `code`.
+template <typename AnalysisT>
+void checkDataflow(
+    llvm::StringRef Code, llvm::StringRef TargetFun,
+    std::function<AnalysisT(ASTContext &, Environment &)> MakeAnalysis,
+    std::function<void(
+        llvm::ArrayRef<std::pair<
+            std::string, DataflowAnalysisState<typename AnalysisT::Lattice>>>,
+        ASTContext &)>
+        Expectations,
+    ArrayRef<std::string> Args,
+    const tooling::FileContentMappings &VirtualMappedFiles = {}) {
+  checkDataflow(Code, ast_matchers::hasName(TargetFun), std::move(MakeAnalysis),
+                std::move(Expectations), Args, VirtualMappedFiles);
+}
+
+} // namespace testing
+} // namespace dataflow
+} // namespace clang
+
+#endif // LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_

diff  --git a/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp b/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp
new file mode 100644
index 0000000000000..a5d5d215ce7e0
--- /dev/null
+++ b/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp
@@ -0,0 +1,174 @@
+#include "TestingSupport.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Tooling/Tooling.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using namespace clang;
+using namespace dataflow;
+
+namespace {
+
+using ::clang::ast_matchers::functionDecl;
+using ::clang::ast_matchers::hasName;
+using ::clang::ast_matchers::isDefinition;
+using ::testing::_;
+using ::testing::IsEmpty;
+using ::testing::Pair;
+using ::testing::UnorderedElementsAre;
+
+class NoopLattice {
+public:
+  bool operator==(const NoopLattice &) const { return true; }
+
+  LatticeJoinEffect join(const NoopLattice &) {
+    return LatticeJoinEffect::Unchanged;
+  }
+};
+
+class NoopAnalysis : public DataflowAnalysis<NoopAnalysis, NoopLattice> {
+public:
+  NoopAnalysis(ASTContext &Context)
+      : DataflowAnalysis<NoopAnalysis, NoopLattice>(Context) {}
+
+  static NoopLattice initialElement() { return {}; }
+
+  NoopLattice transfer(const Stmt *S, const NoopLattice &E, Environment &Env) {
+    return {};
+  }
+};
+
+template <typename T>
+const FunctionDecl *findTargetFunc(ASTContext &Context, T FunctionMatcher) {
+  auto TargetMatcher =
+      functionDecl(FunctionMatcher, isDefinition()).bind("target");
+  for (const auto &Node : ast_matchers::match(TargetMatcher, Context)) {
+    const auto *Func = Node.template getNodeAs<FunctionDecl>("target");
+    if (Func == nullptr)
+      continue;
+    if (Func->isTemplated())
+      continue;
+    return Func;
+  }
+  return nullptr;
+}
+
+class BuildStatementToAnnotationMappingTest : public ::testing::Test {
+public:
+  void
+  runTest(llvm::StringRef Code, llvm::StringRef TargetName,
+          std::function<void(const llvm::DenseMap<const Stmt *, std::string> &)>
+              RunChecks) {
+    llvm::Annotations AnnotatedCode(Code);
+    auto Unit = tooling::buildASTFromCodeWithArgs(
+        AnnotatedCode.code(), {"-fsyntax-only", "-std=c++17"});
+    auto &Context = Unit->getASTContext();
+    const FunctionDecl *Func = findTargetFunc(Context, hasName(TargetName));
+    ASSERT_NE(Func, nullptr);
+
+    llvm::Expected<llvm::DenseMap<const Stmt *, std::string>> Mapping =
+        clang::dataflow::testing::buildStatementToAnnotationMapping(
+            Func, AnnotatedCode);
+    ASSERT_TRUE(static_cast<bool>(Mapping));
+
+    RunChecks(Mapping.get());
+  }
+};
+
+TEST_F(BuildStatementToAnnotationMappingTest, ReturnStmt) {
+  runTest(R"(
+    int target() {
+      return 42;
+      /*[[ok]]*/
+    }
+  )",
+          "target",
+          [](const llvm::DenseMap<const Stmt *, std::string> &Annotations) {
+            ASSERT_EQ(Annotations.size(), static_cast<unsigned int>(1));
+            EXPECT_TRUE(isa<ReturnStmt>(Annotations.begin()->first));
+            EXPECT_EQ(Annotations.begin()->second, "ok");
+          });
+}
+
+void checkDataflow(
+    llvm::StringRef Code, llvm::StringRef Target,
+    std::function<void(llvm::ArrayRef<std::pair<
+                           std::string, DataflowAnalysisState<NoopLattice>>>,
+                       ASTContext &)>
+        Expectations) {
+  clang::dataflow::testing::checkDataflow<NoopAnalysis>(
+      Code, Target,
+      [](ASTContext &Context, Environment &) { return NoopAnalysis(Context); },
+      std::move(Expectations), {"-fsyntax-only", "-std=c++17"});
+}
+
+TEST(ProgramPointAnnotations, NoAnnotations) {
+  ::testing::MockFunction<void(
+      llvm::ArrayRef<
+          std::pair<std::string, DataflowAnalysisState<NoopLattice>>>,
+      ASTContext &)>
+      Expectations;
+
+  EXPECT_CALL(Expectations, Call(IsEmpty(), _)).Times(1);
+
+  checkDataflow("void target() {}", "target", Expectations.AsStdFunction());
+}
+
+TEST(ProgramPointAnnotations, NoAnnotationsDifferentTarget) {
+  ::testing::MockFunction<void(
+      llvm::ArrayRef<
+          std::pair<std::string, DataflowAnalysisState<NoopLattice>>>,
+      ASTContext &)>
+      Expectations;
+
+  EXPECT_CALL(Expectations, Call(IsEmpty(), _)).Times(1);
+
+  checkDataflow("void fun() {}", "fun", Expectations.AsStdFunction());
+}
+
+TEST(ProgramPointAnnotations, WithCodepoint) {
+  ::testing::MockFunction<void(
+      llvm::ArrayRef<
+          std::pair<std::string, DataflowAnalysisState<NoopLattice>>>,
+      ASTContext &)>
+      Expectations;
+
+  EXPECT_CALL(Expectations,
+              Call(UnorderedElementsAre(Pair("program-point", _)), _))
+      .Times(1);
+
+  checkDataflow(R"cc(void target() {
+                     int n;
+                     // [[program-point]]
+                   })cc",
+                "target", Expectations.AsStdFunction());
+}
+
+TEST(ProgramPointAnnotations, MultipleCodepoints) {
+  ::testing::MockFunction<void(
+      llvm::ArrayRef<
+          std::pair<std::string, DataflowAnalysisState<NoopLattice>>>,
+      ASTContext &)>
+      Expectations;
+
+  EXPECT_CALL(Expectations,
+              Call(UnorderedElementsAre(Pair("program-point-1", _),
+                                        Pair("program-point-2", _)),
+                   _))
+      .Times(1);
+
+  checkDataflow(R"cc(void target(bool b) {
+                     if (b) {
+                       int n;
+                       // [[program-point-1]]
+                     } else {
+                       int m;
+                       // [[program-point-2]]
+                     }
+                   })cc",
+                "target", Expectations.AsStdFunction());
+}
+
+} // namespace


        


More information about the cfe-commits mailing list