aboutsummaryrefslogtreecommitdiff
path: root/clang/lib/Analysis/FlowSensitive/Transfer.cpp
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2023-02-11 12:38:04 +0000
committerDimitry Andric <dim@FreeBSD.org>2023-02-11 12:38:11 +0000
commite3b557809604d036af6e00c60f012c2025b59a5e (patch)
tree8a11ba2269a3b669601e2fd41145b174008f4da8 /clang/lib/Analysis/FlowSensitive/Transfer.cpp
parent08e8dd7b9db7bb4a9de26d44c1cbfd24e869c014 (diff)
Diffstat (limited to 'clang/lib/Analysis/FlowSensitive/Transfer.cpp')
-rw-r--r--clang/lib/Analysis/FlowSensitive/Transfer.cpp288
1 files changed, 225 insertions, 63 deletions
diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
index bbf7526adce9..0e6c484b67e7 100644
--- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
@@ -28,6 +28,7 @@
#include "clang/Basic/OperatorKinds.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/ErrorHandling.h"
#include <cassert>
#include <memory>
#include <tuple>
@@ -46,11 +47,91 @@ static BoolValue &evaluateBooleanEquality(const Expr &LHS, const Expr &RHS,
return Env.makeAtomicBoolValue();
}
+// Functionally updates `V` such that any instances of `TopBool` are replaced
+// with fresh atomic bools. Note: This implementation assumes that `B` is a
+// tree; if `B` is a DAG, it will lose any sharing between subvalues that was
+// present in the original .
+static BoolValue &unpackValue(BoolValue &V, Environment &Env);
+
+template <typename Derived, typename M>
+BoolValue &unpackBinaryBoolValue(Environment &Env, BoolValue &B, M build) {
+ auto &V = *cast<Derived>(&B);
+ BoolValue &Left = V.getLeftSubValue();
+ BoolValue &Right = V.getRightSubValue();
+ BoolValue &ULeft = unpackValue(Left, Env);
+ BoolValue &URight = unpackValue(Right, Env);
+
+ if (&ULeft == &Left && &URight == &Right)
+ return V;
+
+ return (Env.*build)(ULeft, URight);
+}
+
+static BoolValue &unpackValue(BoolValue &V, Environment &Env) {
+ switch (V.getKind()) {
+ case Value::Kind::Integer:
+ case Value::Kind::Reference:
+ case Value::Kind::Pointer:
+ case Value::Kind::Struct:
+ llvm_unreachable("BoolValue cannot have any of these kinds.");
+
+ case Value::Kind::AtomicBool:
+ return V;
+
+ case Value::Kind::TopBool:
+ // Unpack `TopBool` into a fresh atomic bool.
+ return Env.makeAtomicBoolValue();
+
+ case Value::Kind::Negation: {
+ auto &N = *cast<NegationValue>(&V);
+ BoolValue &Sub = N.getSubVal();
+ BoolValue &USub = unpackValue(Sub, Env);
+
+ if (&USub == &Sub)
+ return V;
+ return Env.makeNot(USub);
+ }
+ case Value::Kind::Conjunction:
+ return unpackBinaryBoolValue<ConjunctionValue>(Env, V,
+ &Environment::makeAnd);
+ case Value::Kind::Disjunction:
+ return unpackBinaryBoolValue<DisjunctionValue>(Env, V,
+ &Environment::makeOr);
+ case Value::Kind::Implication:
+ return unpackBinaryBoolValue<ImplicationValue>(
+ Env, V, &Environment::makeImplication);
+ case Value::Kind::Biconditional:
+ return unpackBinaryBoolValue<BiconditionalValue>(Env, V,
+ &Environment::makeIff);
+ }
+ llvm_unreachable("All reachable cases in switch return");
+}
+
+// Unpacks the value (if any) associated with `E` and updates `E` to the new
+// value, if any unpacking occured.
+static Value *maybeUnpackLValueExpr(const Expr &E, Environment &Env) {
+ // FIXME: this is too flexible: it _allows_ a reference, while it should
+ // _require_ one, since lvalues should always be wrapped in `ReferenceValue`.
+ auto *Loc = Env.getStorageLocation(E, SkipPast::Reference);
+ if (Loc == nullptr)
+ return nullptr;
+ auto *Val = Env.getValue(*Loc);
+
+ auto *B = dyn_cast_or_null<BoolValue>(Val);
+ if (B == nullptr)
+ return Val;
+
+ auto &UnpackedVal = unpackValue(*B, Env);
+ if (&UnpackedVal == Val)
+ return Val;
+ Env.setValue(*Loc, UnpackedVal);
+ return &UnpackedVal;
+}
+
class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
public:
- TransferVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env,
- TransferOptions Options)
- : StmtToEnv(StmtToEnv), Env(Env), Options(Options) {}
+ TransferVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env)
+ : StmtToEnv(StmtToEnv), Env(Env) {}
void VisitBinaryOperator(const BinaryOperator *S) {
const Expr *LHS = S->getLHS();
@@ -109,12 +190,15 @@ public:
}
void VisitDeclRefExpr(const DeclRefExpr *S) {
- assert(S->getDecl() != nullptr);
- auto *DeclLoc = Env.getStorageLocation(*S->getDecl(), SkipPast::None);
+ const ValueDecl *VD = S->getDecl();
+ assert(VD != nullptr);
+ auto *DeclLoc = Env.getStorageLocation(*VD, SkipPast::None);
if (DeclLoc == nullptr)
return;
- if (S->getDecl()->getType()->isReferenceType()) {
+ if (VD->getType()->isReferenceType()) {
+ assert(isa_and_nonnull<ReferenceValue>(Env.getValue((*DeclLoc))) &&
+ "reference-typed declarations map to `ReferenceValue`s");
Env.setStorageLocation(*S, *DeclLoc);
} else {
auto &Loc = Env.createStorageLocation(*S);
@@ -133,8 +217,15 @@ public:
if (D.hasGlobalStorage())
return;
- auto &Loc = Env.createStorageLocation(D);
- Env.setStorageLocation(D, Loc);
+ // The storage location for `D` could have been created earlier, before the
+ // variable's declaration statement (for example, in the case of
+ // BindingDecls).
+ auto *MaybeLoc = Env.getStorageLocation(D, SkipPast::None);
+ if (MaybeLoc == nullptr) {
+ MaybeLoc = &Env.createStorageLocation(D);
+ Env.setStorageLocation(D, *MaybeLoc);
+ }
+ auto &Loc = *MaybeLoc;
const Expr *InitExpr = D.getInit();
if (InitExpr == nullptr) {
@@ -178,24 +269,30 @@ public:
// needs to be evaluated after initializing the values in the storage for
// VarDecl, as the bindings refer to them.
// FIXME: Add support for ArraySubscriptExpr.
- // FIXME: Consider adding AST nodes that are used for structured bindings
- // to the CFG.
+ // FIXME: Consider adding AST nodes used in BindingDecls to the CFG.
for (const auto *B : Decomp->bindings()) {
- auto *ME = dyn_cast_or_null<MemberExpr>(B->getBinding());
- if (ME == nullptr)
- continue;
-
- auto *DE = dyn_cast_or_null<DeclRefExpr>(ME->getBase());
- if (DE == nullptr)
- continue;
-
- // ME and its base haven't been visited because they aren't included in
- // the statements of the CFG basic block.
- VisitDeclRefExpr(DE);
- VisitMemberExpr(ME);
-
- if (auto *Loc = Env.getStorageLocation(*ME, SkipPast::Reference))
- Env.setStorageLocation(*B, *Loc);
+ if (auto *ME = dyn_cast_or_null<MemberExpr>(B->getBinding())) {
+ auto *DE = dyn_cast_or_null<DeclRefExpr>(ME->getBase());
+ if (DE == nullptr)
+ continue;
+
+ // ME and its base haven't been visited because they aren't included
+ // in the statements of the CFG basic block.
+ VisitDeclRefExpr(DE);
+ VisitMemberExpr(ME);
+
+ if (auto *Loc = Env.getStorageLocation(*ME, SkipPast::Reference))
+ Env.setStorageLocation(*B, *Loc);
+ } else if (auto *VD = B->getHoldingVar()) {
+ // Holding vars are used to back the BindingDecls of tuple-like
+ // types. The holding var declarations appear *after* this statement,
+ // so we have to create a location for them here to share with `B`. We
+ // don't visit the binding, because we know it will be a DeclRefExpr
+ // to `VD`.
+ auto &VDLoc = Env.createStorageLocation(*VD);
+ Env.setStorageLocation(*VD, VDLoc);
+ Env.setStorageLocation(*B, VDLoc);
+ }
}
}
}
@@ -222,7 +319,9 @@ public:
}
case CK_LValueToRValue: {
- auto *SubExprVal = Env.getValue(*SubExpr, SkipPast::Reference);
+ // When an L-value is used as an R-value, it may result in sharing, so we
+ // need to unpack any nested `Top`s.
+ auto *SubExprVal = maybeUnpackLValueExpr(*SubExpr, Env);
if (SubExprVal == nullptr)
break;
@@ -332,6 +431,32 @@ public:
std::make_unique<PointerValue>(*ThisPointeeLoc)));
}
+ void VisitReturnStmt(const ReturnStmt *S) {
+ if (!Env.getAnalysisOptions().ContextSensitiveOpts)
+ return;
+
+ 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: Support reference-type returns.
+ if (Loc->getType()->isReferenceType())
+ return;
+
+ // FIXME: Model NRVO.
+ Env.setValue(*Loc, *Val);
+ }
+
void VisitMemberExpr(const MemberExpr *S) {
ValueDecl *Member = S->getMemberDecl();
assert(Member != nullptr);
@@ -340,6 +465,10 @@ public:
if (Member->isFunctionOrFunctionTemplate())
return;
+ // FIXME: if/when we add support for modeling enums, use that support here.
+ if (isa<EnumConstantDecl>(Member))
+ return;
+
if (auto *D = dyn_cast<VarDecl>(Member)) {
if (D->hasGlobalStorage()) {
auto *VarDeclLoc = Env.getStorageLocation(*D, SkipPast::None);
@@ -347,6 +476,8 @@ public:
return;
if (VarDeclLoc->getType()->isReferenceType()) {
+ assert(isa_and_nonnull<ReferenceValue>(Env.getValue((*VarDeclLoc))) &&
+ "reference-typed declarations map to `ReferenceValue`s");
Env.setStorageLocation(*S, *VarDeclLoc);
} else {
auto &Loc = Env.createStorageLocation(*S);
@@ -365,13 +496,24 @@ public:
if (BaseLoc == nullptr)
return;
- // FIXME: Add support for union types.
- if (BaseLoc->getType()->isUnionType())
- return;
-
auto &MemberLoc = BaseLoc->getChild(*Member);
if (MemberLoc.getType()->isReferenceType()) {
- Env.setStorageLocation(*S, MemberLoc);
+ // Based on its type, `MemberLoc` must be mapped either to nothing or to a
+ // `ReferenceValue`. For the former, we won't set a storage location for
+ // this expression, so as to maintain an invariant lvalue expressions;
+ // namely, that their location maps to a `ReferenceValue`. In this,
+ // lvalues are unlike other expressions, where it is valid for their
+ // location to map to nothing (because they are not modeled).
+ //
+ // Note: we need this invariant for lvalues so that, when accessing a
+ // value, we can distinguish an rvalue from an lvalue. An alternative
+ // design, which takes the expression's value category into account, would
+ // avoid the need for this invariant.
+ if (auto *V = Env.getValue(MemberLoc)) {
+ assert(isa<ReferenceValue>(V) &&
+ "reference-typed declarations map to `ReferenceValue`s");
+ Env.setStorageLocation(*S, MemberLoc);
+ }
} else {
auto &Loc = Env.createStorageLocation(*S);
Env.setStorageLocation(*S, Loc);
@@ -425,6 +567,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) {
@@ -507,34 +651,7 @@ public:
return;
Env.setStorageLocation(*S, *ArgLoc);
} else if (const FunctionDecl *F = S->getDirectCallee()) {
- // This case is for context-sensitive analysis, which we only do if we
- // have the callee body available in the translation unit.
- if (!Options.ContextSensitive || F->getBody() == nullptr)
- return;
-
- auto &ASTCtx = F->getASTContext();
-
- // FIXME: Cache these CFGs.
- auto CFCtx = ControlFlowContext::build(F, F->getBody(), &ASTCtx);
- // FIXME: Handle errors here and below.
- assert(CFCtx);
- auto ExitBlock = CFCtx->getCFG().getExit().getBlockID();
-
- auto CalleeEnv = Env.pushCall(S);
-
- // FIXME: Use the same analysis as the caller for the callee.
- DataflowAnalysisOptions Options;
- auto Analysis = NoopAnalysis(ASTCtx, Options);
-
- auto BlockToOutputState =
- dataflow::runDataflowAnalysis(*CFCtx, Analysis, CalleeEnv);
- assert(BlockToOutputState);
- assert(ExitBlock < BlockToOutputState->size());
-
- auto ExitState = (*BlockToOutputState)[ExitBlock];
- assert(ExitState);
-
- Env = ExitState->Env;
+ transferInlineCall(S, F);
}
}
@@ -663,14 +780,59 @@ 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) {
+ const auto &Options = Env.getAnalysisOptions();
+ if (!(Options.ContextSensitiveOpts &&
+ Env.canDescend(Options.ContextSensitiveOpts->Depth, F)))
+ 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{Options});
+
+ 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;
};
-void transfer(const StmtToEnvMap &StmtToEnv, const Stmt &S, Environment &Env,
- TransferOptions Options) {
- TransferVisitor(StmtToEnv, Env, Options).Visit(&S);
+void transfer(const StmtToEnvMap &StmtToEnv, const Stmt &S, Environment &Env) {
+ TransferVisitor(StmtToEnv, Env).Visit(&S);
}
} // namespace dataflow