diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2023-02-11 12:38:04 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2023-02-11 12:38:11 +0000 |
commit | e3b557809604d036af6e00c60f012c2025b59a5e (patch) | |
tree | 8a11ba2269a3b669601e2fd41145b174008f4da8 /clang/lib/Analysis/FlowSensitive/Transfer.cpp | |
parent | 08e8dd7b9db7bb4a9de26d44c1cbfd24e869c014 (diff) |
Diffstat (limited to 'clang/lib/Analysis/FlowSensitive/Transfer.cpp')
-rw-r--r-- | clang/lib/Analysis/FlowSensitive/Transfer.cpp | 288 |
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 |