#include "TestingSupport.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Analysis/FlowSensitive/NoopAnalysis.h" #include "clang/Tooling/Tooling.h" #include "llvm/Testing/ADT/StringMapEntry.h" #include "llvm/Testing/Support/Error.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::hasAnyName; using ::clang::ast_matchers::hasName; using ::clang::ast_matchers::isDefinition; using ::clang::dataflow::test::AnalysisInputs; using ::clang::dataflow::test::AnalysisOutputs; using ::clang::dataflow::test::checkDataflow; using ::llvm::IsStringMapEntry; using ::testing::_; using ::testing::IsEmpty; using ::testing::UnorderedElementsAre; template 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("target"); if (Func == nullptr) continue; if (Func->isTemplated()) continue; return Func; } return nullptr; } void runTest( llvm::StringRef Code, llvm::StringRef TargetName, std::function &)> 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> Mapping = test::buildStatementToAnnotationMapping(Func, AnnotatedCode); ASSERT_TRUE(static_cast(Mapping)); RunChecks(Mapping.get()); } TEST(BuildStatementToAnnotationMappingTest, ReturnStmt) { runTest(R"( int target() { return 42; /*[[ok]]*/ } )", "target", [](const llvm::DenseMap &Annotations) { ASSERT_EQ(Annotations.size(), static_cast(1)); EXPECT_TRUE(isa(Annotations.begin()->first)); EXPECT_EQ(Annotations.begin()->second, "ok"); }); } void checkDataflow( llvm::StringRef Code, ast_matchers::internal::Matcher TargetFuncMatcher, std::function< void(const llvm::StringMap> &, const AnalysisOutputs &)> Expectations) { ASSERT_THAT_ERROR(checkDataflow( AnalysisInputs( Code, std::move(TargetFuncMatcher), [](ASTContext &Context, Environment &) { return NoopAnalysis( Context, // Don't apply builtin transfer function. DataflowAnalysisOptions{std::nullopt}); }) .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}), /*VerifyResults=*/std::move(Expectations)), llvm::Succeeded()); } TEST(ProgramPointAnnotations, NoAnnotations) { ::testing::MockFunction> &, const AnalysisOutputs &)> Expectations; EXPECT_CALL(Expectations, Call(IsEmpty(), _)).Times(1); checkDataflow("void target() {}", hasName("target"), Expectations.AsStdFunction()); } TEST(ProgramPointAnnotations, NoAnnotationsDifferentTarget) { ::testing::MockFunction> &, const AnalysisOutputs &)> Expectations; EXPECT_CALL(Expectations, Call(IsEmpty(), _)).Times(1); checkDataflow("void target() {}", hasName("target"), Expectations.AsStdFunction()); } TEST(ProgramPointAnnotations, WithProgramPoint) { ::testing::MockFunction> &, const AnalysisOutputs &)> Expectations; EXPECT_CALL( Expectations, Call(UnorderedElementsAre(IsStringMapEntry("program-point", _)), _)) .Times(1); checkDataflow(R"cc(void target() { int n; // [[program-point]] })cc", hasName("target"), Expectations.AsStdFunction()); } TEST(ProgramPointAnnotations, MultipleProgramPoints) { ::testing::MockFunction> &, const AnalysisOutputs &)> Expectations; EXPECT_CALL(Expectations, Call(UnorderedElementsAre(IsStringMapEntry("program-point-1", _), IsStringMapEntry("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", hasName("target"), Expectations.AsStdFunction()); } TEST(ProgramPointAnnotations, MultipleFunctionsMultipleProgramPoints) { ::testing::MockFunction> &, const AnalysisOutputs &)> Expectations; EXPECT_CALL(Expectations, Call(UnorderedElementsAre( IsStringMapEntry("program-point-1a", _), IsStringMapEntry("program-point-1b", _)), _)) .Times(1); EXPECT_CALL(Expectations, Call(UnorderedElementsAre( IsStringMapEntry("program-point-2a", _), IsStringMapEntry("program-point-2b", _)), _)) .Times(1); checkDataflow( R"cc( void target1(bool b) { if (b) { int n; // [[program-point-1a]] } else { int m; // [[program-point-1b]] } } void target2(bool b) { if (b) { int n; // [[program-point-2a]] } else { int m; // [[program-point-2b]] } } )cc", functionDecl(hasAnyName("target1", "target2")), Expectations.AsStdFunction()); } } // namespace