Index: clang/include/clang/Analysis/CFG.h =================================================================== --- clang/include/clang/Analysis/CFG.h +++ clang/include/clang/Analysis/CFG.h @@ -860,6 +860,14 @@ Stmt *getTerminatorStmt() { return Terminator.getStmt(); } const Stmt *getTerminatorStmt() const { return Terminator.getStmt(); } + /// \returns the last (\c rbegin()) condition, e.g. observe the following code + /// snippet: + /// if (A && B && C) + /// A block would be created for \c A, \c B, and \c C. For the latter, + /// \c getTerminatorStmt() would retrieve the entire condition, rather than + /// C itself, while this method would only return C. + const Expr *getLastCondition() const; + Stmt *getTerminatorCondition(bool StripParens = true); const Stmt *getTerminatorCondition(bool StripParens = true) const { Index: clang/lib/Analysis/CFG.cpp =================================================================== --- clang/lib/Analysis/CFG.cpp +++ clang/lib/Analysis/CFG.cpp @@ -5615,6 +5615,30 @@ Out << JsonFormat(TempOut.str(), AddQuotes); } +const Expr *CFGBlock::getLastCondition() const { + // If the terminator is a temporary dtor or a virtual base, etc, we can't + // retrieve a meaningful condition, bail out. + if (Terminator.getKind() != CFGTerminator::StmtBranch) + return nullptr; + + // Also, if this method was called on a block that doesn't have 2 successors, + // this block doesn't have retrievable condition. + if (succ_size() < 2) + return nullptr; + + auto StmtElem = rbegin()->getAs(); + if (!StmtElem) + return nullptr; + + const Stmt *Cond = StmtElem->getStmt(); + if (isa(Cond)) + return nullptr; + + // Only ObjCForCollectionStmt is known not to be a non-Expr terminator, hence + // the cast<>. + return cast(Cond)->IgnoreParens(); +} + Stmt *CFGBlock::getTerminatorCondition(bool StripParens) { Stmt *Terminator = getTerminatorStmt(); if (!Terminator) Index: clang/unittests/Analysis/CFGTest.cpp =================================================================== --- clang/unittests/Analysis/CFGTest.cpp +++ clang/unittests/Analysis/CFGTest.cpp @@ -67,6 +67,58 @@ expectLinear(true, "void foo() { foo(); }"); // Recursion is not our problem. } +TEST(CFG, ConditionExpr) { + const char *Code = R"(void f(bool A, bool B, bool C) { + if (A && B && C) + int x; + })"; + BuildResult Result = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); + + // [B5 (ENTRY)] -> [B4] -> [B3] -> [B2] -> [B1] -> [B0 (EXIT)] + // \ \ \ / + // -------------------------------> + + CFG *cfg = Result.getCFG(); + + auto GetBlock = [cfg] (unsigned Index) -> CFGBlock * { + assert(Index < cfg->size()); + return *(cfg->begin() + Index); + }; + + auto GetExprText = [] (const Expr *E) -> std::string { + // It's very awkward trying to recover the actual expression text without + // a real source file, so use this as a workaround. We know that the + // condition expression looks like this: + // + // ImplicitCastExpr 0xd07bf8 '_Bool' + // `-DeclRefExpr 0xd07bd8 '_Bool' lvalue ParmVar 0xd07960 'C' '_Bool' + + assert(isa(E)); + assert(++E->child_begin() == E->child_end()); + const auto *D = dyn_cast(*E->child_begin()); + return D->getFoundDecl()->getNameAsString(); + }; + + EXPECT_EQ(GetBlock(1)->getLastCondition(), nullptr); + EXPECT_EQ(GetExprText(GetBlock(4)->getLastCondition()), "A"); + EXPECT_EQ(GetExprText(GetBlock(3)->getLastCondition()), "B"); + EXPECT_EQ(GetExprText(GetBlock(2)->getLastCondition()), "C"); + + //===--------------------------------------------------------------------===// + + Code = R"(void foo(int x, int y) { + (void)(x + y); + })"; + Result = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); + + // [B2 (ENTRY)] -> [B1] -> [B0 (EXIT)] + + cfg = Result.getCFG(); + EXPECT_EQ(GetBlock(1)->getLastCondition(), nullptr); +} + } // namespace } // namespace analysis } // namespace clang