[clang] 5a40df6 - [clang][dataflow] Add framework for testing analyses.
Yitzhak Mandelbaum via cfe-commits
cfe-commits at lists.llvm.org
Fri Dec 10 07:30:33 PST 2021
Author: Yitzhak Mandelbaum
Date: 2021-12-10T15:24:12Z
New Revision: 5a40df6381819b38df66e4b6eaa02e7140e07a0c
URL: https://github.com/llvm/llvm-project/commit/5a40df6381819b38df66e4b6eaa02e7140e07a0c
DIFF: https://github.com/llvm/llvm-project/commit/5a40df6381819b38df66e4b6eaa02e7140e07a0c.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..d01f4e76901e9
--- /dev/null
+++ b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
@@ -0,0 +1,172 @@
+//===--- 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>
+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..0364fb8c936a9
--- /dev/null
+++ b/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp
@@ -0,0 +1,179 @@
+#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;
+ }
+};
+
+std::ostream &operator<<(std::ostream &OS, const NoopLattice &S) {
+ OS << "noop";
+ return OS;
+}
+
+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