|  | //===- 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<check::BlockEntrance> { | 
|  | 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<FunctionDecl>(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<PathSensitiveBugReport>(Bug, Description, Node); | 
|  | C.emitReport(std::move(Report)); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class BranchConditionCallbackTester final | 
|  | : public Checker<check::BranchCondition> { | 
|  | 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<FunctionDecl>(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<PathSensitiveBugReport>(Bug, Buffer, Node); | 
|  | C.emitReport(std::move(Report)); | 
|  |  | 
|  | C.addTransition(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | template <typename Checker> void registerChecker(CheckerManager &Mgr) { | 
|  | Mgr.registerChecker<Checker>(); | 
|  | } | 
|  |  | 
|  | 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<BlockEntranceCallbackTester>, | 
|  | &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<BranchConditionCallbackTester>, | 
|  | &shouldAlwaysRegister, "test.BranchConditionTester", | 
|  | "EmptyDescription", "EmptyDocsUri", | 
|  | /*IsHidden=*/false); | 
|  | }); | 
|  | } | 
|  |  | 
|  | llvm::SmallVector<StringRef> parseEachDiag(StringRef Diags) { | 
|  | llvm::SmallVector<StringRef> Fragments; | 
|  | llvm::SplitString(Diags, Fragments, "\n"); | 
|  | // Drop the prefix like "test.BlockEntranceTester: " from each fragment. | 
|  | for (StringRef &Fragment : Fragments) { | 
|  | Fragment = Fragment.drop_until([](char Ch) { return Ch == ' '; }); | 
|  | Fragment.consume_front(" "); | 
|  | } | 
|  | llvm::sort(Fragments); | 
|  | return Fragments; | 
|  | } | 
|  |  | 
|  | template <AddCheckerFn Fn = addBlockEntranceTester, AddCheckerFn... Fns> | 
|  | bool runChecker(const std::string &Code, std::string &Diags) { | 
|  | std::string RawDiags; | 
|  | bool Res = runCheckerOnCode<Fn, Fns...>(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 <AddCheckerFn... Fns> | 
|  | [[maybe_unused]] bool debugChecker(const std::string &Code, | 
|  | std::string &Diags) { | 
|  | return runChecker<dumpCFGAndEgraph, Fns...>(Code, Diags); | 
|  | } | 
|  |  | 
|  | std::string expected(SmallVector<StringRef> 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<addBlockEntranceTester, addBranchConditionTester>( | 
|  | 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 |