diff --git a/clang/include/clang/StaticAnalyzer/Core/Checker.h b/clang/include/clang/StaticAnalyzer/Core/Checker.h index acd83602aeec4..c6866cb561551 100644 --- a/clang/include/clang/StaticAnalyzer/Core/Checker.h +++ b/clang/include/clang/StaticAnalyzer/Core/Checker.h @@ -221,6 +221,22 @@ class Bind { } }; +class BlockEntrance { + template + static void _checkBlockEntrance(void *Checker, + const clang::BlockEntrance &Entrance, + CheckerContext &C) { + ((const CHECKER *)Checker)->checkBlockEntrance(Entrance, C); + } + +public: + template + static void _register(CHECKER *checker, CheckerManager &mgr) { + mgr._registerForBlockEntrance(CheckerManager::CheckBlockEntranceFunc( + checker, _checkBlockEntrance)); + } +}; + class EndAnalysis { template static void _checkEndAnalysis(void *checker, ExplodedGraph &G, @@ -536,6 +552,8 @@ class CheckerBase : public CheckerFrontend, public CheckerBackend { template class Checker : public CheckerBase, public CHECKs... { public: + using BlockEntrance = clang::BlockEntrance; + template static void _register(CHECKER *Chk, CheckerManager &Mgr) { (CHECKs::_register(Chk, Mgr), ...); @@ -565,6 +583,8 @@ class Checker : public CheckerBase, public CHECKs... { template class CheckerFamily : public CheckerBackend, public CHECKs... { public: + using BlockEntrance = clang::BlockEntrance; + template static void _register(CHECKER *Chk, CheckerManager &Mgr) { (CHECKs::_register(Chk, Mgr), ...); diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h index 58f59f7ab049f..c8e6f1265a3db 100644 --- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h @@ -344,6 +344,12 @@ class CheckerManager { const Stmt *S, ExprEngine &Eng, const ProgramPoint &PP); + /// Run checkers after taking a control flow edge. + void runCheckersForBlockEntrance(ExplodedNodeSet &Dst, + const ExplodedNodeSet &Src, + const BlockEntrance &Entrance, + ExprEngine &Eng) const; + /// Run checkers for end of analysis. void runCheckersForEndAnalysis(ExplodedGraph &G, BugReporter &BR, ExprEngine &Eng); @@ -496,6 +502,9 @@ class CheckerManager { using CheckBindFunc = CheckerFn; + using CheckBlockEntranceFunc = + CheckerFn; + using CheckEndAnalysisFunc = CheckerFn; @@ -557,6 +566,8 @@ class CheckerManager { void _registerForBind(CheckBindFunc checkfn); + void _registerForBlockEntrance(CheckBlockEntranceFunc checkfn); + void _registerForEndAnalysis(CheckEndAnalysisFunc checkfn); void _registerForBeginFunction(CheckBeginFunctionFunc checkfn); @@ -663,6 +674,8 @@ class CheckerManager { std::vector BindCheckers; + std::vector BlockEntranceCheckers; + std::vector EndAnalysisCheckers; std::vector BeginFunctionCheckers; diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index b8a4dcbc727a6..6370586e218ef 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -321,6 +321,10 @@ class ExprEngine { NodeBuilderWithSinks &nodeBuilder, ExplodedNode *Pred); + void runCheckersForBlockEntrance(const NodeBuilderContext &BldCtx, + const BlockEntrance &Entrance, + ExplodedNode *Pred, ExplodedNodeSet &Dst); + /// ProcessBranch - Called by CoreEngine. Used to generate successor nodes by /// processing the 'effects' of a branch condition. If the branch condition /// is a loop condition, IterationsCompletedInLoop is the number of completed diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp index 949a71d1c42d7..350db4b1bc2bc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp @@ -39,6 +39,7 @@ class CheckerDocumentation check::ASTDecl, check::BeginFunction, check::Bind, + check::BlockEntrance, check::BranchCondition, check::ConstPointerEscape, check::DeadSymbols, @@ -128,7 +129,20 @@ class CheckerDocumentation /// check::PostCall void checkPostCall(const CallEvent &Call, CheckerContext &C) const {} - /// Pre-visit of the condition statement of a branch (such as IfStmt). + /// Pre-visit of the condition statement of a branch. + /// For example: + /// - logical operators (&&, ||) + /// - if, do, while, for, ranged-for statements + /// - ternary operators (?:), gnu conditionals, gnu choose expressions + /// Interestingly, switch statements don't seem to trigger BranchCondition. + /// + /// check::BlockEntrance is a similar callback, which is strictly more + /// generic. Prefer check::BranchCondition to check::BlockEntrance if + /// pre-visiting conditional statements is enough for the checker. + /// Note that check::BlockEntrance is also invoked for leaving basic blocks + /// while entering the next. + /// + /// check::BranchCondition void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const {} /// Post-visit the C++ operator new's allocation call. @@ -165,6 +179,29 @@ class CheckerDocumentation /// check::Bind void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &) const {} + /// Called after a CFG edge is taken within a function. + /// + /// This callback can be used to obtain information about potential branching + /// points or any other constructs that involve traversing a CFG edge. + /// + /// check::BranchCondition is a similar callback, which is only invoked for + /// pre-visiting the condition statement of a branch. Prefer that callback if + /// possible. + /// + /// \remark There is no CFG edge from the caller to a callee, consequently + /// this callback is not invoked for "inlining" a function call. + /// \remark Once a function call is inlined, we will start from the imaginary + /// "entry" basic block of that CFG. This callback will be invoked for + /// entering the real first basic block of the "inlined" function body from + /// that "entry" basic block. + /// \remark This callback is also invoked for entering the imaginary "exit" + /// basic block of the CFG when returning from a function. + /// + /// \param E The ProgramPoint that describes the transition. + /// + /// check::BlockEntrance + void checkBlockEntrance(const BlockEntrance &E, CheckerContext &) const {} + /// Called whenever a symbol becomes dead. /// /// This callback should be used by the checkers to aggressively clean diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index 945822339e6a0..d2b7b2bfbb019 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -41,11 +41,11 @@ bool CheckerManager::hasPathSensitiveCheckers() const { return IfAnyAreNonEmpty( StmtCheckers, PreObjCMessageCheckers, ObjCMessageNilCheckers, PostObjCMessageCheckers, PreCallCheckers, PostCallCheckers, - LocationCheckers, BindCheckers, EndAnalysisCheckers, - BeginFunctionCheckers, EndFunctionCheckers, BranchConditionCheckers, - NewAllocatorCheckers, LiveSymbolsCheckers, DeadSymbolsCheckers, - RegionChangesCheckers, PointerEscapeCheckers, EvalAssumeCheckers, - EvalCallCheckers, EndOfTranslationUnitCheckers); + LocationCheckers, BindCheckers, BlockEntranceCheckers, + EndAnalysisCheckers, BeginFunctionCheckers, EndFunctionCheckers, + BranchConditionCheckers, NewAllocatorCheckers, LiveSymbolsCheckers, + DeadSymbolsCheckers, RegionChangesCheckers, PointerEscapeCheckers, + EvalAssumeCheckers, EvalCallCheckers, EndOfTranslationUnitCheckers); } void CheckerManager::reportInvalidCheckerOptionValue( @@ -418,6 +418,42 @@ void CheckerManager::runCheckersForBind(ExplodedNodeSet &Dst, expandGraphWithCheckers(C, Dst, Src); } +namespace { +struct CheckBlockEntranceContext { + using CheckBlockEntranceFunc = CheckerManager::CheckBlockEntranceFunc; + using CheckersTy = std::vector; + + const CheckersTy &Checkers; + const BlockEntrance &Entrance; + ExprEngine &Eng; + + CheckBlockEntranceContext(const CheckersTy &Checkers, + const BlockEntrance &Entrance, ExprEngine &Eng) + : Checkers(Checkers), Entrance(Entrance), Eng(Eng) {} + + auto checkers_begin() const { return Checkers.begin(); } + auto checkers_end() const { return Checkers.end(); } + + void runChecker(CheckBlockEntranceFunc CheckFn, NodeBuilder &Bldr, + ExplodedNode *Pred) { + llvm::TimeTraceScope TimeScope( + checkerScopeName("BlockEntrance", CheckFn.Checker)); + CheckerContext C(Bldr, Eng, Pred, Entrance.withTag(CheckFn.Checker)); + CheckFn(Entrance, C); + } +}; + +} // namespace + +void CheckerManager::runCheckersForBlockEntrance(ExplodedNodeSet &Dst, + const ExplodedNodeSet &Src, + const BlockEntrance &Entrance, + ExprEngine &Eng) const { + CheckBlockEntranceContext C(BlockEntranceCheckers, Entrance, Eng); + llvm::TimeTraceScope TimeScope{"CheckerManager::runCheckersForBlockEntrance"}; + expandGraphWithCheckers(C, Dst, Src); +} + void CheckerManager::runCheckersForEndAnalysis(ExplodedGraph &G, BugReporter &BR, ExprEngine &Eng) { @@ -875,6 +911,10 @@ void CheckerManager::_registerForBind(CheckBindFunc checkfn) { BindCheckers.push_back(checkfn); } +void CheckerManager::_registerForBlockEntrance(CheckBlockEntranceFunc checkfn) { + BlockEntranceCheckers.push_back(checkfn); +} + void CheckerManager::_registerForEndAnalysis(CheckEndAnalysisFunc checkfn) { EndAnalysisCheckers.push_back(checkfn); } diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp index 84d1502a2c25b..95a843ee87571 100644 --- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -304,26 +304,37 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { } } + ExplodedNodeSet CheckerNodes; + BlockEntrance BE(L.getSrc(), L.getDst(), Pred->getLocationContext()); + ExprEng.runCheckersForBlockEntrance(BuilderCtx, BE, Pred, CheckerNodes); + // Process the final state transition. - ExprEng.processEndOfFunction(BuilderCtx, Pred, RS); + for (ExplodedNode *P : CheckerNodes) { + ExprEng.processEndOfFunction(BuilderCtx, P, RS); + } // This path is done. Don't enqueue any more nodes. return; } // Call into the ExprEngine to process entering the CFGBlock. - ExplodedNodeSet dstNodes; BlockEntrance BE(L.getSrc(), L.getDst(), Pred->getLocationContext()); - NodeBuilderWithSinks nodeBuilder(Pred, dstNodes, BuilderCtx, BE); - ExprEng.processCFGBlockEntrance(L, nodeBuilder, Pred); + ExplodedNodeSet DstNodes; + NodeBuilderWithSinks NodeBuilder(Pred, DstNodes, BuilderCtx, BE); + ExprEng.processCFGBlockEntrance(L, NodeBuilder, Pred); // Auto-generate a node. - if (!nodeBuilder.hasGeneratedNodes()) { - nodeBuilder.generateNode(Pred->State, Pred); + if (!NodeBuilder.hasGeneratedNodes()) { + NodeBuilder.generateNode(Pred->State, Pred); + } + + ExplodedNodeSet CheckerNodes; + for (auto *N : DstNodes) { + ExprEng.runCheckersForBlockEntrance(BuilderCtx, BE, N, CheckerNodes); } // Enqueue nodes onto the worklist. - enqueue(dstNodes); + enqueue(CheckerNodes); } void CoreEngine::HandleBlockEntrance(const BlockEntrance &L, diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 0fac44cb2f80e..b28deee41d1c5 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -2617,6 +2617,19 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L, } } +void ExprEngine::runCheckersForBlockEntrance(const NodeBuilderContext &BldCtx, + const BlockEntrance &Entrance, + ExplodedNode *Pred, + ExplodedNodeSet &Dst) { + llvm::PrettyStackTraceFormat CrashInfo( + "Processing block entrance B%d -> B%d", + Entrance.getPreviousBlock()->getBlockID(), + Entrance.getBlock()->getBlockID()); + currBldrCtx = &BldCtx; + getCheckerManager().runCheckersForBlockEntrance(Dst, Pred, Entrance, *this); + currBldrCtx = nullptr; +} + //===----------------------------------------------------------------------===// // Branch processing. //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index b91954eda19b8..fa8e669b6bb2f 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -711,9 +711,10 @@ void ExprEngine::VisitLogicalExpr(const BinaryOperator* B, ExplodedNode *Pred, } ExplodedNode *N = Pred; - while (!N->getLocation().getAs()) { + while (!N->getLocation().getAs()) { ProgramPoint P = N->getLocation(); - assert(P.getAs()|| P.getAs()); + assert(P.getAs() || P.getAs() || + P.getAs()); (void) P; if (N->pred_size() != 1) { // We failed to track back where we came from. @@ -729,7 +730,6 @@ void ExprEngine::VisitLogicalExpr(const BinaryOperator* B, ExplodedNode *Pred, return; } - N = *N->pred_begin(); BlockEdge BE = N->getLocation().castAs(); SVal X; diff --git a/clang/unittests/StaticAnalyzer/BlockEntranceCallbackTest.cpp b/clang/unittests/StaticAnalyzer/BlockEntranceCallbackTest.cpp new file mode 100644 index 0000000000000..9f62d8b87d96d --- /dev/null +++ b/clang/unittests/StaticAnalyzer/BlockEntranceCallbackTest.cpp @@ -0,0 +1,371 @@ +//===- unittests/StaticAnalyzer/BlockEntranceCallbackTest.cpp -------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "CheckerRegistration.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/ProgramPoint.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" +#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" +#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/raw_ostream.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace ento; + +namespace { + +class BlockEntranceCallbackTester final : public Checker { + const BugType Bug{this, "BlockEntranceTester"}; + +public: + void checkBlockEntrance(const BlockEntrance &Entrance, + CheckerContext &C) const { + ExplodedNode *Node = C.generateNonFatalErrorNode(C.getState()); + if (!Node) + return; + + const auto *FD = + cast(C.getLocationContext()->getStackFrame()->getDecl()); + + std::string Description = llvm::formatv( + "Within '{0}' B{1} -> B{2}", FD->getIdentifier()->getName(), + Entrance.getPreviousBlock()->getBlockID(), + Entrance.getBlock()->getBlockID()); + auto Report = + std::make_unique(Bug, Description, Node); + C.emitReport(std::move(Report)); + } +}; + +class BranchConditionCallbackTester final + : public Checker { + const BugType Bug{this, "BranchConditionCallbackTester"}; + +public: + void checkBranchCondition(const Stmt *Condition, CheckerContext &C) const { + ExplodedNode *Node = C.generateNonFatalErrorNode(C.getState()); + if (!Node) + return; + const auto *FD = + cast(C.getLocationContext()->getStackFrame()->getDecl()); + + std::string Buffer = + (llvm::Twine("Within '") + FD->getIdentifier()->getName() + + "': branch condition '") + .str(); + llvm::raw_string_ostream OS(Buffer); + Condition->printPretty(OS, /*Helper=*/nullptr, + C.getASTContext().getPrintingPolicy()); + OS << "'"; + auto Report = std::make_unique(Bug, Buffer, Node); + C.emitReport(std::move(Report)); + + C.addTransition(); + } +}; + +template void registerChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool shouldAlwaysRegister(const CheckerManager &) { return true; } + +void addBlockEntranceTester(AnalysisASTConsumer &AnalysisConsumer, + AnalyzerOptions &AnOpts) { + AnOpts.CheckersAndPackages.emplace_back("test.BlockEntranceTester", true); + AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { + Registry.addChecker(®isterChecker, + &shouldAlwaysRegister, "test.BlockEntranceTester", + "EmptyDescription", "EmptyDocsUri", + /*IsHidden=*/false); + }); +} + +void addBranchConditionTester(AnalysisASTConsumer &AnalysisConsumer, + AnalyzerOptions &AnOpts) { + AnOpts.CheckersAndPackages.emplace_back("test.BranchConditionTester", true); + AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { + Registry.addChecker(®isterChecker, + &shouldAlwaysRegister, "test.BranchConditionTester", + "EmptyDescription", "EmptyDocsUri", + /*IsHidden=*/false); + }); +} + +llvm::SmallVector parseEachDiag(StringRef Diags) { + llvm::SmallVector Fragments; + llvm::SplitString(Diags, Fragments, "\n"); + // Drop the prefix like "test.BlockEntranceTester: " from each fragment. + llvm::for_each(Fragments, [](StringRef &Fragment) { + Fragment = Fragment.drop_until([](char Ch) { return Ch == ' '; }); + Fragment.consume_front(" "); + }); + llvm::sort(Fragments); + return Fragments; +} + +template +bool runChecker(const std::string &Code, std::string &Diags) { + std::string RawDiags; + bool Res = runCheckerOnCode(Code, RawDiags, + /*OnlyEmitWarnings=*/true); + llvm::raw_string_ostream OS(Diags); + llvm::interleave(parseEachDiag(RawDiags), OS, "\n"); + return Res; +} + +[[maybe_unused]] void dumpCFGAndEgraph(AnalysisASTConsumer &AnalysisConsumer, + AnalyzerOptions &AnOpts) { + AnOpts.CheckersAndPackages.emplace_back("debug.DumpCFG", true); + AnOpts.CheckersAndPackages.emplace_back("debug.ViewExplodedGraph", true); +} + +/// Use this instead of \c runChecker to enable the debugging a test case. +template +[[maybe_unused]] bool debugChecker(const std::string &Code, + std::string &Diags) { + return runChecker(Code, Diags); +} + +std::string expected(SmallVector Diags) { + llvm::sort(Diags); + std::string Result; + llvm::raw_string_ostream OS(Result); + llvm::interleave(Diags, OS, "\n"); + return Result; +} + +TEST(BlockEntranceTester, FromEntryToExit) { + constexpr auto Code = R"cpp( + void top() { + // empty + })cpp"; + + std::string Diags; + // Use "debugChecker" instead of "runChecker" for debugging. + EXPECT_TRUE(runChecker(Code, Diags)); + EXPECT_EQ(expected({"Within 'top' B1 -> B0"}), Diags); +} + +TEST(BlockEntranceTester, SingleOpaqueIfCondition) { + constexpr auto Code = R"cpp( + bool coin(); + int glob; + void top() { + if (coin()) { + glob = 1; + } else { + glob = 2; + } + glob = 3; + })cpp"; + + std::string Diags; + // Use "debugChecker" instead of "runChecker" for debugging. + EXPECT_TRUE(runChecker(Code, Diags)); + EXPECT_EQ(expected({ + "Within 'top' B1 -> B0", + "Within 'top' B2 -> B1", + "Within 'top' B3 -> B1", + "Within 'top' B4 -> B2", + "Within 'top' B4 -> B3", + "Within 'top' B5 -> B4", + }), + Diags); + // entry true exit + // B5 -------> B4 --> B2 --> B1 --> B0 + // | ^ + // | false | + // v | + // B3 -----------------------+ +} + +TEST(BlockEntranceTester, TrivialIfCondition) { + constexpr auto Code = R"cpp( + bool coin(); + int glob; + void top() { + int cond = true; + if (cond) { + glob = 1; + } else { + glob = 2; + } + glob = 3; + })cpp"; + + std::string Diags; + // Use "debugChecker" instead of "runChecker" for debugging. + EXPECT_TRUE(runChecker(Code, Diags)); + EXPECT_EQ(expected({ + "Within 'top' B1 -> B0", + "Within 'top' B3 -> B1", + "Within 'top' B4 -> B3", + "Within 'top' B5 -> B4", + }), + Diags); + // entry true exit + // B5 ----------> B4 --> B3 --> B1 --> B0 +} + +TEST(BlockEntranceTester, AcrossFunctions) { + constexpr auto Code = R"cpp( + bool coin(); + int glob; + void nested() { glob = 1; } + void top() { + glob = 0; + nested(); + glob = 2; + })cpp"; + + std::string Diags; + // Use "debugChecker" instead of "runChecker" for debugging. + EXPECT_TRUE(runChecker(Code, Diags)); + EXPECT_EQ( + expected({ + // Going from the "top" entry artificial node to the "top" body. + // Ideally, we shouldn't observe this edge because it's artificial. + "Within 'top' B2 -> B1", + + // We encounter the call to "nested()" in the "top" body, thus we have + // a "CallEnter" node, but importantly, we also elide the transition + // to the "entry" node of "nested()". + // We only see the edge from the "nested()" entry to the "nested()" + // body: + "Within 'nested' B2 -> B1", + + // Once we return from "nested()", we transition to the "exit" node of + // "nested()": + "Within 'nested' B1 -> B0", + + // We will eventually return to the "top" body, thus we transition to + // its "exit" node: + "Within 'top' B1 -> B0", + }), + Diags); +} + +TEST(BlockEntranceTester, ShortCircuitingLogicalOperator) { + constexpr auto Code = R"cpp( + bool coin(); + void top(int x) { + int v = 0; + if (coin() && (v = x)) { + v = 2; + } + v = 3; + })cpp"; + // coin(): false + // +--------------------------------+ + // entry | v exit + // +----+ +----+ +----+ +----+ +----+ +----+ + // | B5 | --> | B4 | --> | B3 | --> | B2 | --> | B1 | --> | B0 | + // +----+ +----+ +----+ +----+ +----+ +----+ + // | ^ + // +---------------------+ + // (v = x): false + + std::string Diags; + // Use "debugChecker" instead of "runChecker" for debugging. + EXPECT_TRUE(runChecker(Code, Diags)); + EXPECT_EQ(expected({ + "Within 'top' B1 -> B0", + "Within 'top' B2 -> B1", + "Within 'top' B3 -> B1", + "Within 'top' B3 -> B2", + "Within 'top' B4 -> B1", + "Within 'top' B4 -> B3", + "Within 'top' B5 -> B4", + }), + Diags); +} + +TEST(BlockEntranceTester, Switch) { + constexpr auto Code = R"cpp( + bool coin(); + int top(int x) { + int v = 0; + switch (x) { + case 1: v = 10; break; + case 2: v = 20; break; + default: v = 30; break; + } + return v; + })cpp"; + // +----+ + // | B5 | -------------------------+ + // +----+ | + // ^ [case 1] | + // entry | v exit + // +----+ +----+ [default] +----+ +----+ +----+ + // | B6 | --> | B2 | ----------> | B3 | --> | B1 | --> | B0 | + // +----+ +----+ +----+ +----+ +----+ + // | ^ + // v [case 2] | + // +----+ | + // | B4 | -------------------------+ + // +----+ + + std::string Diags; + // Use "debugChecker" instead of "runChecker" for debugging. + EXPECT_TRUE(runChecker(Code, Diags)); + EXPECT_EQ(expected({ + "Within 'top' B1 -> B0", + "Within 'top' B2 -> B3", + "Within 'top' B2 -> B4", + "Within 'top' B2 -> B5", + "Within 'top' B3 -> B1", + "Within 'top' B4 -> B1", + "Within 'top' B5 -> B1", + "Within 'top' B6 -> B2", + }), + Diags); +} + +TEST(BlockEntranceTester, BlockEntranceVSBranchCondition) { + constexpr auto Code = R"cpp( + bool coin(); + int top(int x) { + int v = 0; + switch (x) { + default: v = 30; break; + } + if (x == 6) { + v = 40; + } + return v; + })cpp"; + std::string Diags; + // Use "debugChecker" instead of "runChecker" for debugging. + EXPECT_TRUE((runChecker( + Code, Diags))); + EXPECT_EQ(expected({ + "Within 'top' B1 -> B0", + "Within 'top' B2 -> B1", + "Within 'top' B3 -> B1", + "Within 'top' B3 -> B2", + "Within 'top' B4 -> B5", + "Within 'top' B5 -> B3", + "Within 'top' B6 -> B4", + "Within 'top': branch condition 'x == 6'", + }), + Diags); +} + +} // namespace diff --git a/clang/unittests/StaticAnalyzer/CMakeLists.txt b/clang/unittests/StaticAnalyzer/CMakeLists.txt index 143b7eedbfe05..9e10c4a4e637d 100644 --- a/clang/unittests/StaticAnalyzer/CMakeLists.txt +++ b/clang/unittests/StaticAnalyzer/CMakeLists.txt @@ -1,6 +1,7 @@ add_clang_unittest(StaticAnalysisTests AnalyzerOptionsTest.cpp APSIntTypeTest.cpp + BlockEntranceCallbackTest.cpp BugReportInterestingnessTest.cpp CallDescriptionTest.cpp CallEventTest.cpp