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: |
50 | ErrorNotPreventedFuncVisitor() |
51 | : NoStateChangeFuncVisitor(bugreporter::TrackingKind::Thorough) {} |
52 | |
53 | virtual PathDiagnosticPieceRef |
54 | maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R, |
55 | const ObjCMethodCall &Call, |
56 | const ExplodedNode *N) override { |
57 | return nullptr; |
58 | } |
59 | |
60 | virtual PathDiagnosticPieceRef |
61 | maybeEmitNoteForCXXThis(PathSensitiveBugReport &R, |
62 | const CXXConstructorCall &Call, |
63 | const ExplodedNode *N) override { |
64 | return nullptr; |
65 | } |
66 | |
67 | virtual PathDiagnosticPieceRef |
68 | maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call, |
69 | const ExplodedNode *N) override { |
70 | PathDiagnosticLocation L = PathDiagnosticLocation::create( |
71 | P: N->getLocation(), |
72 | SMng: N->getState()->getStateManager().getContext().getSourceManager()); |
73 | return std::make_shared<PathDiagnosticEventPiece>( |
74 | args&: L, args: "Returning without prevening the error" ); |
75 | } |
76 | |
77 | void Profile(llvm::FoldingSetNodeID &ID) const override { |
78 | static int Tag = 0; |
79 | ID.AddPointer(Ptr: &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: |
88 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const { |
89 | if (CallDescription{{"preventError" }, 0}.matches(Call)) { |
90 | C.addTransition(State: C.getState()->set<ErrorPrevented>(true)); |
91 | return; |
92 | } |
93 | |
94 | if (CallDescription{{"allowError" }, 0}.matches(Call)) { |
95 | C.addTransition(State: 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 | auto R = |
106 | std::make_unique<PathSensitiveBugReport>(args: BT, args: "error() called" , args&: N); |
107 | R->template addVisitor<Visitor>(); |
108 | C.emitReport(R: 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 |
130 | wasModifiedInFunction(const ExplodedNode *CallEnterN, |
131 | const ExplodedNode *CallExitEndN) override { |
132 | return CallEnterN->getState()->get<ErrorPrevented>() != |
133 | CallExitEndN->getState()->get<ErrorPrevented>(); |
134 | } |
135 | }; |
136 | |
137 | void addNonThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer, |
138 | AnalyzerOptions &AnOpts) { |
139 | AnOpts.CheckersAndPackages = {{"test.StatefulChecker" , true}}; |
140 | AnalysisConsumer.AddCheckerRegistrationFn(Fn: [](CheckerRegistry &Registry) { |
141 | Registry |
142 | .addChecker<StatefulChecker<NonThoroughErrorNotPreventedFuncVisitor>>( |
143 | FullName: "test.StatefulChecker" , Desc: "Description" , DocsUri: "" ); |
144 | }); |
145 | } |
146 | |
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 |
224 | wasModifiedBeforeCallExit(const ExplodedNode *CurrN, |
225 | const ExplodedNode *CallExitBeginN) override { |
226 | return CurrN->getState()->get<ErrorPrevented>() != |
227 | CallExitBeginN->getState()->get<ErrorPrevented>(); |
228 | } |
229 | }; |
230 | |
231 | void addThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer, |
232 | AnalyzerOptions &AnOpts) { |
233 | AnOpts.CheckersAndPackages = {{"test.StatefulChecker" , true}}; |
234 | AnalysisConsumer.AddCheckerRegistrationFn(Fn: [](CheckerRegistry &Registry) { |
235 | Registry.addChecker<StatefulChecker<ThoroughErrorNotPreventedFuncVisitor>>( |
236 | FullName: "test.StatefulChecker" , Desc: "Description" , DocsUri: "" ); |
237 | }); |
238 | } |
239 | |
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 | |