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/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
12 #include "clang/Tooling/Tooling.h"
13 #include "gtest/gtest.h"
14 
15 namespace clang {
16 namespace ento {
17 namespace {
18 
19 // A wrapper around CallDescriptionMap<bool> that allows verifying that
20 // all functions have been found. This is needed because CallDescriptionMap
21 // isn't supposed to support iteration.
22 class ResultMap {
23   size_t Found, Total;
24   CallDescriptionMap<bool> Impl;
25 
26 public:
27   ResultMap(std::initializer_list<std::pair<CallDescription, bool>> Data)
28       : Found(0),
29         Total(std::count_if(Data.begin(), Data.end(),
30                             [](const std::pair<CallDescription, bool> &Pair) {
31                               return Pair.second == true;
32                             })),
33         Impl(std::move(Data)) {}
34 
35   const bool *lookup(const CallEvent &Call) {
36     const bool *Result = Impl.lookup(Call);
37     // If it's a function we expected to find, remember that we've found it.
38     if (Result && *Result)
39       ++Found;
40     return Result;
41   }
42 
43   // Fail the test if we haven't found all the true-calls we were looking for.
44   ~ResultMap() { EXPECT_EQ(Found, Total); }
45 };
46 
47 // Scan the code body for call expressions and see if we find all calls that
48 // we were supposed to find ("true" in the provided ResultMap) and that we
49 // don't find the ones that we weren't supposed to find
50 // ("false" in the ResultMap).
51 class CallDescriptionConsumer : public ExprEngineConsumer {
52   ResultMap &RM;
53   void performTest(const Decl *D) {
54     using namespace ast_matchers;
55 
56     if (!D->hasBody())
57       return;
58 
59     const CallExpr *CE = findNode<CallExpr>(D, callExpr());
60     const StackFrameContext *SFC =
61         Eng.getAnalysisDeclContextManager().getStackFrame(D);
62     ProgramStateRef State = Eng.getInitialState(SFC);
63     CallEventRef<> Call =
64         Eng.getStateManager().getCallEventManager().getCall(CE, State, SFC);
65 
66     const bool *LookupResult = RM.lookup(*Call);
67     // Check that we've found the function in the map
68     // with the correct description.
69     EXPECT_TRUE(LookupResult && *LookupResult);
70 
71     // ResultMap is responsible for making sure that we've found *all* calls.
72   }
73 
74 public:
75   CallDescriptionConsumer(CompilerInstance &C,
76                           ResultMap &RM)
77       : ExprEngineConsumer(C), RM(RM) {}
78 
79   bool HandleTopLevelDecl(DeclGroupRef DG) override {
80     for (const auto *D : DG)
81       performTest(D);
82     return true;
83   }
84 };
85 
86 class CallDescriptionAction : public ASTFrontendAction {
87   ResultMap RM;
88 
89 public:
90   CallDescriptionAction(
91       std::initializer_list<std::pair<CallDescription, bool>> Data)
92       : RM(Data) {}
93 
94   std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
95                                                  StringRef File) override {
96     return std::make_unique<CallDescriptionConsumer>(Compiler, RM);
97   }
98 };
99 
100 TEST(CallEvent, CallDescription) {
101   // Test simple name matching.
102   EXPECT_TRUE(tooling::runToolOnCode(
103       new CallDescriptionAction({
104           {{"bar"}, false}, // false: there's no call to 'bar' in this code.
105           {{"foo"}, true},  // true: there's a call to 'foo' in this code.
106       }), "void foo(); void bar() { foo(); }"));
107 
108   // Test arguments check.
109   EXPECT_TRUE(tooling::runToolOnCode(
110       new CallDescriptionAction({
111           {{"foo", 1}, true},
112           {{"foo", 2}, false},
113       }), "void foo(int); void foo(int, int); void bar() { foo(1); }"));
114 
115   // Test lack of arguments check.
116   EXPECT_TRUE(tooling::runToolOnCode(
117       new CallDescriptionAction({
118           {{"foo", None}, true},
119           {{"foo", 2}, false},
120       }), "void foo(int); void foo(int, int); void bar() { foo(1); }"));
121 
122   // Test qualified names.
123   EXPECT_TRUE(tooling::runToolOnCode(
124       new CallDescriptionAction({
125           {{{"std", "basic_string", "c_str"}}, true},
126       }),
127       "namespace std { inline namespace __1 {"
128       "  template<typename T> class basic_string {"
129       "  public:"
130       "    T *c_str();"
131       "  };"
132       "}}"
133       "void foo() {"
134       "  using namespace std;"
135       "  basic_string<char> s;"
136       "  s.c_str();"
137       "}"));
138 
139   // A negative test for qualified names.
140   EXPECT_TRUE(tooling::runToolOnCode(
141       new CallDescriptionAction({
142           {{{"foo", "bar"}}, false},
143           {{{"bar", "foo"}}, false},
144           {{"foo"}, true},
145       }), "void foo(); struct bar { void foo(); }; void test() { foo(); }"));
146 
147   // Test CDF_MaybeBuiltin - a flag that allows matching weird builtins.
148   EXPECT_TRUE(tooling::runToolOnCode(
149       new CallDescriptionAction({
150           {{"memset", 3}, false},
151           {{CDF_MaybeBuiltin, "memset", 3}, true}
152       }),
153       "void foo() {"
154       "  int x;"
155       "  __builtin___memset_chk(&x, 0, sizeof(x),"
156       "                         __builtin_object_size(&x, 0));"
157       "}"));
158 }
159 
160 } // namespace
161 } // namespace ento
162 } // namespace clang
163