[clang][dataflow] Analyze constructor bodies

This patch adds the ability to context-sensitively analyze constructor bodies, by changing `pushCall` to allow both `CallExpr` and `CXXConstructExpr`, and extracting the main context-sensitive logic out of `VisitCallExpr` into a new `transferInlineCall` method which is now also called at the end of `VisitCXXConstructExpr`.

Reviewed By: ymandel, sgatev, xazax.hun

Differential Revision: https://reviews.llvm.org/D131438
This commit is contained in:
Sam Estep 2022-08-10 14:01:18 +00:00 committed by Wei Yi Tee
parent f9563967ca
commit eb91fd5cbc
4 changed files with 199 additions and 65 deletions

View File

@ -135,7 +135,7 @@ public:
///
/// Requirements:
///
/// The callee of `Call` must be a `FunctionDecl` with a body.
/// The callee of `Call` must be a `FunctionDecl`.
///
/// The body of the callee must not reference globals.
///
@ -143,6 +143,7 @@ public:
///
/// Each argument of `Call` must already have a `StorageLocation`.
Environment pushCall(const CallExpr *Call) const;
Environment pushCall(const CXXConstructExpr *Call) const;
/// Moves gathered information back into `this` from a `CalleeEnv` created via
/// `pushCall`.
@ -381,6 +382,12 @@ private:
StorageLocation &skip(StorageLocation &Loc, SkipPast SP) const;
const StorageLocation &skip(const StorageLocation &Loc, SkipPast SP) const;
/// Shared implementation of `pushCall` overloads. Note that unlike
/// `pushCall`, this member is invoked on the environment of the callee, not
/// of the caller.
void pushCallInternal(const FunctionDecl *FuncDecl,
ArrayRef<const Expr *> Args);
// `DACtx` is not null and not owned by this object.
DataflowAnalysisContext *DACtx;

View File

