xref: /llvm-project/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp (revision 72d04d7b2b53eea977e160551077cf1a3f51ba9a)
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 "Reusables.h"
10 
11 #include "clang/AST/ExprCXX.h"
12 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
13 #include "clang/Tooling/Tooling.h"
14 #include "gtest/gtest.h"
15 #include <type_traits>
16 
17 namespace clang {
18 namespace ento {
19 namespace {
20 
21 // A wrapper around CallDescriptionMap<bool> that allows verifying that
22 // all functions have been found. This is needed because CallDescriptionMap
23 // isn't supposed to support iteration.
24 class ResultMap {
25   size_t Found, Total;
26   CallDescriptionMap<bool> Impl;
27 
28 public:
29   ResultMap(std::initializer_list<std::pair<CallDescription, bool>> Data)
30       : Found(0),
31         Total(std::count_if(Data.begin(), Data.end(),
32                             [](const std::pair<CallDescription, bool> &Pair) {
33                               return Pair.second == true;
34                             })),
35         Impl(std::move(Data)) {}
36 
37   const bool *lookup(const CallEvent &Call) {
38     const bool *Result = Impl.lookup(Call);
39     // If it's a function we expected to find, remember that we've found it.
40     if (Result && *Result)
41       ++Found;
42     return Result;
43   }
44 
45   // Fail the test if we haven't found all the true-calls we were looking for.
46   ~ResultMap() { EXPECT_EQ(Found, Total); }
47 };
48 
49 // Scan the code body for call expressions and see if we find all calls that
50 // we were supposed to find ("true" in the provided ResultMap) and that we
51 // don't find the ones that we weren't supposed to find
52 // ("false" in the ResultMap).
53 template <typename MatchedExprT>
54 class CallDescriptionConsumer : public ExprEngineConsumer {
55   ResultMap &RM;
56   void performTest(const Decl *D) {
57     using namespace ast_matchers;
58     using T = MatchedExprT;
59 
60     if (!D->hasBody())
61       return;
62 
63     const StackFrameContext *SFC =
64         Eng.getAnalysisDeclContextManager().getStackFrame(D);
65     const ProgramStateRef State = Eng.getInitialState(SFC);
66 
67     // FIXME: Maybe use std::variant and std::visit for these.
68     const auto MatcherCreator = []() {
69       if (std::is_same<T, CallExpr>::value)
70         return callExpr();
71       if (std::is_same<T, CXXConstructExpr>::value)
72         return cxxConstructExpr();
73       if (std::is_same<T, CXXMemberCallExpr>::value)
74         return cxxMemberCallExpr();
75       if (std::is_same<T, CXXOperatorCallExpr>::value)
76         return cxxOperatorCallExpr();
77       llvm_unreachable("Only these expressions are supported for now.");
78     };
79 
80     const Expr *E = findNode<T>(D, MatcherCreator());
81 
82     CallEventManager &CEMgr = Eng.getStateManager().getCallEventManager();
83     CallEventRef<> Call = [=, &CEMgr]() -> CallEventRef<CallEvent> {
84       if (std::is_base_of<CallExpr, T>::value)
85         return CEMgr.getCall(E, State, SFC);
86       if (std::is_same<T, CXXConstructExpr>::value)
87         return CEMgr.getCXXConstructorCall(cast<CXXConstructExpr>(E),
88                                            /*Target=*/nullptr, State, SFC);
89       llvm_unreachable("Only these expressions are supported for now.");
90     }();
91 
92     // If the call actually matched, check if we really expected it to match.
93     const bool *LookupResult = RM.lookup(*Call);
94     EXPECT_TRUE(!LookupResult || *LookupResult);
95 
96     // ResultMap is responsible for making sure that we've found *all* calls.
97   }
98 
99 public:
100   CallDescriptionConsumer(CompilerInstance &C,
101                           ResultMap &RM)
102       : ExprEngineConsumer(C), RM(RM) {}
103 
104   bool HandleTopLevelDecl(DeclGroupRef DG) override {
105     for (const auto *D : DG)
106       performTest(D);
107     return true;
108   }
109 };
110 
111 template <typename MatchedExprT = CallExpr>
112 class CallDescriptionAction : public ASTFrontendAction {
113   ResultMap RM;
114 
115 public:
116   CallDescriptionAction(
117       std::initializer_list<std::pair<CallDescription, bool>> Data)
118       : RM(Data) {}
119 
120   std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
121                                                  StringRef File) override {
122     return std::make_unique<CallDescriptionConsumer<MatchedExprT>>(Compiler,
123                                                                    RM);
124   }
125 };
126 
127 TEST(CallDescription, SimpleNameMatching) {
128   EXPECT_TRUE(tooling::runToolOnCode(
129       std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
130           {{"bar"}, false}, // false: there's no call to 'bar' in this code.
131           {{"foo"}, true},  // true: there's a call to 'foo' in this code.
132       })),
133       "void foo(); void bar() { foo(); }"));
134 }
135 
136 TEST(CallDescription, RequiredArguments) {
137   EXPECT_TRUE(tooling::runToolOnCode(
138       std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
139           {{"foo", 1}, true},
140           {{"foo", 2}, false},
141       })),
142       "void foo(int); void foo(int, int); void bar() { foo(1); }"));
143 }
144 
145 TEST(CallDescription, LackOfRequiredArguments) {
146   EXPECT_TRUE(tooling::runToolOnCode(
147       std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
148           {{"foo", None}, true},
149           {{"foo", 2}, false},
150       })),
151       "void foo(int); void foo(int, int); void bar() { foo(1); }"));
152 }
153 
154 constexpr StringRef MockStdStringHeader = R"code(
155   namespace std { inline namespace __1 {
156     template<typename T> class basic_string {
157       class Allocator {};
158     public:
159       basic_string();
160       explicit basic_string(const char*, const Allocator & = Allocator());
161       ~basic_string();
162       T *c_str();
163     };
164   } // namespace __1
165   using string = __1::basic_string<char>;
166   } // namespace std
167 )code";
168 
169 TEST(CallDescription, QualifiedNames) {
170   constexpr StringRef AdditionalCode = R"code(
171     void foo() {
172       using namespace std;
173       basic_string<char> s;
174       s.c_str();
175     })code";
176   const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str();
177   EXPECT_TRUE(tooling::runToolOnCode(
178       std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
179           {{{"std", "basic_string", "c_str"}}, true},
180       })),
181       Code));
182 }
183 
184 TEST(CallDescription, MatchConstructor) {
185   constexpr StringRef AdditionalCode = R"code(
186     void foo() {
187       using namespace std;
188       basic_string<char> s("hello");
189     })code";
190   const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str();
191   EXPECT_TRUE(tooling::runToolOnCode(
192       std::unique_ptr<FrontendAction>(
193           new CallDescriptionAction<CXXConstructExpr>({
194               {{{"std", "basic_string", "basic_string"}, 2, 2}, true},
195           })),
196       Code));
197 }
198 
199 // FIXME: Test matching destructors: {"std", "basic_string", "~basic_string"}
200 //        This feature is actually implemented, but the test infra is not yet
201 //        sophisticated enough for testing this. To do that, we will need to
202 //        implement a much more advanced dispatching mechanism using the CFG for
203 //        the implicit destructor events.
204 
205 TEST(CallDescription, MatchConversionOperator) {
206   constexpr StringRef Code = R"code(
207     namespace aaa {
208     namespace bbb {
209     struct Bar {
210       operator int();
211     };
212     } // bbb
213     } // aaa
214     void foo() {
215       aaa::bbb::Bar x;
216       int tmp = x;
217     })code";
218   EXPECT_TRUE(tooling::runToolOnCode(
219       std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
220           {{{"aaa", "bbb", "Bar", "operator int"}}, true},
221       })),
222       Code));
223 }
224 
225 TEST(CallDescription, RejectOverQualifiedNames) {
226   constexpr auto Code = R"code(
227     namespace my {
228     namespace std {
229       struct container {
230         const char *data() const;
231       };
232     } // namespace std
233     } // namespace my
234 
235     void foo() {
236       using namespace my;
237       std::container v;
238       v.data();
239     })code";
240 
241   // FIXME: We should **not** match.
242   EXPECT_TRUE(tooling::runToolOnCode(
243       std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
244           {{{"std", "container", "data"}}, true},
245       })),
246       Code));
247 }
248 
249 TEST(CallDescription, DontSkipNonInlineNamespaces) {
250   constexpr auto Code = R"code(
251     namespace my {
252     /*not inline*/ namespace v1 {
253       void bar();
254     } // namespace v1
255     } // namespace my
256     void foo() {
257       my::v1::bar();
258     })code";
259 
260   {
261     SCOPED_TRACE("my v1 bar");
262     EXPECT_TRUE(tooling::runToolOnCode(
263         std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
264             {{{"my", "v1", "bar"}}, true},
265         })),
266         Code));
267   }
268   {
269     // FIXME: We should **not** skip non-inline namespaces.
270     SCOPED_TRACE("my bar");
271     EXPECT_TRUE(tooling::runToolOnCode(
272         std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
273             {{{"my", "bar"}}, true},
274         })),
275         Code));
276   }
277 }
278 
279 TEST(CallDescription, SkipTopInlineNamespaces) {
280   constexpr auto Code = R"code(
281     inline namespace my {
282     namespace v1 {
283       void bar();
284     } // namespace v1
285     } // namespace my
286     void foo() {
287       using namespace v1;
288       bar();
289     })code";
290 
291   {
292     SCOPED_TRACE("my v1 bar");
293     EXPECT_TRUE(tooling::runToolOnCode(
294         std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
295             {{{"my", "v1", "bar"}}, true},
296         })),
297         Code));
298   }
299   {
300     SCOPED_TRACE("v1 bar");
301     EXPECT_TRUE(tooling::runToolOnCode(
302         std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
303             {{{"v1", "bar"}}, true},
304         })),
305         Code));
306   }
307 }
308 
309 TEST(CallDescription, SkipAnonimousNamespaces) {
310   constexpr auto Code = R"code(
311     namespace {
312     namespace std {
313     namespace {
314     inline namespace {
315       struct container {
316         const char *data() const { return nullptr; };
317       };
318     } // namespace inline anonymous
319     } // namespace anonymous
320     } // namespace std
321     } // namespace anonymous
322 
323     void foo() {
324       std::container v;
325       v.data();
326     })code";
327 
328   EXPECT_TRUE(tooling::runToolOnCode(
329       std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
330           {{{"std", "container", "data"}}, true},
331       })),
332       Code));
333 }
334 
335 TEST(CallDescription, AliasNames) {
336   constexpr StringRef AliasNamesCode = R"code(
337   namespace std {
338     struct container {
339       const char *data() const;
340     };
341     using cont = container;
342   } // std
343 )code";
344 
345   constexpr StringRef UseAliasInSpelling = R"code(
346     void foo() {
347       std::cont v;
348       v.data();
349     })code";
350   constexpr StringRef UseStructNameInSpelling = R"code(
351     void foo() {
352       std::container v;
353       v.data();
354     })code";
355   const std::string UseAliasInSpellingCode =
356       (Twine{AliasNamesCode} + UseAliasInSpelling).str();
357   const std::string UseStructNameInSpellingCode =
358       (Twine{AliasNamesCode} + UseStructNameInSpelling).str();
359 
360   // Test if the code spells the alias, wile we match against the struct name,
361   // and again matching against the alias.
362   {
363     SCOPED_TRACE("Using alias in spelling");
364     {
365       SCOPED_TRACE("std container data");
366       EXPECT_TRUE(tooling::runToolOnCode(
367           std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
368               {{{"std", "container", "data"}}, true},
369           })),
370           UseAliasInSpellingCode));
371     }
372     {
373       // FIXME: We should be able to see-through aliases.
374       SCOPED_TRACE("std cont data");
375       EXPECT_TRUE(tooling::runToolOnCode(
376           std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
377               {{{"std", "cont", "data"}}, false},
378           })),
379           UseAliasInSpellingCode));
380     }
381   }
382 
383   // Test if the code spells the struct name, wile we match against the struct
384   // name, and again matching against the alias.
385   {
386     SCOPED_TRACE("Using struct name in spelling");
387     {
388       SCOPED_TRACE("std container data");
389       EXPECT_TRUE(tooling::runToolOnCode(
390           std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
391               {{{"std", "container", "data"}}, true},
392           })),
393           UseAliasInSpellingCode));
394     }
395     {
396       // FIXME: We should be able to see-through aliases.
397       SCOPED_TRACE("std cont data");
398       EXPECT_TRUE(tooling::runToolOnCode(
399           std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
400               {{{"std", "cont", "data"}}, false},
401           })),
402           UseAliasInSpellingCode));
403     }
404   }
405 }
406 
407 TEST(CallDescription, AliasSingleNamespace) {
408   constexpr StringRef Code = R"code(
409     namespace aaa {
410     namespace bbb {
411     namespace ccc {
412       void bar();
413     }} // namespace bbb::ccc
414     namespace bbb_alias = bbb;
415     } // namespace aaa
416     void foo() {
417       aaa::bbb_alias::ccc::bar();
418     })code";
419   {
420     SCOPED_TRACE("aaa bbb ccc bar");
421     EXPECT_TRUE(tooling::runToolOnCode(
422         std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
423             {{{"aaa", "bbb", "ccc", "bar"}}, true},
424         })),
425         Code));
426   }
427   {
428     // FIXME: We should be able to see-through namespace aliases.
429     SCOPED_TRACE("aaa bbb_alias ccc bar");
430     EXPECT_TRUE(tooling::runToolOnCode(
431         std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
432             {{{"aaa", "bbb_alias", "ccc", "bar"}}, false},
433         })),
434         Code));
435   }
436 }
437 
438 TEST(CallDescription, AliasMultipleNamespaces) {
439   constexpr StringRef Code = R"code(
440     namespace aaa {
441     namespace bbb {
442     namespace ccc {
443       void bar();
444     }}} // namespace aaa::bbb::ccc
445     namespace aaa_bbb_ccc = aaa::bbb::ccc;
446     void foo() {
447       using namespace aaa_bbb_ccc;
448       bar();
449     })code";
450   {
451     SCOPED_TRACE("aaa bbb ccc bar");
452     EXPECT_TRUE(tooling::runToolOnCode(
453         std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
454             {{{"aaa", "bbb", "ccc", "bar"}}, true},
455         })),
456         Code));
457   }
458   {
459     // FIXME: We should be able to see-through namespace aliases.
460     SCOPED_TRACE("aaa_bbb_ccc bar");
461     EXPECT_TRUE(tooling::runToolOnCode(
462         std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
463             {{{"aaa_bbb_ccc", "bar"}}, false},
464         })),
465         Code));
466   }
467 }
468 
469 TEST(CallDescription, NegativeMatchQualifiedNames) {
470   EXPECT_TRUE(tooling::runToolOnCode(
471       std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({
472           {{{"foo", "bar"}}, false},
473           {{{"bar", "foo"}}, false},
474           {{"foo"}, true},
475       })),
476       "void foo(); struct bar { void foo(); }; void test() { foo(); }"));
477 }
478 
479 TEST(CallDescription, MatchBuiltins) {
480   // Test CDF_MaybeBuiltin - a flag that allows matching weird builtins.
481   EXPECT_TRUE(tooling::runToolOnCode(
482       std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(
483           {{{"memset", 3}, false}, {{CDF_MaybeBuiltin, "memset", 3}, true}})),
484       "void foo() {"
485       "  int x;"
486       "  __builtin___memset_chk(&x, 0, sizeof(x),"
487       "                         __builtin_object_size(&x, 0));"
488       "}"));
489 }
490 
491 } // namespace
492 } // namespace ento
493 } // namespace clang
494