Skip to content

Commit 342dca7

Browse files
authored
[clang][dataflow] Check for backedges directly (instead of loop statements). (#68923)
Widen on backedge nodes, instead of nodes with a loop statement as terminator. This fixes #67834 and a precision loss from assignment in a loop condition. The commit contains tests for both of these issues.
1 parent eb14f47 commit 342dca7

File tree

3 files changed

+52
-20
lines changed

3 files changed

+52
-20
lines changed

clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
//===----------------------------------------------------------------------===//
1313

1414
#include <algorithm>
15-
#include <memory>
1615
#include <optional>
1716
#include <system_error>
1817
#include <utility>
@@ -33,8 +32,8 @@
3332
#include "clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h"
3433
#include "clang/Analysis/FlowSensitive/Value.h"
3534
#include "llvm/ADT/ArrayRef.h"
36-
#include "llvm/ADT/DenseSet.h"
3735
#include "llvm/ADT/STLExtras.h"
36+
#include "llvm/ADT/SmallBitVector.h"
3837
#include "llvm/Support/Debug.h"
3938
#include "llvm/Support/Error.h"
4039

@@ -53,19 +52,14 @@ static int blockIndexInPredecessor(const CFGBlock &Pred,
5352
return BlockPos - Pred.succ_begin();
5453
}
5554

56-
static bool isLoopHead(const CFGBlock &B) {
57-
if (const auto *T = B.getTerminatorStmt())
58-
switch (T->getStmtClass()) {
59-
case Stmt::WhileStmtClass:
60-
case Stmt::DoStmtClass:
61-
case Stmt::ForStmtClass:
62-
case Stmt::CXXForRangeStmtClass:
63-
return true;
64-
default:
65-
return false;
66-
}
67-
68-
return false;
55+
// A "backedge" node is a block introduced in the CFG exclusively to indicate a
56+
// loop backedge. They are exactly identified by the presence of a non-null
57+
// pointer to the entry block of the loop condition. Note that this is not
58+
// necessarily the block with the loop statement as terminator, because
59+
// short-circuit operators will result in multiple blocks encoding the loop
60+
// condition, only one of which will contain the loop statement as terminator.
61+
static bool isBackedgeNode(const CFGBlock &B) {
62+
return B.getLoopTarget() != nullptr;
6963
}
7064

7165
namespace {
@@ -502,14 +496,15 @@ runTypeErasedDataflowAnalysis(
502496
PostVisitCFG) {
503497
PrettyStackTraceAnalysis CrashInfo(CFCtx, "runTypeErasedDataflowAnalysis");
504498

505-
PostOrderCFGView POV(&CFCtx.getCFG());
506-
ForwardDataflowWorklist Worklist(CFCtx.getCFG(), &POV);
499+
const clang::CFG &CFG = CFCtx.getCFG();
500+
PostOrderCFGView POV(&CFG);
501+
ForwardDataflowWorklist Worklist(CFG, &POV);
507502

508503
std::vector<std::optional<TypeErasedDataflowAnalysisState>> BlockStates(
509-
CFCtx.getCFG().size());
504+
CFG.size());
510505

511506
// The entry basic block doesn't contain statements so it can be skipped.
512-
const CFGBlock &Entry = CFCtx.getCFG().getEntry();
507+
const CFGBlock &Entry = CFG.getEntry();
513508
BlockStates[Entry.getBlockID()] = {Analysis.typeErasedInitialElement(),
514509
InitEnv.fork()};
515510
Worklist.enqueueSuccessors(&Entry);
@@ -553,7 +548,7 @@ runTypeErasedDataflowAnalysis(
553548
llvm::errs() << "Old Env:\n";
554549
OldBlockState->Env.dump();
555550
});
556-
if (isLoopHead(*Block)) {
551+
if (isBackedgeNode(*Block)) {
557552
LatticeJoinEffect Effect1 = Analysis.widenTypeErased(
558553
NewBlockState.Lattice, OldBlockState->Lattice);
559554
LatticeJoinEffect Effect2 =

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4099,6 +4099,20 @@ TEST(TransferTest, LoopDereferencingChangingRecordPointerConverges) {
40994099
ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded());
41004100
}
41014101

4102+
TEST(TransferTest, LoopWithShortCircuitedConditionConverges) {
4103+
std::string Code = R"cc(
4104+
bool foo();
4105+
4106+
void target() {
4107+
bool c = false;
4108+
while (foo() || foo()) {
4109+
c = true;
4110+
}
4111+
}
4112+
)cc";
4113+
ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded());
4114+
}
4115+
41024116
TEST(TransferTest, DoesNotCrashOnUnionThisExpr) {
41034117
std::string Code = R"(
41044118
union Union {

clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,29 @@ TEST_F(FlowConditionTest, WhileStmt) {
912912
});
913913
}
914914

915+
TEST_F(FlowConditionTest, WhileStmtWithAssignmentInCondition) {
916+
std::string Code = R"(
917+
void target(bool Foo) {
918+
// This test checks whether the analysis preserves the connection between
919+
// the value of `Foo` and the assignment expression, despite widening.
920+
// The equality operator generates a fresh boolean variable on each
921+
// interpretation, which forces use of widening.
922+
while ((Foo = (3 == 4))) {
923+
(void)0;
924+
/*[[p]]*/
925+
}
926+
}
927+
)";
928+
runDataflow(
929+
Code,
930+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
931+
ASTContext &ASTCtx) {
932+
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
933+
auto &FooVal = getValueForDecl<BoolValue>(ASTCtx, Env, "Foo").formula();
934+
EXPECT_TRUE(Env.flowConditionImplies(FooVal));
935+
});
936+
}
937+
915938
TEST_F(FlowConditionTest, Conjunction) {
916939
std::string Code = R"(
917940
void target(bool Foo, bool Bar) {

0 commit comments

Comments
 (0)