forked from OSchip/llvm-project
[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:
parent
f9563967ca
commit
eb91fd5cbc
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue