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/Analysis/FlowSensitive/NoopAnalysis.h" |
6 | #include "clang/Tooling/Tooling.h" |
7 | #include "llvm/Testing/ADT/StringMapEntry.h" |
8 | #include "llvm/Testing/Support/Error.h" |
9 | #include "gmock/gmock.h" |
10 | #include "gtest/gtest.h" |
11 | |
12 | using namespace clang; |
13 | using namespace dataflow; |
14 | |
15 | namespace { |
16 | |
17 | using ::clang::ast_matchers::functionDecl; |
18 | using ::clang::ast_matchers::hasAnyName; |
19 | using ::clang::ast_matchers::hasName; |
20 | using ::clang::ast_matchers::isDefinition; |
21 | using ::clang::dataflow::test::AnalysisInputs; |
22 | using ::clang::dataflow::test::AnalysisOutputs; |
23 | using ::clang::dataflow::test::checkDataflow; |
24 | using ::llvm::IsStringMapEntry; |
25 | using ::testing::_; |
26 | using ::testing::IsEmpty; |
27 | using ::testing::UnorderedElementsAre; |
28 | |
29 | template <typename T> |
30 | const FunctionDecl *findTargetFunc(ASTContext &Context, T FunctionMatcher) { |
31 | auto TargetMatcher = |
32 | functionDecl(FunctionMatcher, isDefinition()).bind("target" ); |
33 | for (const auto &Node : ast_matchers::match(TargetMatcher, Context)) { |
34 | const auto *Func = Node.template getNodeAs<FunctionDecl>("target" ); |
35 | if (Func == nullptr) |
36 | continue; |
37 | if (Func->isTemplated()) |
38 | continue; |
39 | return Func; |
40 | } |
41 | return nullptr; |
42 | } |
43 | |
44 | void runTest( |
45 | llvm::StringRef Code, llvm::StringRef TargetName, |
46 | std::function<void(const llvm::DenseMap<const Stmt *, std::string> &)> |
47 | RunChecks) { |
48 | llvm::Annotations AnnotatedCode(Code); |
49 | auto Unit = tooling::buildASTFromCodeWithArgs( |
50 | Code: AnnotatedCode.code(), Args: {"-fsyntax-only" , "-std=c++17" }); |
51 | auto &Context = Unit->getASTContext(); |
52 | const FunctionDecl *Func = findTargetFunc(Context, FunctionMatcher: hasName(Name: TargetName)); |
53 | ASSERT_NE(Func, nullptr); |
54 | |
55 | llvm::Expected<llvm::DenseMap<const Stmt *, std::string>> Mapping = |
56 | test::buildStatementToAnnotationMapping(Func, AnnotatedCode); |
57 | ASSERT_TRUE(static_cast<bool>(Mapping)); |
58 | |
59 | RunChecks(Mapping.get()); |
60 | } |
61 | |
62 | TEST(BuildStatementToAnnotationMappingTest, ReturnStmt) { |
63 | runTest(Code: R"( |
64 | int target() { |
65 | return 42; |
66 | /*[[ok]]*/ |
67 | } |
68 | )" , |
69 | TargetName: "target" , |
70 | RunChecks: [](const llvm::DenseMap<const Stmt *, std::string> &Annotations) { |
71 | ASSERT_EQ(Annotations.size(), static_cast<unsigned int>(1)); |
72 | EXPECT_TRUE(isa<ReturnStmt>(Annotations.begin()->first)); |
73 | EXPECT_EQ(Annotations.begin()->second, "ok" ); |
74 | }); |
75 | } |
76 | |
77 | void checkDataflow( |
78 | llvm::StringRef Code, |
79 | ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcher, |
80 | std::function< |
81 | void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
82 | const AnalysisOutputs &)> |
83 | Expectations) { |
84 | ASSERT_THAT_ERROR(checkDataflow<NoopAnalysis>( |
85 | AnalysisInputs<NoopAnalysis>( |
86 | Code, std::move(TargetFuncMatcher), |
87 | [](ASTContext &Context, Environment &) { |
88 | return NoopAnalysis( |
89 | Context, |
90 | // Don't apply builtin transfer function. |
91 | DataflowAnalysisOptions{std::nullopt}); |
92 | }) |
93 | .withASTBuildArgs({"-fsyntax-only" , "-std=c++17" }), |
94 | /*VerifyResults=*/std::move(Expectations)), |
95 | llvm::Succeeded()); |
96 | } |
97 | |
98 | TEST(ProgramPointAnnotations, NoAnnotations) { |
99 | ::testing::MockFunction<void( |
100 | const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
101 | const AnalysisOutputs &)> |
102 | Expectations; |
103 | |
104 | EXPECT_CALL(Expectations, Call(IsEmpty(), _)).Times(n: 1); |
105 | |
106 | checkDataflow(Code: "void target() {}" , TargetFuncMatcher: hasName(Name: "target" ), |
107 | Expectations: Expectations.AsStdFunction()); |
108 | } |
109 | |
110 | TEST(ProgramPointAnnotations, NoAnnotationsDifferentTarget) { |
111 | ::testing::MockFunction<void( |
112 | const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
113 | const AnalysisOutputs &)> |
114 | Expectations; |
115 | |
116 | EXPECT_CALL(Expectations, Call(IsEmpty(), _)).Times(n: 1); |
117 | |
118 | checkDataflow(Code: "void target() {}" , TargetFuncMatcher: hasName(Name: "target" ), |
119 | Expectations: Expectations.AsStdFunction()); |
120 | } |
121 | |
122 | TEST(ProgramPointAnnotations, WithProgramPoint) { |
123 | ::testing::MockFunction<void( |
124 | const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
125 | const AnalysisOutputs &)> |
126 | Expectations; |
127 | |
128 | EXPECT_CALL( |
129 | Expectations, |
130 | Call(UnorderedElementsAre(IsStringMapEntry("program-point" , _)), _)) |
131 | .Times(n: 1); |
132 | |
133 | checkDataflow(Code: R"cc(void target() { |
134 | int n; |
135 | // [[program-point]] |
136 | })cc" , |
137 | TargetFuncMatcher: hasName(Name: "target" ), Expectations: Expectations.AsStdFunction()); |
138 | } |
139 | |
140 | TEST(ProgramPointAnnotations, MultipleProgramPoints) { |
141 | ::testing::MockFunction<void( |
142 | const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
143 | const AnalysisOutputs &)> |
144 | Expectations; |
145 | |
146 | EXPECT_CALL(Expectations, |
147 | Call(UnorderedElementsAre(IsStringMapEntry("program-point-1" , _), |
148 | IsStringMapEntry("program-point-2" , _)), |
149 | _)) |
150 | .Times(n: 1); |
151 | |
152 | checkDataflow(Code: R"cc(void target(bool b) { |
153 | if (b) { |
154 | int n; |
155 | // [[program-point-1]] |
156 | } else { |
157 | int m; |
158 | // [[program-point-2]] |
159 | } |
160 | })cc" , |
161 | TargetFuncMatcher: hasName(Name: "target" ), Expectations: Expectations.AsStdFunction()); |
162 | } |
163 | |
164 | TEST(ProgramPointAnnotations, MultipleFunctionsMultipleProgramPoints) { |
165 | ::testing::MockFunction<void( |
166 | const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
167 | const AnalysisOutputs &)> |
168 | Expectations; |
169 | |
170 | EXPECT_CALL(Expectations, Call(UnorderedElementsAre( |
171 | IsStringMapEntry("program-point-1a" , _), |
172 | IsStringMapEntry("program-point-1b" , _)), |
173 | _)) |
174 | .Times(n: 1); |
175 | |
176 | EXPECT_CALL(Expectations, Call(UnorderedElementsAre( |
177 | IsStringMapEntry("program-point-2a" , _), |
178 | IsStringMapEntry("program-point-2b" , _)), |
179 | _)) |
180 | .Times(n: 1); |
181 | |
182 | checkDataflow( |
183 | Code: R"cc( |
184 | void target1(bool b) { |
185 | if (b) { |
186 | int n; |
187 | // [[program-point-1a]] |
188 | } else { |
189 | int m; |
190 | // [[program-point-1b]] |
191 | } |
192 | } |
193 | |
194 | void target2(bool b) { |
195 | if (b) { |
196 | int n; |
197 | // [[program-point-2a]] |
198 | } else { |
199 | int m; |
200 | // [[program-point-2b]] |
201 | } |
202 | } |
203 | )cc" , |
204 | TargetFuncMatcher: functionDecl(hasAnyName("target1" , "target2" )), |
205 | Expectations: Expectations.AsStdFunction()); |
206 | } |
207 | |
208 | } // namespace |
209 | |