1 | //===- unittests/StaticAnalyzer/CallDescriptionTest.cpp -------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "CheckerRegistration.h" |
10 | #include "Reusables.h" |
11 | |
12 | #include "clang/AST/ExprCXX.h" |
13 | #include "clang/Analysis/PathDiagnostic.h" |
14 | #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" |
15 | #include "clang/StaticAnalyzer/Core/Checker.h" |
16 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" |
17 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
18 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
19 | #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" |
20 | #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" |
21 | #include "clang/Tooling/Tooling.h" |
22 | #include "gtest/gtest.h" |
23 | #include <type_traits> |
24 | |
25 | namespace clang { |
26 | namespace ento { |
27 | namespace { |
28 | |
29 | // A wrapper around CallDescriptionMap<bool> that allows verifying that |
30 | // all functions have been found. This is needed because CallDescriptionMap |
31 | // isn't supposed to support iteration. |
32 | class ResultMap { |
33 | size_t Found, Total; |
34 | CallDescriptionMap<bool> Impl; |
35 | |
36 | public: |
37 | ResultMap(std::initializer_list<std::pair<CallDescription, bool>> Data) |
38 | : Found(0), |
39 | Total(std::count_if(first: Data.begin(), last: Data.end(), |
40 | pred: [](const std::pair<CallDescription, bool> &Pair) { |
41 | return Pair.second == true; |
42 | })), |
43 | Impl(std::move(Data)) {} |
44 | |
45 | const bool *lookup(const CallEvent &Call) { |
46 | const bool *Result = Impl.lookup(Call); |
47 | // If it's a function we expected to find, remember that we've found it. |
48 | if (Result && *Result) |
49 | ++Found; |
50 | return Result; |
51 | } |
52 | |
53 | // Fail the test if we haven't found all the true-calls we were looking for. |
54 | ~ResultMap() { EXPECT_EQ(Found, Total); } |
55 | }; |
56 | |
57 | // Scan the code body for call expressions and see if we find all calls that |
58 | // we were supposed to find ("true" in the provided ResultMap) and that we |
59 | // don't find the ones that we weren't supposed to find |
60 | // ("false" in the ResultMap). |
61 | template <typename MatchedExprT> |
62 | class CallDescriptionConsumer : public ExprEngineConsumer { |
63 | ResultMap &RM; |
64 | void performTest(const Decl *D) { |
65 | using namespace ast_matchers; |
66 | using T = MatchedExprT; |
67 | |
68 | if (!D->hasBody()) |
69 | return; |
70 | |
71 | const StackFrameContext *SFC = |
72 | Eng.getAnalysisDeclContextManager().getStackFrame(D); |
73 | const ProgramStateRef State = Eng.getInitialState(SFC); |
74 | |
75 | // FIXME: Maybe use std::variant and std::visit for these. |
76 | const auto MatcherCreator = []() { |
77 | if (std::is_same<T, CallExpr>::value) |
78 | return callExpr(); |
79 | if (std::is_same<T, CXXConstructExpr>::value) |
80 | return cxxConstructExpr(); |
81 | if (std::is_same<T, CXXMemberCallExpr>::value) |
82 | return cxxMemberCallExpr(); |
83 | if (std::is_same<T, CXXOperatorCallExpr>::value) |
84 | return cxxOperatorCallExpr(); |
85 | llvm_unreachable("Only these expressions are supported for now." ); |
86 | }; |
87 | |
88 | const Expr *E = findNode<T>(D, MatcherCreator()); |
89 | |
90 | CallEventManager &CEMgr = Eng.getStateManager().getCallEventManager(); |
91 | CallEventRef<> Call = [=, &CEMgr]() -> CallEventRef<CallEvent> { |
92 | CFGBlock::ConstCFGElementRef ElemRef = {SFC->getCallSiteBlock(), |
93 | SFC->getIndex()}; |
94 | if (std::is_base_of<CallExpr, T>::value) |
95 | return CEMgr.getCall(E, State, SFC, ElemRef); |
96 | if (std::is_same<T, CXXConstructExpr>::value) |
97 | return CEMgr.getCXXConstructorCall(E: cast<CXXConstructExpr>(Val: E), |
98 | /*Target=*/nullptr, State, LCtx: SFC, |
99 | ElemRef); |
100 | llvm_unreachable("Only these expressions are supported for now." ); |
101 | }(); |
102 | |
103 | // If the call actually matched, check if we really expected it to match. |
104 | const bool *LookupResult = RM.lookup(Call: *Call); |
105 | EXPECT_TRUE(!LookupResult || *LookupResult); |
106 | |
107 | // ResultMap is responsible for making sure that we've found *all* calls. |
108 | } |
109 | |
110 | public: |
111 | CallDescriptionConsumer(CompilerInstance &C, |
112 | ResultMap &RM) |
113 | : ExprEngineConsumer(C), RM(RM) {} |
114 | |
115 | bool HandleTopLevelDecl(DeclGroupRef DG) override { |
116 | for (const auto *D : DG) |
117 | performTest(D); |
118 | return true; |
119 | } |
120 | }; |
121 | |
122 | template <typename MatchedExprT = CallExpr> |
123 | class CallDescriptionAction : public ASTFrontendAction { |
124 | ResultMap RM; |
125 | |
126 | public: |
127 | CallDescriptionAction( |
128 | std::initializer_list<std::pair<CallDescription, bool>> Data) |
129 | : RM(Data) {} |
130 | |
131 | std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler, |
132 | StringRef File) override { |
133 | return std::make_unique<CallDescriptionConsumer<MatchedExprT>>(Compiler, |
134 | RM); |
135 | } |
136 | }; |
137 | |
138 | TEST(CallDescription, SimpleNameMatching) { |
139 | EXPECT_TRUE(tooling::runToolOnCode( |
140 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
141 | {{{"bar" }}, false}, // false: there's no call to 'bar' in this code. |
142 | {{{"foo" }}, true}, // true: there's a call to 'foo' in this code. |
143 | })), |
144 | "void foo(); void bar() { foo(); }" )); |
145 | } |
146 | |
147 | TEST(CallDescription, RequiredArguments) { |
148 | EXPECT_TRUE(tooling::runToolOnCode( |
149 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
150 | {{{"foo" }, 1}, true}, |
151 | {{{"foo" }, 2}, false}, |
152 | })), |
153 | "void foo(int); void foo(int, int); void bar() { foo(1); }" )); |
154 | } |
155 | |
156 | TEST(CallDescription, LackOfRequiredArguments) { |
157 | EXPECT_TRUE(tooling::runToolOnCode( |
158 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
159 | {{{"foo" }, std::nullopt}, true}, |
160 | {{{"foo" }, 2}, false}, |
161 | })), |
162 | "void foo(int); void foo(int, int); void bar() { foo(1); }" )); |
163 | } |
164 | |
165 | constexpr StringRef = R"code( |
166 | namespace std { inline namespace __1 { |
167 | template<typename T> class basic_string { |
168 | class Allocator {}; |
169 | public: |
170 | basic_string(); |
171 | explicit basic_string(const char*, const Allocator & = Allocator()); |
172 | ~basic_string(); |
173 | T *c_str(); |
174 | }; |
175 | } // namespace __1 |
176 | using string = __1::basic_string<char>; |
177 | } // namespace std |
178 | )code" ; |
179 | |
180 | TEST(CallDescription, QualifiedNames) { |
181 | constexpr StringRef AdditionalCode = R"code( |
182 | void foo() { |
183 | using namespace std; |
184 | basic_string<char> s; |
185 | s.c_str(); |
186 | })code" ; |
187 | const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str(); |
188 | EXPECT_TRUE(tooling::runToolOnCode( |
189 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
190 | {{{"std" , "basic_string" , "c_str" }}, true}, |
191 | })), |
192 | Code)); |
193 | } |
194 | |
195 | TEST(CallDescription, MatchConstructor) { |
196 | constexpr StringRef AdditionalCode = R"code( |
197 | void foo() { |
198 | using namespace std; |
199 | basic_string<char> s("hello"); |
200 | })code" ; |
201 | const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str(); |
202 | EXPECT_TRUE(tooling::runToolOnCode( |
203 | std::unique_ptr<FrontendAction>( |
204 | new CallDescriptionAction<CXXConstructExpr>({ |
205 | {{{"std" , "basic_string" , "basic_string" }, 2, 2}, true}, |
206 | })), |
207 | Code)); |
208 | } |
209 | |
210 | // FIXME: Test matching destructors: {"std", "basic_string", "~basic_string"} |
211 | // This feature is actually implemented, but the test infra is not yet |
212 | // sophisticated enough for testing this. To do that, we will need to |
213 | // implement a much more advanced dispatching mechanism using the CFG for |
214 | // the implicit destructor events. |
215 | |
216 | TEST(CallDescription, MatchConversionOperator) { |
217 | constexpr StringRef Code = R"code( |
218 | namespace aaa { |
219 | namespace bbb { |
220 | struct Bar { |
221 | operator int(); |
222 | }; |
223 | } // bbb |
224 | } // aaa |
225 | void foo() { |
226 | aaa::bbb::Bar x; |
227 | int tmp = x; |
228 | })code" ; |
229 | EXPECT_TRUE(tooling::runToolOnCode( |
230 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
231 | {{{"aaa" , "bbb" , "Bar" , "operator int" }}, true}, |
232 | })), |
233 | Code)); |
234 | } |
235 | |
236 | TEST(CallDescription, RejectOverQualifiedNames) { |
237 | constexpr auto Code = R"code( |
238 | namespace my { |
239 | namespace std { |
240 | struct container { |
241 | const char *data() const; |
242 | }; |
243 | } // namespace std |
244 | } // namespace my |
245 | |
246 | void foo() { |
247 | using namespace my; |
248 | std::container v; |
249 | v.data(); |
250 | })code" ; |
251 | |
252 | // FIXME: We should **not** match. |
253 | EXPECT_TRUE(tooling::runToolOnCode( |
254 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
255 | {{{"std" , "container" , "data" }}, true}, |
256 | })), |
257 | Code)); |
258 | } |
259 | |
260 | TEST(CallDescription, DontSkipNonInlineNamespaces) { |
261 | constexpr auto Code = R"code( |
262 | namespace my { |
263 | /*not inline*/ namespace v1 { |
264 | void bar(); |
265 | } // namespace v1 |
266 | } // namespace my |
267 | void foo() { |
268 | my::v1::bar(); |
269 | })code" ; |
270 | |
271 | { |
272 | SCOPED_TRACE("my v1 bar" ); |
273 | EXPECT_TRUE(tooling::runToolOnCode( |
274 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
275 | {{{"my" , "v1" , "bar" }}, true}, |
276 | })), |
277 | Code)); |
278 | } |
279 | { |
280 | // FIXME: We should **not** skip non-inline namespaces. |
281 | SCOPED_TRACE("my bar" ); |
282 | EXPECT_TRUE(tooling::runToolOnCode( |
283 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
284 | {{{"my" , "bar" }}, true}, |
285 | })), |
286 | Code)); |
287 | } |
288 | } |
289 | |
290 | TEST(CallDescription, SkipTopInlineNamespaces) { |
291 | constexpr auto Code = R"code( |
292 | inline namespace my { |
293 | namespace v1 { |
294 | void bar(); |
295 | } // namespace v1 |
296 | } // namespace my |
297 | void foo() { |
298 | using namespace v1; |
299 | bar(); |
300 | })code" ; |
301 | |
302 | { |
303 | SCOPED_TRACE("my v1 bar" ); |
304 | EXPECT_TRUE(tooling::runToolOnCode( |
305 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
306 | {{{"my" , "v1" , "bar" }}, true}, |
307 | })), |
308 | Code)); |
309 | } |
310 | { |
311 | SCOPED_TRACE("v1 bar" ); |
312 | EXPECT_TRUE(tooling::runToolOnCode( |
313 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
314 | {{{"v1" , "bar" }}, true}, |
315 | })), |
316 | Code)); |
317 | } |
318 | } |
319 | |
320 | TEST(CallDescription, SkipAnonimousNamespaces) { |
321 | constexpr auto Code = R"code( |
322 | namespace { |
323 | namespace std { |
324 | namespace { |
325 | inline namespace { |
326 | struct container { |
327 | const char *data() const { return nullptr; }; |
328 | }; |
329 | } // namespace inline anonymous |
330 | } // namespace anonymous |
331 | } // namespace std |
332 | } // namespace anonymous |
333 | |
334 | void foo() { |
335 | std::container v; |
336 | v.data(); |
337 | })code" ; |
338 | |
339 | EXPECT_TRUE(tooling::runToolOnCode( |
340 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
341 | {{{"std" , "container" , "data" }}, true}, |
342 | })), |
343 | Code)); |
344 | } |
345 | |
346 | TEST(CallDescription, AliasNames) { |
347 | constexpr StringRef AliasNamesCode = R"code( |
348 | namespace std { |
349 | struct container { |
350 | const char *data() const; |
351 | }; |
352 | using cont = container; |
353 | } // std |
354 | )code" ; |
355 | |
356 | constexpr StringRef UseAliasInSpelling = R"code( |
357 | void foo() { |
358 | std::cont v; |
359 | v.data(); |
360 | })code" ; |
361 | constexpr StringRef UseStructNameInSpelling = R"code( |
362 | void foo() { |
363 | std::container v; |
364 | v.data(); |
365 | })code" ; |
366 | const std::string UseAliasInSpellingCode = |
367 | (Twine{AliasNamesCode} + UseAliasInSpelling).str(); |
368 | const std::string UseStructNameInSpellingCode = |
369 | (Twine{AliasNamesCode} + UseStructNameInSpelling).str(); |
370 | |
371 | // Test if the code spells the alias, wile we match against the struct name, |
372 | // and again matching against the alias. |
373 | { |
374 | SCOPED_TRACE("Using alias in spelling" ); |
375 | { |
376 | SCOPED_TRACE("std container data" ); |
377 | EXPECT_TRUE(tooling::runToolOnCode( |
378 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
379 | {{{"std" , "container" , "data" }}, true}, |
380 | })), |
381 | UseAliasInSpellingCode)); |
382 | } |
383 | { |
384 | // FIXME: We should be able to see-through aliases. |
385 | SCOPED_TRACE("std cont data" ); |
386 | EXPECT_TRUE(tooling::runToolOnCode( |
387 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
388 | {{{"std" , "cont" , "data" }}, false}, |
389 | })), |
390 | UseAliasInSpellingCode)); |
391 | } |
392 | } |
393 | |
394 | // Test if the code spells the struct name, wile we match against the struct |
395 | // name, and again matching against the alias. |
396 | { |
397 | SCOPED_TRACE("Using struct name in spelling" ); |
398 | { |
399 | SCOPED_TRACE("std container data" ); |
400 | EXPECT_TRUE(tooling::runToolOnCode( |
401 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
402 | {{{"std" , "container" , "data" }}, true}, |
403 | })), |
404 | UseAliasInSpellingCode)); |
405 | } |
406 | { |
407 | // FIXME: We should be able to see-through aliases. |
408 | SCOPED_TRACE("std cont data" ); |
409 | EXPECT_TRUE(tooling::runToolOnCode( |
410 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
411 | {{{"std" , "cont" , "data" }}, false}, |
412 | })), |
413 | UseAliasInSpellingCode)); |
414 | } |
415 | } |
416 | } |
417 | |
418 | TEST(CallDescription, AliasSingleNamespace) { |
419 | constexpr StringRef Code = R"code( |
420 | namespace aaa { |
421 | namespace bbb { |
422 | namespace ccc { |
423 | void bar(); |
424 | }} // namespace bbb::ccc |
425 | namespace bbb_alias = bbb; |
426 | } // namespace aaa |
427 | void foo() { |
428 | aaa::bbb_alias::ccc::bar(); |
429 | })code" ; |
430 | { |
431 | SCOPED_TRACE("aaa bbb ccc bar" ); |
432 | EXPECT_TRUE(tooling::runToolOnCode( |
433 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
434 | {{{"aaa" , "bbb" , "ccc" , "bar" }}, true}, |
435 | })), |
436 | Code)); |
437 | } |
438 | { |
439 | // FIXME: We should be able to see-through namespace aliases. |
440 | SCOPED_TRACE("aaa bbb_alias ccc bar" ); |
441 | EXPECT_TRUE(tooling::runToolOnCode( |
442 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
443 | {{{"aaa" , "bbb_alias" , "ccc" , "bar" }}, false}, |
444 | })), |
445 | Code)); |
446 | } |
447 | } |
448 | |
449 | TEST(CallDescription, AliasMultipleNamespaces) { |
450 | constexpr StringRef Code = R"code( |
451 | namespace aaa { |
452 | namespace bbb { |
453 | namespace ccc { |
454 | void bar(); |
455 | }}} // namespace aaa::bbb::ccc |
456 | namespace aaa_bbb_ccc = aaa::bbb::ccc; |
457 | void foo() { |
458 | using namespace aaa_bbb_ccc; |
459 | bar(); |
460 | })code" ; |
461 | { |
462 | SCOPED_TRACE("aaa bbb ccc bar" ); |
463 | EXPECT_TRUE(tooling::runToolOnCode( |
464 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
465 | {{{"aaa" , "bbb" , "ccc" , "bar" }}, true}, |
466 | })), |
467 | Code)); |
468 | } |
469 | { |
470 | // FIXME: We should be able to see-through namespace aliases. |
471 | SCOPED_TRACE("aaa_bbb_ccc bar" ); |
472 | EXPECT_TRUE(tooling::runToolOnCode( |
473 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
474 | {{{"aaa_bbb_ccc" , "bar" }}, false}, |
475 | })), |
476 | Code)); |
477 | } |
478 | } |
479 | |
480 | TEST(CallDescription, NegativeMatchQualifiedNames) { |
481 | EXPECT_TRUE(tooling::runToolOnCode( |
482 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ |
483 | {{{"foo" , "bar" }}, false}, |
484 | {{{"bar" , "foo" }}, false}, |
485 | {{{"foo" }}, true}, |
486 | })), |
487 | "void foo(); struct bar { void foo(); }; void test() { foo(); }" )); |
488 | } |
489 | |
490 | TEST(CallDescription, MatchBuiltins) { |
491 | // Test the matching modes CDM::CLibrary and CDM::CLibraryMaybeHardened, |
492 | // which can recognize builtin variants of C library functions. |
493 | { |
494 | SCOPED_TRACE("hardened variants of functions" ); |
495 | EXPECT_TRUE(tooling::runToolOnCode( |
496 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( |
497 | {{{CDM::Unspecified, {"memset" }, 3}, false}, |
498 | {{CDM::CLibrary, {"memset" }, 3}, false}, |
499 | {{CDM::CLibraryMaybeHardened, {"memset" }, 3}, true}})), |
500 | "void foo() {" |
501 | " int x;" |
502 | " __builtin___memset_chk(&x, 0, sizeof(x)," |
503 | " __builtin_object_size(&x, 0));" |
504 | "}" )); |
505 | } |
506 | { |
507 | SCOPED_TRACE("multiple similar builtins" ); |
508 | EXPECT_TRUE(tooling::runToolOnCode( |
509 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( |
510 | {{{CDM::CLibrary, {"memcpy" }, 3}, false}, |
511 | {{CDM::CLibrary, {"wmemcpy" }, 3}, true}})), |
512 | R"(void foo(wchar_t *x, wchar_t *y) { |
513 | __builtin_wmemcpy(x, y, sizeof(wchar_t)); |
514 | })" )); |
515 | } |
516 | { |
517 | SCOPED_TRACE("multiple similar builtins reversed order" ); |
518 | EXPECT_TRUE(tooling::runToolOnCode( |
519 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( |
520 | {{{CDM::CLibrary, {"wmemcpy" }, 3}, true}, |
521 | {{CDM::CLibrary, {"memcpy" }, 3}, false}})), |
522 | R"(void foo(wchar_t *x, wchar_t *y) { |
523 | __builtin_wmemcpy(x, y, sizeof(wchar_t)); |
524 | })" )); |
525 | } |
526 | { |
527 | SCOPED_TRACE("multiple similar builtins with hardened variant" ); |
528 | EXPECT_TRUE(tooling::runToolOnCode( |
529 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( |
530 | {{{CDM::CLibraryMaybeHardened, {"memcpy" }, 3}, false}, |
531 | {{CDM::CLibraryMaybeHardened, {"wmemcpy" }, 3}, true}})), |
532 | R"(typedef __typeof(sizeof(int)) size_t; |
533 | extern wchar_t *__wmemcpy_chk (wchar_t *__restrict __s1, |
534 | const wchar_t *__restrict __s2, |
535 | size_t __n, size_t __ns1); |
536 | void foo(wchar_t *x, wchar_t *y) { |
537 | __wmemcpy_chk(x, y, sizeof(wchar_t), 1234); |
538 | })" )); |
539 | } |
540 | { |
541 | SCOPED_TRACE( |
542 | "multiple similar builtins with hardened variant reversed order" ); |
543 | EXPECT_TRUE(tooling::runToolOnCode( |
544 | std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( |
545 | {{{CDM::CLibraryMaybeHardened, {"wmemcpy" }, 3}, true}, |
546 | {{CDM::CLibraryMaybeHardened, {"memcpy" }, 3}, false}})), |
547 | R"(typedef __typeof(sizeof(int)) size_t; |
548 | extern wchar_t *__wmemcpy_chk (wchar_t *__restrict __s1, |
549 | const wchar_t *__restrict __s2, |
550 | size_t __n, size_t __ns1); |
551 | void foo(wchar_t *x, wchar_t *y) { |
552 | __wmemcpy_chk(x, y, sizeof(wchar_t), 1234); |
553 | })" )); |
554 | } |
555 | { |
556 | SCOPED_TRACE("lookbehind and lookahead mismatches" ); |
557 | EXPECT_TRUE(tooling::runToolOnCode( |
558 | std::unique_ptr<FrontendAction>( |
559 | new CallDescriptionAction<>({{{CDM::CLibrary, {"func" }}, false}})), |
560 | R"( |
561 | void funcXXX(); |
562 | void XXXfunc(); |
563 | void XXXfuncXXX(); |
564 | void test() { |
565 | funcXXX(); |
566 | XXXfunc(); |
567 | XXXfuncXXX(); |
568 | })" )); |
569 | } |
570 | { |
571 | SCOPED_TRACE("lookbehind and lookahead matches" ); |
572 | EXPECT_TRUE(tooling::runToolOnCode( |
573 | std::unique_ptr<FrontendAction>( |
574 | new CallDescriptionAction<>({{{CDM::CLibrary, {"func" }}, true}})), |
575 | R"( |
576 | void func(); |
577 | void func_XXX(); |
578 | void XXX_func(); |
579 | void XXX_func_XXX(); |
580 | |
581 | void test() { |
582 | func(); // exact match |
583 | func_XXX(); |
584 | XXX_func(); |
585 | XXX_func_XXX(); |
586 | })" )); |
587 | } |
588 | } |
589 | |
590 | //===----------------------------------------------------------------------===// |
591 | // Testing through a checker interface. |
592 | // |
593 | // Above, the static analyzer isn't run properly, only the bare minimum to |
594 | // create CallEvents. This causes CallEvents through function pointers to not |
595 | // refer to the pointee function, but this works fine if we run |
596 | // AnalysisASTConsumer. |
597 | //===----------------------------------------------------------------------===// |
598 | |
599 | class CallDescChecker |
600 | : public Checker<check::PreCall, check::PreStmt<CallExpr>> { |
601 | CallDescriptionSet Set = {{{"bar" }, 0}}; |
602 | |
603 | public: |
604 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const { |
605 | if (Set.contains(Call)) { |
606 | C.getBugReporter().EmitBasicReport( |
607 | DeclWithIssue: Call.getDecl(), Checker: this, BugName: "CallEvent match" , BugCategory: categories::LogicError, |
608 | BugStr: "CallEvent match" , |
609 | Loc: PathDiagnosticLocation{Call.getDecl(), C.getSourceManager()}); |
610 | } |
611 | } |
612 | |
613 | void checkPreStmt(const CallExpr *CE, CheckerContext &C) const { |
614 | if (Set.containsAsWritten(CE: *CE)) { |
615 | C.getBugReporter().EmitBasicReport( |
616 | DeclWithIssue: CE->getCalleeDecl(), Checker: this, BugName: "CallExpr match" , BugCategory: categories::LogicError, |
617 | BugStr: "CallExpr match" , |
618 | Loc: PathDiagnosticLocation{CE->getCalleeDecl(), C.getSourceManager()}); |
619 | } |
620 | } |
621 | }; |
622 | |
623 | void addCallDescChecker(AnalysisASTConsumer &AnalysisConsumer, |
624 | AnalyzerOptions &AnOpts) { |
625 | AnOpts.CheckersAndPackages = {{"test.CallDescChecker" , true}}; |
626 | AnalysisConsumer.AddCheckerRegistrationFn(Fn: [](CheckerRegistry &Registry) { |
627 | Registry.addChecker<CallDescChecker>(FullName: "test.CallDescChecker" , Desc: "Description" , |
628 | DocsUri: "" ); |
629 | }); |
630 | } |
631 | |
632 | TEST(CallDescription, CheckCallExprMatching) { |
633 | // Imprecise matching shouldn't catch the call to bar, because its obscured |
634 | // by a function pointer. |
635 | constexpr StringRef FnPtrCode = R"code( |
636 | void bar(); |
637 | void foo() { |
638 | void (*fnptr)() = bar; |
639 | fnptr(); |
640 | })code" ; |
641 | std::string Diags; |
642 | EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(FnPtrCode.str(), Diags, |
643 | /*OnlyEmitWarnings*/ true)); |
644 | EXPECT_EQ("test.CallDescChecker: CallEvent match\n" , Diags); |
645 | |
646 | // This should be caught properly by imprecise matching, as the call is done |
647 | // purely through syntactic means. |
648 | constexpr StringRef Code = R"code( |
649 | void bar(); |
650 | void foo() { |
651 | bar(); |
652 | })code" ; |
653 | Diags.clear(); |
654 | EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(Code.str(), Diags, |
655 | /*OnlyEmitWarnings*/ true)); |
656 | EXPECT_EQ("test.CallDescChecker: CallEvent match\n" |
657 | "test.CallDescChecker: CallExpr match\n" , |
658 | Diags); |
659 | } |
660 | |
661 | } // namespace |
662 | } // namespace ento |
663 | } // namespace clang |
664 | |