Skip to content

Commit 222ba6f

Browse files
authored
[clang][dataflow] Handle more glvalue cases of the ConditionalOperator transfer (#168994)
In the dataflow framework, the builtin transfer function currently only handles the GLValue result case of ConditionalOperator when the true and false expression StorageLocations are exactly the same. Ideally / we have wanted to introduce alias sets to handle when the Locs are different. However, that is a larger change to the framework (and we may need to introduce weak updates). For now, do something simpler to at least handle when the GLValue is immediately cast to an RValue, by making up a distinct StorageLocation that holds the join of the true and false expression values (when not a record). This seems like the most common case, so seems worth covering. The case when an LValue is needed and can be updated later (and thus needs a link to the original storage locations) seems more rare, and we currently do not handle such updates either, so this intermediate step is no different (for that case).
1 parent 1ea4aa1 commit 222ba6f

File tree

2 files changed

+156
-1
lines changed

2 files changed

+156
-1
lines changed

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,8 +769,29 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
769769
StorageLocation *TrueLoc = TrueEnv->getStorageLocation(*S->getTrueExpr());
770770
StorageLocation *FalseLoc =
771771
FalseEnv->getStorageLocation(*S->getFalseExpr());
772-
if (TrueLoc == FalseLoc && TrueLoc != nullptr)
772+
if (TrueLoc == FalseLoc && TrueLoc != nullptr) {
773773
Env.setStorageLocation(*S, *TrueLoc);
774+
} else if (!S->getType()->isRecordType()) {
775+
// Ideally, we would have something like an "alias set" to say that the
776+
// result StorageLocation can be either of the locations from the
777+
// TrueEnv or FalseEnv. Then, when this ConditionalOperator is
778+
// (a) used in an LValueToRValue cast, the value is the join of all of
779+
// the values in the alias set.
780+
// (b) or, used in an assignment to the resulting LValue, the assignment
781+
// *may* update all of the locations in the alias set.
782+
// For now, we do the simpler thing of creating a new StorageLocation
783+
// and joining the values right away, handling only case (a).
784+
// Otherwise, the dataflow framework needs to be updated be able to
785+
// represent alias sets and weak updates (for the "may").
786+
if (Value *Val = Environment::joinValues(
787+
S->getType(), TrueEnv->getValue(*S->getTrueExpr()), *TrueEnv,
788+
FalseEnv->getValue(*S->getFalseExpr()), *FalseEnv, Env,
789+
Model)) {
790+
StorageLocation &Loc = Env.createStorageLocation(*S);
791+
Env.setStorageLocation(*S, Loc);
792+
Env.setValue(Loc, *Val);
793+
}
794+
}
774795
} else if (!S->getType()->isRecordType()) {
775796
// The conditional operator can evaluate to either of the values of the
776797
// two branches. To model this, join these two values together to yield

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
1818
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
1919
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
20+
#include "clang/Analysis/FlowSensitive/Formula.h"
2021
#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
2122
#include "clang/Analysis/FlowSensitive/NoopLattice.h"
2223
#include "clang/Analysis/FlowSensitive/RecordOps.h"
@@ -4382,6 +4383,40 @@ TEST(TransferTest, VarDeclInitAssignConditionalOperator) {
43824383
});
43834384
}
43844385

4386+
TEST(TransferTest, VarDeclInitReferenceAssignConditionalOperator) {
4387+
std::string Code = R"(
4388+
struct A {
4389+
int i;
4390+
};
4391+
4392+
void target(A Foo, A Bar, bool Cond) {
4393+
A &Baz = Cond ? Foo : Bar;
4394+
// Make sure A::i is modeled.
4395+
Baz.i;
4396+
/*[[p]]*/
4397+
}
4398+
)";
4399+
runDataflow(
4400+
Code,
4401+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
4402+
ASTContext &ASTCtx) {
4403+
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
4404+
4405+
auto *FooIVal = cast<IntegerValue>(getFieldValue(
4406+
&getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Foo"), "i",
4407+
ASTCtx, Env));
4408+
auto *BarIVal = cast<IntegerValue>(getFieldValue(
4409+
&getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Bar"), "i",
4410+
ASTCtx, Env));
4411+
auto *BazIVal = cast<IntegerValue>(getFieldValue(
4412+
&getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Baz"), "i",
4413+
ASTCtx, Env));
4414+
4415+
EXPECT_NE(BazIVal, FooIVal);
4416+
EXPECT_NE(BazIVal, BarIVal);
4417+
});
4418+
}
4419+
43854420
TEST(TransferTest, VarDeclInDoWhile) {
43864421
std::string Code = R"(
43874422
void target(int *Foo) {
@@ -6150,6 +6185,45 @@ TEST(TransferTest, ConditionalOperatorValue) {
61506185
});
61516186
}
61526187

