//===- CallEvent.cpp - Wrapper for all function and method calls ----------===// // // 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 // //===----------------------------------------------------------------------===// // /// \file This file defines CallEvent and its subclasses, which represent path- /// sensitive instances of different kinds of function and method calls /// (C, C++, and Objective-C). // //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMap.h" #include "clang/AST/Stmt.h" #include "clang/AST/Type.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" #include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/ImmutableList.h" #include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" #include #include #include #define DEBUG_TYPE "static-analyzer-call-event" using namespace clang; using namespace ento; QualType CallEvent::getResultType() const { ASTContext &Ctx = getState()->getStateManager().getContext(); const Expr *E = getOriginExpr(); if (!E) return Ctx.VoidTy; return Ctx.getReferenceQualifiedType(E); } static bool isCallback(QualType T) { // If a parameter is a block or a callback, assume it can modify pointer. if (T->isBlockPointerType() || T->isFunctionPointerType() || T->isObjCSelType()) return true; // Check if a callback is passed inside a struct (for both, struct passed by // reference and by value). Dig just one level into the struct for now. if (T->isAnyPointerType() || T->isReferenceType()) T = T->getPointeeType(); if (const RecordType *RT = T->getAsStructureType()) { const RecordDecl *RD = RT->getDecl(); for (const auto *I : RD->fields()) { QualType FieldT = I->getType(); if (FieldT->isBlockPointerType() || FieldT->isFunctionPointerType()) return true; } } return false; } static bool isVoidPointerToNonConst(QualType T) { if (const auto *PT = T->getAs()) { QualType PointeeTy = PT->getPointeeType(); if (PointeeTy.isConstQualified()) return false; return PointeeTy->isVoidType(); } else return false; } bool CallEvent::hasNonNullArgumentsWithType(bool (*Condition)(QualType)) const { unsigned NumOfArgs = getNumArgs(); // If calling using a function pointer, assume the function does not // satisfy the callback. // TODO: We could check the types of the arguments here. if (!getDecl()) return false; unsigned Idx = 0; for (CallEvent::param_type_iterator I = param_type_begin(), E = param_type_end(); I != E && Idx < NumOfArgs; ++I, ++Idx) { // If the parameter is 0, it's harmless. if (getArgSVal(Idx).isZeroConstant()) continue; if (Condition(*I)) return true; } return false; } bool CallEvent::hasNonZeroCallbackArg() const { return hasNonNullArgumentsWithType(isCallback); } bool CallEvent::hasVoidPointerToNonConstArg() const { return hasNonNullArgumentsWithType(isVoidPointerToNonConst); } bool CallEvent::isGlobalCFunction(StringRef FunctionName) const { const auto *FD = dyn_cast_or_null(getDecl()); if (!FD) return false; return CheckerContext::isCLibraryFunction(FD, FunctionName); } AnalysisDeclContext *CallEvent::getCalleeAnalysisDeclContext() const { const Decl *D = getDecl(); if (!D) return nullptr; AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext()->getManager()->getContext(D); return ADC; } const StackFrameContext * CallEvent::getCalleeStackFrame(unsigned BlockCount) const { AnalysisDeclContext *ADC = getCalleeAnalysisDeclContext(); if (!ADC) return nullptr; const Expr *E = getOriginExpr(); if (!E) return nullptr; // Recover CFG block via reverse lookup. // TODO: If we were to keep CFG element information as part of the CallEvent // instead of doing this reverse lookup, we would be able to build the stack // frame for non-expression-based calls, and also we wouldn't need the reverse // lookup. CFGStmtMap *Map = LCtx->getAnalysisDeclContext()->getCFGStmtMap(); const CFGBlock *B = Map->getBlock(E); assert(B); // Also recover CFG index by scanning the CFG block. unsigned Idx = 0, Sz = B->size(); for (; Idx < Sz; ++Idx) if (auto StmtElem = (*B)[Idx].getAs()) if (StmtElem->getStmt() == E) break; assert(Idx < Sz); return ADC->getManager()->getStackFrame(ADC, LCtx, E, B, BlockCount, Idx); } const ParamVarRegion *CallEvent::getParameterLocation(unsigned Index, unsigned BlockCount) const { const StackFrameContext *SFC = getCalleeStackFrame(BlockCount); // We cannot construct a VarRegion without a stack frame. if (!SFC) return nullptr; const ParamVarRegion *PVR = State->getStateManager().getRegionManager().getParamVarRegion( getOriginExpr(), Index, SFC); return PVR; } /// Returns true if a type is a pointer-to-const or reference-to-const /// with no further indirection. static bool isPointerToConst(QualType Ty) { QualType PointeeTy = Ty->getPointeeType(); if (PointeeTy == QualType()) return false; if (!PointeeTy.isConstQualified()) return false; if (PointeeTy->isAnyPointerType()) return false; return true; } // Try to retrieve the function declaration and find the function parameter // types which are pointers/references to a non-pointer const. // We will not invalidate the corresponding argument regions. static void findPtrToConstParams(llvm::SmallSet &PreserveArgs, const CallEvent &Call) { unsigned Idx = 0; for (CallEvent::param_type_iterator I = Call.param_type_begin(), E = Call.param_type_end(); I != E; ++I, ++Idx) { if (isPointerToConst(*I)) PreserveArgs.insert(Idx); } } ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, ProgramStateRef Orig) const { ProgramStateRef Result = (Orig ? Orig : getState()); // Don't invalidate anything if the callee is marked pure/const. if (const Decl *callee = getDecl()) if (callee->hasAttr() || callee->hasAttr()) return Result; SmallVector ValuesToInvalidate; RegionAndSymbolInvalidationTraits ETraits; getExtraInvalidatedValues(ValuesToInvalidate, &ETraits); // Indexes of arguments whose values will be preserved by the call. llvm::SmallSet PreserveArgs; if (!argumentsMayEscape()) findPtrToConstParams(PreserveArgs, *this); for (unsigned Idx = 0, Count = getNumArgs(); Idx != Count; ++Idx) { // Mark this region for invalidation. We batch invalidate regions // below for efficiency. if (PreserveArgs.count(Idx)) if (const MemRegion *MR = getArgSVal(Idx).getAsRegion()) ETraits.setTrait(MR->getBaseRegion(), RegionAndSymbolInvalidationTraits::TK_PreserveContents); // TODO: Factor this out + handle the lower level const pointers. ValuesToInvalidate.push_back(getArgSVal(Idx)); // If a function accepts an object by argument (which would of course be a // temporary that isn't lifetime-extended), invalidate the object itself, // not only other objects reachable from it. This is necessary because the // destructor has access to the temporary object after the call. // TODO: Support placement arguments once we start // constructing them directly. // TODO: This is unnecessary when there's no destructor, but that's // currently hard to figure out. if (getKind() != CE_CXXAllocator) if (isArgumentConstructedDirectly(Idx)) if (auto AdjIdx = getAdjustedParameterIndex(Idx)) if (const TypedValueRegion *TVR = getParameterLocation(*AdjIdx, BlockCount)) ValuesToInvalidate.push_back(loc::MemRegionVal(TVR)); } // Invalidate designated regions using the batch invalidation API. // NOTE: Even if RegionsToInvalidate is empty, we may still invalidate // global variables. return Result->invalidateRegions(ValuesToInvalidate, getOriginExpr(), BlockCount, getLocationContext(), /*CausedByPointerEscape*/ true, /*Symbols=*/nullptr, this, &ETraits); } ProgramPoint CallEvent::getProgramPoint(bool IsPreVisit, const ProgramPointTag *Tag) const { if (const Expr *E = getOriginExpr()) { if (IsPreVisit) return PreStmt(E, getLocationContext(), Tag); return PostStmt(E, getLocationContext(), Tag); } const Decl *D = getDecl(); assert(D && "Cannot get a program point without a statement or decl"); assert(ElemRef.getParent() && "Cannot get a program point without a CFGElementRef"); SourceLocation Loc = getSourceRange().getBegin(); if (IsPreVisit) return PreImplicitCall(D, Loc, getLocationContext(), ElemRef, Tag); return PostImplicitCall(D, Loc, getLocationContext(), ElemRef, Tag); } SVal CallEvent::getArgSVal(unsigned Index) const { const Expr *ArgE = getArgExpr(Index); if (!ArgE) return UnknownVal(); return getSVal(ArgE); } SourceRange CallEvent::getArgSourceRange(unsigned Index) const { const Expr *ArgE = getArgExpr(Index); if (!ArgE) return {}; return ArgE->getSourceRange(); } SVal CallEvent::getReturnValue() const { const Expr *E = getOriginExpr(); if (!E) return UndefinedVal(); return getSVal(E); } LLVM_DUMP_METHOD void CallEvent::dump() const { dump(llvm::errs()); } void CallEvent::dump(raw_ostream &Out) const { ASTContext &Ctx = getState()->getStateManager().getContext(); if (const Expr *E = getOriginExpr()) { E->printPretty(Out, nullptr, Ctx.getPrintingPolicy()); return; } if (const Decl *D = getDecl()) { Out << "Call to "; D->print(Out, Ctx.getPrintingPolicy()); return; } Out << "Unknown call (type " << getKindAsString() << ")"; } bool CallEvent::isCallStmt(const Stmt *S) { return isa(S); } QualType CallEvent::getDeclaredResultType(const Decl *D) { assert(D); if (const auto *FD = dyn_cast(D)) return FD->getReturnType(); if (const auto *MD = dyn_cast(D)) return MD->getReturnType(); if (const auto *BD = dyn_cast(D)) { // Blocks are difficult because the return type may not be stored in the // BlockDecl itself. The AST should probably be enhanced, but for now we // just do what we can. // If the block is declared without an explicit argument list, the // signature-as-written just includes the return type, not the entire // function type. // FIXME: All blocks should have signatures-as-written, even if the return // type is inferred. (That's signified with a dependent result type.) if (const TypeSourceInfo *TSI = BD->getSignatureAsWritten()) { QualType Ty = TSI->getType(); if (const FunctionType *FT = Ty->getAs()) Ty = FT->getReturnType(); if (!Ty->isDependentType()) return Ty; } return {}; } llvm_unreachable("unknown callable kind"); } bool CallEvent::isVariadic(const Decl *D) { assert(D); if (const auto *FD = dyn_cast(D)) return FD->isVariadic(); if (const auto *MD = dyn_cast(D)) return MD->isVariadic(); if (const auto *BD = dyn_cast(D)) return BD->isVariadic(); llvm_unreachable("unknown callable kind"); } static bool isTransparentUnion(QualType T) { const RecordType *UT = T->getAsUnionType(); return UT && UT->getDecl()->hasAttr(); } // In some cases, symbolic cases should be transformed before we associate // them with parameters. This function incapsulates such cases. static SVal processArgument(SVal Value, const Expr *ArgumentExpr, const ParmVarDecl *Parameter, SValBuilder &SVB) { QualType ParamType = Parameter->getType(); QualType ArgumentType = ArgumentExpr->getType(); // Transparent unions allow users to easily convert values of union field // types into union-typed objects. // // Also, more importantly, they allow users to define functions with different // different parameter types, substituting types matching transparent union // field types with the union type itself. // // Here, we check specifically for latter cases and prevent binding // field-typed values to union-typed regions. if (isTransparentUnion(ParamType) && // Let's check that we indeed trying to bind different types. !isTransparentUnion(ArgumentType)) { BasicValueFactory &BVF = SVB.getBasicValueFactory(); llvm::ImmutableList CompoundSVals = BVF.getEmptySValList(); CompoundSVals = BVF.prependSVal(Value, CompoundSVals); // Wrap it with compound value. return SVB.makeCompoundVal(ParamType, CompoundSVals); } return Value; } /// Cast the argument value to the type of the parameter at the function /// declaration. /// Returns the argument value if it didn't need a cast. /// Or returns the cast argument if it needed a cast. /// Or returns 'Unknown' if it would need a cast but the callsite and the /// runtime definition don't match in terms of argument and parameter count. static SVal castArgToParamTypeIfNeeded(const CallEvent &Call, unsigned ArgIdx, SVal ArgVal, SValBuilder &SVB) { const FunctionDecl *RTDecl = Call.getRuntimeDefinition().getDecl()->getAsFunction(); const auto *CallExprDecl = dyn_cast_or_null(Call.getDecl()); if (!RTDecl || !CallExprDecl) return ArgVal; // The function decl of the Call (in the AST) will not have any parameter // declarations, if it was 'only' declared without a prototype. However, the // engine will find the appropriate runtime definition - basically a // redeclaration, which has a function body (and a function prototype). if (CallExprDecl->hasPrototype() || !RTDecl->hasPrototype()) return ArgVal; // Only do this cast if the number arguments at the callsite matches with // the parameters at the runtime definition. if (Call.getNumArgs() != RTDecl->getNumParams()) return UnknownVal(); const Expr *ArgExpr = Call.getArgExpr(ArgIdx); const ParmVarDecl *Param = RTDecl->getParamDecl(ArgIdx); return SVB.evalCast(ArgVal, Param->getType(), ArgExpr->getType()); } static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, CallEvent::BindingsTy &Bindings, SValBuilder &SVB, const CallEvent &Call, ArrayRef parameters) { MemRegionManager &MRMgr = SVB.getRegionManager(); // If the function has fewer parameters than the call has arguments, we simply // do not bind any values to them. unsigned NumArgs = Call.getNumArgs(); unsigned Idx = 0; ArrayRef::iterator I = parameters.begin(), E = parameters.end(); for (; I != E && Idx < NumArgs; ++I, ++Idx) { assert(*I && "Formal parameter has no decl?"); // TODO: Support allocator calls. if (Call.getKind() != CE_CXXAllocator) if (Call.isArgumentConstructedDirectly(Call.getASTArgumentIndex(Idx))) continue; // TODO: Allocators should receive the correct size and possibly alignment, // determined in compile-time but not represented as arg-expressions, // which makes getArgSVal() fail and return UnknownVal. SVal ArgVal = Call.getArgSVal(Idx); const Expr *ArgExpr = Call.getArgExpr(Idx); if (ArgVal.isUnknown()) continue; // Cast the argument value to match the type of the parameter in some // edge-cases. ArgVal = castArgToParamTypeIfNeeded(Call, Idx, ArgVal, SVB); Loc ParamLoc = SVB.makeLoc( MRMgr.getParamVarRegion(Call.getOriginExpr(), Idx, CalleeCtx)); Bindings.push_back( std::make_pair(ParamLoc, processArgument(ArgVal, ArgExpr, *I, SVB))); } // FIXME: Variadic arguments are not handled at all right now. } const ConstructionContext *CallEvent::getConstructionContext() const { const StackFrameContext *StackFrame = getCalleeStackFrame(0); if (!StackFrame) return nullptr; const CFGElement Element = StackFrame->getCallSiteCFGElement(); if (const auto Ctor = Element.getAs()) { return Ctor->getConstructionContext(); } if (const auto RecCall = Element.getAs()) { return RecCall->getConstructionContext(); } return nullptr; } const CallEventRef<> CallEvent::getCaller() const { const auto *CallLocationContext = this->getLocationContext(); if (!CallLocationContext || CallLocationContext->inTopFrame()) return nullptr; const auto *CallStackFrameContext = CallLocationContext->getStackFrame(); if (!CallStackFrameContext) return nullptr; CallEventManager &CEMgr = State->getStateManager().getCallEventManager(); return CEMgr.getCaller(CallStackFrameContext, State); } bool CallEvent::isCalledFromSystemHeader() const { if (const CallEventRef<> Caller = getCaller()) return Caller->isInSystemHeader(); return false; } std::optional CallEvent::getReturnValueUnderConstruction() const { const auto *CC = getConstructionContext(); if (!CC) return std::nullopt; EvalCallOptions CallOpts; ExprEngine &Engine = getState()->getStateManager().getOwningEngine(); SVal RetVal = Engine.computeObjectUnderConstruction( getOriginExpr(), getState(), &Engine.getBuilderContext(), getLocationContext(), CC, CallOpts); return RetVal; } ArrayRef AnyFunctionCall::parameters() const { const FunctionDecl *D = getDecl(); if (!D) return std::nullopt; return D->parameters(); } RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const { const FunctionDecl *FD = getDecl(); if (!FD) return {}; // Note that the AnalysisDeclContext will have the FunctionDecl with // the definition (if one exists). AnalysisDeclContext *AD = getLocationContext()->getAnalysisDeclContext()-> getManager()->getContext(FD); bool IsAutosynthesized; Stmt* Body = AD->getBody(IsAutosynthesized); LLVM_DEBUG({ if (IsAutosynthesized) llvm::dbgs() << "Using autosynthesized body for " << FD->getName() << "\n"; }); ExprEngine &Engine = getState()->getStateManager().getOwningEngine(); cross_tu::CrossTranslationUnitContext &CTUCtx = *Engine.getCrossTranslationUnitContext(); AnalyzerOptions &Opts = Engine.getAnalysisManager().options; if (Body) { const Decl* Decl = AD->getDecl(); if (Opts.IsNaiveCTUEnabled && CTUCtx.isImportedAsNew(Decl)) { // A newly created definition, but we had error(s) during the import. if (CTUCtx.hasError(Decl)) return {}; return RuntimeDefinition(Decl, /*Foreign=*/true); } return RuntimeDefinition(Decl, /*Foreign=*/false); } // Try to get CTU definition only if CTUDir is provided. if (!Opts.IsNaiveCTUEnabled) return {}; llvm::Expected CTUDeclOrError = CTUCtx.getCrossTUDefinition(FD, Opts.CTUDir, Opts.CTUIndexName, Opts.DisplayCTUProgress); if (!CTUDeclOrError) { handleAllErrors(CTUDeclOrError.takeError(), [&](const cross_tu::IndexError &IE) { CTUCtx.emitCrossTUDiagnostics(IE); }); return {}; } return RuntimeDefinition(*CTUDeclOrError, /*Foreign=*/true); } void AnyFunctionCall::getInitialStackFrameContents( const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { const auto *D = cast(CalleeCtx->getDecl()); SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); addParameterValuesToBindings(CalleeCtx, Bindings, SVB, *this, D->parameters()); } bool AnyFunctionCall::argumentsMayEscape() const { if (CallEvent::argumentsMayEscape() || hasVoidPointerToNonConstArg()) return true; const FunctionDecl *D = getDecl(); if (!D) return true; const IdentifierInfo *II = D->getIdentifier(); if (!II) return false; // This set of "escaping" APIs is // - 'int pthread_setspecific(ptheread_key k, const void *)' stores a // value into thread local storage. The value can later be retrieved with // 'void *ptheread_getspecific(pthread_key)'. So even thought the // parameter is 'const void *', the region escapes through the call. if (II->isStr("pthread_setspecific")) return true; // - xpc_connection_set_context stores a value which can be retrieved later // with xpc_connection_get_context. if (II->isStr("xpc_connection_set_context")) return true; // - funopen - sets a buffer for future IO calls. if (II->isStr("funopen")) return true; // - __cxa_demangle - can reallocate memory and can return the pointer to // the input buffer. if (II->isStr("__cxa_demangle")) return true; StringRef FName = II->getName(); // - CoreFoundation functions that end with "NoCopy" can free a passed-in // buffer even if it is const. if (FName.ends_with("NoCopy")) return true; // - NSXXInsertXX, for example NSMapInsertIfAbsent, since they can // be deallocated by NSMapRemove. if (FName.starts_with("NS") && FName.contains("Insert")) return true; // - Many CF containers allow objects to escape through custom // allocators/deallocators upon container construction. (PR12101) if (FName.starts_with("CF") || FName.starts_with("CG")) { return StrInStrNoCase(FName, "InsertValue") != StringRef::npos || StrInStrNoCase(FName, "AddValue") != StringRef::npos || StrInStrNoCase(FName, "SetValue") != StringRef::npos || StrInStrNoCase(FName, "WithData") != StringRef::npos || StrInStrNoCase(FName, "AppendValue") != StringRef::npos || StrInStrNoCase(FName, "SetAttribute") != StringRef::npos; } return false; } const FunctionDecl *SimpleFunctionCall::getDecl() const { const FunctionDecl *D = getOriginExpr()->getDirectCallee(); if (D) return D; return getSVal(getOriginExpr()->getCallee()).getAsFunctionDecl(); } const FunctionDecl *CXXInstanceCall::getDecl() const { const auto *CE = cast_or_null(getOriginExpr()); if (!CE) return AnyFunctionCall::getDecl(); const FunctionDecl *D = CE->getDirectCallee(); if (D) return D; return getSVal(CE->getCallee()).getAsFunctionDecl(); } void CXXInstanceCall::getExtraInvalidatedValues( ValueList &Values, RegionAndSymbolInvalidationTraits *ETraits) const { SVal ThisVal = getCXXThisVal(); Values.push_back(ThisVal); // Don't invalidate if the method is const and there are no mutable fields. if (const auto *D = cast_or_null(getDecl())) { if (!D->isConst()) return; // Get the record decl for the class of 'This'. D->getParent() may return a // base class decl, rather than the class of the instance which needs to be // checked for mutable fields. // TODO: We might as well look at the dynamic type of the object. const Expr *Ex = getCXXThisExpr()->IgnoreParenBaseCasts(); QualType T = Ex->getType(); if (T->isPointerType()) // Arrow or implicit-this syntax? T = T->getPointeeType(); const CXXRecordDecl *ParentRecord = T->getAsCXXRecordDecl(); assert(ParentRecord); if (ParentRecord->hasMutableFields()) return; // Preserve CXXThis. const MemRegion *ThisRegion = ThisVal.getAsRegion(); if (!ThisRegion) return; ETraits->setTrait(ThisRegion->getBaseRegion(), RegionAndSymbolInvalidationTraits::TK_PreserveContents); } } SVal CXXInstanceCall::getCXXThisVal() const { const Expr *Base = getCXXThisExpr(); // FIXME: This doesn't handle an overloaded ->* operator. SVal ThisVal = Base ? getSVal(Base) : UnknownVal(); if (isa(ThisVal)) { SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); QualType OriginalTy = ThisVal.getType(SVB.getContext()); return SVB.evalCast(ThisVal, Base->getType(), OriginalTy); } assert(ThisVal.isUnknownOrUndef() || isa(ThisVal)); return ThisVal; } RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const { // Do we have a decl at all? const Decl *D = getDecl(); if (!D) return {}; // If the method is non-virtual, we know we can inline it. const auto *MD = cast(D); if (!MD->isVirtual()) return AnyFunctionCall::getRuntimeDefinition(); // Do we know the implicit 'this' object being called? const MemRegion *R = getCXXThisVal().getAsRegion(); if (!R) return {}; // Do we know anything about the type of 'this'? DynamicTypeInfo DynType = getDynamicTypeInfo(getState(), R); if (!DynType.isValid()) return {}; // Is the type a C++ class? (This is mostly a defensive check.) QualType RegionType = DynType.getType()->getPointeeType(); assert(!RegionType.isNull() && "DynamicTypeInfo should always be a pointer."); const CXXRecordDecl *RD = RegionType->getAsCXXRecordDecl(); if (!RD || !RD->hasDefinition()) return {}; // Find the decl for this method in that class. const CXXMethodDecl *Result = MD->getCorrespondingMethodInClass(RD, true); if (!Result) { // We might not even get the original statically-resolved method due to // some particularly nasty casting (e.g. casts to sister classes). // However, we should at least be able to search up and down our own class // hierarchy, and some real bugs have been caught by checking this. assert(!RD->isDerivedFrom(MD->getParent()) && "Couldn't find known method"); // FIXME: This is checking that our DynamicTypeInfo is at least as good as // the static type. However, because we currently don't update // DynamicTypeInfo when an object is cast, we can't actually be sure the // DynamicTypeInfo is up to date. This assert should be re-enabled once // this is fixed. // // assert(!MD->getParent()->isDerivedFrom(RD) && "Bad DynamicTypeInfo"); return {}; } // Does the decl that we found have an implementation? const FunctionDecl *Definition; if (!Result->hasBody(Definition)) { if (!DynType.canBeASubClass()) return AnyFunctionCall::getRuntimeDefinition(); return {}; } // We found a definition. If we're not sure that this devirtualization is // actually what will happen at runtime, make sure to provide the region so // that ExprEngine can decide what to do with it. if (DynType.canBeASubClass()) return RuntimeDefinition(Definition, R->StripCasts()); return RuntimeDefinition(Definition, /*DispatchRegion=*/nullptr); } void CXXInstanceCall::getInitialStackFrameContents( const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { AnyFunctionCall::getInitialStackFrameContents(CalleeCtx, Bindings); // Handle the binding of 'this' in the new stack frame. SVal ThisVal = getCXXThisVal(); if (!ThisVal.isUnknown()) { ProgramStateManager &StateMgr = getState()->getStateManager(); SValBuilder &SVB = StateMgr.getSValBuilder(); const auto *MD = cast(CalleeCtx->getDecl()); Loc ThisLoc = SVB.getCXXThis(MD, CalleeCtx); // If we devirtualized to a different member function, we need to make sure // we have the proper layering of CXXBaseObjectRegions. if (MD->getCanonicalDecl() != getDecl()->getCanonicalDecl()) { ASTContext &Ctx = SVB.getContext(); const CXXRecordDecl *Class = MD->getParent(); QualType Ty = Ctx.getPointerType(Ctx.getRecordType(Class)); // FIXME: CallEvent maybe shouldn't be directly accessing StoreManager. std::optional V = StateMgr.getStoreManager().evalBaseToDerived(ThisVal, Ty); if (!V) { // We might have suffered some sort of placement new earlier, so // we're constructing in a completely unexpected storage. // Fall back to a generic pointer cast for this-value. const CXXMethodDecl *StaticMD = cast(getDecl()); const CXXRecordDecl *StaticClass = StaticMD->getParent(); QualType StaticTy = Ctx.getPointerType(Ctx.getRecordType(StaticClass)); ThisVal = SVB.evalCast(ThisVal, Ty, StaticTy); } else ThisVal = *V; } if (!ThisVal.isUnknown()) Bindings.push_back(std::make_pair(ThisLoc, ThisVal)); } } const Expr *CXXMemberCall::getCXXThisExpr() const { return getOriginExpr()->getImplicitObjectArgument(); } RuntimeDefinition CXXMemberCall::getRuntimeDefinition() const { // C++11 [expr.call]p1: ...If the selected function is non-virtual, or if the // id-expression in the class member access expression is a qualified-id, // that function is called. Otherwise, its final overrider in the dynamic type // of the object expression is called. if (const auto *ME = dyn_cast(getOriginExpr()->getCallee())) if (ME->hasQualifier()) return AnyFunctionCall::getRuntimeDefinition(); return CXXInstanceCall::getRuntimeDefinition(); } const Expr *CXXMemberOperatorCall::getCXXThisExpr() const { return getOriginExpr()->getArg(0); } const BlockDataRegion *BlockCall::getBlockRegion() const { const Expr *Callee = getOriginExpr()->getCallee(); const MemRegion *DataReg = getSVal(Callee).getAsRegion(); return dyn_cast_or_null(DataReg); } ArrayRef BlockCall::parameters() const { const BlockDecl *D = getDecl(); if (!D) return std::nullopt; return D->parameters(); } void BlockCall::getExtraInvalidatedValues(ValueList &Values, RegionAndSymbolInvalidationTraits *ETraits) const { // FIXME: This also needs to invalidate captured globals. if (const MemRegion *R = getBlockRegion()) Values.push_back(loc::MemRegionVal(R)); } void BlockCall::getInitialStackFrameContents(const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); ArrayRef Params; if (isConversionFromLambda()) { auto *LambdaOperatorDecl = cast(CalleeCtx->getDecl()); Params = LambdaOperatorDecl->parameters(); // For blocks converted from a C++ lambda, the callee declaration is the // operator() method on the lambda so we bind "this" to // the lambda captured by the block. const VarRegion *CapturedLambdaRegion = getRegionStoringCapturedLambda(); SVal ThisVal = loc::MemRegionVal(CapturedLambdaRegion); Loc ThisLoc = SVB.getCXXThis(LambdaOperatorDecl, CalleeCtx); Bindings.push_back(std::make_pair(ThisLoc, ThisVal)); } else { Params = cast(CalleeCtx->getDecl())->parameters(); } addParameterValuesToBindings(CalleeCtx, Bindings, SVB, *this, Params); } SVal AnyCXXConstructorCall::getCXXThisVal() const { if (Data) return loc::MemRegionVal(static_cast(Data)); return UnknownVal(); } void AnyCXXConstructorCall::getExtraInvalidatedValues(ValueList &Values, RegionAndSymbolInvalidationTraits *ETraits) const { SVal V = getCXXThisVal(); if (SymbolRef Sym = V.getAsSymbol(true)) ETraits->setTrait(Sym, RegionAndSymbolInvalidationTraits::TK_SuppressEscape); Values.push_back(V); } void AnyCXXConstructorCall::getInitialStackFrameContents( const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { AnyFunctionCall::getInitialStackFrameContents(CalleeCtx, Bindings); SVal ThisVal = getCXXThisVal(); if (!ThisVal.isUnknown()) { SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); const auto *MD = cast(CalleeCtx->getDecl()); Loc ThisLoc = SVB.getCXXThis(MD, CalleeCtx); Bindings.push_back(std::make_pair(ThisLoc, ThisVal)); } } const StackFrameContext * CXXInheritedConstructorCall::getInheritingStackFrame() const { const StackFrameContext *SFC = getLocationContext()->getStackFrame(); while (isa(SFC->getCallSite())) SFC = SFC->getParent()->getStackFrame(); return SFC; } SVal CXXDestructorCall::getCXXThisVal() const { if (Data) return loc::MemRegionVal(DtorDataTy::getFromOpaqueValue(Data).getPointer()); return UnknownVal(); } RuntimeDefinition CXXDestructorCall::getRuntimeDefinition() const { // Base destructors are always called non-virtually. // Skip CXXInstanceCall's devirtualization logic in this case. if (isBaseDestructor()) return AnyFunctionCall::getRuntimeDefinition(); return CXXInstanceCall::getRuntimeDefinition(); } ArrayRef ObjCMethodCall::parameters() const { const ObjCMethodDecl *D = getDecl(); if (!D) return std::nullopt; return D->parameters(); } void ObjCMethodCall::getExtraInvalidatedValues( ValueList &Values, RegionAndSymbolInvalidationTraits *ETraits) const { // If the method call is a setter for property known to be backed by // an instance variable, don't invalidate the entire receiver, just // the storage for that instance variable. if (const ObjCPropertyDecl *PropDecl = getAccessedProperty()) { if (const ObjCIvarDecl *PropIvar = PropDecl->getPropertyIvarDecl()) { SVal IvarLVal = getState()->getLValue(PropIvar, getReceiverSVal()); if (const MemRegion *IvarRegion = IvarLVal.getAsRegion()) { ETraits->setTrait( IvarRegion, RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); ETraits->setTrait( IvarRegion, RegionAndSymbolInvalidationTraits::TK_SuppressEscape); Values.push_back(IvarLVal); } return; } } Values.push_back(getReceiverSVal()); } SVal ObjCMethodCall::getReceiverSVal() const { // FIXME: Is this the best way to handle class receivers? if (!isInstanceMessage()) return UnknownVal(); if (const Expr *RecE = getOriginExpr()->getInstanceReceiver()) return getSVal(RecE); // An instance message with no expression means we are sending to super. // In this case the object reference is the same as 'self'. assert(getOriginExpr()->getReceiverKind() == ObjCMessageExpr::SuperInstance); SVal SelfVal = getState()->getSelfSVal(getLocationContext()); assert(SelfVal.isValid() && "Calling super but not in ObjC method"); return SelfVal; } bool ObjCMethodCall::isReceiverSelfOrSuper() const { if (getOriginExpr()->getReceiverKind() == ObjCMessageExpr::SuperInstance || getOriginExpr()->getReceiverKind() == ObjCMessageExpr::SuperClass) return true; if (!isInstanceMessage()) return false; SVal RecVal = getSVal(getOriginExpr()->getInstanceReceiver()); SVal SelfVal = getState()->getSelfSVal(getLocationContext()); return (RecVal == SelfVal); } SourceRange ObjCMethodCall::getSourceRange() const { switch (getMessageKind()) { case OCM_Message: return getOriginExpr()->getSourceRange(); case OCM_PropertyAccess: case OCM_Subscript: return getContainingPseudoObjectExpr()->getSourceRange(); } llvm_unreachable("unknown message kind"); } using ObjCMessageDataTy = llvm::PointerIntPair; const PseudoObjectExpr *ObjCMethodCall::getContainingPseudoObjectExpr() const { assert(Data && "Lazy lookup not yet performed."); assert(getMessageKind() != OCM_Message && "Explicit message send."); return ObjCMessageDataTy::getFromOpaqueValue(Data).getPointer(); } static const Expr * getSyntacticFromForPseudoObjectExpr(const PseudoObjectExpr *POE) { const Expr *Syntactic = POE->getSyntacticForm()->IgnoreParens(); // This handles the funny case of assigning to the result of a getter. // This can happen if the getter returns a non-const reference. if (const auto *BO = dyn_cast(Syntactic)) Syntactic = BO->getLHS()->IgnoreParens(); return Syntactic; } ObjCMessageKind ObjCMethodCall::getMessageKind() const { if (!Data) { // Find the parent, ignoring implicit casts. const ParentMap &PM = getLocationContext()->getParentMap(); const Stmt *S = PM.getParentIgnoreParenCasts(getOriginExpr()); // Check if parent is a PseudoObjectExpr. if (const auto *POE = dyn_cast_or_null(S)) { const Expr *Syntactic = getSyntacticFromForPseudoObjectExpr(POE); ObjCMessageKind K; switch (Syntactic->getStmtClass()) { case Stmt::ObjCPropertyRefExprClass: K = OCM_PropertyAccess; break; case Stmt::ObjCSubscriptRefExprClass: K = OCM_Subscript; break; default: // FIXME: Can this ever happen? K = OCM_Message; break; } if (K != OCM_Message) { const_cast(this)->Data = ObjCMessageDataTy(POE, K).getOpaqueValue(); assert(getMessageKind() == K); return K; } } const_cast(this)->Data = ObjCMessageDataTy(nullptr, 1).getOpaqueValue(); assert(getMessageKind() == OCM_Message); return OCM_Message; } ObjCMessageDataTy Info = ObjCMessageDataTy::getFromOpaqueValue(Data); if (!Info.getPointer()) return OCM_Message; return static_cast(Info.getInt()); } const ObjCPropertyDecl *ObjCMethodCall::getAccessedProperty() const { // Look for properties accessed with property syntax (foo.bar = ...) if (getMessageKind() == OCM_PropertyAccess) { const PseudoObjectExpr *POE = getContainingPseudoObjectExpr(); assert(POE && "Property access without PseudoObjectExpr?"); const Expr *Syntactic = getSyntacticFromForPseudoObjectExpr(POE); auto *RefExpr = cast(Syntactic); if (RefExpr->isExplicitProperty()) return RefExpr->getExplicitProperty(); } // Look for properties accessed with method syntax ([foo setBar:...]). const ObjCMethodDecl *MD = getDecl(); if (!MD || !MD->isPropertyAccessor()) return nullptr; // Note: This is potentially quite slow. return MD->findPropertyDecl(); } bool ObjCMethodCall::canBeOverridenInSubclass(ObjCInterfaceDecl *IDecl, Selector Sel) const { assert(IDecl); AnalysisManager &AMgr = getState()->getStateManager().getOwningEngine().getAnalysisManager(); // If the class interface is declared inside the main file, assume it is not // subcassed. // TODO: It could actually be subclassed if the subclass is private as well. // This is probably very rare. SourceLocation InterfLoc = IDecl->getEndOfDefinitionLoc(); if (InterfLoc.isValid() && AMgr.isInCodeFile(InterfLoc)) return false; // Assume that property accessors are not overridden. if (getMessageKind() == OCM_PropertyAccess) return false; // We assume that if the method is public (declared outside of main file) or // has a parent which publicly declares the method, the method could be // overridden in a subclass. // Find the first declaration in the class hierarchy that declares // the selector. ObjCMethodDecl *D = nullptr; while (true) { D = IDecl->lookupMethod(Sel, true); // Cannot find a public definition. if (!D) return false; // If outside the main file, if (D->getLocation().isValid() && !AMgr.isInCodeFile(D->getLocation())) return true; if (D->isOverriding()) { // Search in the superclass on the next iteration. IDecl = D->getClassInterface(); if (!IDecl) return false; IDecl = IDecl->getSuperClass(); if (!IDecl) return false; continue; } return false; }; llvm_unreachable("The while loop should always terminate."); } static const ObjCMethodDecl *findDefiningRedecl(const ObjCMethodDecl *MD) { if (!MD) return MD; // Find the redeclaration that defines the method. if (!MD->hasBody()) { for (auto *I : MD->redecls()) if (I->hasBody()) MD = cast(I); } return MD; } struct PrivateMethodKey { const ObjCInterfaceDecl *Interface; Selector LookupSelector; bool IsClassMethod; }; namespace llvm { template <> struct DenseMapInfo { using InterfaceInfo = DenseMapInfo; using SelectorInfo = DenseMapInfo; static inline PrivateMethodKey getEmptyKey() { return {InterfaceInfo::getEmptyKey(), SelectorInfo::getEmptyKey(), false}; } static inline PrivateMethodKey getTombstoneKey() { return {InterfaceInfo::getTombstoneKey(), SelectorInfo::getTombstoneKey(), true}; } static unsigned getHashValue(const PrivateMethodKey &Key) { return llvm::hash_combine( llvm::hash_code(InterfaceInfo::getHashValue(Key.Interface)), llvm::hash_code(SelectorInfo::getHashValue(Key.LookupSelector)), Key.IsClassMethod); } static bool isEqual(const PrivateMethodKey &LHS, const PrivateMethodKey &RHS) { return InterfaceInfo::isEqual(LHS.Interface, RHS.Interface) && SelectorInfo::isEqual(LHS.LookupSelector, RHS.LookupSelector) && LHS.IsClassMethod == RHS.IsClassMethod; } }; } // end namespace llvm static const ObjCMethodDecl * lookupRuntimeDefinition(const ObjCInterfaceDecl *Interface, Selector LookupSelector, bool InstanceMethod) { // Repeatedly calling lookupPrivateMethod() is expensive, especially // when in many cases it returns null. We cache the results so // that repeated queries on the same ObjCIntefaceDecl and Selector // don't incur the same cost. On some test cases, we can see the // same query being issued thousands of times. // // NOTE: This cache is essentially a "global" variable, but it // only gets lazily created when we get here. The value of the // cache probably comes from it being global across ExprEngines, // where the same queries may get issued. If we are worried about // concurrency, or possibly loading/unloading ASTs, etc., we may // need to revisit this someday. In terms of memory, this table // stays around until clang quits, which also may be bad if we // need to release memory. using PrivateMethodCache = llvm::DenseMap>; static PrivateMethodCache PMC; std::optional &Val = PMC[{Interface, LookupSelector, InstanceMethod}]; // Query lookupPrivateMethod() if the cache does not hit. if (!Val) { Val = Interface->lookupPrivateMethod(LookupSelector, InstanceMethod); if (!*Val) { // Query 'lookupMethod' as a backup. Val = Interface->lookupMethod(LookupSelector, InstanceMethod); } } return *Val; } RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { const ObjCMessageExpr *E = getOriginExpr(); assert(E); Selector Sel = E->getSelector(); if (E->isInstanceMessage()) { // Find the receiver type. const ObjCObjectType *ReceiverT = nullptr; bool CanBeSubClassed = false; bool LookingForInstanceMethod = true; QualType SupersType = E->getSuperType(); const MemRegion *Receiver = nullptr; if (!SupersType.isNull()) { // The receiver is guaranteed to be 'super' in this case. // Super always means the type of immediate predecessor to the method // where the call occurs. ReceiverT = cast(SupersType)->getObjectType(); } else { Receiver = getReceiverSVal().getAsRegion(); if (!Receiver) return {}; DynamicTypeInfo DTI = getDynamicTypeInfo(getState(), Receiver); if (!DTI.isValid()) { assert(isa(Receiver) && "Unhandled untyped region class!"); return {}; } QualType DynType = DTI.getType(); CanBeSubClassed = DTI.canBeASubClass(); const auto *ReceiverDynT = dyn_cast(DynType.getCanonicalType()); if (ReceiverDynT) { ReceiverT = ReceiverDynT->getObjectType(); // It can be actually class methods called with Class object as a // receiver. This type of messages is treated by the compiler as // instance (not class). if (ReceiverT->isObjCClass()) { SVal SelfVal = getState()->getSelfSVal(getLocationContext()); // For [self classMethod], return compiler visible declaration. if (Receiver == SelfVal.getAsRegion()) { return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl())); } // Otherwise, let's check if we know something about the type // inside of this class object. if (SymbolRef ReceiverSym = getReceiverSVal().getAsSymbol()) { DynamicTypeInfo DTI = getClassObjectDynamicTypeInfo(getState(), ReceiverSym); if (DTI.isValid()) { // Let's use this type for lookup. ReceiverT = cast(DTI.getType().getCanonicalType()); CanBeSubClassed = DTI.canBeASubClass(); // And it should be a class method instead. LookingForInstanceMethod = false; } } } if (CanBeSubClassed) if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface()) // Even if `DynamicTypeInfo` told us that it can be // not necessarily this type, but its descendants, we still want // to check again if this selector can be actually overridden. CanBeSubClassed = canBeOverridenInSubclass(IDecl, Sel); } } // Lookup the instance method implementation. if (ReceiverT) if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface()) { const ObjCMethodDecl *MD = lookupRuntimeDefinition(IDecl, Sel, LookingForInstanceMethod); if (MD && !MD->hasBody()) MD = MD->getCanonicalDecl(); if (CanBeSubClassed) return RuntimeDefinition(MD, Receiver); else return RuntimeDefinition(MD, nullptr); } } else { // This is a class method. // If we have type info for the receiver class, we are calling via // class name. if (ObjCInterfaceDecl *IDecl = E->getReceiverInterface()) { // Find/Return the method implementation. return RuntimeDefinition(IDecl->lookupPrivateClassMethod(Sel)); } } return {}; } bool ObjCMethodCall::argumentsMayEscape() const { if (isInSystemHeader() && !isInstanceMessage()) { Selector Sel = getSelector(); if (Sel.getNumArgs() == 1 && Sel.getIdentifierInfoForSlot(0)->isStr("valueWithPointer")) return true; } return CallEvent::argumentsMayEscape(); } void ObjCMethodCall::getInitialStackFrameContents( const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { const auto *D = cast(CalleeCtx->getDecl()); SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); addParameterValuesToBindings(CalleeCtx, Bindings, SVB, *this, D->parameters()); SVal SelfVal = getReceiverSVal(); if (!SelfVal.isUnknown()) { const VarDecl *SelfD = CalleeCtx->getAnalysisDeclContext()->getSelfDecl(); MemRegionManager &MRMgr = SVB.getRegionManager(); Loc SelfLoc = SVB.makeLoc(MRMgr.getVarRegion(SelfD, CalleeCtx)); Bindings.push_back(std::make_pair(SelfLoc, SelfVal)); } } CallEventRef<> CallEventManager::getSimpleCall(const CallExpr *CE, ProgramStateRef State, const LocationContext *LCtx, CFGBlock::ConstCFGElementRef ElemRef) { if (const auto *MCE = dyn_cast(CE)) return create(MCE, State, LCtx, ElemRef); if (const auto *OpCE = dyn_cast(CE)) { const FunctionDecl *DirectCallee = OpCE->getDirectCallee(); if (const auto *MD = dyn_cast(DirectCallee)) if (MD->isImplicitObjectMemberFunction()) return create(OpCE, State, LCtx, ElemRef); } else if (CE->getCallee()->getType()->isBlockPointerType()) { return create(CE, State, LCtx, ElemRef); } // Otherwise, it's a normal function call, static member function call, or // something we can't reason about. return create(CE, State, LCtx, ElemRef); } CallEventRef<> CallEventManager::getCaller(const StackFrameContext *CalleeCtx, ProgramStateRef State) { const LocationContext *ParentCtx = CalleeCtx->getParent(); const LocationContext *CallerCtx = ParentCtx->getStackFrame(); CFGBlock::ConstCFGElementRef ElemRef = {CalleeCtx->getCallSiteBlock(), CalleeCtx->getIndex()}; assert(CallerCtx && "This should not be used for top-level stack frames"); const Stmt *CallSite = CalleeCtx->getCallSite(); if (CallSite) { if (CallEventRef<> Out = getCall(CallSite, State, CallerCtx, ElemRef)) return Out; SValBuilder &SVB = State->getStateManager().getSValBuilder(); const auto *Ctor = cast(CalleeCtx->getDecl()); Loc ThisPtr = SVB.getCXXThis(Ctor, CalleeCtx); SVal ThisVal = State->getSVal(ThisPtr); if (const auto *CE = dyn_cast(CallSite)) return getCXXConstructorCall(CE, ThisVal.getAsRegion(), State, CallerCtx, ElemRef); else if (const auto *CIE = dyn_cast(CallSite)) return getCXXInheritedConstructorCall(CIE, ThisVal.getAsRegion(), State, CallerCtx, ElemRef); else { // All other cases are handled by getCall. llvm_unreachable("This is not an inlineable statement"); } } // Fall back to the CFG. The only thing we haven't handled yet is // destructors, though this could change in the future. const CFGBlock *B = CalleeCtx->getCallSiteBlock(); CFGElement E = (*B)[CalleeCtx->getIndex()]; assert((E.getAs() || E.getAs()) && "All other CFG elements should have exprs"); SValBuilder &SVB = State->getStateManager().getSValBuilder(); const auto *Dtor = cast(CalleeCtx->getDecl()); Loc ThisPtr = SVB.getCXXThis(Dtor, CalleeCtx); SVal ThisVal = State->getSVal(ThisPtr); const Stmt *Trigger; if (std::optional AutoDtor = E.getAs()) Trigger = AutoDtor->getTriggerStmt(); else if (std::optional DeleteDtor = E.getAs()) Trigger = DeleteDtor->getDeleteExpr(); else Trigger = Dtor->getBody(); return getCXXDestructorCall(Dtor, Trigger, ThisVal.getAsRegion(), E.getAs().has_value(), State, CallerCtx, ElemRef); } CallEventRef<> CallEventManager::getCall(const Stmt *S, ProgramStateRef State, const LocationContext *LC, CFGBlock::ConstCFGElementRef ElemRef) { if (const auto *CE = dyn_cast(S)) { return getSimpleCall(CE, State, LC, ElemRef); } else if (const auto *NE = dyn_cast(S)) { return getCXXAllocatorCall(NE, State, LC, ElemRef); } else if (const auto *DE = dyn_cast(S)) { return getCXXDeallocatorCall(DE, State, LC, ElemRef); } else if (const auto *ME = dyn_cast(S)) { return getObjCMethodCall(ME, State, LC, ElemRef); } else { return nullptr; } }