Skip to content

Commit c8f822a

Browse files
committed
[clang][dataflow] Ensure well-formed flow conditions.
Ensure that the expressions associated with terminators are associated with a value. Otherwise, we can generate degenerate flow conditions, where both branches share the same condition. Differential Revision: https://reviews.llvm.org/D123858
1 parent f43ce51 commit c8f822a

File tree

2 files changed

+132
-2
lines changed

2 files changed

+132
-2
lines changed

clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "llvm/ADT/Optional.h"
3333
#include "llvm/ADT/STLExtras.h"
3434
#include "llvm/Support/Error.h"
35+
#include "llvm/Support/ErrorHandling.h"
3536

3637
namespace clang {
3738
namespace dataflow {
@@ -106,10 +107,24 @@ class TerminatorVisitor : public ConstStmtVisitor<TerminatorVisitor> {
106107
if (Env.getValue(Cond, SkipPast::None) == nullptr)
107108
transfer(StmtToEnv, Cond, Env);
108109

110+
// FIXME: The flow condition must be an r-value, so `SkipPast::None` should
111+
// suffice.
109112
auto *Val =
110113
cast_or_null<BoolValue>(Env.getValue(Cond, SkipPast::Reference));
111-
if (Val == nullptr)
112-
return;
114+
// Value merging depends on flow conditions from different environments
115+
// being mutually exclusive -- that is, they cannot both be true in their
116+
// entirety (even if they may share some clauses). So, we need *some* value
117+
// for the condition expression, even if just an atom.
118+
if (Val == nullptr) {
119+
// FIXME: Consider introducing a helper for this get-or-create pattern.
120+
auto *Loc = Env.getStorageLocation(Cond, SkipPast::None);
121+
if (Loc == nullptr) {
122+
Loc = &Env.createStorageLocation(Cond);
123+
Env.setStorageLocation(Cond, *Loc);
124+
}
125+
Val = &Env.makeAtomicBoolValue();
126+
Env.setValue(*Loc, *Val);
127+
}
113128

114129
// The condition must be inverted for the successor that encompasses the
115130
// "else" branch, if such exists.

clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,4 +878,119 @@ TEST_F(FlowConditionTest, Join) {
878878
});
879879
}
880880

881+
// Verifies that flow conditions are properly constructed even when the
882+
// condition is not meaningfully interpreted.
883+
//
884+
// Note: currently, arbitrary function calls are uninterpreted, so the test
885+
// exercises this case. If and when we change that, this test will not add to
886+
// coverage (although it may still test a valuable case).
887+
TEST_F(FlowConditionTest, OpaqueFlowConditionMergesToOpaqueBool) {
888+
std::string Code = R"(
889+
bool foo();
890+
891+
void target() {
892+
bool Bar = true;
893+
if (foo())
894+
Bar = false;
895+
(void)0;
896+
/*[[p]]*/
897+
}
898+
)";
899+
runDataflow(
900+
Code, [](llvm::ArrayRef<
901+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
902+
Results,
903+
ASTContext &ASTCtx) {
904+
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
905+
const Environment &Env = Results[0].second.Env;
906+
907+
const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
908+
ASSERT_THAT(BarDecl, NotNull());
909+
910+
auto &BarVal =
911+
*cast<BoolValue>(Env.getValue(*BarDecl, SkipPast::Reference));
912+
913+
EXPECT_FALSE(Env.flowConditionImplies(BarVal));
914+
});
915+
}
916+
917+
// Verifies that flow conditions are properly constructed even when the
918+
// condition is not meaningfully interpreted.
919+
//
920+
// Note: currently, fields with recursive type calls are uninterpreted (beneath
921+
// the first instance), so the test exercises this case. If and when we change
922+
// that, this test will not add to coverage (although it may still test a
923+
// valuable case).
924+
TEST_F(FlowConditionTest, OpaqueFieldFlowConditionMergesToOpaqueBool) {
925+
std::string Code = R"(
926+
struct Rec {
927+
Rec* Next;
928+
};
929+
930+
struct Foo {
931+
Rec* X;
932+
};
933+
934+
void target(Foo F) {
935+
bool Bar = true;
936+
if (F.X->Next)
937+
Bar = false;
938+
(void)0;
939+
/*[[p]]*/
940+
}
941+
)";
942+
runDataflow(
943+
Code, [](llvm::ArrayRef<
944+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
945+
Results,
946+
ASTContext &ASTCtx) {
947+
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
948+
const Environment &Env = Results[0].second.Env;
949+
950+
const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
951+
ASSERT_THAT(BarDecl, NotNull());
952+
953+
auto &BarVal =
954+
*cast<BoolValue>(Env.getValue(*BarDecl, SkipPast::Reference));
955+
956+
EXPECT_FALSE(Env.flowConditionImplies(BarVal));
957+
});
958+
}
959+
960+
// Verifies that flow conditions are properly constructed even when the
961+
// condition is not meaningfully interpreted. Adds to above by nesting the
962+
// interestnig case inside a normal branch. This protects against degenerate
963+
// solutions which only test for empty flow conditions, for example.
964+
TEST_F(FlowConditionTest, OpaqueFlowConditionInsideBranchMergesToOpaqueBool) {
965+
std::string Code = R"(
966+
bool foo();
967+
968+
void target(bool Cond) {
969+
bool Bar = true;
970+
if (Cond) {
971+
if (foo())
972+
Bar = false;
973+
(void)0;
974+
/*[[p]]*/
975+
}
976+
}
977+
)";
978+
runDataflow(
979+
Code, [](llvm::ArrayRef<
980+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
981+
Results,
982+
ASTContext &ASTCtx) {
983+
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
984+
const Environment &Env = Results[0].second.Env;
985+
986+
const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
987+
ASSERT_THAT(BarDecl, NotNull());
988+
989+
auto &BarVal =
990+
*cast<BoolValue>(Env.getValue(*BarDecl, SkipPast::Reference));
991+
992+
EXPECT_FALSE(Env.flowConditionImplies(BarVal));
993+
});
994+
}
995+
881996
} // namespace

0 commit comments

Comments
 (0)