6188+
TEST(TransferTest, ConditionalOperatorValuesTested) {
6189+
// We should be able to show that the result of the conditional operator,
6190+
// JoinResultMustBeB1, must be equal to B1, because the condition is checking
6191+
// `B1 == B2` and selecting B1 on the false branch, or B2 on the true branch.
6192+
// Similarly, for JoinResultMustBeB2 == B2.
6193+
// Note that the conditional operator involves a join of two *different*
6194+
// glvalues, before casting the lvalue to an rvalue, which may affect the
6195+
// implementation of the transfer function, and thus affect whether or not we
6196+
// can prove that IsB1 == B1.
6197+
std::string Code = R"(
6198+
void target(bool B1, bool B2) {
6199+
bool JoinResultMustBeB1 = (B1 == B2) ? B2 : B1;
6200+
bool JoinResultMustBeB2 = (B1 == B2) ? B1 : B2;
6201+
// [[p]]
6202+
}
6203+
)";
6204+
runDataflow(
6205+
Code,
6206+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
6207+
ASTContext &ASTCtx) {
6208+
Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();
6209+
6210+
auto &B1 = getValueForDecl<BoolValue>(ASTCtx, Env, "B1");
6211+
auto &B2 = getValueForDecl<BoolValue>(ASTCtx, Env, "B2");
6212+
auto &JoinResultMustBeB1 =
6213+
getValueForDecl<BoolValue>(ASTCtx, Env, "JoinResultMustBeB1");
6214+
auto &JoinResultMustBeB2 =
6215+
getValueForDecl<BoolValue>(ASTCtx, Env, "JoinResultMustBeB2");
6216+
6217+
const Formula &MustBeB1_Eq_B1 =
6218+
Env.arena().makeEquals(JoinResultMustBeB1.formula(), B1.formula());
6219+
EXPECT_TRUE(Env.proves(MustBeB1_Eq_B1));
6220+
6221+
const Formula &MustBeB2_Eq_B2 =
6222+
Env.arena().makeEquals(JoinResultMustBeB2.formula(), B2.formula());
6223+
EXPECT_TRUE(Env.proves(MustBeB2_Eq_B2));
6224+
});
6225+
}
6226+
61536227
TEST(TransferTest, ConditionalOperatorLocation) {
61546228
std::string Code = R"(
61556229
void target(bool Cond, int I1, int I2) {
@@ -6177,6 +6251,66 @@ TEST(TransferTest, ConditionalOperatorLocation) {
61776251
});
61786252
}
61796253

6254+
TEST(TransferTest, ConditionalOperatorLocationUpdatedAfter) {
6255+
// We don't currently model a Conditional Operator with an LValue result
6256+
// as having aliases to the LHS and RHS (if it isn't just the same LValue
6257+
// on both sides). We also don't model that the update "may" happen
6258+
// (a weak update). So, we don't consider the LHS and RHS as being weakly
6259+
// updated at [[after_diff]].
6260+
std::string Code = R"(
6261+
void target(bool Cond, bool B1, bool B2) {
6262+
(void)0;
6263+
// [[before_same]]
6264+
(Cond ? B1 : B1) = !B1;
6265+
// [[after_same]]
6266+
(Cond ? B1 : B2) = !B1;
6267+
// [[after_diff]]
6268+
}
6269+
)";
6270+
runDataflow(
6271+
Code,
6272+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
6273+
ASTContext &ASTCtx) {
6274+
Environment BeforeSameEnv =
6275+
getEnvironmentAtAnnotation(Results, "before_same").fork();
6276+
Environment AfterSameEnv =
6277+
getEnvironmentAtAnnotation(Results, "after_same").fork();
6278+
Environment AfterDiffEnv =
6279+
getEnvironmentAtAnnotation(Results, "after_diff").fork();
6280+
6281+
auto &BeforeSameB1 =
6282+
getValueForDecl<BoolValue>(ASTCtx, BeforeSameEnv, "B1");
6283+
auto &AfterSameB1 =
6284+
getValueForDecl<BoolValue>(ASTCtx, AfterSameEnv, "B1");
6285+
auto &AfterDiffB1 =
6286+
getValueForDecl<BoolValue>(ASTCtx, AfterDiffEnv, "B1");
6287+
6288+
EXPECT_NE(&BeforeSameB1, &AfterSameB1);
6289+
EXPECT_NE(&BeforeSameB1, &AfterDiffB1);
6290+
// FIXME: The formula for AfterSameB1 should be different from
6291+
// AfterDiffB1 to reflect that B1 may be updated.
6292+
EXPECT_EQ(&AfterSameB1, &AfterDiffB1);
6293+
6294+
// The value of B1 is definitely different from before_same vs
6295+
// after_same.
6296+
const Formula &B1ChangedForSame =
6297+
AfterSameEnv.arena().makeNot(AfterSameEnv.arena().makeEquals(
6298+
AfterSameB1.formula(), BeforeSameB1.formula()));
6299+
EXPECT_TRUE(AfterSameEnv.allows(B1ChangedForSame));
6300+
EXPECT_TRUE(AfterSameEnv.proves(B1ChangedForSame));
6301+
6302+
const Formula &B1ChangedForDiff =
6303+
AfterDiffEnv.arena().makeNot(AfterDiffEnv.arena().makeEquals(
6304+
AfterDiffB1.formula(), AfterSameB1.formula()));
6305+
// FIXME: It should be possible that B1 *may* be updated, so it may be
6306+
// that AfterSameB1 != AfterDiffB1 or AfterSameB1 == AfterDiffB1.
6307+
EXPECT_FALSE(AfterSameEnv.allows(B1ChangedForDiff));
6308+
// proves() should be false, since B1 may or may not have changed
6309+
// depending on `Cond`.
6310+
EXPECT_FALSE(AfterSameEnv.proves(B1ChangedForDiff));
6311+
});
6312+
}
6313+
61806314
TEST(TransferTest, ConditionalOperatorOnConstantExpr) {
61816315
// This is a regression test: We used to crash when a `ConstantExpr` was used
61826316
// in the branches of a conditional operator.

0 commit comments

Comments
 (0)