1//===- unittests/StaticAnalyzer/BlockEntranceCallbackTest.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/Analysis/AnalysisDeclContext.h"
11#include "clang/Analysis/ProgramPoint.h"
12#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
13#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
14#include "clang/StaticAnalyzer/Core/Checker.h"
15#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
16#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"
17#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
18#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
19#include "llvm/ADT/STLExtras.h"
20#include "llvm/ADT/StringExtras.h"
21#include "llvm/ADT/StringRef.h"
22#include "llvm/ADT/Twine.h"
23#include "llvm/Support/FormatVariadic.h"
24#include "llvm/Support/raw_ostream.h"
25#include "gtest/gtest.h"
26
27using namespace clang;
28using namespace ento;
29
30namespace {
31
32class BlockEntranceCallbackTester final : public Checker<check::BlockEntrance> {
33 const BugType Bug{this, "BlockEntranceTester"};
34
35public:
36 void checkBlockEntrance(const BlockEntrance &Entrance,
37 CheckerContext &C) const {
38 ExplodedNode *Node = C.generateNonFatalErrorNode(State: C.getState());
39 if (!Node)
40 return;
41
42 const auto *FD =
43 cast<FunctionDecl>(Val: C.getLocationContext()->getStackFrame()->getDecl());
44
45 std::string Description = llvm::formatv(
46 "Within '{0}' B{1} -> B{2}", FD->getIdentifier()->getName(),
47 Entrance.getPreviousBlock()->getBlockID(),
48 Entrance.getBlock()->getBlockID());
49 auto Report =
50 std::make_unique<PathSensitiveBugReport>(args: Bug, args&: Description, args&: Node);
51 C.emitReport(R: std::move(Report));
52 }
53};
54
55class BranchConditionCallbackTester final
56 : public Checker<check::BranchCondition> {
57 const BugType Bug{this, "BranchConditionCallbackTester"};
58
59public:
60 void checkBranchCondition(const Stmt *Condition, CheckerContext &C) const {
61 ExplodedNode *Node = C.generateNonFatalErrorNode(State: C.getState());
62 if (!Node)
63 return;
64 const auto *FD =
65 cast<FunctionDecl>(Val: C.getLocationContext()->getStackFrame()->getDecl());
66
67 std::string Buffer =
68 (llvm::Twine("Within '") + FD->getIdentifier()->getName() +
69 "': branch condition '")
70 .str();
71 llvm::raw_string_ostream OS(Buffer);
72 Condition->printPretty(OS, /*Helper=*/nullptr,
73 Policy: C.getASTContext().getPrintingPolicy());
74 OS << "'";
75 auto Report = std::make_unique<PathSensitiveBugReport>(args: Bug, args&: Buffer, args&: Node);
76 C.emitReport(R: std::move(Report));
77
78 C.addTransition();
79 }
80};
81
82template <typename Checker> void registerChecker(CheckerManager &Mgr) {
83 Mgr.registerChecker<Checker>();
84}
85
86bool shouldAlwaysRegister(const CheckerManager &) { return true; }
87
88void addBlockEntranceTester(AnalysisASTConsumer &AnalysisConsumer,
89 AnalyzerOptions &AnOpts) {
90 AnOpts.CheckersAndPackages.emplace_back(args: "test.BlockEntranceTester", args: true);
91 AnalysisConsumer.AddCheckerRegistrationFn(Fn: [](CheckerRegistry &Registry) {
92 Registry.addChecker(Fn: &registerChecker<BlockEntranceCallbackTester>,
93 sfn: &shouldAlwaysRegister, FullName: "test.BlockEntranceTester",
94 Desc: "EmptyDescription", DocsUri: "EmptyDocsUri",
95 /*IsHidden=*/false);
96 });
97}
98
99void addBranchConditionTester(AnalysisASTConsumer &AnalysisConsumer,
100 AnalyzerOptions &AnOpts) {
101 AnOpts.CheckersAndPackages.emplace_back(args: "test.BranchConditionTester", args: true);
102 AnalysisConsumer.AddCheckerRegistrationFn(Fn: [](CheckerRegistry &Registry) {
103 Registry.addChecker(Fn: &registerChecker<BranchConditionCallbackTester>,
104 sfn: &shouldAlwaysRegister, FullName: "test.BranchConditionTester",
105 Desc: "EmptyDescription", DocsUri: "EmptyDocsUri",
106 /*IsHidden=*/false);
107 });
108}
109
110llvm::SmallVector<StringRef> parseEachDiag(StringRef Diags) {
111 llvm::SmallVector<StringRef> Fragments;
112 llvm::SplitString(Source: Diags, OutFragments&: Fragments, Delimiters: "\n");
113 // Drop the prefix like "test.BlockEntranceTester: " from each fragment.
114 for (StringRef &Fragment : Fragments) {
115 Fragment = Fragment.drop_until(F: [](char Ch) { return Ch == ' '; });
116 Fragment.consume_front(Prefix: " ");
117 }
118 llvm::sort(C&: Fragments);
119 return Fragments;
120}
121
122template <AddCheckerFn Fn = addBlockEntranceTester, AddCheckerFn... Fns>
123bool runChecker(const std::string &Code, std::string &Diags) {
124 std::string RawDiags;
125 bool Res = runCheckerOnCode<Fn, Fns...>(Code, RawDiags,
126 /*OnlyEmitWarnings=*/true);
127 llvm::raw_string_ostream OS(Diags);
128 llvm::interleave(c: parseEachDiag(Diags: RawDiags), os&: OS, separator: "\n");
129 return Res;
130}
131
132[[maybe_unused]] void dumpCFGAndEgraph(AnalysisASTConsumer &AnalysisConsumer,
133 AnalyzerOptions &AnOpts) {
134 AnOpts.CheckersAndPackages.emplace_back(args: "debug.DumpCFG", args: true);
135 AnOpts.CheckersAndPackages.emplace_back(args: "debug.ViewExplodedGraph", args: true);
136}
137
138/// Use this instead of \c runChecker to enable the debugging a test case.
139template <AddCheckerFn... Fns>
140[[maybe_unused]] bool debugChecker(const std::string &Code,
141 std::string &Diags) {
142 return runChecker<dumpCFGAndEgraph, Fns...>(Code, Diags);
143}
144
145std::string expected(SmallVector<StringRef> Diags) {
146 llvm::sort(C&: Diags);
147 std::string Result;
148 llvm::raw_string_ostream OS(Result);
149 llvm::interleave(c: Diags, os&: OS, separator: "\n");
150 return Result;
151}
152
153TEST(BlockEntranceTester, FromEntryToExit) {
154 constexpr auto Code = R"cpp(
155 void top() {
156 // empty
157 })cpp";
158
159 std::string Diags;
160 // Use "debugChecker" instead of "runChecker" for debugging.
161 EXPECT_TRUE(runChecker(Code, Diags));
162 EXPECT_EQ(expected({"Within 'top' B1 -> B0"}), Diags);
163}
164
165TEST(BlockEntranceTester, SingleOpaqueIfCondition) {
166 constexpr auto Code = R"cpp(
167 bool coin();
168 int glob;
169 void top() {
170 if (coin()) {
171 glob = 1;
172 } else {
173 glob = 2;
174 }
175 glob = 3;
176 })cpp";
177
178 std::string Diags;
179 // Use "debugChecker" instead of "runChecker" for debugging.
180 EXPECT_TRUE(runChecker(Code, Diags));
181 EXPECT_EQ(expected({
182 "Within 'top' B1 -> B0",
183 "Within 'top' B2 -> B1",
184 "Within 'top' B3 -> B1",
185 "Within 'top' B4 -> B2",
186 "Within 'top' B4 -> B3",
187 "Within 'top' B5 -> B4",
188 }),
189 Diags);
190 // entry true exit
191 // B5 -------> B4 --> B2 --> B1 --> B0
192 // | ^
193 // | false |
194 // v |
195 // B3 -----------------------+
196}
197
198TEST(BlockEntranceTester, TrivialIfCondition) {
199 constexpr auto Code = R"cpp(
200 bool coin();
201 int glob;
202 void top() {
203 int cond = true;
204 if (cond) {
205 glob = 1;
206 } else {
207 glob = 2;
208 }
209 glob = 3;
210 })cpp";
211
212 std::string Diags;
213 // Use "debugChecker" instead of "runChecker" for debugging.
214 EXPECT_TRUE(runChecker(Code, Diags));
215 EXPECT_EQ(expected({
216 "Within 'top' B1 -> B0",
217 "Within 'top' B3 -> B1",
218 "Within 'top' B4 -> B3",
219 "Within 'top' B5 -> B4",
220 }),
221 Diags);
222 // entry true exit
223 // B5 ----------> B4 --> B3 --> B1 --> B0
224}
225
226TEST(BlockEntranceTester, AcrossFunctions) {
227 constexpr auto Code = R"cpp(
228 bool coin();
229 int glob;
230 void nested() { glob = 1; }
231 void top() {
232 glob = 0;
233 nested();
234 glob = 2;
235 })cpp";
236
237 std::string Diags;
238 // Use "debugChecker" instead of "runChecker" for debugging.
239 EXPECT_TRUE(runChecker(Code, Diags));
240 EXPECT_EQ(
241 expected({
242 // Going from the "top" entry artificial node to the "top" body.
243 // Ideally, we shouldn't observe this edge because it's artificial.
244 "Within 'top' B2 -> B1",
245
246 // We encounter the call to "nested()" in the "top" body, thus we have
247 // a "CallEnter" node, but importantly, we also elide the transition
248 // to the "entry" node of "nested()".
249 // We only see the edge from the "nested()" entry to the "nested()"
250 // body:
251 "Within 'nested' B2 -> B1",
252
253 // Once we return from "nested()", we transition to the "exit" node of
254 // "nested()":
255 "Within 'nested' B1 -> B0",
256
257 // We will eventually return to the "top" body, thus we transition to
258 // its "exit" node:
259 "Within 'top' B1 -> B0",
260 }),
261 Diags);
262}
263
264TEST(BlockEntranceTester, ShortCircuitingLogicalOperator) {
265 constexpr auto Code = R"cpp(
266 bool coin();
267 void top(int x) {
268 int v = 0;
269 if (coin() && (v = x)) {
270 v = 2;
271 }
272 v = 3;
273 })cpp";
274 // coin(): false
275 // +--------------------------------+
276 // entry | v exit
277 // +----+ +----+ +----+ +----+ +----+ +----+
278 // | B5 | --> | B4 | --> | B3 | --> | B2 | --> | B1 | --> | B0 |
279 // +----+ +----+ +----+ +----+ +----+ +----+
280 // | ^
281 // +---------------------+
282 // (v = x): false
283
284 std::string Diags;
285 // Use "debugChecker" instead of "runChecker" for debugging.
286 EXPECT_TRUE(runChecker(Code, Diags));
287 EXPECT_EQ(expected({
288 "Within 'top' B1 -> B0",
289 "Within 'top' B2 -> B1",
290 "Within 'top' B3 -> B1",
291 "Within 'top' B3 -> B2",
292 "Within 'top' B4 -> B1",
293 "Within 'top' B4 -> B3",
294 "Within 'top' B5 -> B4",
295 }),
296 Diags);
297}
298
299TEST(BlockEntranceTester, Switch) {
300 constexpr auto Code = R"cpp(
301 bool coin();
302 int top(int x) {
303 int v = 0;
304 switch (x) {
305 case 1: v = 10; break;
306 case 2: v = 20; break;
307 default: v = 30; break;
308 }
309 return v;
310 })cpp";
311 // +----+
312 // | B5 | -------------------------+
313 // +----+ |
314 // ^ [case 1] |
315 // entry | v exit
316 // +----+ +----+ [default] +----+ +----+ +----+
317 // | B6 | --> | B2 | ----------> | B3 | --> | B1 | --> | B0 |
318 // +----+ +----+ +----+ +----+ +----+
319 // | ^
320 // v [case 2] |
321 // +----+ |
322 // | B4 | -------------------------+
323 // +----+
324
325 std::string Diags;
326 // Use "debugChecker" instead of "runChecker" for debugging.
327 EXPECT_TRUE(runChecker(Code, Diags));
328 EXPECT_EQ(expected({
329 "Within 'top' B1 -> B0",
330 "Within 'top' B2 -> B3",
331 "Within 'top' B2 -> B4",
332 "Within 'top' B2 -> B5",
333 "Within 'top' B3 -> B1",
334 "Within 'top' B4 -> B1",
335 "Within 'top' B5 -> B1",
336 "Within 'top' B6 -> B2",
337 }),
338 Diags);
339}
340
341TEST(BlockEntranceTester, BlockEntranceVSBranchCondition) {
342 constexpr auto Code = R"cpp(
343 bool coin();
344 int top(int x) {
345 int v = 0;
346 switch (x) {
347 default: v = 30; break;
348 }
349 if (x == 6) {
350 v = 40;
351 }
352 return v;
353 })cpp";
354 std::string Diags;
355 // Use "debugChecker" instead of "runChecker" for debugging.
356 EXPECT_TRUE((runChecker<addBlockEntranceTester, addBranchConditionTester>(
357 Code, Diags)));
358 EXPECT_EQ(expected({
359 "Within 'top' B1 -> B0",
360 "Within 'top' B2 -> B1",
361 "Within 'top' B3 -> B1",
362 "Within 'top' B3 -> B2",
363 "Within 'top' B4 -> B5",
364 "Within 'top' B5 -> B3",
365 "Within 'top' B6 -> B4",
366 "Within 'top': branch condition 'x == 6'",
367 }),
368 Diags);
369}
370
371} // namespace
372

source code of clang/unittests/StaticAnalyzer/BlockEntranceCallbackTest.cpp