[clang] 2efc8f8 - [clang][dataflow] Add an option for context-sensitive depth
Sam Estep via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 15 12:58:48 PDT 2022
Author: Sam Estep
Date: 2022-08-15T19:58:40Z
New Revision: 2efc8f8d6561421c3f47de2f27a365ddfb734425
URL: https://github.com/llvm/llvm-project/commit/2efc8f8d6561421c3f47de2f27a365ddfb734425
DIFF: https://github.com/llvm/llvm-project/commit/2efc8f8d6561421c3f47de2f27a365ddfb734425.diff
LOG: [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
Added:
Modified:
clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
clang/include/clang/Analysis/FlowSensitive/Transfer.h
clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
clang/lib/Analysis/FlowSensitive/Transfer.cpp
clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
index 5b29915e368ed..6955bcf2fb4ca 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
@@ -348,10 +348,12 @@ class Environment {
/// Returns the `DeclContext` of the block being analysed, if any. Otherwise,
/// returns null.
- const DeclContext *getDeclCtx() { return DeclCtx; }
+ const DeclContext *getDeclCtx() { return CallStack.back(); }
- /// Sets the `DeclContext` of the block being analysed.
- void setDeclCtx(const DeclContext *Ctx) { DeclCtx = Ctx; }
+ /// Returns whether this `Environment` can be extended to analyze the given
+ /// `Callee` (i.e. if `pushCall` can be used), with recursion disallowed and a
+ /// given `MaxDepth`.
+ bool canDescend(unsigned MaxDepth, const DeclContext *Callee) const;
/// Returns the `ControlFlowContext` registered for `F`, if any. Otherwise,
/// returns null.
@@ -390,7 +392,7 @@ class Environment {
DataflowAnalysisContext *DACtx;
// `DeclContext` of the block being analysed if provided.
- const DeclContext *DeclCtx = nullptr;
+ std::vector<const DeclContext *> CallStack;
// In a properly initialized `Environment`, `ReturnLoc` should only be null if
// its `DeclContext` could not be cast to a `FunctionDecl`.
diff --git a/clang/include/clang/Analysis/FlowSensitive/Transfer.h b/clang/include/clang/Analysis/FlowSensitive/Transfer.h
index 93afcc284e1af..fa05013bb7208 100644
--- a/clang/include/clang/Analysis/FlowSensitive/Transfer.h
+++ b/clang/include/clang/Analysis/FlowSensitive/Transfer.h
@@ -21,7 +21,11 @@
namespace clang {
namespace dataflow {
-struct ContextSensitiveOptions {};
+struct ContextSensitiveOptions {
+ /// The maximum depth to analyze. A value of zero is equivalent to disabling
+ /// context-sensitive analysis entirely.
+ unsigned Depth = 2;
+};
struct TransferOptions {
/// Options for analyzing function bodies when present in the translation
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
index 119ef337c6319..f64ade34bcb82 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -154,10 +154,10 @@ Environment::Environment(DataflowAnalysisContext &DACtx)
: DACtx(&DACtx), FlowConditionToken(&DACtx.makeFlowConditionToken()) {}
Environment::Environment(const Environment &Other)
- : DACtx(Other.DACtx), DeclCtx(Other.DeclCtx), ReturnLoc(Other.ReturnLoc),
- ThisPointeeLoc(Other.ThisPointeeLoc), DeclToLoc(Other.DeclToLoc),
- ExprToLoc(Other.ExprToLoc), LocToVal(Other.LocToVal),
- MemberLocToStruct(Other.MemberLocToStruct),
+ : DACtx(Other.DACtx), CallStack(Other.CallStack),
+ ReturnLoc(Other.ReturnLoc), ThisPointeeLoc(Other.ThisPointeeLoc),
+ DeclToLoc(Other.DeclToLoc), ExprToLoc(Other.ExprToLoc),
+ LocToVal(Other.LocToVal), MemberLocToStruct(Other.MemberLocToStruct),
FlowConditionToken(&DACtx->forkFlowCondition(*Other.FlowConditionToken)) {
}
@@ -168,11 +168,11 @@ Environment &Environment::operator=(const Environment &Other) {
}
Environment::Environment(DataflowAnalysisContext &DACtx,
- const DeclContext &DeclCtxArg)
+ const DeclContext &DeclCtx)
: Environment(DACtx) {
- setDeclCtx(&DeclCtxArg);
+ CallStack.push_back(&DeclCtx);
- if (const auto *FuncDecl = dyn_cast<FunctionDecl>(DeclCtx)) {
+ if (const auto *FuncDecl = dyn_cast<FunctionDecl>(&DeclCtx)) {
assert(FuncDecl->getBody() != nullptr);
initGlobalVars(*FuncDecl->getBody(), *this);
for (const auto *ParamDecl : FuncDecl->parameters()) {
@@ -187,7 +187,7 @@ Environment::Environment(DataflowAnalysisContext &DACtx,
ReturnLoc = &createStorageLocation(ReturnType);
}
- if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(DeclCtx)) {
+ if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(&DeclCtx)) {
auto *Parent = MethodDecl->getParent();
assert(Parent != nullptr);
if (Parent->isLambda())
@@ -205,6 +205,13 @@ Environment::Environment(DataflowAnalysisContext &DACtx,
}
}
+bool Environment::canDescend(unsigned MaxDepth,
+ const DeclContext *Callee) const {
+ return CallStack.size() <= MaxDepth &&
+ std::find(CallStack.begin(), CallStack.end(), Callee) ==
+ CallStack.end();
+}
+
Environment Environment::pushCall(const CallExpr *Call) const {
Environment Env(*this);
@@ -239,7 +246,7 @@ Environment Environment::pushCall(const CXXConstructExpr *Call) const {
void Environment::pushCallInternal(const FunctionDecl *FuncDecl,
ArrayRef<const Expr *> Args) {
- setDeclCtx(FuncDecl);
+ CallStack.push_back(FuncDecl);
// FIXME: In order to allow the callee to reference globals, we probably need
// to call `initGlobalVars` here in some way.
@@ -326,13 +333,13 @@ LatticeJoinEffect Environment::join(const Environment &Other,
assert(DACtx == Other.DACtx);
assert(ReturnLoc == Other.ReturnLoc);
assert(ThisPointeeLoc == Other.ThisPointeeLoc);
- assert(DeclCtx == Other.DeclCtx);
+ assert(CallStack == Other.CallStack);
auto Effect = LatticeJoinEffect::Unchanged;
Environment JoinedEnv(*DACtx);
- JoinedEnv.setDeclCtx(DeclCtx);
+ JoinedEnv.CallStack = CallStack;
JoinedEnv.ReturnLoc = ReturnLoc;
JoinedEnv.ThisPointeeLoc = ThisPointeeLoc;
diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
index 1fe8201706840..6f490ed624661 100644
--- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
@@ -661,7 +661,8 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
// `F` of `S`. The type `E` must be either `CallExpr` or `CXXConstructExpr`.
template <typename E>
void transferInlineCall(const E *S, const FunctionDecl *F) {
- if (!Options.ContextSensitiveOpts)
+ if (!(Options.ContextSensitiveOpts &&
+ Env.canDescend(Options.ContextSensitiveOpts->Depth, F)))
return;
const ControlFlowContext *CFCtx = Env.getControlFlowContext(F);
@@ -689,7 +690,7 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
assert(CFCtx->getDecl() != nullptr &&
"ControlFlowContexts in the environment should always carry a decl");
auto Analysis = NoopAnalysis(CFCtx->getDecl()->getASTContext(),
- DataflowAnalysisOptions());
+ DataflowAnalysisOptions{Options});
auto BlockToOutputState =
dataflow::runDataflowAnalysis(*CFCtx, Analysis, CalleeEnv);
diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
index e8b37706d788b..7f055f3ad8b7a 100644
--- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
@@ -3902,6 +3902,36 @@ TEST(TransferTest, ContextSensitiveOptionDisabled) {
{TransferOptions{/*.ContextSensitiveOpts=*/llvm::None}});
}
+TEST(TransferTest, ContextSensitiveDepthZero) {
+ std::string Code = R"(
+ bool GiveBool();
+ void SetBool(bool &Var) { Var = true; }
+
+ void target() {
+ bool Foo = GiveBool();
+ SetBool(Foo);
+ // [[p]]
+ }
+ )";
+ runDataflow(Code,
+ [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+ const Environment &Env = Results[0].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ auto &FooVal =
+ *cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
+ EXPECT_FALSE(Env.flowConditionImplies(FooVal));
+ EXPECT_FALSE(Env.flowConditionImplies(Env.makeNot(FooVal)));
+ },
+ {TransferOptions{ContextSensitiveOptions{/*.Depth=*/0}}});
+}
+
TEST(TransferTest, ContextSensitiveSetTrue) {
std::string Code = R"(
bool GiveBool();
@@ -4000,7 +4030,7 @@ TEST(TransferTest, ContextSensitiveSetBothTrueAndFalse) {
{TransferOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveSetTwoLayers) {
+TEST(TransferTest, ContextSensitiveSetTwoLayersDepthOne) {
std::string Code = R"(
bool GiveBool();
void SetBool1(bool &Var) { Var = true; }
@@ -4028,7 +4058,146 @@ TEST(TransferTest, ContextSensitiveSetTwoLayers) {
EXPECT_FALSE(Env.flowConditionImplies(FooVal));
EXPECT_FALSE(Env.flowConditionImplies(Env.makeNot(FooVal)));
},
- {TransferOptions{ContextSensitiveOptions{}}});
+ {TransferOptions{ContextSensitiveOptions{/*.Depth=*/1}}});
+}
+
+TEST(TransferTest, ContextSensitiveSetTwoLayersDepthTwo) {
+ std::string Code = R"(
+ bool GiveBool();
+ void SetBool1(bool &Var) { Var = true; }
+ void SetBool2(bool &Var) { SetBool1(Var); }
+
+ void target() {
+ bool Foo = GiveBool();
+ SetBool2(Foo);
+ // [[p]]
+ }
+ )";
+ runDataflow(Code,
+ [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+ const Environment &Env = Results[0].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ auto &FooVal =
+ *cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
+ EXPECT_TRUE(Env.flowConditionImplies(FooVal));
+ },
+ {TransferOptions{ContextSensitiveOptions{/*.Depth=*/2}}});
+}
+
+TEST(TransferTest, ContextSensitiveSetThreeLayersDepthTwo) {
+ std::string Code = R"(
+ bool GiveBool();
+ void SetBool1(bool &Var) { Var = true; }
+ void SetBool2(bool &Var) { SetBool1(Var); }
+ void SetBool3(bool &Var) { SetBool2(Var); }
+
+ void target() {
+ bool Foo = GiveBool();
+ SetBool3(Foo);
+ // [[p]]
+ }
+ )";
+ runDataflow(Code,
+ [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+ const Environment &Env = Results[0].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ auto &FooVal =
+ *cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
+ EXPECT_FALSE(Env.flowConditionImplies(FooVal));
+ EXPECT_FALSE(Env.flowConditionImplies(Env.makeNot(FooVal)));
+ },
+ {TransferOptions{ContextSensitiveOptions{/*.Depth=*/2}}});
+}
+
+TEST(TransferTest, ContextSensitiveSetThreeLayersDepthThree) {
+ std::string Code = R"(
+ bool GiveBool();
+ void SetBool1(bool &Var) { Var = true; }
+ void SetBool2(bool &Var) { SetBool1(Var); }
+ void SetBool3(bool &Var) { SetBool2(Var); }
+
+ void target() {
+ bool Foo = GiveBool();
+ SetBool3(Foo);
+ // [[p]]
+ }
+ )";
+ runDataflow(Code,
+ [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+ const Environment &Env = Results[0].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ auto &FooVal =
+ *cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
+ EXPECT_TRUE(Env.flowConditionImplies(FooVal));
+ },
+ {TransferOptions{ContextSensitiveOptions{/*.Depth=*/3}}});
+}
+
+TEST(TransferTest, ContextSensitiveMutualRecursion) {
+ std::string Code = R"(
+ bool Pong(bool X, bool Y);
+
+ bool Ping(bool X, bool Y) {
+ if (X) {
+ return Y;
+ } else {
+ return Pong(!X, Y);
+ }
+ }
+
+ bool Pong(bool X, bool Y) {
+ if (Y) {
+ return X;
+ } else {
+ return Ping(X, !Y);
+ }
+ }
+
+ void target() {
+ bool Foo = Ping(false, false);
+ // [[p]]
+ }
+ )";
+ runDataflow(Code,
+ [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+ // The analysis doesn't crash...
+ const Environment &Env = Results[0].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ auto &FooVal =
+ *cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
+ // ... but it also can't prove anything here.
+ EXPECT_FALSE(Env.flowConditionImplies(FooVal));
+ EXPECT_FALSE(Env.flowConditionImplies(Env.makeNot(FooVal)));
+ },
+ {TransferOptions{ContextSensitiveOptions{/*.Depth=*/4}}});
}
TEST(TransferTest, ContextSensitiveSetMultipleLines) {
More information about the cfe-commits
mailing list