diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index b9cad5340c940..7e383ff4299e5 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -152,7 +152,8 @@ class ReturnOfOriginFact : public Fact { class UseFact : public Fact { const Expr *UseExpr; - OriginID OID; + // The origins of the expression being used. + llvm::SmallVector OIDs; // True if this use is a write operation (e.g., left-hand side of assignment). // Write operations are exempted from use-after-free checks. bool IsWritten = false; @@ -160,10 +161,10 @@ class UseFact : public Fact { public: static bool classof(const Fact *F) { return F->getKind() == Kind::Use; } - UseFact(const Expr *UseExpr, OriginManager &OM) - : Fact(Kind::Use), UseExpr(UseExpr), OID(OM.get(*UseExpr)) {} + UseFact(const Expr *UseExpr, llvm::ArrayRef OIDs) + : Fact(Kind::Use), UseExpr(UseExpr), OIDs(OIDs.begin(), OIDs.end()) {} - OriginID getUsedOrigin() const { return OID; } + llvm::ArrayRef getUsedOrigins() const { return OIDs; } const Expr *getUseExpr() const { return UseExpr; } void markAsWritten() { IsWritten = true; } bool isWritten() const { return IsWritten; } @@ -191,8 +192,8 @@ class TestPointFact : public Fact { class FactManager { public: - void init(const CFG &Cfg) { - assert(BlockToFacts.empty() && "FactManager already initialized"); + FactManager(const AnalysisDeclContext &AC, const CFG &Cfg) + : OriginMgr(AC.getASTContext()) { BlockToFacts.resize(Cfg.getNumBlockIDs()); } diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index 4c8ab3f859a49..bf19f87f9e0b7 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -50,6 +50,11 @@ class FactsGenerator : public ConstStmtVisitor { void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE); private: + OriginTree *getTree(const ValueDecl &D); + OriginTree *getTree(const Expr &E); + + void flow(OriginTree *Dst, OriginTree *Src, bool Kill); + void handleDestructor(const CFGAutomaticObjDtor &DtorOpt); void handleGSLPointerConstruction(const CXXConstructExpr *CCE); @@ -64,32 +69,24 @@ class FactsGenerator : public ConstStmtVisitor { template void flowOrigin(const Destination &D, const Source &S) { - OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D); - OriginID SrcOID = FactMgr.getOriginMgr().get(S); - CurrentBlockFacts.push_back(FactMgr.createFact( - DestOID, SrcOID, /*KillDest=*/false)); + flow(getTree(D), getTree(S), /*Kill=*/false); } template void killAndFlowOrigin(const Destination &D, const Source &S) { - OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D); - OriginID SrcOID = FactMgr.getOriginMgr().get(S); - CurrentBlockFacts.push_back( - FactMgr.createFact(DestOID, SrcOID, /*KillDest=*/true)); + flow(getTree(D), getTree(S), /*Kill=*/true); } /// Checks if the expression is a `void("__lifetime_test_point_...")` cast. /// If so, creates a `TestPointFact` and returns true. bool handleTestPoint(const CXXFunctionalCastExpr *FCE); - void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr); - // A DeclRefExpr will be treated as a use of the referenced decl. It will be // checked for use-after-free unless it is later marked as being written to // (e.g. on the left-hand side of an assignment). void handleUse(const DeclRefExpr *DRE); - void markUseAsWrite(const DeclRefExpr *DRE); + void markUseAsWrite(const Expr *E); FactManager &FactMgr; AnalysisDeclContext &AC; diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index f02969e0a9563..1a16fb82f9a84 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h @@ -38,6 +38,11 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD); /// method or because it's a normal assignment operator. bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD); +// Tells whether the type is annotated with [[gsl::Pointer]]. +bool isGslPointerType(QualType QT); +// Tells whether the type is annotated with [[gsl::Owner]]. +bool isGslOwnerType(QualType QT); + } // namespace clang::lifetimes #endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 91ffbb169f947..2de735ceac270 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -71,13 +71,13 @@ class LifetimeSafetyAnalysis { return *LoanPropagation; } LiveOriginsAnalysis &getLiveOrigins() const { return *LiveOrigins; } - FactManager &getFactManager() { return FactMgr; } + FactManager &getFactManager() { return *FactMgr; } private: AnalysisDeclContext &AC; LifetimeSafetyReporter *Reporter; LifetimeFactory Factory; - FactManager FactMgr; + std::unique_ptr FactMgr; std::unique_ptr LiveOrigins; std::unique_ptr LoanPropagation; }; diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h index 56b9010f41fa2..190102f9cb894 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h @@ -28,12 +28,10 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) { /// An Origin is a symbolic identifier that represents the set of possible /// loans a pointer-like object could hold at any given time. -/// TODO: Enhance the origin model to handle complex types, pointer -/// indirection and reborrowing. The plan is to move from a single origin per -/// variable/expression to a "list of origins" governed by the Type. -/// For example, the type 'int**' would have two origins. -/// See discussion: -/// https://github.com/llvm/llvm-project/pull/142313/commits/0cd187b01e61b200d92ca0b640789c1586075142#r2137644238 +/// +/// Each Origin corresponds to a single level of indirection. For complex types +/// with multiple levels of indirection (e.g., `int**`), multiple Origins are +/// organized into an OriginTree structure (see below). struct Origin { OriginID ID; /// A pointer to the AST node that this origin represents. This union @@ -52,28 +50,80 @@ struct Origin { } }; +/// A tree of origins representing levels of indirection for pointer-like types. +/// +/// Each node in the tree contains an OriginID representing a level of +/// indirection. The tree structure captures the multi-level nature of +/// pointer and reference types in the lifetime analysis. +/// +/// Examples: +/// - For `int& x`, the tree has depth 2: +/// * Root: origin for the reference storage itself (the lvalue `x`) +/// * Pointee: origin for what `x` refers to +/// +/// - For `int* p`, the tree has depth 2: +/// * Root: origin for the pointer variable `p` +/// * Pointee: origin for what `p` points to +/// +/// - For `View v` (where View is gsl::Pointer), the tree has depth 2: +/// * Root: origin for the view object itself +/// * Pointee: origin for what the view refers to +/// +/// - For `int** pp`, the tree has depth 3: +/// * Root: origin for `pp` itself +/// * Pointee: origin for `*pp` (what `pp` points to) +/// * Pointee->Pointee: origin for `**pp` (what `*pp` points to) +/// +/// The tree structure enables the analysis to track how loans flow through +/// different levels of indirection when assignments and dereferences occur. +struct OriginTree { + OriginID OID; + OriginTree *Pointee = nullptr; + + OriginTree(OriginID OID) : OID(OID) {} + + size_t getDepth() const { + size_t Depth = 1; + const OriginTree *T = this; + while (T->Pointee) { + T = T->Pointee; + Depth++; + } + return Depth; + } +}; + +bool isPointerLikeType(QualType QT); + /// Manages the creation, storage, and retrieval of origins for pointer-like /// variables and expressions. class OriginManager { public: - OriginManager() = default; - - Origin &addOrigin(OriginID ID, const clang::ValueDecl &D); - Origin &addOrigin(OriginID ID, const clang::Expr &E); - - // TODO: Mark this method as const once we remove the call to getOrCreate. - OriginID get(const Expr &E); - - OriginID get(const ValueDecl &D); - OriginID getOrCreate(const Expr &E); + explicit OriginManager(ASTContext& AST) : AST(AST) {} + + /// Gets or creates the OriginTree for a given ValueDecl. + /// + /// Creates a tree structure mirroring the levels of indirection in the + /// declaration's type (e.g., `int** p` creates depth 2). + /// + /// \returns The OriginTree, or nullptr if the type is not pointer-like. + OriginTree *getOrCreateTree(const ValueDecl *D); + + /// Gets or creates the OriginTree for a given Expr. + /// + /// Creates a tree based on the expression's type and value category: + /// - Lvalues get an implicit reference level (modeling addressability) + /// - Rvalues of non-pointer type return nullptr (no trackable origin) + /// - DeclRefExpr may reuse the underlying declaration's tree + /// + /// \returns The OriginTree, or nullptr for non-pointer rvalues. + OriginTree *getOrCreateTree(const Expr *E); const Origin &getOrigin(OriginID ID) const; llvm::ArrayRef getOrigins() const { return AllOrigins; } - OriginID getOrCreate(const ValueDecl &D); - unsigned getNumOrigins() const { return NextOriginID.Value; } void dump(OriginID OID, llvm::raw_ostream &OS) const; @@ -81,12 +131,29 @@ class OriginManager { private: OriginID getNextOriginID() { return NextOriginID++; } + OriginTree *createNode(const ValueDecl *D) { + OriginID NewID = getNextOriginID(); + AllOrigins.emplace_back(NewID, D); + return new (TreeAllocator.Allocate()) OriginTree(NewID); + } + + OriginTree *createNode(const Expr *E) { + OriginID NewID = getNextOriginID(); + AllOrigins.emplace_back(NewID, E); + return new (TreeAllocator.Allocate()) OriginTree(NewID); + } + + template + OriginTree *buildTreeForType(QualType QT, const T *Node); + + ASTContext& AST; OriginID NextOriginID{0}; - /// TODO(opt): Profile and evaluate the usefullness of small buffer + /// TODO(opt): Profile and evaluate the usefulness of small buffer /// optimisation. llvm::SmallVector AllOrigins; - llvm::DenseMap DeclToOriginID; - llvm::DenseMap ExprToOriginID; + llvm::BumpPtrAllocator TreeAllocator; + llvm::DenseMap DeclToTree; + llvm::DenseMap ExprToTree; }; } // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 190c038f46401..b7f39564c140c 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -53,7 +53,10 @@ void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM) const { OS << "Use ("; - OM.dump(getUsedOrigin(), OS); + for (OriginID OID : getUsedOrigins()) { + OM.dump(OID, OS); + OS << ", "; + } OS << ", " << (isWritten() ? "Write" : "Read") << ")\n"; } diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 381ff99aae420..7a5b67d245b46 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -6,6 +6,10 @@ // //===----------------------------------------------------------------------===// +#include +#include + +#include "clang/AST/OperationKinds.h" #include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h" #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" #include "clang/Analysis/Analyses/PostOrderCFGView.h" @@ -15,28 +19,30 @@ namespace clang::lifetimes::internal { using llvm::isa_and_present; -static bool isGslPointerType(QualType QT) { - if (const auto *RD = QT->getAsCXXRecordDecl()) { - // We need to check the template definition for specializations. - if (auto *CTSD = dyn_cast(RD)) - return CTSD->getSpecializedTemplate() - ->getTemplatedDecl() - ->hasAttr(); - return RD->hasAttr(); - } - return false; +// Check if a type has an origin. +static bool hasOrigin(const Expr *E) { + return E->isGLValue() || isPointerLikeType(E->getType()); } -static bool isPointerType(QualType QT) { - return QT->isPointerOrReferenceType() || isGslPointerType(QT); +OriginTree *FactsGenerator::getTree(const ValueDecl &D) { + return FactMgr.getOriginMgr().getOrCreateTree(&D); } -// Check if a type has an origin. -static bool hasOrigin(const Expr *E) { - return E->isGLValue() || isPointerType(E->getType()); +OriginTree *FactsGenerator::getTree(const Expr &E) { + return FactMgr.getOriginMgr().getOrCreateTree(&E); } -static bool hasOrigin(const VarDecl *VD) { - return isPointerType(VD->getType()); +void FactsGenerator::flow(OriginTree *Dst, OriginTree *Src, bool Kill) { + if (!Dst) + return; + assert(Dst->getDepth() == Src->getDepth() && + "Trees must have the same shape"); + + while (Dst && Src) { + CurrentBlockFacts.push_back( + FactMgr.createFact(Dst->OID, Src->OID, Kill)); + Dst = Dst->Pointee; + Src = Src->Pointee; + } } /// Creates a loan for the storage path of a given declaration reference. @@ -60,10 +66,12 @@ void FactsGenerator::run() { CurrentBlockFacts.clear(); for (unsigned I = 0; I < Block->size(); ++I) { const CFGElement &Element = Block->Elements[I]; - if (std::optional CS = Element.getAs()) + if (std::optional CS = Element.getAs()) { + DEBUG_WITH_TYPE("multi", llvm::errs() << "Processing: \n"); + DEBUG_WITH_TYPE("multi", CS->getStmt()->dumpColor()); Visit(CS->getStmt()); - else if (std::optional DtorOpt = - Element.getAs()) + } else if (std::optional DtorOpt = + Element.getAs()) handleDestructor(*DtorOpt); } FactMgr.addBlockFacts(Block, CurrentBlockFacts); @@ -73,29 +81,44 @@ void FactsGenerator::run() { void FactsGenerator::VisitDeclStmt(const DeclStmt *DS) { for (const Decl *D : DS->decls()) if (const auto *VD = dyn_cast(D)) - if (hasOrigin(VD)) - if (const Expr *InitExpr = VD->getInit()) - killAndFlowOrigin(*VD, *InitExpr); + if (const Expr *InitExpr = VD->getInit()) { + OriginTree *VDTree = getTree(*VD); + if (!VDTree) + continue; + OriginTree *InitTree = getTree(*InitExpr); + assert(InitTree && "VarDecl had origins but InitExpr did not"); + // Special handling for rvalue references initialized with xvalues. + // For declarations like `Ranges&& r = std::move(ranges);`, the rvalue + // reference should directly refer to the object being moved from, + // rather than creating a new indirection level. We skip the outer + // reference level and flow the pointee origins directly. + if (VD->getType()->isRValueReferenceType() && InitExpr->isXValue()) { + flow(VDTree->Pointee, InitTree->Pointee, /*Kill=*/true); + continue; + } + flow(VDTree, InitTree, /*Kill=*/true); + } } void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) { + // Skip function references and PR values. + if (DRE->getFoundDecl()->isFunctionOrFunctionTemplate() || !DRE->isGLValue()) + return; handleUse(DRE); - // For non-pointer/non-view types, a reference to the variable's storage - // is a borrow. We create a loan for it. - // For pointer/view types, we stick to the existing model for now and do - // not create an extra origin for the l-value expression itself. - - // TODO: A single origin for a `DeclRefExpr` for a pointer or view type is - // not sufficient to model the different levels of indirection. The current - // single-origin model cannot distinguish between a loan to the variable's - // storage and a loan to what it points to. A multi-origin model would be - // required for this. - if (!isPointerType(DRE->getType())) { - if (const Loan *L = createLoan(FactMgr, DRE)) { - OriginID ExprOID = FactMgr.getOriginMgr().getOrCreate(*DRE); - CurrentBlockFacts.push_back( - FactMgr.createFact(L->ID, ExprOID)); - } + // For pointer/view types, handleUse tracks all levels of indirection through + // the OriginTree structure. + // + // For non-pointer/non-reference types (e.g., `int x`), taking the address + // creates a borrow of the variable's storage. We issue a loan for this case. + if (!isPointerLikeType(DRE->getType()) && + !DRE->getDecl()->getType()->isReferenceType()) { + const Loan *L = createLoan(FactMgr, DRE); + assert(L); + OriginTree *tree = getTree(*DRE); + assert(tree && + "gl-value DRE of non-pointer type should have an origin tree"); + CurrentBlockFacts.push_back( + FactMgr.createFact(L->ID, tree->OID)); } } @@ -109,12 +132,14 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) { 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())) { + if (isa_and_present(MCE->getCalleeDecl()) && + isGslPointerType(MCE->getType()) && + 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 @@ -136,15 +161,41 @@ void FactsGenerator::VisitCXXNullPtrLiteralExpr( const CXXNullPtrLiteralExpr *N) { /// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized /// pointers can use the same type of loan. - FactMgr.getOriginMgr().getOrCreate(*N); + getTree(*N); } void FactsGenerator::VisitImplicitCastExpr(const ImplicitCastExpr *ICE) { - if (!hasOrigin(ICE)) + OriginTree *Dest = getTree(*ICE); + if (!Dest) + return; + OriginTree *SrcTree = getTree(*ICE->getSubExpr()); + + if (ICE->getCastKind() == CK_LValueToRValue) { + // TODO: Decide what to do for x-values here. + if (!ICE->getSubExpr()->isLValue()) + return; + + assert(SrcTree && "LValue being cast to RValue has no origin tree"); + // The result of an LValue-to-RValue cast on a reference-to-pointer like + // has the inner origin. Get rid of the outer origin. + flow(getTree(*ICE), SrcTree->Pointee, /*Kill=*/true); + return; + } + if (ICE->getCastKind() == CK_NullToPointer) { + getTree(*ICE); + // TODO: Flow into them a null origin. + return; + } + if (ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_ConstructorConversion || + ICE->getCastKind() == CK_UserDefinedConversion) + flow(Dest, SrcTree, /*Kill=*/true); + if (ICE->getCastKind() == CK_FunctionToPointerDecay || + ICE->getCastKind() == CK_BuiltinFnToFnPtr || + ICE->getCastKind() == CK_ArrayToPointerDecay) { + // Ignore function-to-pointer decays. return; - // An ImplicitCastExpr node itself gets an origin, which flows from the - // origin of its sub-expression (after stripping its own parens/casts). - killAndFlowOrigin(*ICE, *ICE->getSubExpr()); + } } void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) { @@ -152,7 +203,7 @@ void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) { const Expr *SubExpr = UO->getSubExpr(); // Taking address of a pointer-type expression is not yet supported and // will be supported in multi-origin model. - if (isPointerType(SubExpr->getType())) + if (isPointerLikeType(SubExpr->getType())) return; // 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 @@ -164,19 +215,34 @@ void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) { void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) { if (const Expr *RetExpr = RS->getRetValue()) { - if (hasOrigin(RetExpr)) { - OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr); - CurrentBlockFacts.push_back(FactMgr.createFact(OID)); - } + if (OriginTree *Tree = getTree(*RetExpr)) + CurrentBlockFacts.push_back( + FactMgr.createFact(Tree->OID)); } } void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) { - if (BO->isAssignmentOp()) - handleAssignment(BO->getLHS(), BO->getRHS()); + if (BO->isCompoundAssignmentOp()) + return; + if (BO->isAssignmentOp()) { + const Expr *LHSExpr = BO->getLHS(); + const Expr *RHSExpr = BO->getRHS(); + + if (const auto *DRE_LHS = + dyn_cast(LHSExpr->IgnoreParenImpCasts())) { + OriginTree *LHSTree = getTree(*DRE_LHS); + OriginTree *RHSTree = getTree(*RHSExpr); + // TODO: Handle reference types. + markUseAsWrite(DRE_LHS); + // Kill the old loans of the destination origin and flow the new loans + // from the source origin. + flow(LHSTree->Pointee, RHSTree, /*Kill=*/true); + } + } } void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) { + if (hasOrigin(CO)) { // Merge origins from both branches of the conditional operator. // We kill to clear the initial state and merge both origins into it. @@ -188,8 +254,26 @@ void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) { void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) { // Assignment operators have special "kill-then-propagate" semantics // and are handled separately. - if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) { - handleAssignment(OCE->getArg(0), OCE->getArg(1)); + if (OCE->getOperator() == OO_Equal && OCE->getNumArgs() == 2) { + + const Expr *LHSExpr = OCE->getArg(0); + const Expr *RHSExpr = OCE->getArg(1); + + if (const auto *DRE_LHS = + dyn_cast(LHSExpr->IgnoreParenImpCasts())) { + OriginTree *LHSTree = getTree(*DRE_LHS); + OriginTree *RHSTree = getTree(*RHSExpr); + + // TODO: Doc why. + // Construction of GSL! View &(const View &). + if (RHSExpr->isGLValue()) + RHSTree = RHSTree->Pointee; + // TODO: Handle reference types. + markUseAsWrite(DRE_LHS); + // Kill the old loans of the destination origin and flow the new loans + // from the source origin. + flow(LHSTree->Pointee, RHSTree, /*Kill=*/true); + } return; } handleFunctionCall(OCE, OCE->getDirectCallee(), @@ -218,11 +302,24 @@ void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) { void FactsGenerator::VisitMaterializeTemporaryExpr( const MaterializeTemporaryExpr *MTE) { - if (!hasOrigin(MTE)) + OriginTree *MTETree = getTree(*MTE); + OriginTree *SubExprTree = getTree(*MTE->getSubExpr()); + if (!MTETree) return; - // A temporary object's origin is the same as the origin of the - // expression that initializes it. - killAndFlowOrigin(*MTE, *MTE->getSubExpr()); + if (MTE->isGLValue()) { + assert(!SubExprTree || + MTETree->getDepth() == SubExprTree->getDepth() + 1 && "todo doc."); + // Issue a loan to the MTE. + // const Loan *L = createLoan(FactMgr, MTE); + // CurrentBlockFacts.push_back( + // FactMgr.createFact(L->ID, MTETree->OID)); + if (SubExprTree) + flow(MTETree->Pointee, SubExprTree, /*Kill=*/true); + } else { + assert(MTE->isXValue()); + flow(MTETree, SubExprTree, /*Kill=*/true); + } + // TODO: MTE top level origin should contain a loan to the MTE itself. } void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) { @@ -251,13 +348,23 @@ void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) { assert(isGslPointerType(CCE->getType())); if (CCE->getNumArgs() != 1) return; - if (hasOrigin(CCE->getArg(0))) - killAndFlowOrigin(*CCE, *CCE->getArg(0)); - else + + if (hasOrigin(CCE->getArg(0))) { + // TODO: Add code example here. + OriginTree *ArgTree = getTree(*CCE->getArg(0)); + assert(ArgTree && "GSL pointer argument should have an origin tree"); + // GSL pointer is constructed from another gsl pointer. + // TODO: Add proper assertions. + if (ArgTree->getDepth() == 2) + ArgTree = ArgTree->Pointee; + flow(getTree(*CCE), ArgTree, /*Kill=*/true); + } else { // This could be a new borrow. + // TODO: Add code example here. handleFunctionCall(CCE, CCE->getConstructor(), {CCE->getArgs(), CCE->getNumArgs()}, /*IsGslConstruction=*/true); + } } /// Checks if a call-like expression creates a borrow by passing a value to a @@ -268,8 +375,9 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, const FunctionDecl *FD, ArrayRef Args, bool IsGslConstruction) { + OriginTree *CallTree = getTree(*Call); // Ignore functions returning values with no origin. - if (!FD || !hasOrigin(Call)) + if (!FD || !CallTree) return; auto IsArgLifetimeBound = [FD](unsigned I) -> bool { const ParmVarDecl *PVD = nullptr; @@ -282,22 +390,39 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, // For explicit arguments, find the corresponding parameter // declaration. PVD = Method->getParamDecl(I - 1); - } else if (I < FD->getNumParams()) + } else if (I < FD->getNumParams()) { // For free functions or static methods. PVD = FD->getParamDecl(I); + } return PVD ? PVD->hasAttr() : false; }; if (Args.empty()) return; - bool killedSrc = false; - for (unsigned I = 0; I < Args.size(); ++I) - if (IsGslConstruction || IsArgLifetimeBound(I)) { - if (!killedSrc) { - killedSrc = true; - killAndFlowOrigin(*Call, *Args[I]); - } else - flowOrigin(*Call, *Args[I]); + bool KillSrc = true; + for (unsigned I = 0; I < Args.size(); ++I) { + OriginTree *ArgTree = getTree(*Args[I]); + if (!ArgTree) + continue; + if (IsGslConstruction) { + // TODO: document with code example. + // std::string_view(const std::string_view& from) + if (isGslPointerType(Args[I]->getType()) && Args[I]->isGLValue()) { + assert(ArgTree->getDepth() >= 2); + ArgTree = ArgTree->Pointee; + } + // GSL construction creates a view that borrows from arguments. + // This implies flowing origins through the tree structure. + flow(CallTree, ArgTree, 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 constraints the top-level origin. + CurrentBlockFacts.push_back(FactMgr.createFact( + CallTree->OID, ArgTree->OID, KillSrc)); + KillSrc = false; } + } } /// Checks if the expression is a `void("__lifetime_test_point_...")` cast. @@ -321,36 +446,32 @@ bool FactsGenerator::handleTestPoint(const CXXFunctionalCastExpr *FCE) { return false; } -void FactsGenerator::handleAssignment(const Expr *LHSExpr, - const Expr *RHSExpr) { - if (!hasOrigin(LHSExpr)) - return; - // Find the underlying variable declaration for the left-hand side. - if (const auto *DRE_LHS = - dyn_cast(LHSExpr->IgnoreParenImpCasts())) { - markUseAsWrite(DRE_LHS); - if (const auto *VD_LHS = dyn_cast(DRE_LHS->getDecl())) { - // Kill the old loans of the destination origin and flow the new loans - // from the source origin. - killAndFlowOrigin(*VD_LHS, *RHSExpr); - } - } -} - // A DeclRefExpr will be treated as a use of the referenced decl. It will be // checked for use-after-free unless it is later marked as being written to // (e.g. on the left-hand side of an assignment). void FactsGenerator::handleUse(const DeclRefExpr *DRE) { - if (isPointerType(DRE->getType())) { - UseFact *UF = FactMgr.createFact(DRE, FactMgr.getOriginMgr()); - CurrentBlockFacts.push_back(UF); - assert(!UseFacts.contains(DRE)); - UseFacts[DRE] = UF; + if (!isPointerLikeType(DRE->getType())) + return; + OriginTree *tree = getTree(*DRE); + if (!tree) + return; + llvm::SmallVector UsedOrigins; + OriginTree *T = tree; + while (T) { + UsedOrigins.push_back(T->OID); + T = T->Pointee; } + UseFact *UF = FactMgr.createFact(DRE, UsedOrigins); + CurrentBlockFacts.push_back(UF); + assert(!UseFacts.contains(DRE)); + UseFacts[DRE] = UF; } -void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) { - if (!isPointerType(DRE->getType())) +void FactsGenerator::markUseAsWrite(const Expr *E) { + auto *DRE = dyn_cast(E); + if (!DRE) + return; + if (!isPointerLikeType(DRE->getType())) return; assert(UseFacts.contains(DRE)); UseFacts[DRE]->markAsWritten(); diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index ad61a42c0eaeb..54e343fc2ee5e 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -10,6 +10,7 @@ #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" #include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" @@ -70,4 +71,34 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) { return isNormalAssignmentOperator(FD); } +template static bool isRecordWithAttr(QualType Type) { + auto *RD = Type->getAsCXXRecordDecl(); + if (!RD) + return false; + // Generally, if a primary template class declaration is annotated with an + // attribute, all its specializations generated from template instantiations + // should inherit the attribute. + // + // However, since lifetime analysis occurs during parsing, we may encounter + // cases where a full definition of the specialization is not required. In + // such cases, the specialization declaration remains incomplete and lacks the + // attribute. Therefore, we fall back to checking the primary template class. + // + // Note: it is possible for a specialization declaration to have an attribute + // even if the primary template does not. + // + // FIXME: What if the primary template and explicit specialization + // declarations have conflicting attributes? We should consider diagnosing + // this scenario. + bool Result = RD->hasAttr(); + + if (auto *CTSD = dyn_cast(RD)) + Result |= CTSD->getSpecializedTemplate()->getTemplatedDecl()->hasAttr(); + + return Result; +} + +bool isGslPointerType(QualType QT) { return isRecordWithAttr(QT); } +bool isGslOwnerType(QualType QT) { return isRecordWithAttr(QT); } + } // namespace clang::lifetimes diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp index a51ba4280f284..93aca54ad2a68 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp @@ -41,11 +41,12 @@ void LifetimeSafetyAnalysis::run() { const CFG &Cfg = *AC.getCFG(); DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(), /*ShowColors=*/true)); - FactMgr.init(Cfg); - FactsGenerator FactGen(FactMgr, AC); + FactMgr = std::make_unique(AC, Cfg); + + FactsGenerator FactGen(*FactMgr, AC); FactGen.run(); - DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC)); + DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC)); /// TODO(opt): Consider optimizing individual blocks before running the /// dataflow analysis. @@ -59,14 +60,14 @@ void LifetimeSafetyAnalysis::run() { /// 3. Collapse ExpireFacts belonging to same source location into a single /// Fact. LoanPropagation = std::make_unique( - Cfg, AC, FactMgr, Factory.OriginMapFactory, Factory.LoanSetFactory); + Cfg, AC, *FactMgr, Factory.OriginMapFactory, Factory.LoanSetFactory); LiveOrigins = std::make_unique( - Cfg, AC, FactMgr, Factory.LivenessMapFactory); + Cfg, AC, *FactMgr, Factory.LivenessMapFactory); DEBUG_WITH_TYPE("LiveOrigins", - LiveOrigins->dump(llvm::dbgs(), FactMgr.getTestPoints())); + LiveOrigins->dump(llvm::dbgs(), FactMgr->getTestPoints())); - runLifetimeChecker(*LoanPropagation, *LiveOrigins, FactMgr, AC, Reporter); + runLifetimeChecker(*LoanPropagation, *LiveOrigins, *FactMgr, AC, Reporter); } } // namespace internal diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp index 59f594e50fb46..eb054a3cc6761 100644 --- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp @@ -111,13 +111,19 @@ class AnalysisImpl /// dominates this program point. A write operation kills the liveness of /// the origin since it overwrites the value. Lattice transfer(Lattice In, const UseFact &UF) { - OriginID OID = UF.getUsedOrigin(); - // Write kills liveness. - if (UF.isWritten()) - return Lattice(Factory.remove(In.LiveOrigins, OID)); - // Read makes origin live with definite confidence (dominates this point). - return Lattice(Factory.add(In.LiveOrigins, OID, - LivenessInfo(&UF, LivenessKind::Must))); + Lattice Out = In; + for (OriginID OID : UF.getUsedOrigins()) { + // Write kills liveness. + if (UF.isWritten()) { + Out = Lattice(Factory.remove(Out.LiveOrigins, OID)); + } else { + // Read makes origin live with definite confidence (dominates this + // point). + Out = Lattice(Factory.add(Out.LiveOrigins, OID, + LivenessInfo(&UF, LivenessKind::Must))); + } + } + return Out; } /// Issuing a new loan to an origin kills its liveness. diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 0e6c194123df8..4511572215bd6 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -62,7 +62,8 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr, CheckOrigin(F->getAs()->getReturnedOriginID()); break; case Fact::Kind::Use: - CheckOrigin(F->getAs()->getUsedOrigin()); + for (OriginID OID : F->getAs()->getUsedOrigins()) + CheckOrigin(OID); break; case Fact::Kind::Expire: case Fact::Kind::TestPoint: diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp index 0f2eaa94a5987..fdebd2a56ba0c 100644 --- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp @@ -7,70 +7,121 @@ //===----------------------------------------------------------------------===// #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/TypeBase.h" +#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" namespace clang::lifetimes::internal { +bool isPointerLikeType(QualType QT) { + return QT->isPointerOrReferenceType() || isGslPointerType(QT); +} + +static bool hasOrigins(QualType QT) { return isPointerLikeType(QT); } + +/// Determines if an expression has origins that need to be tracked. +/// +/// An expression has origins if: +/// - It's a glvalue (has addressable storage), OR +/// - Its type is pointer-like (pointer, reference, or gsl::Pointer) +/// +/// Examples: +/// - `int x; x` : has origin (glvalue) +/// - `int* p; p` : has 2 origins (1 for glvalue and 1 for pointer type) +/// - `std::string_view{}` : has 1 origin (prvalue of pointer type) +/// - `42` : no origin (prvalue of non-pointer type) +/// - `x + y` : (where x, y are int) → no origin (prvalue of non-pointer type) +static bool hasOrigins(const Expr *E) { + return E->isGLValue() || hasOrigins(E->getType()); +} + void OriginManager::dump(OriginID OID, llvm::raw_ostream &OS) const { OS << OID << " ("; Origin O = getOrigin(OID); - if (const ValueDecl *VD = O.getDecl()) + if (const ValueDecl *VD = O.getDecl()) { OS << "Decl: " << VD->getNameAsString(); - else if (const Expr *E = O.getExpr()) + } else if (const Expr *E = O.getExpr()) { OS << "Expr: " << E->getStmtClassName(); - else + if (auto *DRE = dyn_cast(E)) { + if (const ValueDecl *VD = DRE->getDecl()) + OS << "(" << VD->getNameAsString() << ")"; + } + } else { OS << "Unknown"; + } OS << ")"; } -Origin &OriginManager::addOrigin(OriginID ID, const clang::ValueDecl &D) { - AllOrigins.emplace_back(ID, &D); - return AllOrigins.back(); -} - -Origin &OriginManager::addOrigin(OriginID ID, const clang::Expr &E) { - AllOrigins.emplace_back(ID, &E); - return AllOrigins.back(); +template +OriginTree *OriginManager::buildTreeForType(QualType QT, const T *Node) { + assert(isPointerLikeType(QT) && + "buildTreeForType called for non-pointer type"); + OriginTree *Root = createNode(Node); + if (QT->isPointerOrReferenceType()) { + QualType PointeeTy = QT->getPointeeType(); + // We recurse if the pointee type is pointer-like, to build the next + // level in the origin tree. E.g., for T*& / View&. + if (isPointerLikeType(PointeeTy)) + Root->Pointee = buildTreeForType(PointeeTy, Node); + } + return Root; } -// TODO: Mark this method as const once we remove the call to getOrCreate. -OriginID OriginManager::get(const Expr &E) { - if (auto *ParenIgnored = E.IgnoreParens(); ParenIgnored != &E) - return get(*ParenIgnored); - auto It = ExprToOriginID.find(&E); - if (It != ExprToOriginID.end()) +OriginTree *OriginManager::getOrCreateTree(const ValueDecl *D) { + if (!isPointerLikeType(D->getType())) + return nullptr; + auto It = DeclToTree.find(D); + if (It != DeclToTree.end()) return It->second; - // If the expression itself has no specific origin, and it's a reference - // to a declaration, its origin is that of the declaration it refers to. - // For pointer types, where we don't pre-emptively create an origin for the - // DeclRefExpr itself. - if (const auto *DRE = dyn_cast(&E)) - return get(*DRE->getDecl()); - // TODO: This should be an assert(It != ExprToOriginID.end()). The current - // implementation falls back to getOrCreate to avoid crashing on - // yet-unhandled pointer expressions, creating an empty origin for them. - return getOrCreate(E); + return DeclToTree[D] = buildTreeForType(D->getType(), D); } -OriginID OriginManager::get(const ValueDecl &D) { - auto It = DeclToOriginID.find(&D); - // TODO: This should be an assert(It != DeclToOriginID.end()). The current - // implementation falls back to getOrCreate to avoid crashing on - // yet-unhandled pointer expressions, creating an empty origin for them. - if (It == DeclToOriginID.end()) - return getOrCreate(D); +OriginTree *OriginManager::getOrCreateTree(const Expr *E) { + if (auto *ParenIgnored = E->IgnoreParens(); ParenIgnored != E) + return getOrCreateTree(ParenIgnored); - return It->second; -} + if (!hasOrigins(E)) + return nullptr; -OriginID OriginManager::getOrCreate(const Expr &E) { - auto It = ExprToOriginID.find(&E); - if (It != ExprToOriginID.end()) + auto It = ExprToTree.find(E); + if (It != ExprToTree.end()) return It->second; - OriginID NewID = getNextOriginID(); - addOrigin(NewID, E); - ExprToOriginID[&E] = NewID; - return NewID; + QualType Type = E->getType(); + + // Special handling for DeclRefExpr to share origins with the underlying decl. + // + // For reference-typed declarations (e.g., `int& r`), the DeclRefExpr + // directly reuses the declaration's tree since references don't add an + // extra level of indirection at the expression level. + // + // For non-reference declarations (e.g., `int* p`), the DeclRefExpr is an + // lvalue (addressable), so we create an outer origin for the lvalue itself, + // with the pointee being the declaration's tree. This models taking the + // address: `&p` borrows the storage of `p`, not what `p` points to. + // + // This ensures origin sharing: multiple DeclRefExprs to the same declaration + // share the same underlying origins. + if (auto *DRE = dyn_cast(E)) { + OriginTree *Root = nullptr; + if (DRE->getDecl()->getType()->isReferenceType()) + Root = getOrCreateTree(DRE->getDecl()); + else { + Root = createNode(DRE); + Root->Pointee = getOrCreateTree(DRE->getDecl()); + } + return ExprToTree[E] = Root; + } + // If E is an lvalue , it refers to storage. We model this storage as the + // first level of origin tree, as if it were a reference, because l-values are + // addressable. + if (E->isGLValue() && !Type->isReferenceType()) + Type = AST.getLValueReferenceType(Type); + ExprToTree[E] = buildTreeForType(Type, E); + return ExprToTree[E]; } const Origin &OriginManager::getOrigin(OriginID ID) const { @@ -78,14 +129,4 @@ const Origin &OriginManager::getOrigin(OriginID ID) const { return AllOrigins[ID.Value]; } -OriginID OriginManager::getOrCreate(const ValueDecl &D) { - auto It = DeclToOriginID.find(&D); - if (It != DeclToOriginID.end()) - return It->second; - OriginID NewID = getNextOriginID(); - addOrigin(NewID, D); - DeclToOriginID[&D] = NewID; - return NewID; -} - } // namespace clang::lifetimes::internal diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index f9665b5e59831..c91ca751984c9 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -17,6 +17,9 @@ #include "llvm/ADT/PointerIntPair.h" namespace clang::sema { +using lifetimes::isGslOwnerType; +using lifetimes::isGslPointerType; + namespace { enum LifetimeKind { /// The lifetime of a temporary bound to this entity ends at the end of the @@ -257,38 +260,8 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path, Expr *Init, ReferenceKind RK, LocalVisitor Visit); -template static bool isRecordWithAttr(QualType Type) { - auto *RD = Type->getAsCXXRecordDecl(); - if (!RD) - return false; - // Generally, if a primary template class declaration is annotated with an - // attribute, all its specializations generated from template instantiations - // should inherit the attribute. - // - // However, since lifetime analysis occurs during parsing, we may encounter - // cases where a full definition of the specialization is not required. In - // such cases, the specialization declaration remains incomplete and lacks the - // attribute. Therefore, we fall back to checking the primary template class. - // - // Note: it is possible for a specialization declaration to have an attribute - // even if the primary template does not. - // - // FIXME: What if the primary template and explicit specialization - // declarations have conflicting attributes? We should consider diagnosing - // this scenario. - bool Result = RD->hasAttr(); - - if (auto *CTSD = dyn_cast(RD)) - Result |= CTSD->getSpecializedTemplate()->getTemplatedDecl()->hasAttr(); - - return Result; -} - -// Tells whether the type is annotated with [[gsl::Pointer]]. -bool isGLSPointerType(QualType QT) { return isRecordWithAttr(QT); } - static bool isPointerLikeType(QualType QT) { - return isGLSPointerType(QT) || QT->isPointerType() || QT->isNullPtrType(); + return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType(); } // Decl::isInStdNamespace will return false for iterators in some STL @@ -331,7 +304,7 @@ static bool isContainerOfOwner(const RecordDecl *Container) { return false; const auto &TAs = CTSD->getTemplateArgs(); return TAs.size() > 0 && TAs[0].getKind() == TemplateArgument::Type && - isRecordWithAttr(TAs[0].getAsType()); + isGslOwnerType(TAs[0].getAsType()); } // Returns true if the given Record is `std::initializer_list`. @@ -349,14 +322,13 @@ static bool isStdInitializerListOfPointer(const RecordDecl *RD) { static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) { if (auto *Conv = dyn_cast_or_null(Callee)) - if (isRecordWithAttr(Conv->getConversionType()) && + if (isGslPointerType(Conv->getConversionType()) && Callee->getParent()->hasAttr()) return true; if (!isInStlNamespace(Callee->getParent())) return false; - if (!isRecordWithAttr( - Callee->getFunctionObjectParameterType()) && - !isRecordWithAttr(Callee->getFunctionObjectParameterType())) + if (!isGslPointerType(Callee->getFunctionObjectParameterType()) && + !isGslOwnerType(Callee->getFunctionObjectParameterType())) return false; if (isPointerLikeType(Callee->getReturnType())) { if (!Callee->getIdentifier()) @@ -393,7 +365,7 @@ static bool shouldTrackFirstArgument(const FunctionDecl *FD) { if (!RD->hasAttr() && !RD->hasAttr()) return false; if (FD->getReturnType()->isPointerType() || - isRecordWithAttr(FD->getReturnType())) { + isGslPointerType(FD->getReturnType())) { return llvm::StringSwitch(FD->getName()) .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true) .Cases({"end", "rend", "cend", "crend"}, true) @@ -465,7 +437,7 @@ shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) { return true; // RHS must be an owner. - if (!isRecordWithAttr(RHSArgType)) + if (!isGslOwnerType(RHSArgType)) return false; // Bail out if the RHS is Owner. @@ -547,7 +519,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call, // Once we initialized a value with a non gsl-owner reference, it can no // longer dangle. if (ReturnType->isReferenceType() && - !isRecordWithAttr(ReturnType->getPointeeType())) { + !isGslOwnerType(ReturnType->getPointeeType())) { for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) { if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit || PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall) @@ -1158,8 +1130,7 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path, // auto p2 = Temp().owner; // Here p2 is dangling. if (const auto *FD = llvm::dyn_cast_or_null(E.D); FD && !FD->getType()->isReferenceType() && - isRecordWithAttr(FD->getType()) && - LK != LK_MemInitializer) { + isGslOwnerType(FD->getType()) && LK != LK_MemInitializer) { return Report; } return Abandon; @@ -1191,10 +1162,9 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path, // const GSLOwner& func(const Foo& foo [[clang::lifetimebound]]) // GSLOwner* func(cosnt Foo& foo [[clang::lifetimebound]]) // GSLPointer func(const Foo& foo [[clang::lifetimebound]]) - if (FD && - ((FD->getReturnType()->isPointerOrReferenceType() && - isRecordWithAttr(FD->getReturnType()->getPointeeType())) || - isGLSPointerType(FD->getReturnType()))) + if (FD && ((FD->getReturnType()->isPointerOrReferenceType() && + isGslOwnerType(FD->getReturnType()->getPointeeType())) || + isGslPointerType(FD->getReturnType()))) return Report; return Abandon; @@ -1206,7 +1176,7 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path, // int &p = *localUniquePtr; // someContainer.add(std::move(localUniquePtr)); // return p; - if (!pathContainsInit(Path) && isRecordWithAttr(L->getType())) + if (!pathContainsInit(Path) && isGslOwnerType(L->getType())) return Report; return Abandon; } @@ -1215,8 +1185,7 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path, auto *MTE = dyn_cast(L); bool IsGslPtrValueFromGslTempOwner = - MTE && !MTE->getExtendingDecl() && - isRecordWithAttr(MTE->getType()); + MTE && !MTE->getExtendingDecl() && isGslOwnerType(MTE->getType()); // Skipping a chain of initializing gsl::Pointer annotated objects. // We are looking only for the final source to find out if it was // a local or temporary owner or the address of a local @@ -1231,7 +1200,7 @@ static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef, bool EnableGSLAssignmentWarnings = !SemaRef.getDiagnostics().isIgnored( diag::warn_dangling_lifetime_pointer_assignment, SourceLocation()); return (EnableGSLAssignmentWarnings && - (isRecordWithAttr(Entity.LHS->getType()) || + (isGslPointerType(Entity.LHS->getType()) || lifetimes::isAssignmentOperatorLifetimeBound( Entity.AssignmentOperator))); } @@ -1400,7 +1369,7 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity, // Suppress false positives for code like the one below: // Ctor(unique_ptr up) : pointer(up.get()), owner(move(up)) {} // FIXME: move this logic to analyzePathForGSLPointer. - if (DRE && isRecordWithAttr(DRE->getType())) + if (DRE && isGslOwnerType(DRE->getType())) return false; auto *VD = DRE ? dyn_cast(DRE->getDecl()) : nullptr; diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h index 16595d0ca1b36..38b7061988dc7 100644 --- a/clang/lib/Sema/CheckExprLifetime.h +++ b/clang/lib/Sema/CheckExprLifetime.h @@ -18,9 +18,6 @@ namespace clang::sema { -// Tells whether the type is annotated with [[gsl::Pointer]]. -bool isGLSPointerType(QualType QT); - /// Describes an entity that is being assigned. struct AssignedEntity { // The left-hand side expression of the assignment. diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp index 8411a3da8322d..7729c113e422e 100644 --- a/clang/lib/Sema/SemaAttr.cpp +++ b/clang/lib/Sema/SemaAttr.cpp @@ -11,11 +11,11 @@ // //===----------------------------------------------------------------------===// -#include "CheckExprLifetime.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" +#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/Lookup.h" @@ -289,7 +289,7 @@ void Sema::inferLifetimeCaptureByAttribute(FunctionDecl *FD) { // We only apply the lifetime_capture_by attribute to parameters of // pointer-like reference types (`const T&`, `T&&`). if (PVD->getType()->isReferenceType() && - sema::isGLSPointerType(PVD->getType().getNonReferenceType())) { + lifetimes::isGslPointerType(PVD->getType().getNonReferenceType())) { int CaptureByThis[] = {LifetimeCaptureByAttr::This}; PVD->addAttr( LifetimeCaptureByAttr::CreateImplicit(Context, CaptureByThis, 1)); diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index b9368db550805..d8b166336ea0f 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -1,9 +1,13 @@ // RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety -verify %s -struct MyObj { +struct View; + +struct [[gsl::Owner]] MyObj { int id; ~MyObj() {} // Non-trivial destructor MyObj operator+(MyObj); + + View getView() const [[clang::lifetimebound]]; }; struct [[gsl::Pointer()]] View { @@ -220,6 +224,16 @@ void potential_for_loop_use_after_loop_body(MyObj safe) { (void)*p; // expected-note {{later used here}} } +void safe_for_loop_gsl() { + MyObj safe; + View v = safe; + for (int i = 0; i < 1; ++i) { + MyObj s; + v = s; + v.use(); + } +} + void potential_for_loop_gsl() { MyObj safe; View v = safe; @@ -444,13 +458,6 @@ MyObj* Identity(MyObj* v [[clang::lifetimebound]]); View Choose(bool cond, View a [[clang::lifetimebound]], View b [[clang::lifetimebound]]); MyObj* GetPointer(const MyObj& obj [[clang::lifetimebound]]); -struct [[gsl::Pointer()]] LifetimeBoundView { - LifetimeBoundView(); - LifetimeBoundView(const MyObj& obj [[clang::lifetimebound]]); - LifetimeBoundView pass() [[clang::lifetimebound]] { return *this; } - operator View() const [[clang::lifetimebound]]; -}; - void lifetimebound_simple_function() { View v; { @@ -497,25 +504,34 @@ void lifetimebound_mixed_args() { v.use(); // expected-note {{later used here}} } +struct LifetimeBoundMember { + LifetimeBoundMember(); + View get() const [[clang::lifetimebound]]; + operator View() const [[clang::lifetimebound]]; +}; + void lifetimebound_member_function() { - LifetimeBoundView lbv, lbv2; + View v; { MyObj obj; - lbv = obj; // expected-warning {{object whose reference is captured does not live long enough}} - lbv2 = lbv.pass(); - } // expected-note {{destroyed here}} - View v = lbv2; // expected-note {{later used here}} - v.use(); + v = obj.getView(); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + v.use(); // expected-note {{later used here}} } +struct LifetimeBoundConversionView { + LifetimeBoundConversionView(); + ~LifetimeBoundConversionView(); + operator View() const [[clang::lifetimebound]]; +}; + void lifetimebound_conversion_operator() { View v; { - MyObj obj; - LifetimeBoundView lbv = obj; // expected-warning {{object whose reference is captured does not live long enough}} - v = lbv; // Conversion operator is lifetimebound - } // expected-note {{destroyed here}} - v.use(); // expected-note {{later used here}} + LifetimeBoundConversionView obj; + v = obj; // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + v.use(); // expected-note {{later used here}} } void lifetimebound_chained_calls() { @@ -556,18 +572,18 @@ void lifetimebound_partial_safety(bool cond) { v.use(); // expected-note {{later used here}} } -// FIXME: Creating reference from lifetimebound call doesn't propagate loans. +// TODO:Extensively test references types. const MyObj& GetObject(View v [[clang::lifetimebound]]); void lifetimebound_return_reference() { View v; const MyObj* ptr; { MyObj obj; - View temp_v = obj; + View temp_v = obj; // expected-warning {{object whose reference is captured does not live long enough}} const MyObj& ref = GetObject(temp_v); ptr = &ref; - } - (void)*ptr; + } // expected-note {{destroyed here}} + (void)*ptr; // expected-note {{later used here}} } // FIXME: No warning for non gsl::Pointer types. Origin tracking is only supported for pointer types. @@ -575,6 +591,7 @@ struct LifetimeBoundCtor { LifetimeBoundCtor(); LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]); }; + void lifetimebound_ctor() { LifetimeBoundCtor v; { @@ -686,3 +703,67 @@ void parentheses(bool cond) { } // expected-note 4 {{destroyed here}} (void)*p; // expected-note 4 {{later used here}} } + +namespace GH162834 { +template +struct StatusOr { + ~StatusOr() {} + const T& value() const& [[clang::lifetimebound]] { return data; } + + private: + T data; +}; + +StatusOr getViewOr(); +StatusOr getStringOr(); +StatusOr getPointerOr(); + +void foo() { + View view; + { + StatusOr view_or = getViewOr(); + view = view_or.value(); + } + (void)view; +} + +void bar() { + MyObj* pointer; + { + StatusOr pointer_or = getPointerOr(); + pointer = pointer_or.value(); + } + (void)*pointer; +} + +void foobar() { + View view; + { + StatusOr string_or = getStringOr(); + view = string_or. // expected-warning {{object whose reference is captured does not live long enough}} + value(); + } // expected-note {{destroyed here}} + (void)view; // expected-note {{later used here}} +} +} // namespace GH162834 + +namespace CppCoverage { + +int getInt(); + +void ReferenceParam(unsigned Value, unsigned &Ref) { + Value = getInt(); + Ref = getInt(); +} + +inline void normalize(int &exponent, int &mantissa) { + const int shift = 1; + exponent -= shift; + mantissa <<= shift; +} + +void add(int c, MyObj* node) { + MyObj* arr[10]; + arr[4] = node; +} +} // namespace CppCoverage diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index 601308c53f9a9..b63ddbb7367ac 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -101,8 +101,9 @@ class LifetimeTestHelper { // This assumes the OriginManager's `get` can find an existing origin. // We might need a `find` method on OriginManager to avoid `getOrCreate` // logic in a const-query context if that becomes an issue. - return const_cast(Analysis.getFactManager().getOriginMgr()) - .get(*VD); + // return const_cast(Analysis.getFactManager().getOriginMgr()) + // .get(*VD); + return std::nullopt; } std::vector getLoansForVar(llvm::StringRef VarName) {