@ -207,52 +207,68 @@ 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);
Env.ReturnLoc = getStorageLocation(*Call, SkipPast::Reference);
const auto *FuncDecl = Call->getDirectCallee();
assert(FuncDecl != nullptr);
if (const auto *MethodCall = dyn_cast<CXXMemberCallExpr>(Call)) {
if (const Expr *Arg = MethodCall->getImplicitObjectArgument()) {
Env.ThisPointeeLoc = getStorageLocation(*Arg, SkipPast::Reference);
}
}
Env.setDeclCtx(FuncDecl);
Env.pushCallInternal(Call->getDirectCallee(),
llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
return Env;
}
Environment Environment::pushCall(const CXXConstructExpr *Call) const {
Environment Env(*this);
// FIXME: Support references here.
Env.ReturnLoc = getStorageLocation(*Call, SkipPast::Reference);
Env.ThisPointeeLoc = Env.ReturnLoc;
Env.pushCallInternal(Call->getConstructor(),
llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
return Env;
}
void Environment::pushCallInternal(const FunctionDecl *FuncDecl,
ArrayRef<const Expr *> Args) {
setDeclCtx(FuncDecl);
// FIXME: In order to allow the callee to reference globals, we probably need
// to call `initGlobalVars` here in some way.
if (const auto *MethodCall = dyn_cast<CXXMemberCallExpr>(Call)) {
if (const Expr *Arg = MethodCall->getImplicitObjectArgument()) {
Env.ThisPointeeLoc = Env.getStorageLocation(*Arg, SkipPast::Reference);
}
}
auto ParamIt = FuncDecl->param_begin();
auto ArgIt = Call->arg_begin();
auto ArgEnd = Call->arg_end();
// FIXME: Parameters don't always map to arguments 1:1; examples include
// overloaded operators implemented as member functions, and parameter packs.
for (; ArgIt != ArgEnd; ++ParamIt, ++ArgIt) {
for (unsigned ArgIndex = 0; ArgIndex < Args.size(); ++ParamIt, ++ArgIndex) {
assert(ParamIt != FuncDecl->param_end());
const Expr *Arg = *ArgIt;
auto *ArgLoc = Env.getStorageLocation(*Arg, SkipPast::Reference);
const Expr *Arg = Args[ArgIndex];
auto *ArgLoc = getStorageLocation(*Arg, SkipPast::Reference);
assert(ArgLoc != nullptr);
const VarDecl *Param = *ParamIt;
auto &Loc = Env.createStorageLocation(*Param);
Env.setStorageLocation(*Param, Loc);
auto &Loc = createStorageLocation(*Param);
setStorageLocation(*Param, Loc);
QualType ParamType = Param->getType();
if (ParamType->isReferenceType()) {
auto &Val = Env.takeOwnership(std::make_unique<ReferenceValue>(*ArgLoc));
Env.setValue(Loc, Val);
} else if (auto *ArgVal = Env.getValue(*ArgLoc)) {
Env.setValue(Loc, *ArgVal);
} else if (Value *Val = Env.createValue(ParamType)) {
Env.setValue(Loc, *Val);
auto &Val = takeOwnership(std::make_unique<ReferenceValue>(*ArgLoc));
setValue(Loc, Val);
} else if (auto *ArgVal = getValue(*ArgLoc)) {
setValue(Loc, *ArgVal);
} else if (Value *Val = createValue(ParamType)) {
setValue(Loc, *Val);
}
}
return Env;
}
void Environment::popCall(const Environment &CalleeEnv) {

View File

@ -444,6 +444,8 @@ public:
Env.setStorageLocation(*S, Loc);
if (Value *Val = Env.createValue(S->getType()))
Env.setValue(Loc, *Val);
transferInlineCall(S, ConstructorDecl);
}
void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *S) {
@ -526,45 +528,7 @@ public:
return;
Env.setStorageLocation(*S, *ArgLoc);
} else if (const FunctionDecl *F = S->getDirectCallee()) {
// This case is for context-sensitive analysis.
if (!Options.ContextSensitive)
return;
const ControlFlowContext *CFCtx = Env.getControlFlowContext(F);
if (!CFCtx)
return;
// FIXME: We don't support context-sensitive analysis of recursion, so
// we should return early here if `F` is the same as the `FunctionDecl`
// holding `S` itself.
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,
// though, that doing so would require support for changing the analysis's
// ASTContext.
assert(
CFCtx->getDecl() != nullptr &&
"ControlFlowContexts in the environment should always carry a decl");
auto Analysis = NoopAnalysis(CFCtx->getDecl()->getASTContext(),
DataflowAnalysisOptions());
auto BlockToOutputState =
dataflow::runDataflowAnalysis(*CFCtx, Analysis, CalleeEnv);
assert(BlockToOutputState);
assert(ExitBlock < BlockToOutputState->size());
auto ExitState = (*BlockToOutputState)[ExitBlock];
assert(ExitState);
Env.popCall(ExitState->Env);
transferInlineCall(S, F);
}
}
@ -693,6 +657,51 @@ private:
return Env.makeAtomicBoolValue();
}
// If context sensitivity is enabled, try to analyze the body of the callee
// `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.ContextSensitive)
return;
const ControlFlowContext *CFCtx = Env.getControlFlowContext(F);
if (!CFCtx)
return;
// FIXME: We don't support context-sensitive analysis of recursion, so
// we should return early here if `F` is the same as the `FunctionDecl`
// holding `S` itself.
auto ExitBlock = CFCtx->getCFG().getExit().getBlockID();
if (const auto *NonConstructExpr = dyn_cast<CallExpr>(S)) {
// 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,
// though, that doing so would require support for changing the analysis's
// ASTContext.
assert(CFCtx->getDecl() != nullptr &&
"ControlFlowContexts in the environment should always carry a decl");
auto Analysis = NoopAnalysis(CFCtx->getDecl()->getASTContext(),
DataflowAnalysisOptions());
auto BlockToOutputState =
dataflow::runDataflowAnalysis(*CFCtx, Analysis, CalleeEnv);
assert(BlockToOutputState);
assert(ExitBlock < BlockToOutputState->size());
auto ExitState = (*BlockToOutputState)[ExitBlock];
assert(ExitState);
Env.popCall(ExitState->Env);
}
const StmtToEnvMap &StmtToEnv;
Environment &Env;
TransferOptions Options;

View File

@ -4368,4 +4368,106 @@ TEST(TransferTest, ContextSensitiveMethodGetterAndSetter) {
/*.BuiltinTransferOptions=*/{/*.ContextSensitive=*/true}});
}
TEST(TransferTest, ContextSensitiveConstructorBody) {
std::string Code = R"(
class MyClass {
public:
MyClass() { MyField = true; }
bool MyField;
};
void target() {
MyClass MyObj;
bool Foo = MyObj.MyField;
// [[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, ContextSensitiveConstructorInitializer) {
std::string Code = R"(
class MyClass {
public:
MyClass() : MyField(true) {}
bool MyField;
};
void target() {
MyClass MyObj;
bool Foo = MyObj.MyField;
// [[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, ContextSensitiveConstructorDefault) {
std::string Code = R"(
class MyClass {
public:
MyClass() = default;
bool MyField = true;
};
void target() {
MyClass MyObj;
bool Foo = MyObj.MyField;
// [[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}});
}
} // namespace