Skip to content

Commit 2efc8f8

Browse files
committed
[clang][dataflow] Add an option for context-sensitive depth
This patch adds a `Depth` field (default value 2) to `ContextSensitiveOptions`, allowing context-sensitive analysis of functions that call other functions. This also requires replacing the `DeclCtx` field on `Environment` with a `CallString` field that contains a vector of decl contexts, to ensure that the analysis doesn't try to analyze recursive or mutually recursive calls (which would result in a crash, due to the way we handle `StorageLocation`s). Reviewed By: xazax.hun Differential Revision: https://reviews.llvm.org/D131809
1 parent ff8aadf commit 2efc8f8

File tree

5 files changed

+203
-20
lines changed

5 files changed

+203
-20
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,10 +348,12 @@ class Environment {
348348

349349
/// Returns the `DeclContext` of the block being analysed, if any. Otherwise,
350350
/// returns null.
351-
const DeclContext *getDeclCtx() { return DeclCtx; }
351+
const DeclContext *getDeclCtx() { return CallStack.back(); }
352352

353-
/// Sets the `DeclContext` of the block being analysed.
354-
void setDeclCtx(const DeclContext *Ctx) { DeclCtx = Ctx; }
353+
/// Returns whether this `Environment` can be extended to analyze the given
354+
/// `Callee` (i.e. if `pushCall` can be used), with recursion disallowed and a
355+
/// given `MaxDepth`.
356+
bool canDescend(unsigned MaxDepth, const DeclContext *Callee) const;
355357

356358
/// Returns the `ControlFlowContext` registered for `F`, if any. Otherwise,
357359
/// returns null.
@@ -390,7 +392,7 @@ class Environment {
390392
DataflowAnalysisContext *DACtx;
391393

392394
// `DeclContext` of the block being analysed if provided.
393-
const DeclContext *DeclCtx = nullptr;
395+
std::vector<const DeclContext *> CallStack;
394396

395397
// In a properly initialized `Environment`, `ReturnLoc` should only be null if
396398
// its `DeclContext` could not be cast to a `FunctionDecl`.

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
namespace clang {
2222
namespace dataflow {
2323

24-
struct ContextSensitiveOptions {};
24+
struct ContextSensitiveOptions {
25+
/// The maximum depth to analyze. A value of zero is equivalent to disabling
26+
/// context-sensitive analysis entirely.
27+
unsigned Depth = 2;
28+
};
2529

2630
struct TransferOptions {
2731
/// Options for analyzing function bodies when present in the translation

clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,10 @@ Environment::Environment(DataflowAnalysisContext &DACtx)
154154
: DACtx(&DACtx), FlowConditionToken(&DACtx.makeFlowConditionToken()) {}
155155

156156
Environment::Environment(const Environment &Other)
157-
: DACtx(Other.DACtx), DeclCtx(Other.DeclCtx), ReturnLoc(Other.ReturnLoc),
158-
ThisPointeeLoc(Other.ThisPointeeLoc), DeclToLoc(Other.DeclToLoc),
159-
ExprToLoc(Other.ExprToLoc), LocToVal(Other.LocToVal),
160-
MemberLocToStruct(Other.MemberLocToStruct),
157+
: DACtx(Other.DACtx), CallStack(Other.CallStack),
158+
ReturnLoc(Other.ReturnLoc), ThisPointeeLoc(Other.ThisPointeeLoc),
159+
DeclToLoc(Other.DeclToLoc), ExprToLoc(Other.ExprToLoc),
160+
LocToVal(Other.LocToVal), MemberLocToStruct(Other.MemberLocToStruct),
161161
FlowConditionToken(&DACtx->forkFlowCondition(*Other.FlowConditionToken)) {
162162
}
163163

@@ -168,11 +168,11 @@ Environment &Environment::operator=(const Environment &Other) {
168168
}
169169

170170
Environment::Environment(DataflowAnalysisContext &DACtx,
171-
const DeclContext &DeclCtxArg)
171+
const DeclContext &DeclCtx)
172172
: Environment(DACtx) {
173-
setDeclCtx(&DeclCtxArg);
173+
CallStack.push_back(&DeclCtx);
174174

175-
if (const auto *FuncDecl = dyn_cast<FunctionDecl>(DeclCtx)) {
175+
if (const auto *FuncDecl = dyn_cast<FunctionDecl>(&DeclCtx)) {
176176
assert(FuncDecl->getBody() != nullptr);
177177
initGlobalVars(*FuncDecl->getBody(), *this);
178178
for (const auto *ParamDecl : FuncDecl->parameters()) {
@@ -187,7 +187,7 @@ Environment::Environment(DataflowAnalysisContext &DACtx,
187187
ReturnLoc = &createStorageLocation(ReturnType);
188188
}
189189

190-
if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(DeclCtx)) {
190+
if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(&DeclCtx)) {
191191
auto *Parent = MethodDecl->getParent();
192192
assert(Parent != nullptr);
193193
if (Parent->isLambda())
@@ -205,6 +205,13 @@ Environment::Environment(DataflowAnalysisContext &DACtx,
205205
}
206206
}
207207

208+
bool Environment::canDescend(unsigned MaxDepth,
209+
const DeclContext *Callee) const {
210+
return CallStack.size() <= MaxDepth &&
211+
std::find(CallStack.begin(), CallStack.end(), Callee) ==
212+
CallStack.end();
213+
}
214+
208215
Environment Environment::pushCall(const CallExpr *Call) const {
209216
Environment Env(*this);
210217

@@ -239,7 +246,7 @@ Environment Environment::pushCall(const CXXConstructExpr *Call) const {
239246

240247
void Environment::pushCallInternal(const FunctionDecl *FuncDecl,
241248
ArrayRef<const Expr *> Args) {
242-
setDeclCtx(FuncDecl);
249+
CallStack.push_back(FuncDecl);
243250

244251
// FIXME: In order to allow the callee to reference globals, we probably need
245252
// to call `initGlobalVars` here in some way.
@@ -326,13 +333,13 @@ LatticeJoinEffect Environment::join(const Environment &Other,
326333
assert(DACtx == Other.DACtx);
327334
assert(ReturnLoc == Other.ReturnLoc);
328335
assert(ThisPointeeLoc == Other.ThisPointeeLoc);
329-
assert(DeclCtx == Other.DeclCtx);
336+
assert(CallStack == Other.CallStack);
330337

331338
auto Effect = LatticeJoinEffect::Unchanged;
332339

333340
Environment JoinedEnv(*DACtx);
334341

335-
JoinedEnv.setDeclCtx(DeclCtx);
342+
JoinedEnv.CallStack = CallStack;
336343
JoinedEnv.ReturnLoc = ReturnLoc;
337344
JoinedEnv.ThisPointeeLoc = ThisPointeeLoc;
338345

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,8 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
661661
// `F` of `S`. The type `E` must be either `CallExpr` or `CXXConstructExpr`.
662662
template <typename E>
663663
void transferInlineCall(const E *S, const FunctionDecl *F) {
664-
if (!Options.ContextSensitiveOpts)
664+
if (!(Options.ContextSensitiveOpts &&
665+
Env.canDescend(Options.ContextSensitiveOpts->Depth, F)))
665666
return;
666667

667668
const ControlFlowContext *CFCtx = Env.getControlFlowContext(F);
@@ -689,7 +690,7 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
689690
assert(CFCtx->getDecl() != nullptr &&
690691
"ControlFlowContexts in the environment should always carry a decl");
691692
auto Analysis = NoopAnalysis(CFCtx->getDecl()->getASTContext(),
692-
DataflowAnalysisOptions());
693+
DataflowAnalysisOptions{Options});
693694

694695
auto BlockToOutputState =
695696
dataflow::runDataflowAnalysis(*CFCtx, Analysis, CalleeEnv);

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3902,6 +3902,36 @@ TEST(TransferTest, ContextSensitiveOptionDisabled) {
39023902
{TransferOptions{/*.ContextSensitiveOpts=*/llvm::None}});
39033903
}
39043904

3905+
TEST(TransferTest, ContextSensitiveDepthZero) {
3906+
std::string Code = R"(
3907+
bool GiveBool();
3908+
void SetBool(bool &Var) { Var = true; }
3909+
3910+
void target() {
3911+
bool Foo = GiveBool();
3912+
SetBool(Foo);
3913+
// [[p]]
3914+
}
3915+
)";
3916+
runDataflow(Code,
3917+
[](llvm::ArrayRef<
3918+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
3919+
Results,
3920+
ASTContext &ASTCtx) {
3921+
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
3922+
const Environment &Env = Results[0].second.Env;
3923+
3924+
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
3925+
ASSERT_THAT(FooDecl, NotNull());
3926+
3927+
auto &FooVal =
3928+
*cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
3929+
EXPECT_FALSE(Env.flowConditionImplies(FooVal));
3930+
EXPECT_FALSE(Env.flowConditionImplies(Env.makeNot(FooVal)));
3931+
},
3932+
{TransferOptions{ContextSensitiveOptions{/*.Depth=*/0}}});
3933+
}
3934+
39053935
TEST(TransferTest, ContextSensitiveSetTrue) {
39063936
std::string Code = R"(
39073937
bool GiveBool();
@@ -4000,7 +4030,7 @@ TEST(TransferTest, ContextSensitiveSetBothTrueAndFalse) {
40004030
{TransferOptions{ContextSensitiveOptions{}}});
40014031
}
40024032

4003-
TEST(TransferTest, ContextSensitiveSetTwoLayers) {
4033+
TEST(TransferTest, ContextSensitiveSetTwoLayersDepthOne) {
40044034
std::string Code = R"(
40054035
bool GiveBool();
40064036
void SetBool1(bool &Var) { Var = true; }
@@ -4028,7 +4058,146 @@ TEST(TransferTest, ContextSensitiveSetTwoLayers) {
40284058
EXPECT_FALSE(Env.flowConditionImplies(FooVal));
40294059
EXPECT_FALSE(Env.flowConditionImplies(Env.makeNot(FooVal)));
40304060
},
4031-
{TransferOptions{ContextSensitiveOptions{}}});
4061+
{TransferOptions{ContextSensitiveOptions{/*.Depth=*/1}}});
4062+
}
4063+
4064+
TEST(TransferTest, ContextSensitiveSetTwoLayersDepthTwo) {
4065+
std::string Code = R"(
4066+
bool GiveBool();
4067+
void SetBool1(bool &Var) { Var = true; }
4068+
void SetBool2(bool &Var) { SetBool1(Var); }
4069+
4070+
void target() {
4071+
bool Foo = GiveBool();
4072+
SetBool2(Foo);
4073+
// [[p]]
4074+
}
4075+
)";
4076+
runDataflow(Code,
4077+
[](llvm::ArrayRef<
4078+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
4079+
Results,
4080+
ASTContext &ASTCtx) {
4081+
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
4082+
const Environment &Env = Results[0].second.Env;
4083+
4084+
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
4085+
ASSERT_THAT(FooDecl, NotNull());
4086+
4087+
auto &FooVal =
4088+
*cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
4089+
EXPECT_TRUE(Env.flowConditionImplies(FooVal));
4090+
},
4091+
{TransferOptions{ContextSensitiveOptions{/*.Depth=*/2}}});
4092+
}
4093+
4094+
TEST(TransferTest, ContextSensitiveSetThreeLayersDepthTwo) {
4095+
std::string Code = R"(
4096+
bool GiveBool();
4097+
void SetBool1(bool &Var) { Var = true; }
4098+
void SetBool2(bool &Var) { SetBool1(Var); }
4099+
void SetBool3(bool &Var) { SetBool2(Var); }
4100+
4101+
void target() {
4102+
bool Foo = GiveBool();
4103+
SetBool3(Foo);
4104+
// [[p]]
4105+
}
4106+
)";
4107+
runDataflow(Code,
4108+
[](llvm::ArrayRef<
4109+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
4110+
Results,
4111+
ASTContext &ASTCtx) {
4112+
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
4113+
const Environment &Env = Results[0].second.Env;
4114+
4115+
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
4116+
ASSERT_THAT(FooDecl, NotNull());
4117+
4118+
auto &FooVal =
4119+
*cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
4120+
EXPECT_FALSE(Env.flowConditionImplies(FooVal));
4121+
EXPECT_FALSE(Env.flowConditionImplies(Env.makeNot(FooVal)));
4122+
},
4123+
{TransferOptions{ContextSensitiveOptions{/*.Depth=*/2}}});
4124+
}
4125+
4126+
TEST(TransferTest, ContextSensitiveSetThreeLayersDepthThree) {
4127+
std::string Code = R"(
4128+
bool GiveBool();
4129+
void SetBool1(bool &Var) { Var = true; }
4130+
void SetBool2(bool &Var) { SetBool1(Var); }
4131+
void SetBool3(bool &Var) { SetBool2(Var); }
4132+
4133+
void target() {
4134+
bool Foo = GiveBool();
4135+
SetBool3(Foo);
4136+
// [[p]]
4137+
}
4138+
)";
4139+
runDataflow(Code,
4140+
[](llvm::ArrayRef<
4141+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
4142+
Results,
4143+
ASTContext &ASTCtx) {
4144+
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
4145+
const Environment &Env = Results[0].second.Env;
4146+
4147+
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
4148+
ASSERT_THAT(FooDecl, NotNull());
4149+
4150+
auto &FooVal =
4151+
*cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
4152+
EXPECT_TRUE(Env.flowConditionImplies(FooVal));
4153+
},
4154+
{TransferOptions{ContextSensitiveOptions{/*.Depth=*/3}}});
4155+
}
4156+
4157+
TEST(TransferTest, ContextSensitiveMutualRecursion) {
4158+
std::string Code = R"(
4159+
bool Pong(bool X, bool Y);
4160+
4161+
bool Ping(bool X, bool Y) {
4162+
if (X) {
4163+
return Y;
4164+
} else {
4165+
return Pong(!X, Y);
4166+
}
4167+
}
4168+
4169+
bool Pong(bool X, bool Y) {
4170+
if (Y) {
4171+
return X;
4172+
} else {
4173+
return Ping(X, !Y);
4174+
}
4175+
}
4176+
4177+
void target() {
4178+
bool Foo = Ping(false, false);
4179+
// [[p]]
4180+
}
4181+
)";
4182+
runDataflow(Code,
4183+
[](llvm::ArrayRef<
4184+
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
4185+
Results,
4186+
ASTContext &ASTCtx) {
4187+
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
4188+
// The analysis doesn't crash...
4189+
const Environment &Env = Results[0].second.Env;
4190+
4191+
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
4192+
ASSERT_THAT(FooDecl, NotNull());
4193+
4194+
auto &FooVal =
4195+
*cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
4196+
// ... but it also can't prove anything here.
4197+
EXPECT_FALSE(Env.flowConditionImplies(FooVal));
4198+
EXPECT_FALSE(Env.flowConditionImplies(Env.makeNot(FooVal)));
4199+
},
4200+
{TransferOptions{ContextSensitiveOptions{/*.Depth=*/4}}});
40324201
}
40334202

40344203
TEST(TransferTest, ContextSensitiveSetMultipleLines) {

0 commit comments

Comments
 (0)