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