Skip to content

Commit abb958f

Browse files
authored
[clang][dataflow] Model conditional operator correctly. (#89213)
1 parent e86ebe4 commit abb958f

File tree

7 files changed

+149
-46
lines changed

7 files changed

+149
-46
lines changed

clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,21 @@ class Environment {
244244
Environment::ValueModel &Model,
245245
ExprJoinBehavior ExprBehavior);
246246

247+
/// Returns a value that approximates both `Val1` and `Val2`, or null if no
248+
/// such value can be produced.
249+
///
250+
/// `Env1` and `Env2` can be used to query child values and path condition
251+
/// implications of `Val1` and `Val2` respectively. The joined value will be
252+
/// produced in `JoinedEnv`.
253+
///
254+
/// Requirements:
255+
///
256+
/// `Val1` and `Val2` must model values of type `Type`.
257+
static Value *joinValues(QualType Ty, Value *Val1, const Environment &Env1,
258+
Value *Val2, const Environment &Env2,
259+
Environment &JoinedEnv,
260+
Environment::ValueModel &Model);
261+
247262
/// Widens the environment point-wise, using `PrevEnv` as needed to inform the
248263
/// approximation.
249264
///

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ class StmtToEnvMap {
5353
/// Requirements:
5454
///
5555
/// `S` must not be `ParenExpr` or `ExprWithCleanups`.
56-
void transfer(const StmtToEnvMap &StmtToEnv, const Stmt &S, Environment &Env);
56+
void transfer(const StmtToEnvMap &StmtToEnv, const Stmt &S, Environment &Env,
57+
Environment::ValueModel &Model);
5758

5859
} // namespace dataflow
5960
} // namespace clang

clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,8 @@ joinLocToVal(const llvm::MapVector<const StorageLocation *, Value *> &LocToVal,
237237
continue;
238238
assert(It->second != nullptr);
239239

240-
if (areEquivalentValues(*Val, *It->second)) {
241-
Result.insert({Loc, Val});
242-
continue;
243-
}
244-
245-
if (Value *JoinedVal = joinDistinctValues(
246-
Loc->getType(), *Val, Env1, *It->second, Env2, JoinedEnv, Model)) {
240+
if (Value *JoinedVal = Environment::joinValues(
241+
Loc->getType(), Val, Env1, It->second, Env2, JoinedEnv, Model)) {
247242
Result.insert({Loc, JoinedVal});
248243
}
249244
}
@@ -775,27 +770,16 @@ Environment Environment::join(const Environment &EnvA, const Environment &EnvB,
775770
JoinedEnv.LocForRecordReturnVal = EnvA.LocForRecordReturnVal;
776771
JoinedEnv.ThisPointeeLoc = EnvA.ThisPointeeLoc;
777772

778-
if (EnvA.ReturnVal == nullptr || EnvB.ReturnVal == nullptr) {
779-
// `ReturnVal` might not always get set -- for example if we have a return
780-
// statement of the form `return some_other_func()` and we decide not to
781-
// analyze `some_other_func()`.
782-
// In this case, we can't say anything about the joined return value -- we
783-
// don't simply want to propagate the return value that we do have, because
784-
// it might not be the correct one.
785-
// This occurs for example in the test `ContextSensitiveMutualRecursion`.
773+
if (EnvA.CallStack.empty()) {
786774
JoinedEnv.ReturnVal = nullptr;
787-
} else if (areEquivalentValues(*EnvA.ReturnVal, *EnvB.ReturnVal)) {
788-
JoinedEnv.ReturnVal = EnvA.ReturnVal;
789775
} else {
790-
assert(!EnvA.CallStack.empty());
791776
// FIXME: Make `CallStack` a vector of `FunctionDecl` so we don't need this
792777
// cast.
793778
auto *Func = dyn_cast<FunctionDecl>(EnvA.CallStack.back());
794779
assert(Func != nullptr);
795-
if (Value *JoinedVal =
796-
joinDistinctValues(Func->getReturnType(), *EnvA.ReturnVal, EnvA,
797-
*EnvB.ReturnVal, EnvB, JoinedEnv, Model))
798-
JoinedEnv.ReturnVal = JoinedVal;
780+
JoinedEnv.ReturnVal =
781+
joinValues(Func->getReturnType(), EnvA.ReturnVal, EnvA, EnvB.ReturnVal,
782+
EnvB, JoinedEnv, Model);
799783
}
800784

801785
if (EnvA.ReturnLoc == EnvB.ReturnLoc)
@@ -821,6 +805,24 @@ Environment Environment::join(const Environment &EnvA, const Environment &EnvB,
821805
return JoinedEnv;
822806
}
823807

808+
Value *Environment::joinValues(QualType Ty, Value *Val1,
809+
const Environment &Env1, Value *Val2,
810+
const Environment &Env2, Environment &JoinedEnv,
811+
Environment::ValueModel &Model) {
812+
if (Val1 == nullptr || Val2 == nullptr)
813+
// We can't say anything about the joined value -- even if one of the values
814+
// is non-null, we don't want to simply propagate it, because it would be
815+
// too specific: Because the other value is null, that means we have no
816+
// information at all about the value (i.e. the value is unconstrained).
817+
return nullptr;
818+
819+
if (areEquivalentValues(*Val1, *Val2))
820+
// Arbitrarily return one of the two values.
821+
return Val1;
822+
823+
return joinDistinctValues(Ty, *Val1, Env1, *Val2, Env2, JoinedEnv, Model);
824+
}
825+
824826
StorageLocation &Environment::createStorageLocation(QualType Type) {
825827
return DACtx->createStorageLocation(Type);
826828
}

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,9 @@ namespace {
124124

125125
class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
126126
public:
127-
TransferVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env)
128-
: StmtToEnv(StmtToEnv), Env(Env) {}
127+
TransferVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env,
128+
Environment::ValueModel &Model)
129+
: StmtToEnv(StmtToEnv), Env(Env), Model(Model) {}
129130

130131
void VisitBinaryOperator(const BinaryOperator *S) {
131132
const Expr *LHS = S->getLHS();
@@ -641,17 +642,41 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
641642
}
642643

643644
void VisitConditionalOperator(const ConditionalOperator *S) {
644-
// FIXME: Revisit this once flow conditions are added to the framework. For
645-
// `a = b ? c : d` we can add `b => a == c && !b => a == d` to the flow
646-
// condition.
647-
// When we do this, we will need to retrieve the values of the operands from
648-
// the environments for the basic blocks they are computed in, in a similar
649-
// way to how this is done for short-circuited logical operators in
650-
// `getLogicOperatorSubExprValue()`.
651-
if (S->isGLValue())
652-
Env.setStorageLocation(*S, Env.createObject(S->getType()));
653-
else if (!S->getType()->isRecordType()) {
654-
if (Value *Val = Env.createValue(S->getType()))
645+
const Environment *TrueEnv = StmtToEnv.getEnvironment(*S->getTrueExpr());
646+
const Environment *FalseEnv = StmtToEnv.getEnvironment(*S->getFalseExpr());
647+
648+
if (TrueEnv == nullptr || FalseEnv == nullptr) {
649+
// We should always have an environment as we should visit the true /
650+
// false branches before the conditional operator.
651+
assert(false);
652+
return;
653+
}
654+
655+
if (S->isGLValue()) {
656+
StorageLocation *TrueLoc = TrueEnv->getStorageLocation(*S->getTrueExpr());
657+
StorageLocation *FalseLoc =
658+
FalseEnv->getStorageLocation(*S->getFalseExpr());
659+
if (TrueLoc == FalseLoc && TrueLoc != nullptr)
660+
Env.setStorageLocation(*S, *TrueLoc);
661+
} else if (!S->getType()->isRecordType()) {
662+
// The conditional operator can evaluate to either of the values of the
663+
// two branches. To model this, join these two values together to yield
664+
// the result of the conditional operator.
665+
// Note: Most joins happen in `computeBlockInputState()`, but this case is
666+
// different:
667+
// - `computeBlockInputState()` (which in turn calls `Environment::join()`
668+
// joins values associated with the _same_ expression or storage
669+
// location, then associates the joined value with that expression or
670+
// storage location. This join has nothing to do with transfer --
671+
// instead, it joins together the results of performing transfer on two
672+
// different blocks.
673+
// - Here, we join values associated with _different_ expressions (the
674+
// true and false branch), then associate the joined value with a third
675+
// expression (the conditional operator itself). This join is what it
676+
// means to perform transfer on the conditional operator.
677+
if (Value *Val = Environment::joinValues(
678+
S->getType(), TrueEnv->getValue(*S->getTrueExpr()), *TrueEnv,
679+
FalseEnv->getValue(*S->getFalseExpr()), *FalseEnv, Env, Model))
655680
Env.setValue(*S, *Val);
656681
}
657682
}
@@ -810,12 +835,14 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
810835

811836
const StmtToEnvMap &StmtToEnv;
812837
Environment &Env;
838+
Environment::ValueModel &Model;
813839
};
814840

815841
} // namespace
816842

817-
void transfer(const StmtToEnvMap &StmtToEnv, const Stmt &S, Environment &Env) {
818-
TransferVisitor(StmtToEnv, Env).Visit(&S);
843+
void transfer(const StmtToEnvMap &StmtToEnv, const Stmt &S, Environment &Env,
844+
Environment::ValueModel &Model) {
845+
TransferVisitor(StmtToEnv, Env, Model).Visit(&S);
819846
}
820847

821848
} // namespace dataflow

clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ builtinTransferStatement(unsigned CurBlockID, const CFGStmt &Elt,
316316
const Stmt *S = Elt.getStmt();
317317
assert(S != nullptr);
318318
transfer(StmtToEnvMap(AC.ACFG, AC.BlockStates, CurBlockID, InputState), *S,
319-
InputState.Env);
319+
InputState.Env, AC.Analysis);
320320
}
321321

