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