Skip to content

Commit 0362a29

Browse files
authored
[clang][dataflow] Fix bug in buildContainsExprConsumedInDifferentBlock(). (#100874)
This was missing a call to `ignoreCFGOmittedNodes()`. As a result, the function would erroneously conclude that a block did not contain an expression consumed in a different block if the expression in question was surrounded by a `ParenExpr` in the consuming block. The patch adds a test that triggers this scenario (and fails without the fix). To prevent this kind of bug in the future, the patch also adds a new method `blockForStmt()` to `AdornedCFG` that calls `ignoreCFGOmittedNodes()` and is preferred over accessing `getStmtToBlock()` directly.
1 parent d86311f commit 0362a29

File tree

5 files changed

+90
-20
lines changed

5 files changed

+90
-20
lines changed

clang/include/clang/Analysis/FlowSensitive/AdornedCFG.h

+31-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "clang/AST/Decl.h"
1919
#include "clang/AST/Stmt.h"
2020
#include "clang/Analysis/CFG.h"
21+
#include "clang/Analysis/FlowSensitive/ASTOps.h"
2122
#include "llvm/ADT/BitVector.h"
2223
#include "llvm/ADT/DenseMap.h"
2324
#include "llvm/Support/Error.h"
@@ -27,6 +28,24 @@
2728
namespace clang {
2829
namespace dataflow {
2930

31+
namespace internal {
32+
class StmtToBlockMap {
33+
public:
34+
StmtToBlockMap(const CFG &Cfg);
35+
36+
const CFGBlock *lookup(const Stmt &S) const {
37+
return StmtToBlock.lookup(&ignoreCFGOmittedNodes(S));
38+
}
39+
40+
const llvm::DenseMap<const Stmt *, const CFGBlock *> &getMap() const {
41+
return StmtToBlock;
42+
}
43+
44+
private:
45+
llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock;
46+
};
47+
} // namespace internal
48+
3049
/// Holds CFG with additional information derived from it that is needed to
3150
/// perform dataflow analysis.
3251
class AdornedCFG {
@@ -49,8 +68,17 @@ class AdornedCFG {
4968
const CFG &getCFG() const { return *Cfg; }
5069

5170
/// Returns a mapping from statements to basic blocks that contain them.
71+
/// Deprecated. Use `blockForStmt()` instead (which prevents the potential
72+
/// error of forgetting to call `ignoreCFGOmittedNodes()` on the statement to
73+
/// look up).
5274
const llvm::DenseMap<const Stmt *, const CFGBlock *> &getStmtToBlock() const {
53-
return StmtToBlock;
75+
return StmtToBlock.getMap();
76+
}
77+
78+
/// Returns the basic block that contains `S`, or null if no basic block
79+
/// containing `S` is found.
80+
const CFGBlock *blockForStmt(const Stmt &S) const {
81+
return StmtToBlock.lookup(S);
5482
}
5583

5684
/// Returns whether `B` is reachable from the entry block.
@@ -73,8 +101,7 @@ class AdornedCFG {
73101
private:
74102
AdornedCFG(
75103
const Decl &D, std::unique_ptr<CFG> Cfg,
76-
llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock,
77-
llvm::BitVector BlockReachable,
104+
internal::StmtToBlockMap StmtToBlock, llvm::BitVector BlockReachable,
78105
llvm::DenseSet<const CFGBlock *> ContainsExprConsumedInDifferentBlock)
79106
: ContainingDecl(D), Cfg(std::move(Cfg)),
80107
StmtToBlock(std::move(StmtToBlock)),
@@ -85,7 +112,7 @@ class AdornedCFG {
85112
/// The `Decl` containing the statement used to construct the CFG.
86113
const Decl &ContainingDecl;
87114
std::unique_ptr<CFG> Cfg;
88-
llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock;
115+
internal::StmtToBlockMap StmtToBlock;
89116
llvm::BitVector BlockReachable;
90117
llvm::DenseSet<const CFGBlock *> ContainsExprConsumedInDifferentBlock;
91118
};

clang/lib/Analysis/FlowSensitive/AdornedCFG.cpp

+11-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "clang/AST/Decl.h"
1717
#include "clang/AST/Stmt.h"
1818
#include "clang/Analysis/CFG.h"
19+
#include "clang/Analysis/FlowSensitive/ASTOps.h"
1920
#include "llvm/ADT/BitVector.h"
2021
#include "llvm/ADT/DenseMap.h"
2122
#include "llvm/Support/Error.h"
@@ -96,16 +97,15 @@ static llvm::BitVector findReachableBlocks(const CFG &Cfg) {
9697

9798
static llvm::DenseSet<const CFGBlock *>
9899
buildContainsExprConsumedInDifferentBlock(
99-
const CFG &Cfg,
100-
const llvm::DenseMap<const Stmt *, const CFGBlock *> &StmtToBlock) {
100+
const CFG &Cfg, const internal::StmtToBlockMap &StmtToBlock) {
101101
llvm::DenseSet<const CFGBlock *> Result;
102102

103103
auto CheckChildExprs = [&Result, &StmtToBlock](const Stmt *S,
104104
const CFGBlock *Block) {
105105
for (const Stmt *Child : S->children()) {
106106
if (!isa_and_nonnull<Expr>(Child))
107107
continue;
108-
const CFGBlock *ChildBlock = StmtToBlock.lookup(Child);
108+
const CFGBlock *ChildBlock = StmtToBlock.lookup(*Child);
109109
if (ChildBlock != Block)
110110
Result.insert(ChildBlock);
111111
}
@@ -126,6 +126,13 @@ buildContainsExprConsumedInDifferentBlock(
126126
return Result;
127127
}
128128

129+
namespace internal {
130+
131+
StmtToBlockMap::StmtToBlockMap(const CFG &Cfg)
132+
: StmtToBlock(buildStmtToBasicBlockMap(Cfg)) {}
133+
134+
} // namespace internal
135+
129136
llvm::Expected<AdornedCFG> AdornedCFG::build(const FunctionDecl &Func) {
130137
if (!Func.doesThisDeclarationHaveABody())
131138
return llvm::createStringError(
@@ -166,8 +173,7 @@ llvm::Expected<AdornedCFG> AdornedCFG::build(const Decl &D, Stmt &S,
166173
std::make_error_code(std::errc::invalid_argument),
167174
"CFG::buildCFG failed");
168175

169-
llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock =
170-
buildStmtToBasicBlockMap(*Cfg);
176+
internal::StmtToBlockMap StmtToBlock(*Cfg);
171177

172178
llvm::BitVector BlockReachable = findReachableBlocks(*Cfg);
173179

clang/lib/Analysis/FlowSensitive/Transfer.cpp

+5-6
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,16 @@ namespace clang {
4040
namespace dataflow {
4141

4242
const Environment *StmtToEnvMap::getEnvironment(const Stmt &S) const {
43-
auto BlockIt = ACFG.getStmtToBlock().find(&ignoreCFGOmittedNodes(S));
44-
if (BlockIt == ACFG.getStmtToBlock().end()) {
43+
const CFGBlock *Block = ACFG.blockForStmt(S);
44+
if (Block == nullptr) {
4545
assert(false);
46-
// Return null to avoid dereferencing the end iterator in non-assert builds.
4746
return nullptr;
4847
}
49-
if (!ACFG.isBlockReachable(*BlockIt->getSecond()))
48+
if (!ACFG.isBlockReachable(*Block))
5049
return nullptr;
51-
if (BlockIt->getSecond()->getBlockID() == CurBlockID)
50+
if (Block->getBlockID() == CurBlockID)
5251
return &CurState.Env;
53-
const auto &State = BlockToState[BlockIt->getSecond()->getBlockID()];
52+
const auto &State = BlockToState[Block->getBlockID()];
5453
if (!(State))
5554
return nullptr;
5655
return &State->Env;

clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp

+5-4
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,11 @@ computeBlockInputState(const CFGBlock &Block, AnalysisContext &AC) {
243243
// See `NoreturnDestructorTest` for concrete examples.
244244
if (Block.succ_begin()->getReachableBlock() != nullptr &&
245245
Block.succ_begin()->getReachableBlock()->hasNoReturnElement()) {
246-
auto &StmtToBlock = AC.ACFG.getStmtToBlock();
247-
auto StmtBlock = StmtToBlock.find(Block.getTerminatorStmt());
248-
assert(StmtBlock != StmtToBlock.end());
249-
llvm::erase(Preds, StmtBlock->getSecond());
246+
const CFGBlock *StmtBlock = nullptr;
247+
if (const Stmt *Terminator = Block.getTerminatorStmt())
248+
StmtBlock = AC.ACFG.blockForStmt(*Terminator);
249+
assert(StmtBlock != nullptr);
250+
llvm::erase(Preds, StmtBlock);
250251
}
251252
}
252253

clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp

+38-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "TestingSupport.h"
1010
#include "clang/AST/Decl.h"
1111
#include "clang/AST/ExprCXX.h"
12+
#include "clang/AST/OperationKinds.h"
1213
#include "clang/AST/Type.h"
1314
#include "clang/ASTMatchers/ASTMatchFinder.h"
1415
#include "clang/ASTMatchers/ASTMatchers.h"
@@ -79,7 +80,7 @@ class DataflowAnalysisTest : public Test {
7980

8081
/// Returns the `CFGBlock` containing `S` (and asserts that it exists).
8182
const CFGBlock *blockForStmt(const Stmt &S) {
82-
const CFGBlock *Block = ACFG->getStmtToBlock().lookup(&S);
83+
const CFGBlock *Block = ACFG->blockForStmt(S);
8384
assert(Block != nullptr);
8485
return Block;
8586
}
@@ -370,6 +371,42 @@ TEST_F(DiscardExprStateTest, ConditionalOperator) {
370371
EXPECT_EQ(CallGState.Env.get<PointerValue>(AddrOfI), nullptr);
371372
}
372373

374+
TEST_F(DiscardExprStateTest, CallWithParenExprTreatedCorrectly) {
375+
// This is a regression test.
376+
// In the CFG for `target()` below, the expression that evaluates the function
377+
// pointer for `expect` and the actual call are separated into different
378+
// baseic blocks (because of the control flow introduced by the `||`
379+
// operator).
380+
// The value for the `expect` function pointer was erroneously discarded
381+
// from the environment between these two blocks because the code that
382+
// determines whether the expression values for a block need to be preserved
383+
// did not ignore the `ParenExpr` around `(i == 1)` (which is not represented
384+
// in the CFG).
385+
std::string Code = R"(
386+
bool expect(bool, bool);
387+
void target(int i) {
388+
expect(false || (i == 1), false);
389+
}
390+
)";
391+
auto BlockStates = llvm::cantFail(runAnalysis<NoopAnalysis>(
392+
Code, [](ASTContext &C) { return NoopAnalysis(C); }));
393+
394+
const auto &FnToPtrDecay = matchNode<ImplicitCastExpr>(
395+
implicitCastExpr(hasCastKind(CK_FunctionToPointerDecay)));
396+
const auto &CallExpect =
397+
matchNode<CallExpr>(callExpr(callee(functionDecl(hasName("expect")))));
398+
399+
// In the block that evaluates the implicit cast of `expect` to a pointer,
400+
// this expression is associated with a value.
401+
const auto &FnToPtrDecayState = blockStateForStmt(BlockStates, FnToPtrDecay);
402+
EXPECT_NE(FnToPtrDecayState.Env.getValue(FnToPtrDecay), nullptr);
403+
404+
// In the block that calls `expect()`, the implicit cast of `expect` to a
405+
// pointer is still associated with a value.
406+
const auto &CallExpectState = blockStateForStmt(BlockStates, CallExpect);
407+
EXPECT_NE(CallExpectState.Env.getValue(FnToPtrDecay), nullptr);
408+
}
409+
373410
struct NonConvergingLattice {
374411
int State;
375412

0 commit comments

Comments
 (0)