322322
/// Built-in transfer function for `CFGInitializer`.
@@ -452,7 +452,7 @@ transferCFGBlock(const CFGBlock &Block, AnalysisContext &AC,
452452
// terminator condition, but not as a `CFGElement`. The condition of an if
453453
// statement is one such example.
454454
transfer(StmtToEnvMap(AC.ACFG, AC.BlockStates, Block.getBlockID(), State),
455-
*TerminatorCond, State.Env);
455+
*TerminatorCond, State.Env, AC.Analysis);
456456

457457
// If the transfer function didn't produce a value, create an atom so that
458458
// we have *some* value for the condition expression. This ensures that

clang/unittests/Analysis/FlowSensitive/TestingSupport.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ const IndirectFieldDecl *findIndirectFieldDecl(ASTContext &ASTCtx,
456456
/// Requirements:
457457
///
458458
/// `Name` must be unique in `ASTCtx`.
459-
template <class LocT>
459+
template <class LocT = StorageLocation>
460460
LocT &getLocForDecl(ASTContext &ASTCtx, const Environment &Env,
461461
llvm::StringRef Name) {
462462
const ValueDecl *VD = findValueDecl(ASTCtx, Name);
@@ -470,7 +470,7 @@ LocT &getLocForDecl(ASTContext &ASTCtx, const Environment &Env,
470470
/// Requirements:
471471
///
472472
/// `Name` must be unique in `ASTCtx`.
473-
template <class ValueT>
473+
template <class ValueT = Value>
474474
ValueT &getValueForDecl(ASTContext &ASTCtx, const Environment &Env,
475475
llvm::StringRef Name) {
476476
const ValueDecl *VD = findValueDecl(ASTCtx, Name);

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5275,6 +5275,67 @@ TEST(TransferTest, BinaryOperatorComma) {
52755275
});
52765276
}
52775277

5278+
TEST(TransferTest, ConditionalOperatorValue) {
5279+
std::string Code = R"(
5280+
void target(bool Cond, bool B1, bool B2) {
5281+
bool JoinSame = Cond ? B1 : B1;
5282+
bool JoinDifferent = Cond ? B1 : B2;
5283+
// [[p]]
5284+
}
5285+
)";
5286+
runDataflow(
5287+
Code,
5288+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
5289+
ASTContext &ASTCtx) {
5290+
Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();
5291+
5292+
auto &B1 = getValueForDecl<BoolValue>(ASTCtx, Env, "B1");
5293+
auto &B2 = getValueForDecl<BoolValue>(ASTCtx, Env, "B2");
5294+
auto &JoinSame = getValueForDecl<BoolValue>(ASTCtx, Env, "JoinSame");
5295+
auto &JoinDifferent =
5296+
getValueForDecl<BoolValue>(ASTCtx, Env, "JoinDifferent");
5297+
5298+
EXPECT_EQ(&JoinSame, &B1);
5299+
5300+
const Formula &JoinDifferentEqB1 =
5301+
Env.arena().makeEquals(JoinDifferent.formula(), B1.formula());
5302+
EXPECT_TRUE(Env.allows(JoinDifferentEqB1));
5303+
EXPECT_FALSE(Env.proves(JoinDifferentEqB1));
5304+
5305+
const Formula &JoinDifferentEqB2 =
5306+
Env.arena().makeEquals(JoinDifferent.formula(), B2.formula());
5307+
EXPECT_TRUE(Env.allows(JoinDifferentEqB2));
5308+
EXPECT_FALSE(Env.proves(JoinDifferentEqB1));
5309+
});
5310+
}
5311+
5312+
TEST(TransferTest, ConditionalOperatorLocation) {
5313+
std::string Code = R"(
5314+
void target(bool Cond, int I1, int I2) {
5315+
int &JoinSame = Cond ? I1 : I1;
5316+
int &JoinDifferent = Cond ? I1 : I2;
5317+
// [[p]]
5318+
}
5319+
)";
5320+
runDataflow(
5321+
Code,
5322+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
5323+
ASTContext &ASTCtx) {
5324+
Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();
5325+
5326+
StorageLocation &I1 = getLocForDecl(ASTCtx, Env, "I1");
5327+
StorageLocation &I2 = getLocForDecl(ASTCtx, Env, "I2");
5328+
StorageLocation &JoinSame = getLocForDecl(ASTCtx, Env, "JoinSame");
5329+
StorageLocation &JoinDifferent =
5330+
getLocForDecl(ASTCtx, Env, "JoinDifferent");
5331+
5332+
EXPECT_EQ(&JoinSame, &I1);
5333+
5334+
EXPECT_NE(&JoinDifferent, &I1);
5335+
EXPECT_NE(&JoinDifferent, &I2);
5336+
});
5337+
}
5338+
52785339
TEST(TransferTest, IfStmtBranchExtendsFlowCondition) {
52795340
std::string Code = R"(
52805341
void target(bool Foo) {
@@ -5522,10 +5583,7 @@ TEST(TransferTest, ContextSensitiveReturnReferenceWithConditionalOperator) {
55225583
auto *Loc = Env.getReturnStorageLocation();
55235584
EXPECT_THAT(Loc, NotNull());
55245585

5525-
// TODO: We would really like to make this stronger assertion, but that
5526-
// doesn't work because we don't propagate values correctly through
5527-
// the conditional operator yet.
5528-
// EXPECT_EQ(Loc, SLoc);
5586+
EXPECT_EQ(Loc, SLoc);
55295587
},
55305588
{BuiltinOptions{ContextSensitiveOptions{}}});
55315589
}

0 commit comments

Comments
 (0)