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