1 #include "TestingSupport.h"
2 #include "clang/AST/ASTContext.h"
3 #include "clang/ASTMatchers/ASTMatchFinder.h"
4 #include "clang/ASTMatchers/ASTMatchers.h"
5 #include "clang/Tooling/Tooling.h"
6 #include "gmock/gmock.h"
7 #include "gtest/gtest.h"
8 
9 using namespace clang;
10 using namespace dataflow;
11 
12 namespace {
13 
14 using ::clang::ast_matchers::functionDecl;
15 using ::clang::ast_matchers::hasName;
16 using ::clang::ast_matchers::isDefinition;
17 using ::testing::_;
18 using ::testing::IsEmpty;
19 using ::testing::Pair;
20 using ::testing::UnorderedElementsAre;
21 
22 class NoopLattice {
23 public:
24   bool operator==(const NoopLattice &) const { return true; }
25 
26   LatticeJoinEffect join(const NoopLattice &) {
27     return LatticeJoinEffect::Unchanged;
28   }
29 };
30 
31 std::ostream &operator<<(std::ostream &OS, const NoopLattice &S) {
32   OS << "noop";
33   return OS;
34 }
35 
36 class NoopAnalysis : public DataflowAnalysis<NoopAnalysis, NoopLattice> {
37 public:
38   NoopAnalysis(ASTContext &Context)
39       : DataflowAnalysis<NoopAnalysis, NoopLattice>(Context) {}
40 
41   static NoopLattice initialElement() { return {}; }
42 
43   NoopLattice transfer(const Stmt *S, const NoopLattice &E, Environment &Env) {
44     return {};
45   }
46 };
47 
48 template <typename T>
49 const FunctionDecl *findTargetFunc(ASTContext &Context, T FunctionMatcher) {
50   auto TargetMatcher =
51       functionDecl(FunctionMatcher, isDefinition()).bind("target");
52   for (const auto &Node : ast_matchers::match(TargetMatcher, Context)) {
53     const auto *Func = Node.template getNodeAs<FunctionDecl>("target");
54     if (Func == nullptr)
55       continue;
56     if (Func->isTemplated())
57       continue;
58     return Func;
59   }
60   return nullptr;
61 }
62 
63 class BuildStatementToAnnotationMappingTest : public ::testing::Test {
64 public:
65   void
66   runTest(llvm::StringRef Code, llvm::StringRef TargetName,
67           std::function<void(const llvm::DenseMap<const Stmt *, std::string> &)>
68               RunChecks) {
69     llvm::Annotations AnnotatedCode(Code);
70     auto Unit = tooling::buildASTFromCodeWithArgs(
71         AnnotatedCode.code(), {"-fsyntax-only", "-std=c++17"});
72     auto &Context = Unit->getASTContext();
73     const FunctionDecl *Func = findTargetFunc(Context, hasName(TargetName));
74     ASSERT_NE(Func, nullptr);
75 
76     llvm::Expected<llvm::DenseMap<const Stmt *, std::string>> Mapping =
77         test::buildStatementToAnnotationMapping(Func, AnnotatedCode);
78     ASSERT_TRUE(static_cast<bool>(Mapping));
79 
80     RunChecks(Mapping.get());
81   }
82 };
83 
84 TEST_F(BuildStatementToAnnotationMappingTest, ReturnStmt) {
85   runTest(R"(
86     int target() {
87       return 42;
88       /*[[ok]]*/
89     }
90   )",
91           "target",
92           [](const llvm::DenseMap<const Stmt *, std::string> &Annotations) {
93             ASSERT_EQ(Annotations.size(), static_cast<unsigned int>(1));
94             EXPECT_TRUE(isa<ReturnStmt>(Annotations.begin()->first));
95             EXPECT_EQ(Annotations.begin()->second, "ok");
96           });
97 }
98 
99 void checkDataflow(
100     llvm::StringRef Code, llvm::StringRef Target,
101     std::function<void(llvm::ArrayRef<std::pair<
102                            std::string, DataflowAnalysisState<NoopLattice>>>,
103                        ASTContext &)>
104         Expectations) {
105   test::checkDataflow<NoopAnalysis>(
106       Code, Target,
107       [](ASTContext &Context, Environment &) { return NoopAnalysis(Context); },
108       std::move(Expectations), {"-fsyntax-only", "-std=c++17"});
109 }
110 
111 TEST(ProgramPointAnnotations, NoAnnotations) {
112   ::testing::MockFunction<void(
113       llvm::ArrayRef<
114           std::pair<std::string, DataflowAnalysisState<NoopLattice>>>,
115       ASTContext &)>
116       Expectations;
117 
118   EXPECT_CALL(Expectations, Call(IsEmpty(), _)).Times(1);
119 
120   checkDataflow("void target() {}", "target", Expectations.AsStdFunction());
121 }
122 
123 TEST(ProgramPointAnnotations, NoAnnotationsDifferentTarget) {
124   ::testing::MockFunction<void(
125       llvm::ArrayRef<
126           std::pair<std::string, DataflowAnalysisState<NoopLattice>>>,
127       ASTContext &)>
128       Expectations;
129 
130   EXPECT_CALL(Expectations, Call(IsEmpty(), _)).Times(1);
131 
132   checkDataflow("void fun() {}", "fun", Expectations.AsStdFunction());
133 }
134 
135 TEST(ProgramPointAnnotations, WithCodepoint) {
136   ::testing::MockFunction<void(
137       llvm::ArrayRef<
138           std::pair<std::string, DataflowAnalysisState<NoopLattice>>>,
139       ASTContext &)>
140       Expectations;
141 
142   EXPECT_CALL(Expectations,
143               Call(UnorderedElementsAre(Pair("program-point", _)), _))
144       .Times(1);
145 
146   checkDataflow(R"cc(void target() {
147                      int n;
148                      // [[program-point]]
149                    })cc",
150                 "target", Expectations.AsStdFunction());
151 }
152 
153 TEST(ProgramPointAnnotations, MultipleCodepoints) {
154   ::testing::MockFunction<void(
155       llvm::ArrayRef<
156           std::pair<std::string, DataflowAnalysisState<NoopLattice>>>,
157       ASTContext &)>
158       Expectations;
159 
160   EXPECT_CALL(Expectations,
161               Call(UnorderedElementsAre(Pair("program-point-1", _),
162                                         Pair("program-point-2", _)),
163                    _))
164       .Times(1);
165 
166   checkDataflow(R"cc(void target(bool b) {
167                      if (b) {
168                        int n;
169                        // [[program-point-1]]
170                      } else {
171                        int m;
172                        // [[program-point-2]]
173                      }
174                    })cc",
175                 "target", Expectations.AsStdFunction());
176 }
177 
178 } // namespace
179