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:
ResultMap(std::initializer_list<std::pair<CallDescription,bool>> Data)37 ResultMap(std::initializer_list<std::pair<CallDescription, bool>> Data)
38 : Found(0),
39 Total(std::count_if(Data.begin(), Data.end(),
40 [](const std::pair<CallDescription, bool> &Pair) {
41 return Pair.second == true;
42 })),
43 Impl(std::move(Data)) {}
44
lookup(const CallEvent & Call)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.
~ResultMap()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;
performTest(const Decl * D)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 if (std::is_base_of<CallExpr, T>::value)
93 return CEMgr.getCall(E, State, SFC);
94 if (std::is_same<T, CXXConstructExpr>::value)
95 return CEMgr.getCXXConstructorCall(cast<CXXConstructExpr>(E),
96 /*Target=*/nullptr, State, SFC);
97 llvm_unreachable("Only these expressions are supported for now.");
98 }();
99
100 // If the call actually matched, check if we really expected it to match.
101 const bool *LookupResult = RM.lookup(*Call);
102 EXPECT_TRUE(!LookupResult || *LookupResult);
103
104 // ResultMap is responsible for making sure that we've found *all* calls.
105 }
106
107 public:
CallDescriptionConsumer(CompilerInstance & C,ResultMap & RM)108 CallDescriptionConsumer(CompilerInstance &C,
109 ResultMap &RM)
110 : ExprEngineConsumer(C), RM(RM) {}
111
HandleTopLevelDecl(DeclGroupRef DG)112 bool HandleTopLevelDecl(DeclGroupRef DG) override {
113 for (const auto *D : DG)
114 performTest(D);
115 return true;
116 }
117 };
118
119 template <typename MatchedExprT = CallExpr>
120 class CallDescriptionAction : public ASTFrontendAction {
121 ResultMap RM;
122
123 public:
CallDescriptionAction(std::initializer_list<std::pair<CallDescription,bool>> Data)124 CallDescriptionAction(
125 std::initializer_list<std::pair<CallDescription, bool>> Data)
126 : RM(Data) {}
127
CreateASTConsumer(CompilerInstance & Compiler,StringRef File)128 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
129 StringRef File) override {
130 return std::make_unique<CallDescriptionConsumer<MatchedExprT>>(Compiler,
131 RM);
132 }
133 };
134
TEST(CallDescription,SimpleNameMatching)135 TEST(CallDescription, SimpleNameMatching) {
136 EXPECT_TRUE(tooling::runToolOnCode(
137 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
138 {{"bar"}, false}, // false: there's no call to 'bar' in this code.
139 {{"foo"}, true}, // true: there's a call to 'foo' in this code.
140 })),
141 "void foo(); void bar() { foo(); }"));
142 }
143
TEST(CallDescription,RequiredArguments)144 TEST(CallDescription, RequiredArguments) {
145 EXPECT_TRUE(tooling::runToolOnCode(
146 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
147 {{"foo", 1}, true},
148 {{"foo", 2}, false},
149 })),
150 "void foo(int); void foo(int, int); void bar() { foo(1); }"));
151 }
152
TEST(CallDescription,LackOfRequiredArguments)153 TEST(CallDescription, LackOfRequiredArguments) {
154 EXPECT_TRUE(tooling::runToolOnCode(
155 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
156 {{"foo", None}, true},
157 {{"foo", 2}, false},
158 })),
159 "void foo(int); void foo(int, int); void bar() { foo(1); }"));
160 }
161
162 constexpr StringRef MockStdStringHeader = R"code(
163 namespace std { inline namespace __1 {
164 template<typename T> class basic_string {
165 class Allocator {};
166 public:
167 basic_string();
168 explicit basic_string(const char*, const Allocator & = Allocator());
169 ~basic_string();
170 T *c_str();
171 };
172 } // namespace __1
173 using string = __1::basic_string<char>;
174 } // namespace std
175 )code";
176
TEST(CallDescription,QualifiedNames)177 TEST(CallDescription, QualifiedNames) {
178 constexpr StringRef AdditionalCode = R"code(
179 void foo() {
180 using namespace std;
181 basic_string<char> s;
182 s.c_str();
183 })code";
184 const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str();
185 EXPECT_TRUE(tooling::runToolOnCode(
186 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
187 {{{"std", "basic_string", "c_str"}}, true},
188 })),
189 Code));
190 }
191
TEST(CallDescription,MatchConstructor)192 TEST(CallDescription, MatchConstructor) {
193 constexpr StringRef AdditionalCode = R"code(
194 void foo() {
195 using namespace std;
196 basic_string<char> s("hello");
197 })code";
198 const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str();
199 EXPECT_TRUE(tooling::runToolOnCode(
200 std::unique_ptr<FrontendAction>(
201 new CallDescriptionAction<CXXConstructExpr>({
202 {{{"std", "basic_string", "basic_string"}, 2, 2}, true},
203 })),
204 Code));
205 }
206
207 // FIXME: Test matching destructors: {"std", "basic_string", "~basic_string"}
208 // This feature is actually implemented, but the test infra is not yet
209 // sophisticated enough for testing this. To do that, we will need to
210 // implement a much more advanced dispatching mechanism using the CFG for
211 // the implicit destructor events.
212
TEST(CallDescription,MatchConversionOperator)213 TEST(CallDescription, MatchConversionOperator) {
214 constexpr StringRef Code = R"code(
215 namespace aaa {
216 namespace bbb {
217 struct Bar {
218 operator int();
219 };
220 } // bbb
221 } // aaa
222 void foo() {
223 aaa::bbb::Bar x;
224 int tmp = x;
225 })code";
226 EXPECT_TRUE(tooling::runToolOnCode(
227 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
228 {{{"aaa", "bbb", "Bar", "operator int"}}, true},
229 })),
230 Code));
231 }
232
TEST(CallDescription,RejectOverQualifiedNames)233 TEST(CallDescription, RejectOverQualifiedNames) {
234 constexpr auto Code = R"code(
235 namespace my {
236 namespace std {
237 struct container {
238 const char *data() const;
239 };
240 } // namespace std
241 } // namespace my
242
243 void foo() {
244 using namespace my;
245 std::container v;
246 v.data();
247 })code";
248
249 // FIXME: We should **not** match.
250 EXPECT_TRUE(tooling::runToolOnCode(
251 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
252 {{{"std", "container", "data"}}, true},
253 })),
254 Code));
255 }
256
TEST(CallDescription,DontSkipNonInlineNamespaces)257 TEST(CallDescription, DontSkipNonInlineNamespaces) {
258 constexpr auto Code = R"code(
259 namespace my {
260 /*not inline*/ namespace v1 {
261 void bar();
262 } // namespace v1
263 } // namespace my
264 void foo() {
265 my::v1::bar();
266 })code";
267
268 {
269 SCOPED_TRACE("my v1 bar");
270 EXPECT_TRUE(tooling::runToolOnCode(
271 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
272 {{{"my", "v1", "bar"}}, true},
273 })),
274 Code));
275 }
276 {
277 // FIXME: We should **not** skip non-inline namespaces.
278 SCOPED_TRACE("my bar");
279 EXPECT_TRUE(tooling::runToolOnCode(
280 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
281 {{{"my", "bar"}}, true},
282 })),
283 Code));
284 }
285 }
286
TEST(CallDescription,SkipTopInlineNamespaces)287 TEST(CallDescription, SkipTopInlineNamespaces) {
288 constexpr auto Code = R"code(
289 inline namespace my {
290 namespace v1 {
291 void bar();
292 } // namespace v1
293 } // namespace my
294 void foo() {
295 using namespace v1;
296 bar();
297 })code";
298
299 {
300 SCOPED_TRACE("my v1 bar");
301 EXPECT_TRUE(tooling::runToolOnCode(
302 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
303 {{{"my", "v1", "bar"}}, true},
304 })),
305 Code));
306 }
307 {
308 SCOPED_TRACE("v1 bar");
309 EXPECT_TRUE(tooling::runToolOnCode(
310 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
311 {{{"v1", "bar"}}, true},
312 })),
313 Code));
314 }
315 }
316
TEST(CallDescription,SkipAnonimousNamespaces)317 TEST(CallDescription, SkipAnonimousNamespaces) {
318 constexpr auto Code = R"code(
319 namespace {
320 namespace std {
321 namespace {
322 inline namespace {
323 struct container {
324 const char *data() const { return nullptr; };
325 };
326 } // namespace inline anonymous
327 } // namespace anonymous
328 } // namespace std
329 } // namespace anonymous
330
331 void foo() {
332 std::container v;
333 v.data();
334 })code";
335
336 EXPECT_TRUE(tooling::runToolOnCode(
337 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
338 {{{"std", "container", "data"}}, true},
339 })),
340 Code));
341 }
342
TEST(CallDescription,AliasNames)343 TEST(CallDescription, AliasNames) {
344 constexpr StringRef AliasNamesCode = R"code(
345 namespace std {
346 struct container {
347 const char *data() const;
348 };
349 using cont = container;
350 } // std
351 )code";
352
353 constexpr StringRef UseAliasInSpelling = R"code(
354 void foo() {
355 std::cont v;
356 v.data();
357 })code";
358 constexpr StringRef UseStructNameInSpelling = R"code(
359 void foo() {
360 std::container v;
361 v.data();
362 })code";
363 const std::string UseAliasInSpellingCode =
364 (Twine{AliasNamesCode} + UseAliasInSpelling).str();
365 const std::string UseStructNameInSpellingCode =
366 (Twine{AliasNamesCode} + UseStructNameInSpelling).str();
367
368 // Test if the code spells the alias, wile we match against the struct name,
369 // and again matching against the alias.
370 {
371 SCOPED_TRACE("Using alias in spelling");
372 {
373 SCOPED_TRACE("std container data");
374 EXPECT_TRUE(tooling::runToolOnCode(
375 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
376 {{{"std", "container", "data"}}, true},
377 })),
378 UseAliasInSpellingCode));
379 }
380 {
381 // FIXME: We should be able to see-through aliases.
382 SCOPED_TRACE("std cont data");
383 EXPECT_TRUE(tooling::runToolOnCode(
384 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
385 {{{"std", "cont", "data"}}, false},
386 })),
387 UseAliasInSpellingCode));
388 }
389 }
390
391 // Test if the code spells the struct name, wile we match against the struct
392 // name, and again matching against the alias.
393 {
394 SCOPED_TRACE("Using struct name in spelling");
395 {
396 SCOPED_TRACE("std container data");
397 EXPECT_TRUE(tooling::runToolOnCode(
398 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
399 {{{"std", "container", "data"}}, true},
400 })),
401 UseAliasInSpellingCode));
402 }
403 {
404 // FIXME: We should be able to see-through aliases.
405 SCOPED_TRACE("std cont data");
406 EXPECT_TRUE(tooling::runToolOnCode(
407 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
408 {{{"std", "cont", "data"}}, false},
409 })),
410 UseAliasInSpellingCode));
411 }
412 }
413 }
414
TEST(CallDescription,AliasSingleNamespace)415 TEST(CallDescription, AliasSingleNamespace) {
416 constexpr StringRef Code = R"code(
417 namespace aaa {
418 namespace bbb {
419 namespace ccc {
420 void bar();
421 }} // namespace bbb::ccc
422 namespace bbb_alias = bbb;
423 } // namespace aaa
424 void foo() {
425 aaa::bbb_alias::ccc::bar();
426 })code";
427 {
428 SCOPED_TRACE("aaa bbb ccc bar");
429 EXPECT_TRUE(tooling::runToolOnCode(
430 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
431 {{{"aaa", "bbb", "ccc", "bar"}}, true},
432 })),
433 Code));
434 }
435 {
436 // FIXME: We should be able to see-through namespace aliases.
437 SCOPED_TRACE("aaa bbb_alias ccc bar");
438 EXPECT_TRUE(tooling::runToolOnCode(
439 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
440 {{{"aaa", "bbb_alias", "ccc", "bar"}}, false},
441 })),
442 Code));
443 }
444 }
445
TEST(CallDescription,AliasMultipleNamespaces)446 TEST(CallDescription, AliasMultipleNamespaces) {
447 constexpr StringRef Code = R"code(
448 namespace aaa {
449 namespace bbb {
450 namespace ccc {
451 void bar();
452 }}} // namespace aaa::bbb::ccc
453 namespace aaa_bbb_ccc = aaa::bbb::ccc;
454 void foo() {
455 using namespace aaa_bbb_ccc;
456 bar();
457 })code";
458 {
459 SCOPED_TRACE("aaa bbb ccc bar");
460 EXPECT_TRUE(tooling::runToolOnCode(
461 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
462 {{{"aaa", "bbb", "ccc", "bar"}}, true},
463 })),
464 Code));
465 }
466 {
467 // FIXME: We should be able to see-through namespace aliases.
468 SCOPED_TRACE("aaa_bbb_ccc bar");
469 EXPECT_TRUE(tooling::runToolOnCode(
470 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
471 {{{"aaa_bbb_ccc", "bar"}}, false},
472 })),
473 Code));
474 }
475 }
476
TEST(CallDescription,NegativeMatchQualifiedNames)477 TEST(CallDescription, NegativeMatchQualifiedNames) {
478 EXPECT_TRUE(tooling::runToolOnCode(
479 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
480 {{{"foo", "bar"}}, false},
481 {{{"bar", "foo"}}, false},
482 {{"foo"}, true},
483 })),
484 "void foo(); struct bar { void foo(); }; void test() { foo(); }"));
485 }
486
TEST(CallDescription,MatchBuiltins)487 TEST(CallDescription, MatchBuiltins) {
488 // Test CDF_MaybeBuiltin - a flag that allows matching weird builtins.
489 EXPECT_TRUE(tooling::runToolOnCode(
490 std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
491 {{{"memset", 3}, false}, {{CDF_MaybeBuiltin, "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 {{{CDF_MaybeBuiltin, "memcpy", 3}, false},
503 {{CDF_MaybeBuiltin, "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 {{{CDF_MaybeBuiltin, "wmemcpy", 3}, true},
513 {{CDF_MaybeBuiltin, "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("lookbehind and lookahead mismatches");
520 EXPECT_TRUE(tooling::runToolOnCode(
521 std::unique_ptr<FrontendAction>(
522 new CallDescriptionAction<>({{{CDF_MaybeBuiltin, "func"}, false}})),
523 R"(
524 void funcXXX();
525 void XXXfunc();
526 void XXXfuncXXX();
527 void test() {
528 funcXXX();
529 XXXfunc();
530 XXXfuncXXX();
531 })"));
532 }
533 {
534 SCOPED_TRACE("lookbehind and lookahead matches");
535 EXPECT_TRUE(tooling::runToolOnCode(
536 std::unique_ptr<FrontendAction>(
537 new CallDescriptionAction<>({{{CDF_MaybeBuiltin, "func"}, true}})),
538 R"(
539 void func();
540 void func_XXX();
541 void XXX_func();
542 void XXX_func_XXX();
543
544 void test() {
545 func(); // exact match
546 func_XXX();
547 XXX_func();
548 XXX_func_XXX();
549 })"));
550 }
551 }
552
553 //===----------------------------------------------------------------------===//
554 // Testing through a checker interface.
555 //
556 // Above, the static analyzer isn't run properly, only the bare minimum to
557 // create CallEvents. This causes CallEvents through function pointers to not
558 // refer to the pointee function, but this works fine if we run
559 // AnalysisASTConsumer.
560 //===----------------------------------------------------------------------===//
561
562 class CallDescChecker
563 : public Checker<check::PreCall, check::PreStmt<CallExpr>> {
564 CallDescriptionSet Set = {{"bar", 0}};
565
566 public:
checkPreCall(const CallEvent & Call,CheckerContext & C) const567 void checkPreCall(const CallEvent &Call, CheckerContext &C) const {
568 if (Set.contains(Call)) {
569 C.getBugReporter().EmitBasicReport(
570 Call.getDecl(), this, "CallEvent match", categories::LogicError,
571 "CallEvent match",
572 PathDiagnosticLocation{Call.getDecl(), C.getSourceManager()});
573 }
574 }
575
checkPreStmt(const CallExpr * CE,CheckerContext & C) const576 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
577 if (Set.containsAsWritten(*CE)) {
578 C.getBugReporter().EmitBasicReport(
579 CE->getCalleeDecl(), this, "CallExpr match", categories::LogicError,
580 "CallExpr match",
581 PathDiagnosticLocation{CE->getCalleeDecl(), C.getSourceManager()});
582 }
583 }
584 };
585
addCallDescChecker(AnalysisASTConsumer & AnalysisConsumer,AnalyzerOptions & AnOpts)586 void addCallDescChecker(AnalysisASTConsumer &AnalysisConsumer,
587 AnalyzerOptions &AnOpts) {
588 AnOpts.CheckersAndPackages = {{"test.CallDescChecker", true}};
589 AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
590 Registry.addChecker<CallDescChecker>("test.CallDescChecker", "Description",
591 "");
592 });
593 }
594
TEST(CallDescription,CheckCallExprMatching)595 TEST(CallDescription, CheckCallExprMatching) {
596 // Imprecise matching shouldn't catch the call to bar, because its obscured
597 // by a function pointer.
598 constexpr StringRef FnPtrCode = R"code(
599 void bar();
600 void foo() {
601 void (*fnptr)() = bar;
602 fnptr();
603 })code";
604 std::string Diags;
605 EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(FnPtrCode.str(), Diags,
606 /*OnlyEmitWarnings*/ true));
607 EXPECT_EQ("test.CallDescChecker: CallEvent match\n", Diags);
608
609 // This should be caught properly by imprecise matching, as the call is done
610 // purely through syntactic means.
611 constexpr StringRef Code = R"code(
612 void bar();
613 void foo() {
614 bar();
615 })code";
616 Diags.clear();
617 EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(Code.str(), Diags,
618 /*OnlyEmitWarnings*/ true));
619 EXPECT_EQ("test.CallDescChecker: CallEvent match\n"
620 "test.CallDescChecker: CallExpr match\n",
621 Diags);
622 }
623
624 } // namespace
625 } // namespace ento
626 } // namespace clang
627