xref: /llvm-project/clang/unittests/StaticAnalyzer/NoStateChangeFuncVisitorTest.cpp (revision 58bad2862cf136f9483eb005bbfa6915d459b46d)
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   const BugType BT{this, "error()", categories::SecurityError};
86 
87 public:
checkPreCall(const CallEvent & Call,CheckerContext & C) const88   void checkPreCall(const CallEvent &Call, CheckerContext &C) const {
89     if (CallDescription{CDM::SimpleFunc, {"preventError"}, 0}.matches(Call)) {
90       C.addTransition(C.getState()->set<ErrorPrevented>(true));
91       return;
92     }
93 
94     if (CallDescription{CDM::SimpleFunc, {"allowError"}, 0}.matches(Call)) {
95       C.addTransition(C.getState()->set<ErrorPrevented>(false));
96       return;
97     }
98 
99     if (CallDescription{CDM::SimpleFunc, {"error"}, 0}.matches(Call)) {
100       if (C.getState()->get<ErrorPrevented>())
101         return;
102       const ExplodedNode *N = C.generateErrorNode();
103       if (!N)
104         return;
105       auto R =
106           std::make_unique<PathSensitiveBugReport>(BT, "error() called", N);
107       R->template addVisitor<Visitor>();
108       C.emitReport(std::move(R));
109     }
110   }
111 };
112 
113 } // namespace
114 } // namespace ento
115 } // namespace clang
116 
117 //===----------------------------------------------------------------------===//
118 // Non-thorough analysis: only the state right before and right after the
119 // function call is checked for the difference in trait value.
120 //===----------------------------------------------------------------------===//
121 
122 namespace clang {
123 namespace ento {
124 namespace {
125 
126 class NonThoroughErrorNotPreventedFuncVisitor
127     : public ErrorNotPreventedFuncVisitor {
128 public:
129   virtual bool
wasModifiedInFunction(const ExplodedNode * CallEnterN,const ExplodedNode * CallExitEndN)130   wasModifiedInFunction(const ExplodedNode *CallEnterN,
131                         const ExplodedNode *CallExitEndN) override {
132     return CallEnterN->getState()->get<ErrorPrevented>() !=
133            CallExitEndN->getState()->get<ErrorPrevented>();
134   }
135 };
136 
addNonThoroughStatefulChecker(AnalysisASTConsumer & AnalysisConsumer,AnalyzerOptions & AnOpts)137 void addNonThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer,
138                                    AnalyzerOptions &AnOpts) {
139   AnOpts.CheckersAndPackages = {{"test.StatefulChecker", true}};
140   AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
141     Registry
142         .addChecker<StatefulChecker<NonThoroughErrorNotPreventedFuncVisitor>>(
143             "test.StatefulChecker", "Description", "");
144   });
145 }
146 
TEST(NoStateChangeFuncVisitor,NonThoroughFunctionAnalysis)147 TEST(NoStateChangeFuncVisitor, NonThoroughFunctionAnalysis) {
148   std::string Diags;
149   EXPECT_TRUE(runCheckerOnCode<addNonThoroughStatefulChecker>(R"(
150     void error();
151     void preventError();
152     void allowError();
153 
154     void g() {
155       //preventError();
156     }
157 
158     void f() {
159       g();
160       error();
161     }
162   )", Diags));
163   EXPECT_EQ(Diags,
164             "test.StatefulChecker: Calling 'g' | Returning without prevening "
165             "the error | Returning from 'g' | error() called\n");
166 
167   Diags.clear();
168 
169   EXPECT_TRUE(runCheckerOnCode<addNonThoroughStatefulChecker>(R"(
170     void error();
171     void preventError();
172     void allowError();
173 
174     void g() {
175       preventError();
176       allowError();
177     }
178 
179     void f() {
180       g();
181       error();
182     }
183   )", Diags));
184   EXPECT_EQ(Diags,
185             "test.StatefulChecker: Calling 'g' | Returning without prevening "
186             "the error | Returning from 'g' | error() called\n");
187 
188   Diags.clear();
189 
190   EXPECT_TRUE(runCheckerOnCode<addNonThoroughStatefulChecker>(R"(
191     void error();
192     void preventError();
193     void allowError();
194 
195     void g() {
196       preventError();
197     }
198 
199     void f() {
200       g();
201       error();
202     }
203   )", Diags));
204   EXPECT_EQ(Diags, "");
205 }
206 
207 } // namespace
208 } // namespace ento
209 } // namespace clang
210 
211 //===----------------------------------------------------------------------===//
212 // Thorough analysis: only the state right before and right after the
213 // function call is checked for the difference in trait value.
214 //===----------------------------------------------------------------------===//
215 
216 namespace clang {
217 namespace ento {
218 namespace {
219 
220 class ThoroughErrorNotPreventedFuncVisitor
221     : public ErrorNotPreventedFuncVisitor {
222 public:
223   virtual bool
wasModifiedBeforeCallExit(const ExplodedNode * CurrN,const ExplodedNode * CallExitBeginN)224   wasModifiedBeforeCallExit(const ExplodedNode *CurrN,
225                             const ExplodedNode *CallExitBeginN) override {
226     return CurrN->getState()->get<ErrorPrevented>() !=
227            CallExitBeginN->getState()->get<ErrorPrevented>();
228   }
229 };
230 
addThoroughStatefulChecker(AnalysisASTConsumer & AnalysisConsumer,AnalyzerOptions & AnOpts)231 void addThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer,
232                                 AnalyzerOptions &AnOpts) {
233   AnOpts.CheckersAndPackages = {{"test.StatefulChecker", true}};
234   AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
235     Registry.addChecker<StatefulChecker<ThoroughErrorNotPreventedFuncVisitor>>(
236         "test.StatefulChecker", "Description", "");
237   });
238 }
239 
TEST(NoStateChangeFuncVisitor,ThoroughFunctionAnalysis)240 TEST(NoStateChangeFuncVisitor, ThoroughFunctionAnalysis) {
241   std::string Diags;
242   EXPECT_TRUE(runCheckerOnCode<addThoroughStatefulChecker>(R"(
243     void error();
244     void preventError();
245     void allowError();
246 
247     void g() {
248       //preventError();
249     }
250 
251     void f() {
252       g();
253       error();
254     }
255   )", Diags));
256   EXPECT_EQ(Diags,
257             "test.StatefulChecker: Calling 'g' | Returning without prevening "
258             "the error | Returning from 'g' | error() called\n");
259 
260   Diags.clear();
261 
262   EXPECT_TRUE(runCheckerOnCode<addThoroughStatefulChecker>(R"(
263     void error();
264     void preventError();
265     void allowError();
266 
267     void g() {
268       preventError();
269       allowError();
270     }
271 
272     void f() {
273       g();
274       error();
275     }
276   )", Diags));
277   EXPECT_EQ(Diags, "test.StatefulChecker: error() called\n");
278 
279   Diags.clear();
280 
281   EXPECT_TRUE(runCheckerOnCode<addThoroughStatefulChecker>(R"(
282     void error();
283     void preventError();
284     void allowError();
285 
286     void g() {
287       preventError();
288     }
289 
290     void f() {
291       g();
292       error();
293     }
294   )", Diags));
295   EXPECT_EQ(Diags, "");
296 }
297 
298 } // namespace
299 } // namespace ento
300 } // namespace clang
301