Skip to content

Commit ccf1e32

Browse files
authored
[clang][dataflow] Process terminator condition within transferCFGBlock(). (#78127)
In particular, it's important that we create the "fallback" atomic at this point (which we produce if the transfer function didn't produce a value for the expression) so that it is placed in the correct environment. Previously, we processed the terminator condition in the `TerminatorVisitor`, which put the fallback atomic in a copy of the environment that is produced as input for the _successor_ block, rather than the environment for the block containing the expression for which we produce the fallback atomic. As a result, we produce different fallback atomics every time we process the successor block, and hence we don't have a consistent representation of the terminator condition in the flow condition. This patch includes a test (authored by ymand@) that fails without the fix.
1 parent 66237d6 commit ccf1e32

File tree

6 files changed

+112
-26
lines changed

6 files changed

+112
-26
lines changed

clang/include/clang/Analysis/FlowSensitive/Transfer.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,17 @@ namespace dataflow {
2525
/// Maps statements to the environments of basic blocks that contain them.
2626
class StmtToEnvMap {
2727
public:
28+
// `CurBlockID` is the ID of the block currently being processed, and
29+
// `CurState` is the pending state currently associated with this block. These
30+
// are supplied separately as the pending state for the current block may not
31+
// yet be represented in `BlockToState`.
2832
StmtToEnvMap(const ControlFlowContext &CFCtx,
2933
llvm::ArrayRef<std::optional<TypeErasedDataflowAnalysisState>>
30-
BlockToState)
31-
: CFCtx(CFCtx), BlockToState(BlockToState) {}
34+
BlockToState,
35+
unsigned CurBlockID,
36+
const TypeErasedDataflowAnalysisState &CurState)
37+
: CFCtx(CFCtx), BlockToState(BlockToState), CurBlockID(CurBlockID),
38+
CurState(CurState) {}
3239

3340
/// Returns the environment of the basic block that contains `S`.
3441
/// The result is guaranteed never to be null.
@@ -37,6 +44,8 @@ class StmtToEnvMap {
3744
private:
3845
const ControlFlowContext &CFCtx;
3946
llvm::ArrayRef<std::optional<TypeErasedDataflowAnalysisState>> BlockToState;
47+
unsigned CurBlockID;
48+
const TypeErasedDataflowAnalysisState &CurState;
4049
};
4150

4251
/// Evaluates `S` and updates `Env` accordingly.

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ const Environment *StmtToEnvMap::getEnvironment(const Stmt &S) const {
4242
assert(BlockIt != CFCtx.getStmtToBlock().end());
4343
if (!CFCtx.isBlockReachable(*BlockIt->getSecond()))
4444
return nullptr;
45+
if (BlockIt->getSecond()->getBlockID() == CurBlockID)
46+
return &CurState.Env;
4547
const auto &State = BlockToState[BlockIt->getSecond()->getBlockID()];
4648
if (!(State))
4749
return nullptr;

clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,8 @@ using TerminatorVisitorRetTy = std::pair<const Expr *, bool>;
7575
class TerminatorVisitor
7676
: public ConstStmtVisitor<TerminatorVisitor, TerminatorVisitorRetTy> {
7777
public:
78-
TerminatorVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env,
79-
int BlockSuccIdx)
80-
: StmtToEnv(StmtToEnv), Env(Env), BlockSuccIdx(BlockSuccIdx) {}
78+
TerminatorVisitor(Environment &Env, int BlockSuccIdx)
79+
: Env(Env), BlockSuccIdx(BlockSuccIdx) {}
8180

8281
TerminatorVisitorRetTy VisitIfStmt(const IfStmt *S) {
8382
auto *Cond = S->getCond();
@@ -126,19 +125,12 @@ class TerminatorVisitor
126125

127126
private:
128127
TerminatorVisitorRetTy extendFlowCondition(const Expr &Cond) {
129-
// The terminator sub-expression might not be evaluated.
130-
if (Env.getValue(Cond) == nullptr)
131-
transfer(StmtToEnv, Cond, Env);
132-
133128
auto *Val = Env.get<BoolValue>(Cond);
134-
// Value merging depends on flow conditions from different environments
135-
// being mutually exclusive -- that is, they cannot both be true in their
136-
// entirety (even if they may share some clauses). So, we need *some* value
137-
// for the condition expression, even if just an atom.
138-
if (Val == nullptr) {
139-
Val = &Env.makeAtomicBoolValue();
140-
Env.setValue(Cond, *Val);
141-
}
129+
// In transferCFGBlock(), we ensure that we always have a `Value` for the
130+
// terminator condition, so assert this.
131+
// We consciously assert ourselves instead of asserting via `cast()` so
132+
// that we get a more meaningful line number if the assertion fails.
133+
assert(Val != nullptr);
142134

143135
bool ConditionValue = true;
144136
// The condition must be inverted for the successor that encompasses the
@@ -152,7 +144,6 @@ class TerminatorVisitor
152144
return {&Cond, ConditionValue};
153145
}
154146

155-
const StmtToEnvMap &StmtToEnv;
156147
Environment &Env;
157148
int BlockSuccIdx;
158149
};
@@ -335,10 +326,8 @@ computeBlockInputState(const CFGBlock &Block, AnalysisContext &AC) {
335326
// when the terminator is taken. Copy now.
336327
TypeErasedDataflowAnalysisState Copy = MaybePredState->fork();
337328

338-
const StmtToEnvMap StmtToEnv(AC.CFCtx, AC.BlockStates);
339329
auto [Cond, CondValue] =
340-
TerminatorVisitor(StmtToEnv, Copy.Env,
341-
blockIndexInPredecessor(*Pred, Block))
330+
TerminatorVisitor(Copy.Env, blockIndexInPredecessor(*Pred, Block))
342331
.Visit(PredTerminatorStmt);
343332
if (Cond != nullptr)
344333
// FIXME: Call transferBranchTypeErased even if BuiltinTransferOpts
@@ -356,12 +345,13 @@ computeBlockInputState(const CFGBlock &Block, AnalysisContext &AC) {
356345

357346
/// Built-in transfer function for `CFGStmt`.
358347
static void
359-
builtinTransferStatement(const CFGStmt &Elt,
348+
builtinTransferStatement(unsigned CurBlockID, const CFGStmt &Elt,
360349
TypeErasedDataflowAnalysisState &InputState,
361350
AnalysisContext &AC) {
362351
const Stmt *S = Elt.getStmt();
363352
assert(S != nullptr);
364-
transfer(StmtToEnvMap(AC.CFCtx, AC.BlockStates), *S, InputState.Env);
353+
transfer(StmtToEnvMap(AC.CFCtx, AC.BlockStates, CurBlockID, InputState), *S,
354+
InputState.Env);
365355
}
366356

367357
/// Built-in transfer function for `CFGInitializer`.
@@ -428,12 +418,12 @@ builtinTransferInitializer(const CFGInitializer &Elt,
428418
}
429419
}
430420

431-
static void builtinTransfer(const CFGElement &Elt,
421+
static void builtinTransfer(unsigned CurBlockID, const CFGElement &Elt,
432422
TypeErasedDataflowAnalysisState &State,
433423
AnalysisContext &AC) {
434424
switch (Elt.getKind()) {
435425
case CFGElement::Statement:
436-
builtinTransferStatement(Elt.castAs<CFGStmt>(), State, AC);
426+
builtinTransferStatement(CurBlockID, Elt.castAs<CFGStmt>(), State, AC);
437427
break;
438428
case CFGElement::Initializer:
439429
builtinTransferInitializer(Elt.castAs<CFGInitializer>(), State);
@@ -477,7 +467,7 @@ transferCFGBlock(const CFGBlock &Block, AnalysisContext &AC,
477467
AC.Log.enterElement(Element);
478468
// Built-in analysis
479469
if (AC.Analysis.builtinOptions()) {
480-
builtinTransfer(Element, State, AC);
470+
builtinTransfer(Block.getBlockID(), Element, State, AC);
481471
}
482472

483473
// User-provided analysis
@@ -489,6 +479,32 @@ transferCFGBlock(const CFGBlock &Block, AnalysisContext &AC,
489479
}
490480
AC.Log.recordState(State);
491481
}
482+
483+
// If we have a terminator, evaluate its condition.
484+
// This `Expr` may not appear as a `CFGElement` anywhere else, and it's
485+
// important that we evaluate it here (rather than while processing the
486+
// terminator) so that we put the corresponding value in the right
487+
// environment.
488+
if (const Expr *TerminatorCond =
489+
dyn_cast_or_null<Expr>(Block.getTerminatorCondition())) {
490+
if (State.Env.getValue(*TerminatorCond) == nullptr)
491+
// FIXME: This only runs the builtin transfer, not the analysis-specific
492+
// transfer. Fixing this isn't trivial, as the analysis-specific transfer
493+
// takes a `CFGElement` as input, but some expressions only show up as a
494+
// terminator condition, but not as a `CFGElement`. The condition of an if
495+
// statement is one such example.
496+
transfer(
497+
StmtToEnvMap(AC.CFCtx, AC.BlockStates, Block.getBlockID(), State),
498+
*TerminatorCond, State.Env);
499+
500+
// If the transfer function didn't produce a value, create an atom so that
501+
// we have *some* value for the condition expression. This ensures that
502+
// when we extend the flow condition, it actually changes.
503+
if (State.Env.getValue(*TerminatorCond) == nullptr)
504+
State.Env.setValue(*TerminatorCond, State.Env.makeAtomicBoolValue());
505+
AC.Log.recordState(State);
506+
}
507+
492508
return State;
493509
}
494510

clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ recordState(Elements=1, Branches=0, Joins=0)
123123
enterElement(b (ImplicitCastExpr, LValueToRValue, _Bool))
124124
transfer()
125125
recordState(Elements=2, Branches=0, Joins=0)
126+
recordState(Elements=2, Branches=0, Joins=0)
126127
127128
enterBlock(3, false)
128129
transferBranch(0)

clang/unittests/Analysis/FlowSensitive/SignAnalysisTest.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,33 @@ TEST(SignAnalysisTest, BinaryEQ) {
895895
LangStandard::lang_cxx17);
896896
}
897897

898+
TEST(SignAnalysisTest, ComplexLoopCondition) {
899+
std::string Code = R"(
900+
int foo();
901+
void fun() {
902+
int a, b;
903+
while ((a = foo()) > 0 && (b = foo()) > 0) {
904+
a;
905+
b;
906+
// [[p]]
907+
}
908+
}
909+
)";
910+
runDataflow(
911+
Code,
912+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
913+
ASTContext &ASTCtx) {
914+
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
915+
916+
const ValueDecl *A = findValueDecl(ASTCtx, "a");
917+
const ValueDecl *B = findValueDecl(ASTCtx, "b");
918+
919+
EXPECT_TRUE(isPositive(A, ASTCtx, Env));
920+
EXPECT_TRUE(isPositive(B, ASTCtx, Env));
921+
},
922+
LangStandard::lang_cxx17);
923+
}
924+
898925
TEST(SignAnalysisTest, JoinToTop) {
899926
std::string Code = R"(
900927
int foo();

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6459,4 +6459,35 @@ TEST(TransferTest, DifferentReferenceLocInJoin) {
64596459
});
64606460
}
64616461

6462+
// This test verifies correct modeling of a relational dependency that goes
6463+
// through unmodeled functions (the simple `cond()` in this case).
6464+
TEST(TransferTest, ConditionalRelation) {
6465+
std::string Code = R"(
6466+
bool cond();
6467+
void target() {
6468+
bool a = true;
6469+
bool b = true;
6470+
if (cond()) {
6471+
a = false;
6472+
if (cond()) {
6473+
b = false;
6474+
}
6475+
}
6476+
(void)0;
6477+
// [[p]]
6478+
}
6479+
)";
6480+
runDataflow(
6481+
Code,
6482+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
6483+
ASTContext &ASTCtx) {
6484+
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
6485+
auto &A = Env.arena();
6486+
auto &VarA = getValueForDecl<BoolValue>(ASTCtx, Env, "a").formula();
6487+
auto &VarB = getValueForDecl<BoolValue>(ASTCtx, Env, "b").formula();
6488+
6489+
EXPECT_FALSE(Env.allows(A.makeAnd(VarA, A.makeNot(VarB))));
6490+
});
6491+
}
6492+
64626493
} // namespace

0 commit comments

Comments
 (0)