1 //===- unittests/StaticAnalyzer/NoStateChangeFuncVisitorTest.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 "clang/Frontend/CompilerInstance.h"
11 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
12 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
13 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
14 #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
15 #include "clang/StaticAnalyzer/Core/Checker.h"
16 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
17 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
18 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
19 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
20 #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
22 #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
23 #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
24 #include "llvm/Support/ErrorHandling.h"
25 #include "llvm/Support/raw_ostream.h"
26 #include "gtest/gtest.h"
27 #include <memory>
28 
29 //===----------------------------------------------------------------------===//
30 // Base classes for testing NoStateChangeFuncVisitor.
31 //
32 // Testing is done by observing a very simple trait change from one node to
33 // another -- the checker sets the ErrorPrevented trait to true if
34 // 'preventError()' is called in the source code, and sets it to false if
35 // 'allowError()' is called. If this trait is false when 'error()' is called,
36 // a warning is emitted.
37 //
38 // The checker then registers a simple NoStateChangeFuncVisitor to add notes to
39 // inlined functions that could have, but neglected to prevent the error.
40 //===----------------------------------------------------------------------===//
41 
42 REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrorPrevented, bool)
43 
44 namespace clang {
45 namespace ento {
46 namespace {
47 
48 class ErrorNotPreventedFuncVisitor : public NoStateChangeFuncVisitor {
49 public:
ErrorNotPreventedFuncVisitor()50   ErrorNotPreventedFuncVisitor()
51       : NoStateChangeFuncVisitor(bugreporter::TrackingKind::Thorough) {}
52 
53   virtual PathDiagnosticPieceRef
maybeEmitNoteForObjCSelf(PathSensitiveBugReport & R,const ObjCMethodCall & Call,const ExplodedNode * N)54   maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R,
55                            const ObjCMethodCall &Call,
56                            const ExplodedNode *N) override {
57     return nullptr;
58   }
59 
60   virtual PathDiagnosticPieceRef
maybeEmitNoteForCXXThis(PathSensitiveBugReport & R,const CXXConstructorCall & Call,const ExplodedNode * N)61   maybeEmitNoteForCXXThis(PathSensitiveBugReport &R,
62                           const CXXConstructorCall &Call,
63                           const ExplodedNode *N) override {
64     return nullptr;
65   }
66 
67   virtual PathDiagnosticPieceRef
maybeEmitNoteForParameters(PathSensitiveBugReport & R,const CallEvent & Call,const ExplodedNode * N)68   maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call,
69                              const ExplodedNode *N) override {
70     PathDiagnosticLocation L = PathDiagnosticLocation::create(
71         N->getLocation(),
72         N->getState()->getStateManager().getContext().getSourceManager());
73     return std::make_shared<PathDiagnosticEventPiece>(
74         L, "Returning without prevening the error");
75   }
76 
Profile(llvm::FoldingSetNodeID & ID) const77   void Profile(llvm::FoldingSetNodeID &ID) const override {
78     static int Tag = 0;
79     ID.AddPointer(&Tag);
80   }
81 };
82 
83 template <class Visitor>
84 class StatefulChecker : public Checker<check::PreCall> {
85   mutable std::unique_ptr<BugType> BT;
86 
87 public:
checkPreCall(const CallEvent & Call,CheckerContext & C) const88   void checkPreCall(const CallEvent &Call, CheckerContext &C) const {
89     if (CallDescription{"preventError", 0}.matches(Call)) {
90       C.addTransition(C.getState()->set<ErrorPrevented>(true));
91       return;
92     }
93 
94     if (CallDescription{"allowError", 0}.matches(Call)) {
95       C.addTransition(C.getState()->set<ErrorPrevented>(false));
96       return;
97     }
98 
99     if (CallDescription{"error", 0}.matches(Call)) {
100       if (C.getState()->get<ErrorPrevented>())
101         return;
102       const ExplodedNode *N = C.generateErrorNode();
103       if (!N)
104         return;
105       if (!BT)
106         BT.reset(new BugType(this->getCheckerName(), "error()",
107                              categories::SecurityError));
108       auto R =
109           std::make_unique<PathSensitiveBugReport>(*BT, "error() called", N);
110       R->template addVisitor<Visitor>();
111       C.emitReport(std::move(R));
112     }
113   }
114 };
115 
116 } // namespace
117 } // namespace ento
118 } // namespace clang
119 
120 //===----------------------------------------------------------------------===//
121 // Non-thorough analysis: only the state right before and right after the
122 // function call is checked for the difference in trait value.
123 //===----------------------------------------------------------------------===//
124 
125 namespace clang {
126 namespace ento {
127 namespace {
128 
129 class NonThoroughErrorNotPreventedFuncVisitor
130     : public ErrorNotPreventedFuncVisitor {
131 public:
132   virtual bool
wasModifiedInFunction(const ExplodedNode * CallEnterN,const ExplodedNode * CallExitEndN)133   wasModifiedInFunction(const ExplodedNode *CallEnterN,
134                         const ExplodedNode *CallExitEndN) override {
135     return CallEnterN->getState()->get<ErrorPrevented>() !=
136            CallExitEndN->getState()->get<ErrorPrevented>();
137   }
138 };
139 
addNonThoroughStatefulChecker(AnalysisASTConsumer & AnalysisConsumer,AnalyzerOptions & AnOpts)140 void addNonThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer,
141                                    AnalyzerOptions &AnOpts) {
142   AnOpts.CheckersAndPackages = {{"test.StatefulChecker", true}};
143   AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
144     Registry
145         .addChecker<StatefulChecker<NonThoroughErrorNotPreventedFuncVisitor>>(
146             "test.StatefulChecker", "Description", "");
147   });
148 }
149 
TEST(NoStateChangeFuncVisitor,NonThoroughFunctionAnalysis)150 TEST(NoStateChangeFuncVisitor, NonThoroughFunctionAnalysis) {
151   std::string Diags;
152   EXPECT_TRUE(runCheckerOnCode<addNonThoroughStatefulChecker>(R"(
153     void error();
154     void preventError();
155     void allowError();
156 
157     void g() {
158       //preventError();
159     }
160 
161     void f() {
162       g();
163       error();
164     }
165   )", Diags));
166   EXPECT_EQ(Diags,
167             "test.StatefulChecker: Calling 'g' | Returning without prevening "
168             "the error | Returning from 'g' | error() called\n");
169 
170   Diags.clear();
171 
172   EXPECT_TRUE(runCheckerOnCode<addNonThoroughStatefulChecker>(R"(
173     void error();
174     void preventError();
175     void allowError();
176 
177     void g() {
178       preventError();
179       allowError();
180     }
181 
182     void f() {
183       g();
184       error();
185     }
186   )", Diags));
187   EXPECT_EQ(Diags,
188             "test.StatefulChecker: Calling 'g' | Returning without prevening "
189             "the error | Returning from 'g' | error() called\n");
190 
191   Diags.clear();
192 
193   EXPECT_TRUE(runCheckerOnCode<addNonThoroughStatefulChecker>(R"(
194     void error();
195     void preventError();
196     void allowError();
197 
198     void g() {
199       preventError();
200     }
201 
202     void f() {
203       g();
204       error();
205     }
206   )", Diags));
207   EXPECT_EQ(Diags, "");
208 }
209 
210 } // namespace
211 } // namespace ento
212 } // namespace clang
213 
214 //===----------------------------------------------------------------------===//
215 // Thorough analysis: only the state right before and right after the
216 // function call is checked for the difference in trait value.
217 //===----------------------------------------------------------------------===//
218 
219 namespace clang {
220 namespace ento {
221 namespace {
222 
223 class ThoroughErrorNotPreventedFuncVisitor
224     : public ErrorNotPreventedFuncVisitor {
225 public:
226   virtual bool
wasModifiedBeforeCallExit(const ExplodedNode * CurrN,const ExplodedNode * CallExitBeginN)227   wasModifiedBeforeCallExit(const ExplodedNode *CurrN,
228                             const ExplodedNode *CallExitBeginN) override {
229     return CurrN->getState()->get<ErrorPrevented>() !=
230            CallExitBeginN->getState()->get<ErrorPrevented>();
231   }
232 };
233 
addThoroughStatefulChecker(AnalysisASTConsumer & AnalysisConsumer,AnalyzerOptions & AnOpts)234 void addThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer,
235                                 AnalyzerOptions &AnOpts) {
236   AnOpts.CheckersAndPackages = {{"test.StatefulChecker", true}};
237   AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
238     Registry.addChecker<StatefulChecker<ThoroughErrorNotPreventedFuncVisitor>>(
239         "test.StatefulChecker", "Description", "");
240   });
241 }
242 
TEST(NoStateChangeFuncVisitor,ThoroughFunctionAnalysis)243 TEST(NoStateChangeFuncVisitor, ThoroughFunctionAnalysis) {
244   std::string Diags;
245   EXPECT_TRUE(runCheckerOnCode<addThoroughStatefulChecker>(R"(
246     void error();
247     void preventError();
248     void allowError();
249 
250     void g() {
251       //preventError();
252     }
253 
254     void f() {
255       g();
256       error();
257     }
258   )", Diags));
259   EXPECT_EQ(Diags,
260             "test.StatefulChecker: Calling 'g' | Returning without prevening "
261             "the error | Returning from 'g' | error() called\n");
262 
263   Diags.clear();
264 
265   EXPECT_TRUE(runCheckerOnCode<addThoroughStatefulChecker>(R"(
266     void error();
267     void preventError();
268     void allowError();
269 
270     void g() {
271       preventError();
272       allowError();
273     }
274 
275     void f() {
276       g();
277       error();
278     }
279   )", Diags));
280   EXPECT_EQ(Diags, "test.StatefulChecker: error() called\n");
281 
282   Diags.clear();
283 
284   EXPECT_TRUE(runCheckerOnCode<addThoroughStatefulChecker>(R"(
285     void error();
286     void preventError();
287     void allowError();
288 
289     void g() {
290       preventError();
291     }
292 
293     void f() {
294       g();
295       error();
296     }
297   )", Diags));
298   EXPECT_EQ(Diags, "");
299 }
300 
301 } // namespace
302 } // namespace ento
303 } // namespace clang
304