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