//===- FactsGenerator.cpp - Lifetime Facts Generation -----------*- 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 // //===----------------------------------------------------------------------===// #include #include #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/OperationKinds.h" #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" #include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h" #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" #include "clang/Analysis/Analyses/PostOrderCFGView.h" #include "clang/Analysis/CFG.h" #include "clang/Basic/OperatorKinds.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Signals.h" #include "llvm/Support/TimeProfiler.h" namespace clang::lifetimes::internal { using llvm::isa_and_present; OriginList *FactsGenerator::getOriginsList(const ValueDecl &D) { return FactMgr.getOriginMgr().getOrCreateList(&D); } OriginList *FactsGenerator::getOriginsList(const Expr &E) { return FactMgr.getOriginMgr().getOrCreateList(&E); } bool FactsGenerator::hasOrigins(QualType QT) const { return FactMgr.getOriginMgr().hasOrigins(QT); } bool FactsGenerator::hasOrigins(const Expr *E) const { return FactMgr.getOriginMgr().hasOrigins(E); } /// Propagates origin information from Src to Dst through all levels of /// indirection, creating OriginFlowFacts at each level. /// /// This function enforces a critical type-safety invariant: both lists must /// have the same shape (same depth/structure). This invariant ensures that /// origins flow only between compatible types during expression evaluation. /// /// Examples: /// - `int* p = &x;` flows origins from `&x` (depth 1) to `p` (depth 1) /// - `int** pp = &p;` flows origins from `&p` (depth 2) to `pp` (depth 2) /// * Level 1: pp <- p's address /// * Level 2: (*pp) <- what p points to (i.e., &x) /// - `View v = obj;` flows origins from `obj` (depth 1) to `v` (depth 1) void FactsGenerator::flow(OriginList *Dst, OriginList *Src, bool Kill) { if (!Dst) return; assert(Src && "Dst is non-null but Src is null. List must have the same length"); assert(Dst->getLength() == Src->getLength() && "Lists must have the same length"); while (Dst && Src) { CurrentBlockFacts.push_back(FactMgr.createFact( Dst->getOuterOriginID(), Src->getOuterOriginID(), Kill)); Dst = Dst->peelOuterOrigin(); Src = Src->peelOuterOrigin(); } } /// Creates a loan for the storage path of a given declaration reference. /// This function should be called whenever a DeclRefExpr represents a borrow. /// \param DRE The declaration reference expression that initiates the borrow. /// \return The new Loan on success, nullptr otherwise. static const Loan *createLoan(FactManager &FactMgr, const DeclRefExpr *DRE) { const ValueDecl *VD = DRE->getDecl(); AccessPath Path(VD); // The loan is created at the location of the DeclRefExpr. return FactMgr.getLoanMgr().createLoan(Path, DRE); } /// Creates a loan for the storage location of a temporary object. /// \param MTE The MaterializeTemporaryExpr that represents the temporary /// binding. \return The new Loan. static const Loan *createLoan(FactManager &FactMgr, const MaterializeTemporaryExpr *MTE) { AccessPath Path(MTE); return FactMgr.getLoanMgr().createLoan(Path, MTE); } /// Creates a loan for an allocation through 'new' /// \param NE The CXXNewExpr that represents the allocation /// \return The new Loan on success, nullptr otherwise static const Loan *createLoan(FactManager &FactMgr, const CXXNewExpr *NE) { AccessPath Path(NE); return FactMgr.getLoanMgr().createLoan(Path, NE); } void FactsGenerator::run() { llvm::TimeTraceScope TimeProfile("FactGenerator"); const CFG &Cfg = *AC.getCFG(); llvm::SmallVector PlaceholderLoanFacts = issuePlaceholderLoans(); // Iterate through the CFG blocks in reverse post-order to ensure that // initializations and destructions are processed in the correct sequence. for (const CFGBlock *Block : *AC.getAnalysis()) { CurrentBlockFacts.clear(); EscapesInCurrentBlock.clear(); CurrentBlock = Block; if (Block == &Cfg.getEntry()) CurrentBlockFacts.append(PlaceholderLoanFacts.begin(), PlaceholderLoanFacts.end()); for (unsigned I = 0; I < Block->size(); ++I) { const CFGElement &Element = Block->Elements[I]; if (std::optional CS = Element.getAs()) Visit(CS->getStmt()); else if (std::optional Initializer = Element.getAs()) handleCXXCtorInitializer(Initializer->getInitializer()); else if (std::optional LifetimeEnds = Element.getAs()) handleLifetimeEnds(*LifetimeEnds); else if (std::optional FullExprCleanup = Element.getAs()) { handleFullExprCleanup(*FullExprCleanup); } } if (Block == &Cfg.getExit()) handleExitBlock(); CurrentBlockFacts.append(EscapesInCurrentBlock.begin(), EscapesInCurrentBlock.end()); FactMgr.addBlockFacts(Block, CurrentBlockFacts); } } /// Simulates LValueToRValue conversion by peeling the outer lvalue origin /// if the expression is a GLValue. For pointer/view GLValues, this strips /// the origin representing the storage location to get the origins of the /// pointed-to value. /// /// Example: For `View& v`, returns the origin of what v points to, not v's /// storage. static OriginList *getRValueOrigins(const Expr *E, OriginList *List) { if (!List) return nullptr; return E->isGLValue() ? List->peelOuterOrigin() : List; } void FactsGenerator::VisitDeclStmt(const DeclStmt *DS) { for (const Decl *D : DS->decls()) if (const auto *VD = dyn_cast(D)) if (const Expr *InitExpr = VD->getInit()) { OriginList *VDList = getOriginsList(*VD); if (!VDList) continue; OriginList *InitList = getOriginsList(*InitExpr); assert(InitList && "VarDecl had origins but InitExpr did not"); flow(VDList, InitList, /*Kill=*/true); } } void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) { // Skip function references as their lifetimes are not interesting. Skip non // GLValues (like EnumConstants). if (DRE->getFoundDecl()->isFunctionOrFunctionTemplate() || !DRE->isGLValue()) return; handleUse(DRE); // For all declarations with storage (non-references), we issue a loan // representing the borrow of the variable's storage itself. // // Examples: // - `int x; x` issues loan to x's storage // - `int* p; p` issues loan to p's storage (the pointer variable) // - `View v; v` issues loan to v's storage (the view object) // - `int& r = x; r` issues no loan (r has no storage, it's an alias to x) if (doesDeclHaveStorage(DRE->getDecl())) { const Loan *L = createLoan(FactMgr, DRE); assert(L); OriginList *List = getOriginsList(*DRE); assert(List && "gl-value DRE of non-pointer type should have an origin list"); // This loan specifically tracks borrowing the variable's storage location // itself and is issued to outermost origin (List->OID). CurrentBlockFacts.push_back( FactMgr.createFact(L->getID(), List->getOuterOriginID())); } } void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) { if (isGslPointerType(CCE->getType())) { handleGSLPointerConstruction(CCE); return; } // For defaulted (implicit or `= default`) copy/move constructors, propagate // origins directly. User-defined copy/move constructors are not handled here // as they have opaque semantics. if (CCE->getConstructor()->isCopyOrMoveConstructor() && CCE->getConstructor()->isDefaulted() && CCE->getNumArgs() == 1 && hasOrigins(CCE->getType())) { const Expr *Arg = CCE->getArg(0); if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) { flow(getOriginsList(*CCE), ArgList, /*Kill=*/true); return; } } // Standard library callable wrappers (e.g., std::function) propagate the // stored lambda's origins. if (const auto *RD = CCE->getType()->getAsCXXRecordDecl(); RD && isStdCallableWrapperType(RD) && CCE->getNumArgs() == 1) { const Expr *Arg = CCE->getArg(0); if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) { flow(getOriginsList(*CCE), ArgList, /*Kill=*/true); return; } } handleFunctionCall(CCE, CCE->getConstructor(), {CCE->getArgs(), CCE->getNumArgs()}, /*IsGslConstruction=*/false); } void FactsGenerator::VisitCXXDefaultInitExpr(const CXXDefaultInitExpr *DIE) { if (const Expr *Init = DIE->getExpr()) killAndFlowOrigin(*DIE, *Init); } void FactsGenerator::handleCXXCtorInitializer(const CXXCtorInitializer *CII) { // Flows origins from the initializer expression to the field. // Example: `MyObj(std::string s) : view(s) {}` if (const FieldDecl *FD = CII->getAnyMember()) killAndFlowOrigin(*FD, *CII->getInit()); } void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) { // Specifically for conversion operators, // like `std::string_view p = std::string{};` if (isGslPointerType(MCE->getType()) && isa_and_present(MCE->getCalleeDecl()) && isGslOwnerType(MCE->getImplicitObjectArgument()->getType())) { // The argument is the implicit object itself. handleFunctionCall(MCE, MCE->getMethodDecl(), {MCE->getImplicitObjectArgument()}, /*IsGslConstruction=*/true); return; } if (const CXXMethodDecl *Method = MCE->getMethodDecl()) { // Construct the argument list, with the implicit 'this' object as the // first argument. llvm::SmallVector Args; Args.push_back(MCE->getImplicitObjectArgument()); Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs()); handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false); } } void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) { auto *MD = ME->getMemberDecl(); if (isa(MD) && doesDeclHaveStorage(MD)) { assert(ME->isGLValue() && "Field member should be GL value"); OriginList *Dst = getOriginsList(*ME); assert(Dst && "Field member should have an origin list as it is GL value"); OriginList *Src = getOriginsList(*ME->getBase()); assert(Src && "Base expression should be a pointer/reference type"); // The field's glvalue (outermost origin) holds the same loans as the base // expression. CurrentBlockFacts.push_back(FactMgr.createFact( Dst->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true)); } } void FactsGenerator::VisitCallExpr(const CallExpr *CE) { handleFunctionCall(CE, CE->getDirectCallee(), {CE->getArgs(), CE->getNumArgs()}); } void FactsGenerator::VisitCXXNullPtrLiteralExpr( const CXXNullPtrLiteralExpr *N) { /// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized /// pointers can use the same type of loan. getOriginsList(*N); } void FactsGenerator::VisitCastExpr(const CastExpr *CE) { OriginList *Dest = getOriginsList(*CE); if (!Dest) return; const Expr *SubExpr = CE->getSubExpr(); OriginList *Src = getOriginsList(*SubExpr); switch (CE->getCastKind()) { case CK_LValueToRValue: if (!SubExpr->isGLValue()) return; assert(Src && "LValue being cast to RValue has no origin list"); // The result of an LValue-to-RValue cast on a pointer lvalue (like `q` in // `int *p, *q; p = q;`) should propagate the inner origin (what the pointer // points to), not the outer origin (the pointer's storage location). Strip // the outer lvalue origin. flow(getOriginsList(*CE), getRValueOrigins(SubExpr, Src), /*Kill=*/true); return; case CK_NullToPointer: getOriginsList(*CE); // TODO: Flow into them a null origin. return; case CK_NoOp: case CK_ConstructorConversion: case CK_UserDefinedConversion: flow(Dest, Src, /*Kill=*/true); return; case CK_UncheckedDerivedToBase: case CK_DerivedToBase: // It is possible that the derived class and base class have different // gsl::Pointer annotations. Skip if their origin shape differ. if (Dest && Src && Dest->getLength() == Src->getLength()) flow(Dest, Src, /*Kill=*/true); return; case CK_ArrayToPointerDecay: assert(Src && "Array expression should have origins as it is GL value"); CurrentBlockFacts.push_back(FactMgr.createFact( Dest->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true)); return; case CK_FunctionToPointerDecay: case CK_BuiltinFnToFnPtr: // Ignore function-to-pointer decays. return; case CK_BitCast: // OriginLists for Src and Dst may differ here. For example when casting // from int** to void* if (Src && Dest && Dest->getLength() == Src->getLength()) flow(Dest, Src, /*Kill=*/true); return; default: return; } } void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) { switch (UO->getOpcode()) { case UO_AddrOf: { const Expr *SubExpr = UO->getSubExpr(); // The origin of an address-of expression (e.g., &x) is the origin of // its sub-expression (x). This fact will cause the dataflow analysis // to propagate any loans held by the sub-expression's origin to the // origin of this UnaryOperator expression. killAndFlowOrigin(*UO, *SubExpr); return; } case UO_Deref: { const Expr *SubExpr = UO->getSubExpr(); killAndFlowOrigin(*UO, *SubExpr); return; } default: return; } } void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) { if (const Expr *RetExpr = RS->getRetValue()) { if (OriginList *List = getOriginsList(*RetExpr)) for (OriginList *L = List; L != nullptr; L = L->peelOuterOrigin()) EscapesInCurrentBlock.push_back(FactMgr.createFact( L->getOuterOriginID(), RetExpr)); } } void FactsGenerator::handleAssignment(const Expr *TargetExpr, const Expr *LHSExpr, const Expr *RHSExpr) { LHSExpr = LHSExpr->IgnoreParenImpCasts(); OriginList *LHSList = nullptr; if (const auto *DRE_LHS = dyn_cast(LHSExpr)) { LHSList = getOriginsList(*DRE_LHS); assert(LHSList && "LHS is a DRE and should have an origin list"); } // Handle assignment to member fields (e.g., `this->view = s` or `view = s`). // This enables detection of dangling fields when local values escape to // fields. if (const auto *ME_LHS = dyn_cast(LHSExpr)) { LHSList = getOriginsList(*ME_LHS); assert(LHSList && "LHS is a MemberExpr and should have an origin list"); } if (!LHSList) return; OriginList *RHSList = getOriginsList(*RHSExpr); // For operator= with reference parameters (e.g., // `View& operator=(const View&)`), the RHS argument stays an lvalue, // unlike built-in assignment where LValueToRValue cast strips the outer // lvalue origin. Strip it manually to get the actual value origins being // assigned. RHSList = getRValueOrigins(RHSExpr, RHSList); if (const auto *DRE_LHS = dyn_cast(LHSExpr)) { QualType QT = DRE_LHS->getDecl()->getType(); if (QT->isReferenceType()) { if (hasOrigins(QT->getPointeeType())) { // Writing through a reference uses the binding but overwrites the // pointee. Model this as a Read of the outer origin (keeping the // binding live) and a Write of the inner origins (killing the pointee's // liveness). if (UseFact *UF = UseFacts.lookup(DRE_LHS)) { const OriginList *FullList = UF->getUsedOrigins(); assert(FullList); UF->setUsedOrigins(FactMgr.getOriginMgr().createSingleOriginList( FullList->getOuterOriginID())); if (const OriginList *InnerList = FullList->peelOuterOrigin()) { UseFact *WriteUF = FactMgr.createFact(DRE_LHS, InnerList); WriteUF->markAsWritten(); CurrentBlockFacts.push_back(WriteUF); } } } } else markUseAsWrite(DRE_LHS); } if (!RHSList) { // RHS has no tracked origins (e.g., assigning a callable without origins // to std::function). Clear loans of the destination. for (OriginList *LHSInner = LHSList->peelOuterOrigin(); LHSInner; LHSInner = LHSInner->peelOuterOrigin()) CurrentBlockFacts.push_back( FactMgr.createFact(LHSInner->getOuterOriginID())); return; } // Kill the old loans of the destination origin and flow the new loans // from the source origin. flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true); killAndFlowOrigin(*TargetExpr, *LHSExpr); } void FactsGenerator::handlePointerArithmetic(const BinaryOperator *BO) { if (Expr *RHS = BO->getRHS(); RHS->getType()->isPointerType()) { killAndFlowOrigin(*BO, *RHS); return; } Expr *LHS = BO->getLHS(); assert(LHS->getType()->isPointerType() && "Pointer arithmetic must have a pointer operand"); killAndFlowOrigin(*BO, *LHS); } void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) { if (BO->isCompoundAssignmentOp()) return; if (BO->getType()->isPointerType() && BO->isAdditiveOp()) handlePointerArithmetic(BO); handleUse(BO->getRHS()); if (BO->isAssignmentOp()) handleAssignment(BO, BO->getLHS(), BO->getRHS()); // TODO: Handle assignments involving dereference like `*p = q`. } void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) { if (!hasOrigins(CO)) return; const Expr *TrueExpr = CO->getTrueExpr(); const Expr *FalseExpr = CO->getFalseExpr(); const auto Preds = CurrentBlock->preds(); // Skip origin flow from conditional operator arms that cannot produce the // result value: throw arms and calls to noreturn functions. bool TBHasEdge = true; bool FBHasEdge = true; switch (CurrentBlock->pred_size()) { case 0: return; case 1: { TBHasEdge = llvm::any_of(**Preds.begin(), [ExpectedStmt = TrueExpr->IgnoreParenImpCasts()]( const CFGElement &Elt) { if (auto CS = Elt.getAs()) return CS->getStmt() == ExpectedStmt; return false; }); FBHasEdge = !TBHasEdge; break; } case 2: { const auto *It = Preds.begin(); TBHasEdge = It->isReachable(); FBHasEdge = (++It)->isReachable(); break; } default: llvm_unreachable("expected at most 2 predecessors"); return; } bool FirstFlow = true; auto HandleFlow = [&](const Expr *E) { if (FirstFlow) { killAndFlowOrigin(*CO, *E); FirstFlow = false; } else { flowOrigin(*CO, *E); } }; if (TBHasEdge) HandleFlow(TrueExpr); if (FBHasEdge) HandleFlow(FalseExpr); } void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) { // Assignment operators have special "kill-then-propagate" semantics // and are handled separately. if (OCE->getOperator() == OO_Equal && OCE->getNumArgs() == 2 && hasOrigins(OCE->getArg(0)->getType())) { // Pointer-like types: assignment inherently propagates origins. QualType LHSTy = OCE->getArg(0)->getType(); if (LHSTy->isPointerOrReferenceType() || isGslPointerType(LHSTy) || isGslOwnerType(LHSTy)) { handleAssignment(OCE, OCE->getArg(0), OCE->getArg(1)); return; } // Standard library callable wrappers (e.g., std::function) can propagate // the stored lambda's origins. if (const auto *RD = LHSTy->getAsCXXRecordDecl(); RD && isStdCallableWrapperType(RD)) { handleAssignment(OCE, OCE->getArg(0), OCE->getArg(1)); return; } // Other tracked types: only defaulted operator= propagates origins. // User-defined operator= has opaque semantics, so don't handle them now. if (const auto *MD = dyn_cast_or_null(OCE->getDirectCallee()); MD && MD->isDefaulted()) { handleAssignment(OCE, OCE->getArg(0), OCE->getArg(1)); return; } } ArrayRef Args = {OCE->getArgs(), OCE->getNumArgs()}; // For `static operator()`, the first argument is the object argument, // remove it from the argument list to avoid off-by-one errors. if (OCE->getOperator() == OO_Call && OCE->getDirectCallee()->isStatic()) Args = Args.slice(1); handleFunctionCall(OCE, OCE->getDirectCallee(), Args); } void FactsGenerator::VisitCXXFunctionalCastExpr( const CXXFunctionalCastExpr *FCE) { // Check if this is a test point marker. If so, we are done with this // expression. if (handleTestPoint(FCE)) return; VisitCastExpr(FCE); } void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) { if (!hasOrigins(ILE)) return; // For list initialization with a single element, like `View{...}`, the // origin of the list itself is the origin of its single element. if (ILE->getNumInits() == 1) killAndFlowOrigin(*ILE, *ILE->getInit(0)); } void FactsGenerator::VisitCXXBindTemporaryExpr( const CXXBindTemporaryExpr *BTE) { killAndFlowOrigin(*BTE, *BTE->getSubExpr()); } void FactsGenerator::VisitMaterializeTemporaryExpr( const MaterializeTemporaryExpr *MTE) { assert(MTE->isGLValue()); OriginList *MTEList = getOriginsList(*MTE); if (!MTEList) return; OriginList *SubExprList = getOriginsList(*MTE->getSubExpr()); assert((!SubExprList || MTEList->getLength() == (SubExprList->getLength() + 1)) && "MTE top level origin should contain a loan to the MTE itself"); OriginList *RValMTEList = getRValueOrigins(MTE, MTEList); flow(RValMTEList, SubExprList, /*Kill=*/true); OriginID OuterMTEID = MTEList->getOuterOriginID(); if (MTE->getStorageDuration() == SD_FullExpression) { // Issue a loan to MTE for the storage location represented by MTE. const Loan *L = createLoan(FactMgr, MTE); CurrentBlockFacts.push_back( FactMgr.createFact(L->getID(), OuterMTEID)); } } void FactsGenerator::VisitLambdaExpr(const LambdaExpr *LE) { // The lambda gets a single merged origin that aggregates all captured // pointer-like origins. Currently we only need to detect whether the lambda // outlives any capture. OriginList *LambdaList = getOriginsList(*LE); if (!LambdaList) return; bool Kill = true; for (const Expr *Init : LE->capture_inits()) { if (!Init) continue; OriginList *InitList = getOriginsList(*Init); if (!InitList) continue; // FIXME: Consider flowing all origin levels once lambdas support more than // one origin. Currently only the outermost origin is flowed, so by-ref // captures like `[&p]` (where p is string_view) miss inner-level // invalidation. CurrentBlockFacts.push_back(FactMgr.createFact( LambdaList->getOuterOriginID(), InitList->getOuterOriginID(), Kill)); Kill = false; } } void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) { assert(ASE->isGLValue() && "Array subscript should be a GL value"); OriginList *Dst = getOriginsList(*ASE); assert(Dst && "Array subscript should have origins as it is a GL value"); OriginList *Src = getOriginsList(*ASE->getBase()); assert(Src && "Base of array subscript should have origins"); CurrentBlockFacts.push_back(FactMgr.createFact( Dst->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true)); } void FactsGenerator::handlePlacementNew(const CXXNewExpr *NE, OriginList *NewList) { // Model only the standard single-argument placement new form, where the // placement argument corresponds to a void* allocation-function parameter. // Other placement forms, such as std::nothrow, are not modeled as providing // storage for the returned pointer. if (NE->getNumPlacementArgs() != 1) return; const FunctionDecl *OperatorNew = NE->getOperatorNew(); if (OperatorNew->getNumParams() <= 1) return; const auto *Arg = OperatorNew->getParamDecl(1)->getType()->getAs(); if (!Arg || !Arg->isVoidPointerType()) return; // Use the placement argument before the implicit conversion to void*, so // inner origins are still available. const Expr *PlacementArg = NE->getPlacementArg(0); if (const auto *ICE = dyn_cast(PlacementArg); ICE && ICE->getCastKind() == CK_BitCast && PlacementArg->getType()->isVoidPointerType()) PlacementArg = ICE->getSubExpr(); OriginList *PlacementList = getOriginsList(*PlacementArg); // FIXME: General placement arguments need separate handling to overwrite // the right origins. // The pointer returned by placement new comes from the placement // argument. if (PlacementList) CurrentBlockFacts.push_back(FactMgr.createFact( NewList->getOuterOriginID(), PlacementList->getOuterOriginID(), true)); } void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) { OriginList *NewList = getOriginsList(*NE); const Expr *Init = NE->getInitializer(); if (NE->getNumPlacementArgs() == 1) { handlePlacementNew(NE, NewList); } else { const Loan *L = createLoan(FactMgr, NE); CurrentBlockFacts.push_back( FactMgr.createFact(L->getID(), NewList->getOuterOriginID())); } NewList = NewList->peelOuterOrigin(); if (!NewList || !Init) return; // FIXME: OriginList is null for `new[]` initializers. Remove this `Init` // check once array origins are supported. if (OriginList *InitList = getOriginsList(*Init); InitList) flow(NewList, InitList, true); } void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) { OriginList *List = getOriginsList(*DE->getArgument()); CurrentBlockFacts.push_back( FactMgr.createFact(List->getOuterOriginID(), DE)); } bool FactsGenerator::escapesViaReturn(OriginID OID) const { return llvm::any_of(EscapesInCurrentBlock, [OID](const Fact *F) { if (const auto *EF = F->getAs()) return EF->getEscapedOriginID() == OID; return false; }); } void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) { const VarDecl *LifetimeEndsVD = LifetimeEnds.getVarDecl(); if (!LifetimeEndsVD) return; // Expire the origin when its variable's lifetime ends to ensure liveness // doesn't persist through loop back-edges. std::optional ExpiredOID; if (OriginList *List = getOriginsList(*LifetimeEndsVD)) { OriginID OID = List->getOuterOriginID(); // Skip origins that escape via return; the escape checker needs their loans // to remain until the return statement is processed. if (!escapesViaReturn(OID)) ExpiredOID = OID; } CurrentBlockFacts.push_back(FactMgr.createFact( AccessPath(LifetimeEndsVD), LifetimeEnds.getTriggerStmt()->getEndLoc(), ExpiredOID)); } void FactsGenerator::handleFullExprCleanup( const CFGFullExprCleanup &FullExprCleanup) { for (const auto *MTE : FullExprCleanup.getExpiringMTEs()) CurrentBlockFacts.push_back(FactMgr.createFact( AccessPath(MTE), FullExprCleanup.getCleanupLoc())); } void FactsGenerator::handleExitBlock() { for (const Origin &O : FactMgr.getOriginMgr().getOrigins()) if (auto *FD = dyn_cast_if_present(O.getDecl())) // Create FieldEscapeFacts for all field origins that remain live at exit. EscapesInCurrentBlock.push_back( FactMgr.createFact(O.ID, FD)); else if (auto *VD = dyn_cast_if_present(O.getDecl())) { // Create GlobalEscapeFacts for all origins with global-storage that // remain live at exit. if (VD->hasGlobalStorage()) { EscapesInCurrentBlock.push_back( FactMgr.createFact(O.ID, VD)); } } } void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) { assert(isGslPointerType(CCE->getType())); if (CCE->getNumArgs() != 1) return; const Expr *Arg = CCE->getArg(0); if (isGslPointerType(Arg->getType())) { OriginList *ArgList = getOriginsList(*Arg); assert(ArgList && "GSL pointer argument should have an origin list"); // GSL pointer is constructed from another gsl pointer. // Example: // View(View v); // View(const View &v); ArgList = getRValueOrigins(Arg, ArgList); flow(getOriginsList(*CCE), ArgList, /*Kill=*/true); } else if (Arg->getType()->isPointerType()) { // GSL pointer is constructed from a raw pointer. Flow only the outermost // raw pointer. Example: // View(const char*); // Span(const in**); OriginList *ArgList = getOriginsList(*Arg); CurrentBlockFacts.push_back(FactMgr.createFact( getOriginsList(*CCE)->getOuterOriginID(), ArgList->getOuterOriginID(), /*Kill=*/true)); } else { // This could be a new borrow. // TODO: Add code example here. handleFunctionCall(CCE, CCE->getConstructor(), {CCE->getArgs(), CCE->getNumArgs()}, /*IsGslConstruction=*/true); } } void FactsGenerator::handleMovedArgsInCall(const FunctionDecl *FD, ArrayRef Args) { unsigned IsInstance = 0; if (const auto *MD = dyn_cast(FD); MD && MD->isInstance() && !isa(FD)) { IsInstance = 1; // std::unique_ptr::release() transfers ownership. // Treat it as a move to prevent false-positive warnings when the unique_ptr // destructor runs after ownership has been transferred. if (isUniquePtrRelease(*MD)) { const Expr *UniquePtrExpr = Args[0]; OriginList *MovedOrigins = getOriginsList(*UniquePtrExpr); if (MovedOrigins) CurrentBlockFacts.push_back(FactMgr.createFact( UniquePtrExpr, MovedOrigins->getOuterOriginID())); } } // Skip 'this' arg as it cannot be moved. for (unsigned I = IsInstance; I < Args.size() && I < FD->getNumParams() + IsInstance; ++I) { const ParmVarDecl *PVD = FD->getParamDecl(I - IsInstance); if (!PVD->getType()->isRValueReferenceType()) continue; const Expr *Arg = Args[I]; OriginList *MovedOrigins = getOriginsList(*Arg); assert(MovedOrigins->getLength() >= 1 && "unexpected length for r-value reference param"); // Arg is being moved to this parameter. Mark the origin as moved. CurrentBlockFacts.push_back(FactMgr.createFact( Arg, MovedOrigins->getOuterOriginID())); } } void FactsGenerator::handleInvalidatingCall(const Expr *Call, const FunctionDecl *FD, ArrayRef Args) { const auto *MD = dyn_cast(FD); if (!MD || !MD->isInstance()) return; if (!isInvalidationMethod(*MD)) return; // Heuristics to turn-down false positives. Skip member field expressions for // now. This is not a perfect filter and will still surface some false // positives (e.g. `auto& r = s.v`). if (!isa(Args[0]->IgnoreImpCasts())) return; OriginList *ThisList = getOriginsList(*Args[0]); if (ThisList) CurrentBlockFacts.push_back(FactMgr.createFact( ThisList->getOuterOriginID(), Call)); } void FactsGenerator::handleDestructiveCall(const Expr *Call, const FunctionDecl *FD, ArrayRef Args) { if (!destructsFirstArg(*FD)) return; OriginList *ArgList = getOriginsList(*Args[0]); if (ArgList) CurrentBlockFacts.push_back(FactMgr.createFact( ArgList->getOuterOriginID(), Call)); } void FactsGenerator::handleImplicitObjectFieldUses(const Expr *Call, const FunctionDecl *FD) { const auto *MemberCall = dyn_cast_or_null(Call); if (!MemberCall) return; if (!isa_and_present( MemberCall->getImplicitObjectArgument()->IgnoreImpCasts())) return; const auto *MD = dyn_cast(FD); assert(MD && "Function must be a CXXMethodDecl for member calls"); const auto *ClassDecl = MD->getParent()->getDefinition(); if (!ClassDecl) return; const auto UseFields = [&](const CXXRecordDecl *RD) { for (const auto *Field : RD->fields()) if (auto *FieldList = getOriginsList(*Field)) CurrentBlockFacts.push_back( FactMgr.createFact(Call, FieldList)); }; UseFields(ClassDecl); ClassDecl->forallBases([&](const CXXRecordDecl *Base) { UseFields(Base); return true; }); } void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, ArrayRef Args) { if (Args.empty()) return; // FIXME: Add support for capture_by on constructors. if (isa(FD)) return; const auto *Method = dyn_cast(FD); bool IsInstance = Method && Method->isInstance() && !isa(FD); auto getArgCaptureBy = [FD, IsInstance](unsigned I) -> LifetimeCaptureByAttr * { const ParmVarDecl *PVD = nullptr; if (IsInstance) { // FIXME: Add support for I == 0 i.e. capture_by on function declarations if (I > 0 && I - 1 < FD->getNumParams()) PVD = FD->getParamDecl(I - 1); } else { if (I < FD->getNumParams()) PVD = FD->getParamDecl(I); } return PVD ? PVD->getAttr() : nullptr; }; for (unsigned I = 0; I < Args.size(); ++I) { const LifetimeCaptureByAttr *Attr = getArgCaptureBy(I); if (!Attr) continue; OriginList *CapturedOriginList = getOriginsList(*Args[I]); if (!CapturedOriginList) continue; if (!CapturedOriginList) continue; for (int CapturingArgIdx : Attr->params()) { // FIXME: Add support for capturing to Global/unknown. if (CapturingArgIdx == LifetimeCaptureByAttr::Global || CapturingArgIdx == LifetimeCaptureByAttr::Unknown || CapturingArgIdx == LifetimeCaptureByAttr::Invalid) continue; ArrayRef CallArgs = IsInstance ? Args.drop_front() : Args; const Expr *CapturedByArg = (CapturingArgIdx == LifetimeCaptureByAttr::This) ? Args[0] : CallArgs[CapturingArgIdx]; assert(CapturedByArg && "Capturer expression must be valid"); OriginList *CapturingOriginList = getOriginsList(*CapturedByArg); OriginList *Dest = getRValueOrigins(CapturedByArg, CapturingOriginList); if (!Dest) continue; // KillDest=false because we cannot know if previous captures are being // replaced or accumulated. Multiple successive captures into the same // destination must all be tracked, so captured lifetimes are always // merged. CurrentBlockFacts.push_back(FactMgr.createFact( Dest->getOuterOriginID(), CapturedOriginList->getOuterOriginID(), /*KillDest=*/false)); } } } void FactsGenerator::handleFunctionCall(const Expr *Call, const FunctionDecl *FD, ArrayRef Args, bool IsGslConstruction) { OriginList *CallList = getOriginsList(*Call); // Ignore functions returning values with no origin. FD = getDeclWithMergedLifetimeBoundAttrs(FD); if (!FD) return; // All arguments to a function are a use of the corresponding expressions. for (const Expr *Arg : Args) handleUse(Arg); handleInvalidatingCall(Call, FD, Args); handleDestructiveCall(Call, FD, Args); handleMovedArgsInCall(FD, Args); handleImplicitObjectFieldUses(Call, FD); handleLifetimeCaptureBy(FD, Args); if (!CallList) return; if (isStdReferenceCast(FD)) { assert(Args.size() == 1 && "std reference cast builtins take exactly one argument"); // std reference-cast functions like std::move return a result that refers // to the same object as the argument, so propagate the full origins. flow(CallList, getOriginsList(*Args[0]), /*Kill=*/true); return; } auto IsArgLifetimeBound = [FD, &Args](unsigned I) -> bool { const ParmVarDecl *PVD = nullptr; if (const auto *Method = dyn_cast(FD); Method && Method->isInstance() && !isa(FD)) { if (I == 0) // For the 'this' argument, the attribute is on the method itself. return implicitObjectParamIsLifetimeBound(Method) || shouldTrackImplicitObjectArg( *Args[0], Method, /*RunningUnderLifetimeSafety=*/true); if ((I - 1) < Method->getNumParams()) // For explicit arguments, find the corresponding parameter // declaration. PVD = Method->getParamDecl(I - 1); } else if (I == 0 && shouldTrackFirstArgument(FD)) { return true; } else if (I == 1 && shouldTrackSecondArgument(FD)) { return true; } else if (I < FD->getNumParams()) { // For free functions or static methods. PVD = FD->getParamDecl(I); } return PVD ? PVD->hasAttr() : false; }; auto shouldTrackPointerImplicitObjectArg = [FD, &Args](unsigned I) -> bool { const auto *Method = dyn_cast(FD); if (!Method || !Method->isInstance()) return false; return I == 0 && isGslPointerType(Method->getFunctionObjectParameterType()) && shouldTrackImplicitObjectArg(*Args[0], Method, /*RunningUnderLifetimeSafety=*/true); }; if (Args.empty()) return; bool KillSrc = true; for (unsigned I = 0; I < Args.size(); ++I) { OriginList *ArgList = getOriginsList(*Args[I]); if (!ArgList) continue; if (IsGslConstruction) { // TODO: document with code example. // std::string_view(const std::string_view& from) if (isGslPointerType(Args[I]->getType())) { assert(!Args[I]->isGLValue() || ArgList->getLength() >= 2); ArgList = getRValueOrigins(Args[I], ArgList); } if (isGslOwnerType(Args[I]->getType())) { // The constructed gsl::Pointer borrows from the Owner's storage, not // from what the Owner itself borrows, so only the outermost origin is // needed. CurrentBlockFacts.push_back(FactMgr.createFact( CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc)); KillSrc = false; } else if (IsArgLifetimeBound(I)) { // Only flow the outer origin here. For lifetimebound args in // gsl::Pointer construction, we do not have enough information to // safely match inner origins, so the source and // destination origin lists may have different lengths. // FIXME: Handle origin-shape mismatches gracefully so we can also flow // inner origins. CurrentBlockFacts.push_back(FactMgr.createFact( CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc)); KillSrc = false; } } else if (shouldTrackPointerImplicitObjectArg(I)) { assert(ArgList->getLength() >= 2 && "Object arg of pointer type should have at least two origins"); // See through the GSLPointer reference to see the pointer's value. CurrentBlockFacts.push_back(FactMgr.createFact( CallList->getOuterOriginID(), ArgList->peelOuterOrigin()->getOuterOriginID(), KillSrc)); KillSrc = false; } else if (IsArgLifetimeBound(I)) { // Lifetimebound on a non-GSL-ctor function means the returned // pointer/reference itself must not outlive the arguments. This // only constrains the top-level origin. CurrentBlockFacts.push_back(FactMgr.createFact( CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc)); KillSrc = false; } } } /// Checks if the expression is a `void("__lifetime_test_point_...")` cast. /// If so, creates a `TestPointFact` and returns true. bool FactsGenerator::handleTestPoint(const CXXFunctionalCastExpr *FCE) { if (!FCE->getType()->isVoidType()) return false; const auto *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts(); if (const auto *SL = dyn_cast(SubExpr)) { llvm::StringRef LiteralValue = SL->getString(); const std::string Prefix = "__lifetime_test_point_"; if (LiteralValue.starts_with(Prefix)) { StringRef Annotation = LiteralValue.drop_front(Prefix.length()); CurrentBlockFacts.push_back( FactMgr.createFact(Annotation)); return true; } } return false; } void FactsGenerator::handleUse(const Expr *E) { OriginList *List = getOriginsList(*E); if (!List) return; // For DeclRefExpr: Remove the outer layer of origin which borrows from the // decl directly (e.g., when this is not a reference). This is a use of the // underlying decl. if (auto *DRE = dyn_cast(E); DRE && !DRE->getDecl()->getType()->isReferenceType()) List = getRValueOrigins(DRE, List); // Skip if there is no inner origin (e.g., when it is not a pointer type). if (!List) return; if (!UseFacts.contains(E)) { UseFact *UF = FactMgr.createFact(E, List); CurrentBlockFacts.push_back(UF); UseFacts[E] = UF; } } void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) { if (UseFacts.contains(DRE)) UseFacts[DRE]->markAsWritten(); } // Creates an IssueFact for a new placeholder loan for each pointer or reference // parameter at the function's entry. llvm::SmallVector FactsGenerator::issuePlaceholderLoans() { const auto *FD = dyn_cast(AC.getDecl()); if (!FD) return {}; llvm::SmallVector PlaceholderLoanFacts; if (auto ThisOrigins = FactMgr.getOriginMgr().getThisOrigins()) { OriginList *List = *ThisOrigins; const Loan *L = FactMgr.getLoanMgr().createLoan( AccessPath::Placeholder(cast(FD)), /*IssuingExpr=*/nullptr); PlaceholderLoanFacts.push_back( FactMgr.createFact(L->getID(), List->getOuterOriginID())); } for (const ParmVarDecl *PVD : FD->parameters()) { OriginList *List = getOriginsList(*PVD); if (!List) continue; const Loan *L = FactMgr.getLoanMgr().createLoan( AccessPath::Placeholder(PVD), /*IssuingExpr=*/nullptr); PlaceholderLoanFacts.push_back( FactMgr.createFact(L->getID(), List->getOuterOriginID())); } return PlaceholderLoanFacts; } } // namespace clang::lifetimes::internal