aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Estep <sam@samestep.com>2022-08-04 17:42:01 +0000
committerSam Estep <sam@samestep.com>2022-08-04 17:42:19 +0000
commit0eaecbbc231883b43d3ac761b276d9f505c89c27 (patch)
tree812c4eed1a839a9a90801cb34330a92448ced48f
parent8c30f4a5ab3e8dc4a75669d497723f9a2d8d39c8 (diff)
[clang][dataflow] Handle return statements
This patch adds a `ReturnLoc` field to the `Environment`, serving a similar to the `ThisPointeeLoc` field in the `DataflowAnalysisContext`. It then uses that (along with a new `VisitReturnStmt` method in `TransferVisitor`) to handle non-`void`-returning functions in context-sensitive analysis. Reviewed By: ymandel, sgatev Differential Revision: https://reviews.llvm.org/D130600
-rw-r--r--clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h1
-rw-r--r--clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h8
-rw-r--r--clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp35
-rw-r--r--clang/lib/Analysis/FlowSensitive/Transfer.cpp24
-rw-r--r--clang/unittests/Analysis/FlowSensitive/TransferTest.cpp108
5 files changed, 166 insertions, 10 deletions
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
index ab60d313f242..c83d0cbbbdbb 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
@@ -322,6 +322,7 @@ private:
llvm::DenseMap<const ValueDecl *, StorageLocation *> DeclToLoc;
llvm::DenseMap<const Expr *, StorageLocation *> ExprToLoc;
+ // FIXME: Move this into `Environment`.
StorageLocation *ThisPointeeLoc = nullptr;
// Null pointer values, keyed by the canonical pointee type.
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
index 1ecac86125a7..e8a1c2db53b5 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
@@ -222,6 +222,9 @@ public:
/// in the environment.
StorageLocation *getThisPointeeStorageLocation() const;
+ /// Returns the storage location of the return value or null, if unset.
+ StorageLocation *getReturnStorageLocation() const;
+
/// Returns a pointer value that represents a null pointer. Calls with
/// `PointeeType` that are canonically equivalent will return the same result.
PointerValue &getOrCreateNullPointerValue(QualType PointeeType);
@@ -374,6 +377,11 @@ private:
// `DACtx` is not null and not owned by this object.
DataflowAnalysisContext *DACtx;
+ // In a properly initialized `Environment`, `ReturnLoc` should only be null if
+ // its `DeclContext` could not be cast to a `FunctionDecl`.
+ StorageLocation *ReturnLoc = nullptr;
+ // FIXME: Move `ThisPointeeLoc` here from `DataflowAnalysisContext`.
+
// Maps from program declarations and statements to storage locations that are
// assigned to them. Unlike the maps in `DataflowAnalysisContext`, these
// include only storage locations that are in scope for a particular basic
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
index 4e5b2adee5c9..f4dadbb79b5c 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -154,9 +154,9 @@ Environment::Environment(DataflowAnalysisContext &DACtx)
: DACtx(&DACtx), FlowConditionToken(&DACtx.makeFlowConditionToken()) {}
Environment::Environment(const Environment &Other)
- : DACtx(Other.DACtx), DeclToLoc(Other.DeclToLoc),
- ExprToLoc(Other.ExprToLoc), LocToVal(Other.LocToVal),
- MemberLocToStruct(Other.MemberLocToStruct),
+ : DACtx(Other.DACtx), ReturnLoc(Other.ReturnLoc),
+ DeclToLoc(Other.DeclToLoc), ExprToLoc(Other.ExprToLoc),
+ LocToVal(Other.LocToVal), MemberLocToStruct(Other.MemberLocToStruct),
FlowConditionToken(&DACtx->forkFlowCondition(*Other.FlowConditionToken)) {
}
@@ -179,6 +179,9 @@ Environment::Environment(DataflowAnalysisContext &DACtx,
if (Value *ParamVal = createValue(ParamDecl->getType()))
setValue(ParamLoc, *ParamVal);
}
+
+ QualType ReturnType = FuncDecl->getReturnType();
+ ReturnLoc = &createStorageLocation(ReturnType);
}
if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(&DeclCtx)) {
@@ -202,10 +205,11 @@ Environment::Environment(DataflowAnalysisContext &DACtx,
Environment Environment::pushCall(const CallExpr *Call) const {
Environment Env(*this);
+ // FIXME: Support references here.
+ Env.ReturnLoc = Env.getStorageLocation(*Call, SkipPast::Reference);
const auto *FuncDecl = Call->getDirectCallee();
assert(FuncDecl != nullptr);
- assert(FuncDecl->getBody() != nullptr);
// FIXME: In order to allow the callee to reference globals, we probably need
// to call `initGlobalVars` here in some way.
@@ -241,12 +245,13 @@ Environment Environment::pushCall(const CallExpr *Call) const {
}
void Environment::popCall(const Environment &CalleeEnv) {
- // We ignore `DACtx` because it's already the same in both. We don't bring
- // back `DeclToLoc` and `ExprToLoc` because we want to be able to later
- // analyze the same callee in a different context, and `setStorageLocation`
- // requires there to not already be a storage location assigned. Conceptually,
- // these maps capture information from the local scope, so when popping that
- // scope, we do not propagate the maps.
+ // We ignore `DACtx` because it's already the same in both. We don't want the
+ // callee's `ReturnLoc`. We don't bring back `DeclToLoc` and `ExprToLoc`
+ // because we want to be able to later analyze the same callee in a different
+ // context, and `setStorageLocation` requires there to not already be a
+ // storage location assigned. Conceptually, these maps capture information
+ // from the local scope, so when popping that scope, we do not propagate the
+ // maps.
this->LocToVal = std::move(CalleeEnv.LocToVal);
this->MemberLocToStruct = std::move(CalleeEnv.MemberLocToStruct);
this->FlowConditionToken = std::move(CalleeEnv.FlowConditionToken);
@@ -256,6 +261,9 @@ bool Environment::equivalentTo(const Environment &Other,
Environment::ValueModel &Model) const {
assert(DACtx == Other.DACtx);
+ if (ReturnLoc != Other.ReturnLoc)
+ return false;
+
if (DeclToLoc != Other.DeclToLoc)
return false;
@@ -285,11 +293,14 @@ bool Environment::equivalentTo(const Environment &Other,
LatticeJoinEffect Environment::join(const Environment &Other,
Environment::ValueModel &Model) {
assert(DACtx == Other.DACtx);
+ assert(ReturnLoc == Other.ReturnLoc);
auto Effect = LatticeJoinEffect::Unchanged;
Environment JoinedEnv(*DACtx);
+ JoinedEnv.ReturnLoc = ReturnLoc;
+
JoinedEnv.DeclToLoc = intersectDenseMaps(DeclToLoc, Other.DeclToLoc);
if (DeclToLoc.size() != JoinedEnv.DeclToLoc.size())
Effect = LatticeJoinEffect::Changed;
@@ -382,6 +393,10 @@ StorageLocation *Environment::getThisPointeeStorageLocation() const {
return DACtx->getThisPointeeStorageLocation();
}
+StorageLocation *Environment::getReturnStorageLocation() const {
+ return ReturnLoc;
+}
+
PointerValue &Environment::getOrCreateNullPointerValue(QualType PointeeType) {
return DACtx->getOrCreateNullPointerValue(PointeeType);
}
diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
index 8b2e79826e81..430dc8d5faa9 100644
--- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
@@ -332,6 +332,25 @@ public:
std::make_unique<PointerValue>(*ThisPointeeLoc)));
}
+ void VisitReturnStmt(const ReturnStmt *S) {
+ auto *Ret = S->getRetValue();
+ if (Ret == nullptr)
+ return;
+
+ auto *Val = Env.getValue(*Ret, SkipPast::None);
+ if (Val == nullptr)
+ return;
+
+ // FIXME: Support reference-type returns.
+ if (Val->getKind() == Value::Kind::Reference)
+ return;
+
+ auto *Loc = Env.getReturnStorageLocation();
+ assert(Loc != nullptr);
+ // FIXME: Model NRVO.
+ Env.setValue(*Loc, *Val);
+ }
+
void VisitMemberExpr(const MemberExpr *S) {
ValueDecl *Member = S->getMemberDecl();
assert(Member != nullptr);
@@ -521,6 +540,11 @@ public:
auto ExitBlock = CFCtx->getCFG().getExit().getBlockID();
+ // Note that it is important for the storage location of `S` to be set
+ // before `pushCall`, because the latter uses it to set the storage
+ // location for `return`.
+ auto &ReturnLoc = Env.createStorageLocation(*S);
+ Env.setStorageLocation(*S, ReturnLoc);
auto CalleeEnv = Env.pushCall(S);
// FIXME: Use the same analysis as the caller for the callee. Note,
diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
index d6171763f4ec..1d114d9df175 100644
--- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
@@ -4121,4 +4121,112 @@ TEST(TransferTest, ContextSensitiveSetMultipleBlocks) {
/*.BuiltinTransferOptions=*/{/*.ContextSensitive=*/true}});
}
+TEST(TransferTest, ContextSensitiveReturnVoid) {
+ std::string Code = R"(
+ void Noop() { return; }
+
+ void target() {
+ Noop();
+ // [[p]]
+ }
+ )";
+ runDataflow(Code,
+ [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+ // This just tests that the analysis doesn't crash.
+ },
+ {/*.ApplyBuiltinTransfer=*/true,
+ /*.BuiltinTransferOptions=*/{/*.ContextSensitive=*/true}});
+}
+
+TEST(TransferTest, ContextSensitiveReturnTrue) {
+ std::string Code = R"(
+ bool GiveBool() { return true; }
+
+ void target() {
+ bool Foo = GiveBool();
+ // [[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));
+ },
+ {/*.ApplyBuiltinTransfer=*/true,
+ /*.BuiltinTransferOptions=*/{/*.ContextSensitive=*/true}});
+}
+
+TEST(TransferTest, ContextSensitiveReturnFalse) {
+ std::string Code = R"(
+ bool GiveBool() { return false; }
+
+ void target() {
+ bool Foo = GiveBool();
+ // [[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(Env.makeNot(FooVal)));
+ },
+ {/*.ApplyBuiltinTransfer=*/true,
+ /*.BuiltinTransferOptions=*/{/*.ContextSensitive=*/true}});
+}
+
+TEST(TransferTest, ContextSensitiveReturnArg) {
+ std::string Code = R"(
+ bool GiveBool();
+ bool GiveBack(bool Arg) { return Arg; }
+
+ void target() {
+ bool Foo = GiveBool();
+ bool Bar = GiveBack(Foo);
+ bool Baz = Foo == Bar;
+ // [[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 *BazDecl = findValueDecl(ASTCtx, "Baz");
+ ASSERT_THAT(BazDecl, NotNull());
+
+ auto &BazVal =
+ *cast<BoolValue>(Env.getValue(*BazDecl, SkipPast::None));
+ EXPECT_TRUE(Env.flowConditionImplies(BazVal));
+ },
+ {/*.ApplyBuiltinTransfer=*/true,
+ /*.BuiltinTransferOptions=*/{/*.ContextSensitive=*/true}});
+}
+
} // namespace