brintos

brintos / llvm-project-archived public Read only

0
0
Text · 11.9 KiB · d15bec0 Raw
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(&registerChecker<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(&registerChecker<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