//===-- Transfer.cpp --------------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file defines transfer functions that evaluate program statements and // update an environment accordingly. // //===----------------------------------------------------------------------===// #include "clang/Analysis/FlowSensitive/Transfer.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/OperationKinds.h" #include "clang/AST/Stmt.h" #include "clang/AST/StmtVisitor.h" #include "clang/Analysis/FlowSensitive/ControlFlowContext.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" #include "clang/Analysis/FlowSensitive/NoopAnalysis.h" #include "clang/Analysis/FlowSensitive/Value.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/OperatorKinds.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" #include #include #include namespace clang { namespace dataflow { static BoolValue &evaluateBooleanEquality(const Expr &LHS, const Expr &RHS, Environment &Env) { if (auto *LHSValue = dyn_cast_or_null(Env.getValue(LHS, SkipPast::Reference))) if (auto *RHSValue = dyn_cast_or_null(Env.getValue(RHS, SkipPast::Reference))) return Env.makeIff(*LHSValue, *RHSValue); 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 BoolValue &unpackBinaryBoolValue(Environment &Env, BoolValue &B, M build) { auto &V = *cast(&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(&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(Env, V, &Environment::makeAnd); case Value::Kind::Disjunction: return unpackBinaryBoolValue(Env, V, &Environment::makeOr); case Value::Kind::Implication: return unpackBinaryBoolValue( Env, V, &Environment::makeImplication); case Value::Kind::Biconditional: return unpackBinaryBoolValue(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(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 { public: TransferVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env) : StmtToEnv(StmtToEnv), Env(Env) {} void VisitBinaryOperator(const BinaryOperator *S) { const Expr *LHS = S->getLHS(); assert(LHS != nullptr); const Expr *RHS = S->getRHS(); assert(RHS != nullptr); switch (S->getOpcode()) { case BO_Assign: { auto *LHSLoc = Env.getStorageLocation(*LHS, SkipPast::Reference); if (LHSLoc == nullptr) break; auto *RHSVal = Env.getValue(*RHS, SkipPast::Reference); if (RHSVal == nullptr) break; // Assign a value to the storage location of the left-hand side. Env.setValue(*LHSLoc, *RHSVal); // Assign a storage location for the whole expression. Env.setStorageLocation(*S, *LHSLoc); break; } case BO_LAnd: case BO_LOr: { BoolValue &LHSVal = getLogicOperatorSubExprValue(*LHS); BoolValue &RHSVal = getLogicOperatorSubExprValue(*RHS); auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); if (S->getOpcode() == BO_LAnd) Env.setValue(Loc, Env.makeAnd(LHSVal, RHSVal)); else Env.setValue(Loc, Env.makeOr(LHSVal, RHSVal)); break; } case BO_NE: case BO_EQ: { auto &LHSEqRHSValue = evaluateBooleanEquality(*LHS, *RHS, Env); auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); Env.setValue(Loc, S->getOpcode() == BO_EQ ? LHSEqRHSValue : Env.makeNot(LHSEqRHSValue)); break; } case BO_Comma: { if (auto *Loc = Env.getStorageLocation(*RHS, SkipPast::None)) Env.setStorageLocation(*S, *Loc); break; } default: break; } } void VisitDeclRefExpr(const DeclRefExpr *S) { const ValueDecl *VD = S->getDecl(); assert(VD != nullptr); auto *DeclLoc = Env.getStorageLocation(*VD, SkipPast::None); if (DeclLoc == nullptr) return; if (VD->getType()->isReferenceType()) { assert(isa_and_nonnull(Env.getValue((*DeclLoc))) && "reference-typed declarations map to `ReferenceValue`s"); Env.setStorageLocation(*S, *DeclLoc); } else { auto &Loc = Env.createStorageLocation(*S); auto &Val = Env.takeOwnership(std::make_unique(*DeclLoc)); Env.setStorageLocation(*S, Loc); Env.setValue(Loc, Val); } } void VisitDeclStmt(const DeclStmt *S) { // Group decls are converted into single decls in the CFG so the cast below // is safe. const auto &D = *cast(S->getSingleDecl()); // Static local vars are already initialized in `Environment`. if (D.hasGlobalStorage()) return; // 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) { // No initializer expression - associate `Loc` with a new value. if (Value *Val = Env.createValue(D.getType())) Env.setValue(Loc, *Val); return; } if (D.getType()->isReferenceType()) { // Initializing a reference variable - do not create a reference to // reference. if (auto *InitExprLoc = Env.getStorageLocation(*InitExpr, SkipPast::Reference)) { auto &Val = Env.takeOwnership(std::make_unique(*InitExprLoc)); Env.setValue(Loc, Val); } } else if (auto *InitExprVal = Env.getValue(*InitExpr, SkipPast::None)) { Env.setValue(Loc, *InitExprVal); } if (Env.getValue(Loc) == nullptr) { // We arrive here in (the few) cases where an expression is intentionally // "uninterpreted". There are two ways to handle this situation: propagate // the status, so that uninterpreted initializers result in uninterpreted // variables, or provide a default value. We choose the latter so that // later refinements of the variable can be used for reasoning about the // surrounding code. // // FIXME. If and when we interpret all language cases, change this to // assert that `InitExpr` is interpreted, rather than supplying a default // value (assuming we don't update the environment API to return // references). if (Value *Val = Env.createValue(D.getType())) Env.setValue(Loc, *Val); } if (const auto *Decomp = dyn_cast(&D)) { // If VarDecl is a DecompositionDecl, evaluate each of its bindings. This // 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 used in BindingDecls to the CFG. for (const auto *B : Decomp->bindings()) { if (auto *ME = dyn_cast_or_null(B->getBinding())) { auto *DE = dyn_cast_or_null(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); } } } } void VisitImplicitCastExpr(const ImplicitCastExpr *S) { const Expr *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); switch (S->getCastKind()) { case CK_IntegralToBoolean: { // This cast creates a new, boolean value from the integral value. We // model that with a fresh value in the environment, unless it's already a // boolean. auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); if (auto *SubExprVal = dyn_cast_or_null( Env.getValue(*SubExpr, SkipPast::Reference))) Env.setValue(Loc, *SubExprVal); else // FIXME: If integer modeling is added, then update this code to create // the boolean based on the integer model. Env.setValue(Loc, Env.makeAtomicBoolValue()); break; } case CK_LValueToRValue: { // 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; auto &ExprLoc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, ExprLoc); Env.setValue(ExprLoc, *SubExprVal); break; } case CK_IntegralCast: // FIXME: This cast creates a new integral value from the // subexpression. But, because we don't model integers, we don't // distinguish between this new value and the underlying one. If integer // modeling is added, then update this code to create a fresh location and // value. case CK_UncheckedDerivedToBase: case CK_ConstructorConversion: case CK_UserDefinedConversion: // FIXME: Add tests that excercise CK_UncheckedDerivedToBase, // CK_ConstructorConversion, and CK_UserDefinedConversion. case CK_NoOp: { // FIXME: Consider making `Environment::getStorageLocation` skip noop // expressions (this and other similar expressions in the file) instead of // assigning them storage locations. auto *SubExprLoc = Env.getStorageLocation(*SubExpr, SkipPast::None); if (SubExprLoc == nullptr) break; Env.setStorageLocation(*S, *SubExprLoc); break; } case CK_NullToPointer: case CK_NullToMemberPointer: { auto &Loc = Env.createStorageLocation(S->getType()); Env.setStorageLocation(*S, Loc); auto &NullPointerVal = Env.getOrCreateNullPointerValue(S->getType()->getPointeeType()); Env.setValue(Loc, NullPointerVal); break; } default: break; } } void VisitUnaryOperator(const UnaryOperator *S) { const Expr *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); switch (S->getOpcode()) { case UO_Deref: { // Skip past a reference to handle dereference of a dependent pointer. const auto *SubExprVal = cast_or_null( Env.getValue(*SubExpr, SkipPast::Reference)); if (SubExprVal == nullptr) break; auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); Env.setValue(Loc, Env.takeOwnership(std::make_unique( SubExprVal->getPointeeLoc()))); break; } case UO_AddrOf: { // Do not form a pointer to a reference. If `SubExpr` is assigned a // `ReferenceValue` then form a value that points to the location of its // pointee. StorageLocation *PointeeLoc = Env.getStorageLocation(*SubExpr, SkipPast::Reference); if (PointeeLoc == nullptr) break; auto &PointerLoc = Env.createStorageLocation(*S); auto &PointerVal = Env.takeOwnership(std::make_unique(*PointeeLoc)); Env.setStorageLocation(*S, PointerLoc); Env.setValue(PointerLoc, PointerVal); break; } case UO_LNot: { auto *SubExprVal = dyn_cast_or_null(Env.getValue(*SubExpr, SkipPast::None)); if (SubExprVal == nullptr) break; auto &ExprLoc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, ExprLoc); Env.setValue(ExprLoc, Env.makeNot(*SubExprVal)); break; } default: break; } } void VisitCXXThisExpr(const CXXThisExpr *S) { auto *ThisPointeeLoc = Env.getThisPointeeStorageLocation(); if (ThisPointeeLoc == nullptr) // Unions are not supported yet, and will not have a location for the // `this` expression's pointee. return; auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); Env.setValue(Loc, Env.takeOwnership( std::make_unique(*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); // FIXME: Consider assigning pointer values to function member expressions. if (Member->isFunctionOrFunctionTemplate()) return; // FIXME: if/when we add support for modeling enums, use that support here. if (isa(Member)) return; if (auto *D = dyn_cast(Member)) { if (D->hasGlobalStorage()) { auto *VarDeclLoc = Env.getStorageLocation(*D, SkipPast::None); if (VarDeclLoc == nullptr) return; if (VarDeclLoc->getType()->isReferenceType()) { assert(isa_and_nonnull(Env.getValue((*VarDeclLoc))) && "reference-typed declarations map to `ReferenceValue`s"); Env.setStorageLocation(*S, *VarDeclLoc); } else { auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); Env.setValue(Loc, Env.takeOwnership( std::make_unique(*VarDeclLoc))); } return; } } // The receiver can be either a value or a pointer to a value. Skip past the // indirection to handle both cases. auto *BaseLoc = cast_or_null( Env.getStorageLocation(*S->getBase(), SkipPast::ReferenceThenPointer)); if (BaseLoc == nullptr) return; auto &MemberLoc = BaseLoc->getChild(*Member); if (MemberLoc.getType()->isReferenceType()) { // 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(V) && "reference-typed declarations map to `ReferenceValue`s"); Env.setStorageLocation(*S, MemberLoc); } } else { auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); Env.setValue( Loc, Env.takeOwnership(std::make_unique(MemberLoc))); } } void VisitCXXDefaultInitExpr(const CXXDefaultInitExpr *S) { const Expr *InitExpr = S->getExpr(); assert(InitExpr != nullptr); Value *InitExprVal = Env.getValue(*InitExpr, SkipPast::None); if (InitExprVal == nullptr) return; const FieldDecl *Field = S->getField(); assert(Field != nullptr); auto &ThisLoc = *cast(Env.getThisPointeeStorageLocation()); auto &FieldLoc = ThisLoc.getChild(*Field); Env.setValue(FieldLoc, *InitExprVal); } void VisitCXXConstructExpr(const CXXConstructExpr *S) { const CXXConstructorDecl *ConstructorDecl = S->getConstructor(); assert(ConstructorDecl != nullptr); if (ConstructorDecl->isCopyOrMoveConstructor()) { assert(S->getNumArgs() == 1); const Expr *Arg = S->getArg(0); assert(Arg != nullptr); if (S->isElidable()) { auto *ArgLoc = Env.getStorageLocation(*Arg, SkipPast::Reference); if (ArgLoc == nullptr) return; Env.setStorageLocation(*S, *ArgLoc); } else if (auto *ArgVal = Env.getValue(*Arg, SkipPast::Reference)) { auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); Env.setValue(Loc, *ArgVal); } return; } auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); if (Value *Val = Env.createValue(S->getType())) Env.setValue(Loc, *Val); transferInlineCall(S, ConstructorDecl); } void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *S) { if (S->getOperator() == OO_Equal) { assert(S->getNumArgs() == 2); const Expr *Arg0 = S->getArg(0); assert(Arg0 != nullptr); const Expr *Arg1 = S->getArg(1); assert(Arg1 != nullptr); // Evaluate only copy and move assignment operators. auto *Arg0Type = Arg0->getType()->getUnqualifiedDesugaredType(); auto *Arg1Type = Arg1->getType()->getUnqualifiedDesugaredType(); if (Arg0Type != Arg1Type) return; auto *ObjectLoc = Env.getStorageLocation(*Arg0, SkipPast::Reference); if (ObjectLoc == nullptr) return; auto *Val = Env.getValue(*Arg1, SkipPast::Reference); if (Val == nullptr) return; // Assign a value to the storage location of the object. Env.setValue(*ObjectLoc, *Val); // FIXME: Add a test for the value of the whole expression. // Assign a storage location for the whole expression. Env.setStorageLocation(*S, *ObjectLoc); } } void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *S) { if (S->getCastKind() == CK_ConstructorConversion) { const Expr *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); auto *SubExprLoc = Env.getStorageLocation(*SubExpr, SkipPast::None); if (SubExprLoc == nullptr) return; Env.setStorageLocation(*S, *SubExprLoc); } } void VisitCXXTemporaryObjectExpr(const CXXTemporaryObjectExpr *S) { auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); if (Value *Val = Env.createValue(S->getType())) Env.setValue(Loc, *Val); } void VisitCallExpr(const CallExpr *S) { // Of clang's builtins, only `__builtin_expect` is handled explicitly, since // others (like trap, debugtrap, and unreachable) are handled by CFG // construction. if (S->isCallToStdMove()) { assert(S->getNumArgs() == 1); const Expr *Arg = S->getArg(0); assert(Arg != nullptr); auto *ArgLoc = Env.getStorageLocation(*Arg, SkipPast::None); if (ArgLoc == nullptr) return; Env.setStorageLocation(*S, *ArgLoc); } else if (S->getDirectCallee() != nullptr && S->getDirectCallee()->getBuiltinID() == Builtin::BI__builtin_expect) { assert(S->getNumArgs() > 0); assert(S->getArg(0) != nullptr); // `__builtin_expect` returns by-value, so strip away any potential // references in the argument. auto *ArgLoc = Env.getStorageLocation(*S->getArg(0), SkipPast::Reference); if (ArgLoc == nullptr) return; Env.setStorageLocation(*S, *ArgLoc); } else if (const FunctionDecl *F = S->getDirectCallee()) { transferInlineCall(S, F); } } void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *S) { const Expr *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); auto *SubExprLoc = Env.getStorageLocation(*SubExpr, SkipPast::None); if (SubExprLoc == nullptr) return; Env.setStorageLocation(*S, *SubExprLoc); } void VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *S) { const Expr *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); auto *SubExprLoc = Env.getStorageLocation(*SubExpr, SkipPast::None); if (SubExprLoc == nullptr) return; Env.setStorageLocation(*S, *SubExprLoc); } void VisitCXXStaticCastExpr(const CXXStaticCastExpr *S) { if (S->getCastKind() == CK_NoOp) { const Expr *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); auto *SubExprLoc = Env.getStorageLocation(*SubExpr, SkipPast::None); if (SubExprLoc == nullptr) return; Env.setStorageLocation(*S, *SubExprLoc); } } void VisitConditionalOperator(const ConditionalOperator *S) { // FIXME: Revisit this once flow conditions are added to the framework. For // `a = b ? c : d` we can add `b => a == c && !b => a == d` to the flow // condition. auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); if (Value *Val = Env.createValue(S->getType())) Env.setValue(Loc, *Val); } void VisitInitListExpr(const InitListExpr *S) { QualType Type = S->getType(); auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); auto *Val = Env.createValue(Type); if (Val == nullptr) return; Env.setValue(Loc, *Val); if (Type->isStructureOrClassType()) { for (auto It : llvm::zip(Type->getAsRecordDecl()->fields(), S->inits())) { const FieldDecl *Field = std::get<0>(It); assert(Field != nullptr); const Expr *Init = std::get<1>(It); assert(Init != nullptr); if (Value *InitVal = Env.getValue(*Init, SkipPast::None)) cast(Val)->setChild(*Field, *InitVal); } } // FIXME: Implement array initialization. } void VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *S) { auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); Env.setValue(Loc, Env.getBoolLiteralValue(S->getValue())); } void VisitParenExpr(const ParenExpr *S) { // The CFG does not contain `ParenExpr` as top-level statements in basic // blocks, however manual traversal to sub-expressions may encounter them. // Redirect to the sub-expression. auto *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); Visit(SubExpr); } void VisitExprWithCleanups(const ExprWithCleanups *S) { // The CFG does not contain `ExprWithCleanups` as top-level statements in // basic blocks, however manual traversal to sub-expressions may encounter // them. Redirect to the sub-expression. auto *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); Visit(SubExpr); } private: BoolValue &getLogicOperatorSubExprValue(const Expr &SubExpr) { // `SubExpr` and its parent logic operator might be part of different basic // blocks. We try to access the value that is assigned to `SubExpr` in the // corresponding environment. if (const Environment *SubExprEnv = StmtToEnv.getEnvironment(SubExpr)) { if (auto *Val = dyn_cast_or_null( SubExprEnv->getValue(SubExpr, SkipPast::Reference))) return *Val; } if (Env.getStorageLocation(SubExpr, SkipPast::None) == nullptr) { // Sub-expressions that are logic operators are not added in basic blocks // (e.g. see CFG for `bool d = a && (b || c);`). If `SubExpr` is a logic // operator, it may not have been evaluated and assigned a value yet. In // that case, we need to first visit `SubExpr` and then try to get the // value that gets assigned to it. Visit(&SubExpr); } if (auto *Val = dyn_cast_or_null( Env.getValue(SubExpr, SkipPast::Reference))) return *Val; // If the value of `SubExpr` is still unknown, we create a fresh symbolic // boolean value for it. 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 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(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; }; void transfer(const StmtToEnvMap &StmtToEnv, const Stmt &S, Environment &Env) { TransferVisitor(StmtToEnv, Env).Visit(&S); } } // namespace dataflow } // namespace clang