370 lines · cpp
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-exception6//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(C.getState());39 if (!Node)40 return;41 42 const auto *FD =43 cast<FunctionDecl>(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>(Bug, Description, Node);51 C.emitReport(std::move(Report));52 }53};54 55class BranchConditionCallbackTester final56 : 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(C.getState());62 if (!Node)63 return;64 const auto *FD =65 cast<FunctionDecl>(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 C.getASTContext().getPrintingPolicy());74 OS << "'";75 auto Report = std::make_unique<PathSensitiveBugReport>(Bug, Buffer, Node);76 C.emitReport(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("test.BlockEntranceTester", true);91 AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {92 Registry.addChecker(®isterChecker<BlockEntranceCallbackTester>,93 &shouldAlwaysRegister, "test.BlockEntranceTester",94 "EmptyDescription");95 });96}97 98void addBranchConditionTester(AnalysisASTConsumer &AnalysisConsumer,99 AnalyzerOptions &AnOpts) {100 AnOpts.CheckersAndPackages.emplace_back("test.BranchConditionTester", true);101 AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {102 Registry.addChecker(®isterChecker<BranchConditionCallbackTester>,103 &shouldAlwaysRegister, "test.BranchConditionTester",104 "EmptyDescription");105 });106}107 108llvm::SmallVector<StringRef> parseEachDiag(StringRef Diags) {109 llvm::SmallVector<StringRef> Fragments;110 llvm::SplitString(Diags, Fragments, "\n");111 // Drop the prefix like "test.BlockEntranceTester: " from each fragment.112 for (StringRef &Fragment : Fragments) {113 Fragment = Fragment.drop_until([](char Ch) { return Ch == ' '; });114 Fragment.consume_front(" ");115 }116 llvm::sort(Fragments);117 return Fragments;118}119 120template <AddCheckerFn Fn = addBlockEntranceTester, AddCheckerFn... Fns>121bool runChecker(const std::string &Code, std::string &Diags) {122 std::string RawDiags;123 bool Res = runCheckerOnCode<Fn, Fns...>(Code, RawDiags,124 /*OnlyEmitWarnings=*/true);125 llvm::raw_string_ostream OS(Diags);126 llvm::interleave(parseEachDiag(RawDiags), OS, "\n");127 return Res;128}129 130[[maybe_unused]] void dumpCFGAndEgraph(AnalysisASTConsumer &AnalysisConsumer,131 AnalyzerOptions &AnOpts) {132 AnOpts.CheckersAndPackages.emplace_back("debug.DumpCFG", true);133 AnOpts.CheckersAndPackages.emplace_back("debug.ViewExplodedGraph", true);134}135 136/// Use this instead of \c runChecker to enable the debugging a test case.137template <AddCheckerFn... Fns>138[[maybe_unused]] bool debugChecker(const std::string &Code,139 std::string &Diags) {140 return runChecker<dumpCFGAndEgraph, Fns...>(Code, Diags);141}142 143std::string expected(SmallVector<StringRef> Diags) {144 llvm::sort(Diags);145 std::string Result;146 llvm::raw_string_ostream OS(Result);147 llvm::interleave(Diags, OS, "\n");148 return Result;149}150 151TEST(BlockEntranceTester, FromEntryToExit) {152 constexpr auto Code = R"cpp(153 void top() {154 // empty155 })cpp";156 157 std::string Diags;158 // Use "debugChecker" instead of "runChecker" for debugging.159 EXPECT_TRUE(runChecker(Code, Diags));160 EXPECT_EQ(expected({"Within 'top' B1 -> B0"}), Diags);161}162 163TEST(BlockEntranceTester, SingleOpaqueIfCondition) {164 constexpr auto Code = R"cpp(165 bool coin();166 int glob;167 void top() {168 if (coin()) {169 glob = 1;170 } else {171 glob = 2;172 }173 glob = 3;174 })cpp";175 176 std::string Diags;177 // Use "debugChecker" instead of "runChecker" for debugging.178 EXPECT_TRUE(runChecker(Code, Diags));179 EXPECT_EQ(expected({180 "Within 'top' B1 -> B0",181 "Within 'top' B2 -> B1",182 "Within 'top' B3 -> B1",183 "Within 'top' B4 -> B2",184 "Within 'top' B4 -> B3",185 "Within 'top' B5 -> B4",186 }),187 Diags);188 // entry true exit189 // B5 -------> B4 --> B2 --> B1 --> B0190 // | ^191 // | false |192 // v |193 // B3 -----------------------+194}195 196TEST(BlockEntranceTester, TrivialIfCondition) {197 constexpr auto Code = R"cpp(198 bool coin();199 int glob;200 void top() {201 int cond = true;202 if (cond) {203 glob = 1;204 } else {205 glob = 2;206 }207 glob = 3;208 })cpp";209 210 std::string Diags;211 // Use "debugChecker" instead of "runChecker" for debugging.212 EXPECT_TRUE(runChecker(Code, Diags));213 EXPECT_EQ(expected({214 "Within 'top' B1 -> B0",215 "Within 'top' B3 -> B1",216 "Within 'top' B4 -> B3",217 "Within 'top' B5 -> B4",218 }),219 Diags);220 // entry true exit221 // B5 ----------> B4 --> B3 --> B1 --> B0222}223 224TEST(BlockEntranceTester, AcrossFunctions) {225 constexpr auto Code = R"cpp(226 bool coin();227 int glob;228 void nested() { glob = 1; }229 void top() {230 glob = 0;231 nested();232 glob = 2;233 })cpp";234 235 std::string Diags;236 // Use "debugChecker" instead of "runChecker" for debugging.237 EXPECT_TRUE(runChecker(Code, Diags));238 EXPECT_EQ(239 expected({240 // Going from the "top" entry artificial node to the "top" body.241 // Ideally, we shouldn't observe this edge because it's artificial.242 "Within 'top' B2 -> B1",243 244 // We encounter the call to "nested()" in the "top" body, thus we have245 // a "CallEnter" node, but importantly, we also elide the transition246 // to the "entry" node of "nested()".247 // We only see the edge from the "nested()" entry to the "nested()"248 // body:249 "Within 'nested' B2 -> B1",250 251 // Once we return from "nested()", we transition to the "exit" node of252 // "nested()":253 "Within 'nested' B1 -> B0",254 255 // We will eventually return to the "top" body, thus we transition to256 // its "exit" node:257 "Within 'top' B1 -> B0",258 }),259 Diags);260}261 262TEST(BlockEntranceTester, ShortCircuitingLogicalOperator) {263 constexpr auto Code = R"cpp(264 bool coin();265 void top(int x) {266 int v = 0;267 if (coin() && (v = x)) {268 v = 2;269 }270 v = 3;271 })cpp";272 // coin(): false273 // +--------------------------------+274 // entry | v exit275 // +----+ +----+ +----+ +----+ +----+ +----+276 // | B5 | --> | B4 | --> | B3 | --> | B2 | --> | B1 | --> | B0 |277 // +----+ +----+ +----+ +----+ +----+ +----+278 // | ^279 // +---------------------+280 // (v = x): false281 282 std::string Diags;283 // Use "debugChecker" instead of "runChecker" for debugging.284 EXPECT_TRUE(runChecker(Code, Diags));285 EXPECT_EQ(expected({286 "Within 'top' B1 -> B0",287 "Within 'top' B2 -> B1",288 "Within 'top' B3 -> B1",289 "Within 'top' B3 -> B2",290 "Within 'top' B4 -> B1",291 "Within 'top' B4 -> B3",292 "Within 'top' B5 -> B4",293 }),294 Diags);295}296 297TEST(BlockEntranceTester, Switch) {298 constexpr auto Code = R"cpp(299 bool coin();300 int top(int x) {301 int v = 0;302 switch (x) {303 case 1: v = 10; break;304 case 2: v = 20; break;305 default: v = 30; break;306 }307 return v;308 })cpp";309 // +----+310 // | B5 | -------------------------+311 // +----+ |312 // ^ [case 1] |313 // entry | v exit314 // +----+ +----+ [default] +----+ +----+ +----+315 // | B6 | --> | B2 | ----------> | B3 | --> | B1 | --> | B0 |316 // +----+ +----+ +----+ +----+ +----+317 // | ^318 // v [case 2] |319 // +----+ |320 // | B4 | -------------------------+321 // +----+322 323 std::string Diags;324 // Use "debugChecker" instead of "runChecker" for debugging.325 EXPECT_TRUE(runChecker(Code, Diags));326 EXPECT_EQ(expected({327 "Within 'top' B1 -> B0",328 "Within 'top' B2 -> B3",329 "Within 'top' B2 -> B4",330 "Within 'top' B2 -> B5",331 "Within 'top' B3 -> B1",332 "Within 'top' B4 -> B1",333 "Within 'top' B5 -> B1",334 "Within 'top' B6 -> B2",335 }),336 Diags);337}338 339TEST(BlockEntranceTester, BlockEntranceVSBranchCondition) {340 constexpr auto Code = R"cpp(341 bool coin();342 int top(int x) {343 int v = 0;344 switch (x) {345 default: v = 30; break;346 }347 if (x == 6) {348 v = 40;349 }350 return v;351 })cpp";352 std::string Diags;353 // Use "debugChecker" instead of "runChecker" for debugging.354 EXPECT_TRUE((runChecker<addBlockEntranceTester, addBranchConditionTester>(355 Code, Diags)));356 EXPECT_EQ(expected({357 "Within 'top' B1 -> B0",358 "Within 'top' B2 -> B1",359 "Within 'top' B3 -> B1",360 "Within 'top' B3 -> B2",361 "Within 'top' B4 -> B5",362 "Within 'top' B5 -> B3",363 "Within 'top' B6 -> B4",364 "Within 'top': branch condition 'x == 6'",365 }),366 Diags);367}368 369} // namespace370