https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/111499
>From 4951a7b9b87f9800bc3629bd44f65141ba98c6b0 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Tue, 8 Oct 2024 08:19:56 +0000 Subject: [PATCH 1/7] start working on lifetime capture --- clang/include/clang/Basic/Attr.td | 40 +++++++ .../clang/Basic/DiagnosticSemaKinds.td | 16 +++ clang/include/clang/Sema/Sema.h | 7 ++ clang/lib/AST/TypePrinter.cpp | 21 ++++ clang/lib/Sema/CheckExprLifetime.cpp | 54 +++++++--- clang/lib/Sema/CheckExprLifetime.h | 26 +++-- clang/lib/Sema/SemaChecking.cpp | 27 +++++ clang/lib/Sema/SemaDecl.cpp | 1 + clang/lib/Sema/SemaDeclAttr.cpp | 102 ++++++++++++++++++ clang/lib/Sema/SemaExpr.cpp | 4 +- clang/lib/Sema/SemaInit.cpp | 2 +- clang/lib/Sema/SemaOverload.cpp | 4 +- clang/lib/Sema/SemaType.cpp | 13 +++ clang/test/SemaCXX/attr-lifetimebound.cpp | 94 ++++++++++++++++ 14 files changed, 387 insertions(+), 24 deletions(-) diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 35b9716e13ff21..4dcb143b91f84f 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1869,6 +1869,46 @@ def LifetimeBound : DeclOrTypeAttr { let SimpleHandler = 1; } +def LifetimeCaptureBy : DeclOrTypeAttr { + let Spellings = [Clang<"lifetime_capture_by", 0>]; + let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>; + let Args = [VariadicParamOrParamIdxArgument<"Params">]; + let Documentation = [LifetimeBoundDocs]; + let LangOpts = [CPlusPlus]; + + // let SimpleHandler = 1; + // let LateParsed = LateAttrParseStandard; + // let HasCustomParsing = 1; + // let ParseArgumentsAsUnevaluated = 1; + + let AdditionalMembers = [{ +private: + SmallVector<IdentifierInfo*, 1> ArgIdents; + SmallVector<SourceLocation, 1> ArgLocs; + +public: + static const int INVALID = -2; + static const int UNKNOWN = -1; + static const int GLOBAL = -1; + static const int THIS = 0; + + void setArgs(SmallVector<IdentifierInfo*, 1> Idents, + SmallVector<SourceLocation, 1> Locs) { + assert(Idents.size() == Locs.size()); + assert(Idents.size() == params_Size); + ArgIdents = std::move(Idents); + ArgLocs = std::move(Locs); + } + + const SmallVector<IdentifierInfo*, 1>& getArgIdents() const { return ArgIdents; } + const SmallVector<SourceLocation, 1>& getArgLocs() const { return ArgLocs; } + void setParamIdx(size_t Idx, int Val) { + assert(Idx < params_Size); + params_[Idx] = Val; + } +}]; +} + def TrivialABI : InheritableAttr { // This attribute does not have a C [[]] spelling because it requires the // CPlusPlus language option. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index e8b64f3c5a0187..ea034af77c3dbe 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3382,6 +3382,18 @@ def err_callback_callee_is_variadic : Error< "'callback' attribute callee may not be variadic">; def err_callback_implicit_this_not_available : Error< "'callback' argument at position %0 references unavailable implicit 'this'">; + +def err_capture_by_attribute_multiple : Error< + "multiple 'lifetime_capture' attributes specified">; +def err_capture_by_attribute_no_entity : Error< + "'lifetime_capture_by' attribute specifies no capturing entity">; +def err_capture_by_implicit_this_not_available : Error< + "'lifetime_capture_by' argument references unavailable implicit 'this'">; +def err_capture_by_attribute_argument_unknown : Error< + "'lifetime_capture_by' attribute argument %0 is not a known function parameter" + ". Must be a function parameter of one of 'this', 'global' or 'unknown'">; +def err_capture_by_references_itself : Error<"'lifetime_capture_by' argument references itself">; + def err_init_method_bad_return_type : Error< "init methods must return an object pointer type, not %0">; def err_attribute_invalid_size : Error< @@ -10185,6 +10197,10 @@ def warn_dangling_pointer_assignment : Warning< "object backing the pointer %0 " "will be destroyed at the end of the full-expression">, InGroup<DanglingAssignment>; +def warn_dangling_reference_captured : Warning< + "object captured by the '%0' " + "will be destroyed at the end of the full-expression">, + InGroup<DanglingAssignment>; // For non-floating point, expressions of the form x == x or x != x // should result in a warning, since these always evaluate to a constant. diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 0809ac1b144ef6..a26b3fa8755161 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1830,6 +1830,10 @@ class Sema final : public SemaBase { /// Add [[gsl::Pointer]] attributes for std:: types. void inferGslPointerAttribute(TypedefNameDecl *TD); + LifetimeCaptureByAttr *ParseLifetimeCaptureByAttr(const ParsedAttr &AL, + StringRef ParamName); + void LazyProcessLifetimeCaptureByParams(FunctionDecl *FD); + /// Add _Nullable attributes for std:: types. void inferNullableClassAttribute(CXXRecordDecl *CRD); @@ -2384,6 +2388,9 @@ class Sema final : public SemaBase { bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res); bool BuiltinVectorToScalarMath(CallExpr *TheCall); + void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction, + const Expr *ThisArg, ArrayRef<const Expr *> Args); + /// Handles the checks for format strings, non-POD arguments to vararg /// functions, NULL arguments passed to non-NULL parameters, diagnose_if /// attributes and AArch64 SME attributes. diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp index ca75bb97c158e1..02bece7cce51d7 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -12,6 +12,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" +#include "clang/AST/Attrs.inc" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" @@ -25,6 +26,7 @@ #include "clang/AST/TextNodeDumper.h" #include "clang/AST/Type.h" #include "clang/Basic/AddressSpaces.h" +#include "clang/Basic/AttrKinds.h" #include "clang/Basic/ExceptionSpecificationType.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" @@ -1907,6 +1909,24 @@ void TypePrinter::printAttributedAfter(const AttributedType *T, OS << " [[clang::lifetimebound]]"; return; } + if (T->getAttrKind() == attr::LifetimeCaptureBy) { + OS << " [[clang::lifetime_capture_by(...)"; + // const LifetimeCaptureByAttr* A= T->getAs<LifetimeCaptureByAttr>(); + // bool valid = true; + // for (int I : A->params()) + // valid &= I != -2; + // if (valid) { + // OS << "invalid)"; + // return; + // } + // for (size_t I = 0; I < A->params_size(); ++I) { + // OS << A->getArgIdents()[I]->getName() + // << "(idx: " << *(A->params_begin() + I) << ")"; + // if (I != A->params_size() - 1) + // OS << ", "; + // } + return; + } // The printing of the address_space attribute is handled by the qualifier // since it is still stored in the qualifier. Return early to prevent printing @@ -1966,6 +1986,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T, case attr::SizedBy: case attr::SizedByOrNull: case attr::LifetimeBound: + case attr::LifetimeCaptureBy: case attr::TypeNonNull: case attr::TypeNullable: case attr::TypeNullableResult: diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index c98fbca849faba..3e2e3565eb2337 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -41,10 +41,14 @@ enum LifetimeKind { /// a default member initializer), the program is ill-formed. LK_MemInitializer, - /// The lifetime of a temporary bound to this entity probably ends too soon, + /// The lifetime of a temporary bound to this entity may end too soon, /// because the entity is a pointer and we assign the address of a temporary /// object to it. LK_Assignment, + + /// The lifetime of a temporary bound to this entity probably ends too soon, + /// because the entity may capture the reference to a temporary object. + LK_LifetimeCapture, }; using LifetimeResult = llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>; @@ -189,6 +193,7 @@ struct IndirectLocalPathEntry { VarInit, LValToRVal, LifetimeBoundCall, + LifetimeCapture, TemporaryCopy, LambdaCaptureInit, GslReferenceInit, @@ -898,6 +903,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I, case IndirectLocalPathEntry::AddressOf: case IndirectLocalPathEntry::LValToRVal: case IndirectLocalPathEntry::LifetimeBoundCall: + case IndirectLocalPathEntry::LifetimeCapture: case IndirectLocalPathEntry::TemporaryCopy: case IndirectLocalPathEntry::GslReferenceInit: case IndirectLocalPathEntry::GslPointerInit: @@ -928,6 +934,7 @@ static bool pathOnlyHandlesGslPointer(IndirectLocalPath &Path) { case IndirectLocalPathEntry::VarInit: case IndirectLocalPathEntry::AddressOf: case IndirectLocalPathEntry::LifetimeBoundCall: + case IndirectLocalPathEntry::LifetimeCapture: continue; case IndirectLocalPathEntry::GslPointerInit: case IndirectLocalPathEntry::GslReferenceInit: @@ -948,11 +955,11 @@ static bool isAssignmentOperatorLifetimeBound(CXXMethodDecl *CMD) { } static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef, - const AssignedEntity &Entity) { + const CapturingEntity &Entity) { bool EnableGSLAssignmentWarnings = !SemaRef.getDiagnostics().isIgnored( diag::warn_dangling_lifetime_pointer_assignment, SourceLocation()); return (EnableGSLAssignmentWarnings && - (isRecordWithAttr<PointerAttr>(Entity.LHS->getType()) || + (isRecordWithAttr<PointerAttr>(Entity.Expression->getType()) || isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator))); } @@ -960,9 +967,10 @@ static void checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity, const InitializedEntity *ExtendingEntity, LifetimeKind LK, - const AssignedEntity *AEntity, Expr *Init) { - assert((AEntity && LK == LK_Assignment) || - (InitEntity && LK != LK_Assignment)); + const CapturingEntity *CEntity, Expr *Init) { + assert(InitEntity || CEntity); + assert(!CEntity || LK == LK_Assignment || LK == LK_LifetimeCapture); + assert(!InitEntity || LK != LK_Assignment); // If this entity doesn't have an interesting lifetime, don't bother looking // for temporaries within its initializer. if (LK == LK_FullExpression) @@ -1046,6 +1054,17 @@ static void checkExprLifetimeImpl(Sema &SemaRef, break; } + case LK_LifetimeCapture: { + if (!MTE) + return false; + assert(shouldLifetimeExtendThroughPath(Path) == + PathLifetimeKind::NoExtend && + "No lifetime extension for in function calls"); + SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured) + << CEntity->Expression << DiagRange; + return false; + } + case LK_Assignment: { if (!MTE || pathContainsInit(Path)) return false; @@ -1056,7 +1075,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef, IsGslPtrValueFromGslTempOwner ? diag::warn_dangling_lifetime_pointer_assignment : diag::warn_dangling_pointer_assignment) - << AEntity->LHS << DiagRange; + << CEntity->Expression << DiagRange; return false; } case LK_MemInitializer: { @@ -1199,6 +1218,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef, break; case IndirectLocalPathEntry::LifetimeBoundCall: + case IndirectLocalPathEntry::LifetimeCapture: case IndirectLocalPathEntry::TemporaryCopy: case IndirectLocalPathEntry::GslPointerInit: case IndirectLocalPathEntry::GslReferenceInit: @@ -1243,8 +1263,10 @@ static void checkExprLifetimeImpl(Sema &SemaRef, }; llvm::SmallVector<IndirectLocalPathEntry, 8> Path; - if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity)) + if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *CEntity)) Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init}); + else if (LK == LK_LifetimeCapture) + Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init}); if (Init->isGLValue()) visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding, @@ -1256,7 +1278,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef, /*RevisitSubinits=*/!InitEntity); } -void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity, +void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity, Expr *Init) { auto LTResult = getEntityLifetime(&Entity); LifetimeKind LK = LTResult.getInt(); @@ -1265,12 +1287,12 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity, /*AEntity*/ nullptr, Init); } -void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, - Expr *Init) { +void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity, + Expr *RHS) { bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored( diag::warn_dangling_pointer_assignment, SourceLocation()); bool RunAnalysis = (EnableDanglingPointerAssignment && - Entity.LHS->getType()->isPointerType()) || + Entity.Expression->getType()->isPointerType()) || shouldRunGSLAssignmentAnalysis(SemaRef, Entity); if (!RunAnalysis) @@ -1278,7 +1300,13 @@ void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr, /*ExtendingEntity=*/nullptr, LK_Assignment, &Entity, - Init); + RHS); } +void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity, + Expr *Captured) { + checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr, + /*ExtendingEntity=*/nullptr, LK_LifetimeCapture, + &Entity, Captured); +} } // namespace clang::sema diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h index 8c8d0806dee0a3..98562fba54b188 100644 --- a/clang/lib/Sema/CheckExprLifetime.h +++ b/clang/lib/Sema/CheckExprLifetime.h @@ -18,22 +18,36 @@ namespace clang::sema { -/// Describes an entity that is being assigned. -struct AssignedEntity { - // The left-hand side expression of the assignment. - Expr *LHS = nullptr; +struct CapturingEntity { + // The expression of the entity which captures another entity. + // For example: + // 1. In an assignment, this would be the left-hand side expression. + // std::string_view sv; + // sv = std::string(); // Here 'sv' is the 'Entity'. + // + // 2. In an function call involving a lifetime capture, this would be the + // argument capturing the lifetime of another argument. + // void addToSet(std::string_view s [[clang::lifetime_capture_by(sv)]], + // set<std::string_view>& setsv); + // set<std::string_view> ssv; + // addToSet(std::string(), ssv); // Here 'ssv' is the 'Entity'. + Expr *Expression = nullptr; CXXMethodDecl *AssignmentOperator = nullptr; }; /// Check that the lifetime of the given expr (and its subobjects) is /// sufficient for initializing the entity, and perform lifetime extension /// (when permitted) if not. -void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity, +void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity, Expr *Init); /// Check that the lifetime of the given expr (and its subobjects) is /// sufficient for assigning to the entity. -void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, Expr *Init); +void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity, + Expr *RHS); + +void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity, + Expr *Captured); } // namespace clang::sema diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 99500daca295c9..b6d74650c3e0bc 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -11,10 +11,12 @@ // //===----------------------------------------------------------------------===// +#include "CheckExprLifetime.h" #include "clang/AST/APValue.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" #include "clang/AST/AttrIterator.h" +#include "clang/AST/Attrs.inc" #include "clang/AST/CharUnits.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" @@ -3203,6 +3205,29 @@ void Sema::CheckArgAlignment(SourceLocation Loc, NamedDecl *FDecl, << ParamName << (FDecl != nullptr) << FDecl; } +void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction, + const Expr *ThisArg, + ArrayRef<const Expr *> Args) { + auto GetArgAt = [&](int Idx) { + if (IsMemberFunction && Idx == 0) + return const_cast<Expr *>(ThisArg); + return const_cast<Expr *>(Args[Idx - int(IsMemberFunction)]); + }; + for (unsigned I = 0; I < FD->getNumParams(); ++I) { + auto *CapturedByAttr = + FD->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>(); + if (!CapturedByAttr) + continue; + for (int CapturingParamIdx : CapturedByAttr->params()) { + Expr *Capturing = GetArgAt(CapturingParamIdx); + Expr *Captured = GetArgAt(I + IsMemberFunction); + CapturingEntity CE{Capturing}; + // Ensure that 'Captured' lives atleast as long as the 'Capturing' entity. + checkCaptureLifetime(*this, CE, Captured); + } + } +} + void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto, const Expr *ThisArg, ArrayRef<const Expr *> Args, bool IsMemberFunction, SourceLocation Loc, @@ -3244,6 +3269,8 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto, } } + if (FD) + checkLifetimeCaptureBy(FD, IsMemberFunction, ThisArg, Args); if (FDecl || Proto) { CheckNonNullArguments(*this, FDecl, Proto, Args, Loc); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 8557c25b93a8da..04e69f355d96df 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -16615,6 +16615,7 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) { } inferLifetimeBoundAttribute(FD); + LazyProcessLifetimeCaptureByParams(FD); AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD); // If C++ exceptions are enabled but we are told extern "C" functions cannot diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 14cc51cf89665a..ccf0b555538dfd 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -14,6 +14,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/ASTMutationListener.h" #include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" @@ -3834,6 +3835,104 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) { S.Context, AL, EncodingIndices.data(), EncodingIndices.size())); } +LifetimeCaptureByAttr *Sema::ParseLifetimeCaptureByAttr(const ParsedAttr &AL, + StringRef ParamName) { + // Atleast one capture by is required. + if (AL.getNumArgs() == 0) { + Diag(AL.getLoc(), diag::err_capture_by_attribute_no_entity) + << AL.getRange(); + return nullptr; + } + SmallVector<IdentifierInfo *, 1> ParamIdents; + SmallVector<SourceLocation, 1> ParamLocs; + for (unsigned I = 0; I < AL.getNumArgs(); ++I) { + if (AL.isArgExpr(I)) { + Expr *E = AL.getArgAsExpr(I); + Diag(E->getExprLoc(), diag::err_capture_by_attribute_argument_unknown) + << E << E->getExprLoc(); + continue; + } + assert(AL.isArgIdent(I)); + IdentifierLoc *IdLoc = AL.getArgAsIdent(I); + if (IdLoc->Ident->getName() == ParamName) { + Diag(IdLoc->Loc, diag::err_capture_by_references_itself) << IdLoc->Loc; + continue; + } + ParamIdents.push_back(IdLoc->Ident); + ParamLocs.push_back(IdLoc->Loc); + } + SmallVector<int, 1> FakeParamIndices(ParamIdents.size(), -2); + LifetimeCaptureByAttr *CapturedBy = ::new (Context) LifetimeCaptureByAttr( + Context, AL, FakeParamIndices.data(), FakeParamIndices.size()); + CapturedBy->setArgs(std::move(ParamIdents), std::move(ParamLocs)); + return CapturedBy; +} + +static void HandleLifetimeCaptureByAttr(Sema &S, Decl *D, + const ParsedAttr &AL) { + // Do not allow multiple attributes. + if (D->hasAttr<LifetimeCaptureByAttr>()) { + S.Diag(AL.getLoc(), diag::err_capture_by_attribute_multiple) + << AL.getRange(); + return; + } + auto *PVD = dyn_cast<ParmVarDecl>(D); + assert(PVD); + auto *CaptureByAttr = S.ParseLifetimeCaptureByAttr(AL, PVD->getName()); + if (CaptureByAttr) + D->addAttr(CaptureByAttr); +} + +void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) { + bool HasImplicitThisParam = isInstanceMethod(FD); + + llvm::StringMap<int> NameIdxMapping; + NameIdxMapping["global"] = -1; + NameIdxMapping["unknown"] = -1; + int Idx = 0; + if (HasImplicitThisParam) { + NameIdxMapping["this"] = 0; + Idx++; + } + for (const ParmVarDecl *PVD : FD->parameters()) + NameIdxMapping[PVD->getName()] = Idx++; + auto HandleCaptureBy = [&](LifetimeCaptureByAttr *CapturedBy) { + if (!CapturedBy) + return; + const auto &Entities = CapturedBy->getArgIdents(); + for (size_t I = 0; I < Entities.size(); ++I) { + StringRef Name = Entities[I]->getName(); + auto It = NameIdxMapping.find(Name); + if (It == NameIdxMapping.end()) { + auto Loc = CapturedBy->getArgLocs()[I]; + if (!HasImplicitThisParam && Name == "this") + Diag(Loc, diag::err_capture_by_implicit_this_not_available) << Loc; + else + Diag(Loc, diag::err_capture_by_attribute_argument_unknown) + << Entities[I] << Loc; + continue; + } + CapturedBy->setParamIdx(I, It->second); + } + }; + for (ParmVarDecl *PVD : FD->parameters()) + HandleCaptureBy(PVD->getAttr<LifetimeCaptureByAttr>()); + if (!HasImplicitThisParam) + return; + TypeSourceInfo *TSI = FD->getTypeSourceInfo(); + if (!TSI) + return; + AttributedTypeLoc ATL; + for (TypeLoc TL = TSI->getTypeLoc(); + (ATL = TL.getAsAdjusted<AttributedTypeLoc>()); + TL = ATL.getModifiedLoc()) { + auto *A = ATL.getAttrAs<LifetimeCaptureByAttr>(); + if (!A) + continue; + HandleCaptureBy(const_cast<LifetimeCaptureByAttr *>(A)); + } +} + static bool isFunctionLike(const Type &T) { // Check for explicit function types. // 'called_once' is only supported in Objective-C and it has @@ -6618,6 +6717,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_Callback: handleCallbackAttr(S, D, AL); break; + case ParsedAttr::AT_LifetimeCaptureBy: + HandleLifetimeCaptureByAttr(S, D, AL); + break; case ParsedAttr::AT_CalledOnce: handleCalledOnceAttr(S, D, AL); break; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 80c252c79e4d7a..55b0f574170f74 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -13707,8 +13707,8 @@ QualType Sema::CheckAssignmentOperands(Expr *LHSExpr, ExprResult &RHS, CheckForNullPointerDereference(*this, LHSExpr); - AssignedEntity AE{LHSExpr}; - checkExprLifetime(*this, AE, RHS.get()); + CapturingEntity AE{LHSExpr}; + checkAssignmentLifetime(*this, AE, RHS.get()); if (getLangOpts().CPlusPlus20 && LHSType.isVolatileQualified()) { if (CompoundType.isNull()) { diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 4d11f2a43fcc6b..026a38e21103b2 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -7397,7 +7397,7 @@ PerformConstructorInitialization(Sema &S, void Sema::checkInitializerLifetime(const InitializedEntity &Entity, Expr *Init) { - return sema::checkExprLifetime(*this, Entity, Init); + return sema::checkInitLifetime(*this, Entity, Init); } static void DiagnoseNarrowingInInitList(Sema &S, diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index d304f322aced64..1c96ba890256c1 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -14800,8 +14800,8 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc, // Check for a self move. DiagnoseSelfMove(Args[0], Args[1], OpLoc); // lifetime check. - checkExprLifetime( - *this, AssignedEntity{Args[0], dyn_cast<CXXMethodDecl>(FnDecl)}, + checkAssignmentLifetime( + *this, CapturingEntity{Args[0], dyn_cast<CXXMethodDecl>(FnDecl)}, Args[1]); } if (ImplicitThis) { diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 950bd6db0359d1..edf99a25088686 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -8548,6 +8548,15 @@ static void HandleLifetimeBoundAttr(TypeProcessingState &State, } } +static void HandleLifetimeCaptureByAttr(TypeProcessingState &State, + QualType &CurType, ParsedAttr &PA) { + if (State.getDeclarator().isDeclarationOfFunction()) { + auto *Attr = State.getSema().ParseLifetimeCaptureByAttr(PA, "this"); + if (Attr) + CurType = State.getAttributedType(Attr, CurType, CurType); + } +} + static void HandleHLSLParamModifierAttr(TypeProcessingState &State, QualType &CurType, const ParsedAttr &Attr, Sema &S) { @@ -8709,6 +8718,10 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type, if (TAL == TAL_DeclChunk) HandleLifetimeBoundAttr(state, type, attr); break; + case ParsedAttr::AT_LifetimeCaptureBy: + if (TAL == TAL_DeclChunk) + HandleLifetimeCaptureByAttr(state, type, attr); + break; case ParsedAttr::AT_NoDeref: { // FIXME: `noderef` currently doesn't work correctly in [[]] syntax. diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp index 0fb997a5671085..7a0215fa3c576a 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -307,3 +307,97 @@ void test(StatusOr<FooView> foo1, StatusOr<NonAnnotatedFooView> foo2) { foo2 = NonAnnotatedFoo(); } } // namespace GH106372 + +namespace lifetime_capture_by { + +struct S { + const int *x; + void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x = &x; } + void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]); +}; + +/////////////////////////// +// Test for valid usages. +/////////////////////////// +[[clang::lifetime_capture_by(unknown)]] // expected-error {{'lifetime_capture_by' attribute only applies to parameters and implicit object parameters}} +void nonMember( + const int &x1 [[clang::lifetime_capture_by(s, t)]], + S &s, + S &t, + const int &x2 [[clang::lifetime_capture_by(12345 + 12)]], // expected-error {{'lifetime_capture_by' attribute argument 12345 + 12 is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}} + const int &x3 [[clang::lifetime_capture_by(abcdefgh)]], // expected-error {{'lifetime_capture_by' attribute argument 'abcdefgh' is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}} + const int &x4 [[clang::lifetime_capture_by("abcdefgh")]], // expected-error {{'lifetime_capture_by' attribute argument "abcdefgh" is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}} + const int &x5 [[clang::lifetime_capture_by(this)]], // expected-error {{'lifetime_capture_by' argument references unavailable implicit 'this'}} + const int &x6 [[clang::lifetime_capture_by()]], // expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}} + const int& x7 [[clang::lifetime_capture_by(u, + x7)]], // expected-error {{'lifetime_capture_by' argument references itself}} + const S& u + ) +{ + s.captureInt(x1); +} + +struct T { + void member( + const int &x [[clang::lifetime_capture_by(s)]], + S &s, + S &t, + const int &y [[clang::lifetime_capture_by(s)]], + const int &z [[clang::lifetime_capture_by(this, x, y)]], + const int &u [[clang::lifetime_capture_by(global, x, s)]]) + { + s.captureInt(x); + } +}; + +struct ThisIsCaptured { + void foo(S& s) [[clang::lifetime_capture_by(s)]]; + void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error {{'lifetime_capture_by' attribute argument 'abcd' is not a known function parameter}} + void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error {{'lifetime_capture_by' argument references itself}} +}; + +/////////////////////////// +// Detect dangling cases. +/////////////////////////// +void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s); +void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s); + +std::string_view substr(const std::string& s [[clang::lifetimebound]]); +std::string_view strcopy(const std::string& s); + +void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s); +void noCaptureSV(std::string_view x, S&s); + +void use() { + S s; + int local; + captureInt(1, // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}} + s); + captureInt(local, s); + + noCaptureInt(1, s); + noCaptureInt(local, s); + + std::string_view local_sv; + captureSV(local_sv, s); + captureSV(local_sv + local_sv, s); + captureSV(std::string(), // expected-warning {{object captured by the 's'}} + s); + captureSV(substr( + std::string() // expected-warning {{object captured by the 's'}} + ), s); + captureSV(strcopy(std::string()), s); + + noCaptureSV(local_sv, s); + noCaptureSV(std::string(), s); + noCaptureSV(substr(std::string()), s); + + s.captureInt(1); // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}} + s.captureSV(std::string()); // expected-warning {{object captured by the 's'}} + s.captureSV(substr(std::string())); // expected-warning {{object captured by the 's'}} + s.captureSV(strcopy(std::string())); +} +} // namespace lifetime_capture_by_usage + +// Test for templated code. +// 2 nested function calls foo(sv, bar(sv, setsv)); \ No newline at end of file >From 51d7dc80690b4416ee972ed1aef31f78b323923d Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Tue, 8 Oct 2024 08:19:56 +0000 Subject: [PATCH 2/7] start working on lifetime capture --- clang/lib/AST/TypePrinter.cpp | 16 ++-------------- clang/lib/Sema/CheckExprLifetime.cpp | 11 ++--------- clang/lib/Sema/CheckExprLifetime.h | 6 ------ clang/test/SemaCXX/attr-lifetimebound.cpp | 1 - 4 files changed, 4 insertions(+), 30 deletions(-) diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp index 02bece7cce51d7..52bf59a9717ecb 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1910,21 +1910,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T, return; } if (T->getAttrKind() == attr::LifetimeCaptureBy) { + // FIXME: Print the attribute arguments once we have a way to retrieve these + // here. OS << " [[clang::lifetime_capture_by(...)"; - // const LifetimeCaptureByAttr* A= T->getAs<LifetimeCaptureByAttr>(); - // bool valid = true; - // for (int I : A->params()) - // valid &= I != -2; - // if (valid) { - // OS << "invalid)"; - // return; - // } - // for (size_t I = 0; I < A->params_size(); ++I) { - // OS << A->getArgIdents()[I]->getName() - // << "(idx: " << *(A->params_begin() + I) << ")"; - // if (I != A->params_size() - 1) - // OS << ", "; - // } return; } diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index c0180add9a1c41..9e58961dfa4226 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -50,7 +50,7 @@ enum LifetimeKind { /// object to it. LK_Assignment, - /// The lifetime of a temporary bound to this entity probably ends too soon, + /// The lifetime of a temporary bound to this entity may end too soon, /// because the entity may capture the reference to a temporary object. LK_LifetimeCapture, }; @@ -1202,7 +1202,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef, return false; assert(shouldLifetimeExtendThroughPath(Path) == PathLifetimeKind::NoExtend && - "No lifetime extension for in function calls"); + "No lifetime extension in function calls"); SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured) << CEntity->Expression << DiagRange; return false; @@ -1435,13 +1435,6 @@ void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity, /*AEntity*/ nullptr, Init); } -void checkExprLifetimeMustTailArg(Sema &SemaRef, - const InitializedEntity &Entity, Expr *Init) { - checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail, - /*AEntity*/ nullptr, Init); -} - - void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity, Expr *RHS) { bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored( diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h index 9c5241e9b5d806..98562fba54b188 100644 --- a/clang/lib/Sema/CheckExprLifetime.h +++ b/clang/lib/Sema/CheckExprLifetime.h @@ -49,12 +49,6 @@ void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity, void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity, Expr *Captured); -/// Check that the lifetime of the given expr (and its subobjects) is -/// sufficient, assuming that it is passed as an argument to a musttail -/// function. -void checkExprLifetimeMustTailArg(Sema &SemaRef, - const InitializedEntity &Entity, Expr *Init); - } // namespace clang::sema #endif // LLVM_CLANG_SEMA_CHECK_EXPR_LIFETIME_H diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp index 7a0215fa3c576a..a7f15896e97a88 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -380,7 +380,6 @@ void use() { std::string_view local_sv; captureSV(local_sv, s); - captureSV(local_sv + local_sv, s); captureSV(std::string(), // expected-warning {{object captured by the 's'}} s); captureSV(substr( >From 37aca98638bee739aa43a7046bf6e83aa61c9d6b Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Tue, 8 Oct 2024 18:34:53 +0000 Subject: [PATCH 3/7] implement: 'this' is captured. --- clang/lib/Sema/CheckExprLifetime.cpp | 6 ++++++ clang/lib/Sema/CheckExprLifetime.h | 5 +++++ clang/lib/Sema/SemaChecking.cpp | 19 +++++++++++++++++++ clang/test/SemaCXX/attr-lifetimebound.cpp | 20 +++++++++++++++++--- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index 9e58961dfa4226..851d4cdb6575f5 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -1435,6 +1435,12 @@ void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity, /*AEntity*/ nullptr, Init); } +void checkExprLifetimeMustTailArg(Sema &SemaRef, + const InitializedEntity &Entity, Expr *Init) { + checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail, + /*AEntity*/ nullptr, Init); +} + void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity, Expr *RHS) { bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored( diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h index 98562fba54b188..976260d40e46bf 100644 --- a/clang/lib/Sema/CheckExprLifetime.h +++ b/clang/lib/Sema/CheckExprLifetime.h @@ -49,6 +49,11 @@ void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity, void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity, Expr *Captured); +/// Check that the lifetime of the given expr (and its subobjects) is +/// sufficient, assuming that it is passed as an argument to a musttail +/// function. +void checkExprLifetimeMustTailArg(Sema &SemaRef, + const InitializedEntity &Entity, Expr *Init); } // namespace clang::sema #endif // LLVM_CLANG_SEMA_CHECK_EXPR_LIFETIME_H diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 3f0452875e0e8f..89214afde06a7d 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -3246,6 +3246,25 @@ void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction, checkCaptureLifetime(*this, CE, Captured); } } + if (IsMemberFunction) { + TypeSourceInfo *TSI = FD->getTypeSourceInfo(); + if (!TSI) + return; + AttributedTypeLoc ATL; + for (TypeLoc TL = TSI->getTypeLoc(); + (ATL = TL.getAsAdjusted<AttributedTypeLoc>()); + TL = ATL.getModifiedLoc()) { + auto *CapturedByAttr = ATL.getAttrAs<LifetimeCaptureByAttr>(); + if (!CapturedByAttr) + continue; + Expr *Captured = GetArgAt(0); + for (int CapturingParamIdx : CapturedByAttr->params()) { + Expr *Capturing = GetArgAt(CapturingParamIdx); + CapturingEntity CE{Capturing}; + checkCaptureLifetime(*this, CE, Captured); + } + } + } } void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto, diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp index a7f15896e97a88..8cd1d4e643b640 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -351,7 +351,7 @@ struct T { }; struct ThisIsCaptured { - void foo(S& s) [[clang::lifetime_capture_by(s)]]; + void capture(S& s) [[clang::lifetime_capture_by(s)]]; void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error {{'lifetime_capture_by' attribute argument 'abcd' is not a known function parameter}} void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error {{'lifetime_capture_by' argument references itself}} }; @@ -366,9 +366,12 @@ std::string_view substr(const std::string& s [[clang::lifetimebound]]); std::string_view strcopy(const std::string& s); void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s); +void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s); void noCaptureSV(std::string_view x, S&s); void use() { + std::string_view local_sv; + std::string local_s; S s; int local; captureInt(1, // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}} @@ -378,23 +381,34 @@ void use() { noCaptureInt(1, s); noCaptureInt(local, s); - std::string_view local_sv; + // Capture using std::string_view. captureSV(local_sv, s); captureSV(std::string(), // expected-warning {{object captured by the 's'}} s); captureSV(substr( std::string() // expected-warning {{object captured by the 's'}} ), s); + captureSV(substr(local_s), s); captureSV(strcopy(std::string()), s); - + noCaptureSV(local_sv, s); noCaptureSV(std::string(), s); noCaptureSV(substr(std::string()), s); + // Capture using std::string. + captureS(std::string(), s); // expected-warning {{object captured by the 's'}} + captureS(local_s, s); + + // Member functions. s.captureInt(1); // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}} s.captureSV(std::string()); // expected-warning {{object captured by the 's'}} s.captureSV(substr(std::string())); // expected-warning {{object captured by the 's'}} s.captureSV(strcopy(std::string())); + + // This is captured. + ThisIsCaptured{}.capture(s); //expected-warning {{object captured by the 's'}} + ThisIsCaptured TIS; + TIS.capture(s); } } // namespace lifetime_capture_by_usage >From c1ed8622f3b42b1aa2dfd1fe6c19854185ccd553 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Tue, 8 Oct 2024 21:12:31 +0000 Subject: [PATCH 4/7] start working on implicit lifetime capture --- clang/include/clang/Sema/Sema.h | 3 +++ clang/lib/Sema/SemaAttr.cpp | 23 +++++++++++++++++++++++ clang/lib/Sema/SemaDecl.cpp | 3 ++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d90e9a4bbbcc19..ab2dc53ee10c3f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1759,6 +1759,9 @@ class Sema final : public SemaBase { /// Add [[clang:::lifetimebound]] attr for std:: functions and methods. void inferLifetimeBoundAttribute(FunctionDecl *FD); + /// Add [[clang:::lifetime_capture(this)]] to std:: methods. + void inferLifetimeCaptureByAttribute(FunctionDecl *FD); + /// Add [[gsl::Pointer]] attributes for std:: types. void inferGslPointerAttribute(TypedefNameDecl *TD); diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp index cf2a5a622a3a4d..3b505043f5db3c 100644 --- a/clang/lib/Sema/SemaAttr.cpp +++ b/clang/lib/Sema/SemaAttr.cpp @@ -13,6 +13,8 @@ #include "clang/AST/ASTConsumer.h" #include "clang/AST/Attr.h" +#include "clang/AST/Attrs.inc" +#include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Preprocessor.h" @@ -269,6 +271,27 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) { } } +void Sema::inferLifetimeCaptureByAttribute(FunctionDecl *FD) { + auto *MD = dyn_cast<CXXMethodDecl>(FD); + if (!MD || !MD->isInStdNamespace()) + return; + static const llvm::StringSet<> CapturingMethods{"insert", "push", + "push_front", "push_back"}; + if (!CapturingMethods.contains(MD->getName())) + return; + for (ParmVarDecl *PVD : MD->parameters()) { + if (PVD->hasAttr<LifetimeCaptureByAttr>()) + continue; + // PVD->dumpColor(); + // auto *RD = PVD->getType()->getAsCXXRecordDecl(); + // if (RD && RD->hasAttr<PointerAttr>()) { + // int CaptureByThis[] = {0}; // Replace with THIS. + // PVD->addAttr( + // LifetimeCaptureByAttr::CreateImplicit(Context, CaptureByThis, 1)); + // } + } +} + void Sema::inferNullableClassAttribute(CXXRecordDecl *CRD) { static const llvm::StringSet<> Nullable{ "auto_ptr", "shared_ptr", "unique_ptr", "exception_ptr", diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 71af8b1f22871a..4297ec2ccce701 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -16636,8 +16636,9 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) { } } - inferLifetimeBoundAttribute(FD); LazyProcessLifetimeCaptureByParams(FD); + inferLifetimeBoundAttribute(FD); + inferLifetimeCaptureByAttribute(FD); AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD); // If C++ exceptions are enabled but we are told extern "C" functions cannot >From 8ace66626fb165a814d5a84573b51e5b2a545b78 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Wed, 9 Oct 2024 14:50:12 +0000 Subject: [PATCH 5/7] define constants, fix false-positive for MTE of pointer-like types --- clang/include/clang/Basic/Attr.td | 8 +-- clang/include/clang/Basic/DiagnosticGroups.td | 1 + .../clang/Basic/DiagnosticSemaKinds.td | 5 +- clang/lib/Sema/CheckExprLifetime.cpp | 8 ++- clang/lib/Sema/SemaDeclAttr.cpp | 7 +-- clang/test/SemaCXX/attr-lifetimebound.cpp | 54 +++++++++++-------- 6 files changed, 50 insertions(+), 33 deletions(-) diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 5753b3cd79fdf9..af385802481a6d 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1895,10 +1895,10 @@ private: SmallVector<SourceLocation, 1> ArgLocs; public: - static const int INVALID = -2; - static const int UNKNOWN = -1; - static const int GLOBAL = -1; - static const int THIS = 0; + static constexpr int INVALID = -2; + static constexpr int UNKNOWN = -1; + static constexpr int GLOBAL = -1; + static constexpr int THIS = 0; void setArgs(SmallVector<IdentifierInfo*, 1> Idents, SmallVector<SourceLocation, 1> Locs) { diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 41e719d4d57816..a4c5f31dfec8d9 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -452,6 +452,7 @@ def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">; def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">; def DanglingAssignment: DiagGroup<"dangling-assignment">; def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">; +def DanglingCapture : DiagGroup<"dangling-capture">; def DanglingElse: DiagGroup<"dangling-else">; def DanglingField : DiagGroup<"dangling-field">; def DanglingInitializerList : DiagGroup<"dangling-initializer-list">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ca5be51791cf44..9e40766675266a 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10201,9 +10201,8 @@ def warn_dangling_pointer_assignment : Warning< "will be destroyed at the end of the full-expression">, InGroup<DanglingAssignment>; def warn_dangling_reference_captured : Warning< - "object captured by the '%0' " - "will be destroyed at the end of the full-expression">, - InGroup<DanglingAssignment>; + "object captured by '%0' will be destroyed at the end of the full-expression">, + InGroup<DanglingCapture>; // For non-floating point, expressions of the form x == x or x != x // should result in a warning, since these always evaluate to a constant. diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index 851d4cdb6575f5..faa001e8b93470 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -1413,8 +1413,14 @@ static void checkExprLifetimeImpl(Sema &SemaRef, llvm::SmallVector<IndirectLocalPathEntry, 8> Path; if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *CEntity)) Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init}); - else if (LK == LK_LifetimeCapture) + else if (LK == LK_LifetimeCapture) { + // Skip the top MaterializeTemoraryExpr if it is temporary object of the + // pointer-like type itself. + if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(Init); + MTE && isPointerLikeType(Init->getType())) + Init = MTE->getSubExpr(); Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init}); + } if (Init->isGLValue()) visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding, diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index b56f69198c788d..e9677fa92176ba 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -3893,7 +3893,8 @@ LifetimeCaptureByAttr *Sema::ParseLifetimeCaptureByAttr(const ParsedAttr &AL, ParamIdents.push_back(IdLoc->Ident); ParamLocs.push_back(IdLoc->Loc); } - SmallVector<int, 1> FakeParamIndices(ParamIdents.size(), -2); + SmallVector<int, 1> FakeParamIndices(ParamIdents.size(), + LifetimeCaptureByAttr::INVALID); LifetimeCaptureByAttr *CapturedBy = ::new (Context) LifetimeCaptureByAttr( Context, AL, FakeParamIndices.data(), FakeParamIndices.size()); CapturedBy->setArgs(std::move(ParamIdents), std::move(ParamLocs)); @@ -3919,8 +3920,8 @@ void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) { bool HasImplicitThisParam = isInstanceMethod(FD); llvm::StringMap<int> NameIdxMapping; - NameIdxMapping["global"] = -1; - NameIdxMapping["unknown"] = -1; + NameIdxMapping["global"] = LifetimeCaptureByAttr::GLOBAL; + NameIdxMapping["unknown"] = LifetimeCaptureByAttr::UNKNOWN; int Idx = 0; if (HasImplicitThisParam) { NameIdxMapping["this"] = 0; diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp index 8cd1d4e643b640..2435cfbb93f9cd 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -75,7 +75,6 @@ namespace usage_ok { } } -# 1 "<std>" 1 3 namespace std { using size_t = __SIZE_TYPE__; struct string { @@ -84,14 +83,16 @@ namespace std { char &operator[](size_t) const [[clang::lifetimebound]]; }; - string operator""s(const char *, size_t); + string operator""s(const char *, size_t); // expected-warning {{}} - struct string_view { - string_view(); - string_view(const char *p [[clang::lifetimebound]]); - string_view(const string &s [[clang::lifetimebound]]); + template<typename T> + struct basic_string_view { + basic_string_view(); + basic_string_view(const T *p [[clang::lifetimebound]]); + basic_string_view(const string &s [[clang::lifetimebound]]); }; - string_view operator""sv(const char *, size_t); + using string_view = basic_string_view<char>; + string_view operator""sv(const char *, size_t); // expected-warning {{}} struct vector { int *data(); @@ -100,7 +101,6 @@ namespace std { template<typename K, typename V> struct map {}; } -# 68 "attr-lifetimebound.cpp" 2 using std::operator""s; using std::operator""sv; @@ -238,11 +238,6 @@ template <class T> T *addressof(T &arg) { &const_cast<char &>(reinterpret_cast<const volatile char &>(arg))); } -template<typename T> -struct basic_string_view { - basic_string_view(const T *); -}; - template <class T> struct span { template<size_t _ArrayExtent> span(const T (&__arr)[_ArrayExtent]) noexcept; @@ -360,22 +355,28 @@ struct ThisIsCaptured { // Detect dangling cases. /////////////////////////// void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s); +void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s); void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s); std::string_view substr(const std::string& s [[clang::lifetimebound]]); std::string_view strcopy(const std::string& s); void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s); -void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s); +void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], S&s); void noCaptureSV(std::string_view x, S&s); +void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s); +void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s); + void use() { std::string_view local_sv; std::string local_s; S s; + // Capture an 'int'. int local; - captureInt(1, // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}} + captureInt(1, // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}} s); + captureRValInt(1, s); // expected-warning {{object captured by 's'}} captureInt(local, s); noCaptureInt(1, s); @@ -383,30 +384,39 @@ void use() { // Capture using std::string_view. captureSV(local_sv, s); - captureSV(std::string(), // expected-warning {{object captured by the 's'}} + captureSV(std::string(), // expected-warning {{object captured by 's'}} s); captureSV(substr( - std::string() // expected-warning {{object captured by the 's'}} + std::string() // expected-warning {{object captured by 's'}} ), s); captureSV(substr(local_s), s); captureSV(strcopy(std::string()), s); + captureRValSV(std::move(local_sv), s); + captureRValSV(std::string(), s); // expected-warning {{object captured by 's'}} + captureRValSV(std::string_view{"abcd"}, s); + captureRValSV(substr(local_s), s); + captureRValSV(substr(std::string()), s); // expected-warning {{object captured by 's'}} + captureRValSV(strcopy(std::string()), s); + noCaptureSV(local_sv, s); noCaptureSV(std::string(), s); noCaptureSV(substr(std::string()), s); // Capture using std::string. - captureS(std::string(), s); // expected-warning {{object captured by the 's'}} + captureS(std::string(), s); // expected-warning {{object captured by 's'}} captureS(local_s, s); + captureRValS(std::move(local_s), s); + captureRValS(std::string(), s); // expected-warning {{object captured by 's'}} // Member functions. - s.captureInt(1); // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}} - s.captureSV(std::string()); // expected-warning {{object captured by the 's'}} - s.captureSV(substr(std::string())); // expected-warning {{object captured by the 's'}} + s.captureInt(1); // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}} + s.captureSV(std::string()); // expected-warning {{object captured by 's'}} + s.captureSV(substr(std::string())); // expected-warning {{object captured by 's'}} s.captureSV(strcopy(std::string())); // This is captured. - ThisIsCaptured{}.capture(s); //expected-warning {{object captured by the 's'}} + ThisIsCaptured{}.capture(s); //expected-warning {{object captured by 's'}} ThisIsCaptured TIS; TIS.capture(s); } >From ff1404233d76f7a9cdae18f370858c48956ba853 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Wed, 9 Oct 2024 18:35:59 +0000 Subject: [PATCH 6/7] restructring tests --- clang/test/SemaCXX/attr-lifetimebound.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp index 2435cfbb93f9cd..fa235c384175ce 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -77,18 +77,20 @@ namespace usage_ok { namespace std { using size_t = __SIZE_TYPE__; - struct string { - string(); - string(const char*); + template<typename T> + struct basic_string { + basic_string(); + basic_string(const T*); char &operator[](size_t) const [[clang::lifetimebound]]; }; + using string = basic_string<char>; string operator""s(const char *, size_t); // expected-warning {{}} template<typename T> struct basic_string_view { basic_string_view(); - basic_string_view(const T *p [[clang::lifetimebound]]); + basic_string_view(const T *p); basic_string_view(const string &s [[clang::lifetimebound]]); }; using string_view = basic_string_view<char>; @@ -112,7 +114,7 @@ namespace p0936r0_examples { void f() { std::string_view sv = "hi"; std::string_view sv2 = sv + sv; // expected-warning {{temporary}} - sv2 = sv + sv; // FIXME: can we infer that we should warn here too? + sv2 = sv + sv; // expected-warning {{object backing the pointer}} } struct X { int a, b; }; >From cb27d7d5fb3baa352e447c775902432b41a31448 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Mon, 14 Oct 2024 12:12:06 +0000 Subject: [PATCH 7/7] move test to existing file --- .../Sema/warn-lifetime-analysis-nocfg.cpp | 87 +++++++++++++ .../test/SemaCXX/attr-lifetime-capture-by.cpp | 40 ++++++ clang/test/SemaCXX/attr-lifetimebound.cpp | 122 ------------------ 3 files changed, 127 insertions(+), 122 deletions(-) create mode 100644 clang/test/SemaCXX/attr-lifetime-capture-by.cpp diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp index 731639ab16a735..d551859b9ea639 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -778,3 +778,90 @@ void test13() { } } // namespace GH100526 + + +namespace lifetime_capture_by { +struct S { + const int *x; + void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x = &x; } + void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]); +}; +/////////////////////////// +// Detect dangling cases. +/////////////////////////// +void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s); +void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s); +void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s); + +std::string_view substr(const std::string& s [[clang::lifetimebound]]); +std::string_view strcopy(const std::string& s); + +void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s); +void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], S&s); +void noCaptureSV(std::string_view x, S&s); + +void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s); +void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s); + +struct ThisIsCaptured { + void capture(S& s) [[clang::lifetime_capture_by(s)]]; + void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error {{'lifetime_capture_by' attribute argument 'abcd' is not a known function parameter}} + void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error {{'lifetime_capture_by' argument references itself}} +}; + +void use() { + std::string_view local_sv; + std::string local_s; + S s; + // Capture an 'int'. + int local; + captureInt(1, // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}} + s); + captureRValInt(1, s); // expected-warning {{object captured by 's'}} + captureInt(local, s); + + noCaptureInt(1, s); + noCaptureInt(local, s); + + // Capture using std::string_view. + captureSV(local_sv, s); + captureSV(std::string(), // expected-warning {{object captured by 's'}} + s); + captureSV(substr( + std::string() // expected-warning {{object captured by 's'}} + ), s); + captureSV(substr(local_s), s); + captureSV(strcopy(std::string()), s); + + captureRValSV(std::move(local_sv), s); + captureRValSV(std::string(), s); // expected-warning {{object captured by 's'}} + captureRValSV(std::string_view{"abcd"}, s); + captureRValSV(substr(local_s), s); + captureRValSV(substr(std::string()), s); // expected-warning {{object captured by 's'}} + captureRValSV(strcopy(std::string()), s); + + noCaptureSV(local_sv, s); + noCaptureSV(std::string(), s); + noCaptureSV(substr(std::string()), s); + + // Capture using std::string. + captureS(std::string(), s); // expected-warning {{object captured by 's'}} + captureS(local_s, s); + captureRValS(std::move(local_s), s); + captureRValS(std::string(), s); // expected-warning {{object captured by 's'}} + + // Member functions. + s.captureInt(1); // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}} + s.captureSV(std::string()); // expected-warning {{object captured by 's'}} + s.captureSV(substr(std::string())); // expected-warning {{object captured by 's'}} + s.captureSV(strcopy(std::string())); + + // This is captured. + ThisIsCaptured{}.capture(s); //expected-warning {{object captured by 's'}} + ThisIsCaptured TIS; + TIS.capture(s); +} +} // namespace lifetime_capture_by_usage + +// Test for templated code. +// 2 nested function calls foo(sv, bar(sv, setsv)); \ No newline at end of file diff --git a/clang/test/SemaCXX/attr-lifetime-capture-by.cpp b/clang/test/SemaCXX/attr-lifetime-capture-by.cpp new file mode 100644 index 00000000000000..192ec9bec640a5 --- /dev/null +++ b/clang/test/SemaCXX/attr-lifetime-capture-by.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -std=c++23 -verify %s + +struct S { + const int *x; + void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x = &x; } +}; + +/////////////////////////// +// Test for valid usages. +/////////////////////////// +[[clang::lifetime_capture_by(unknown)]] // expected-error {{'lifetime_capture_by' attribute only applies to parameters and implicit object parameters}} +void nonMember( + const int &x1 [[clang::lifetime_capture_by(s, t)]], + S &s, + S &t, + const int &x2 [[clang::lifetime_capture_by(12345 + 12)]], // expected-error {{'lifetime_capture_by' attribute argument 12345 + 12 is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}} + const int &x3 [[clang::lifetime_capture_by(abcdefgh)]], // expected-error {{'lifetime_capture_by' attribute argument 'abcdefgh' is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}} + const int &x4 [[clang::lifetime_capture_by("abcdefgh")]], // expected-error {{'lifetime_capture_by' attribute argument "abcdefgh" is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}} + const int &x5 [[clang::lifetime_capture_by(this)]], // expected-error {{'lifetime_capture_by' argument references unavailable implicit 'this'}} + const int &x6 [[clang::lifetime_capture_by()]], // expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}} + const int& x7 [[clang::lifetime_capture_by(u, + x7)]], // expected-error {{'lifetime_capture_by' argument references itself}} + const S& u + ) +{ + s.captureInt(x1); +} + +struct T { + void member( + const int &x [[clang::lifetime_capture_by(s)]], + S &s, + S &t, + const int &y [[clang::lifetime_capture_by(s)]], + const int &z [[clang::lifetime_capture_by(this, x, y)]], + const int &u [[clang::lifetime_capture_by(global, x, s)]]) + { + s.captureInt(x); + } +}; diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp index fa235c384175ce..edeb33eaeb1dd8 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -304,125 +304,3 @@ void test(StatusOr<FooView> foo1, StatusOr<NonAnnotatedFooView> foo2) { foo2 = NonAnnotatedFoo(); } } // namespace GH106372 - -namespace lifetime_capture_by { - -struct S { - const int *x; - void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x = &x; } - void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]); -}; - -/////////////////////////// -// Test for valid usages. -/////////////////////////// -[[clang::lifetime_capture_by(unknown)]] // expected-error {{'lifetime_capture_by' attribute only applies to parameters and implicit object parameters}} -void nonMember( - const int &x1 [[clang::lifetime_capture_by(s, t)]], - S &s, - S &t, - const int &x2 [[clang::lifetime_capture_by(12345 + 12)]], // expected-error {{'lifetime_capture_by' attribute argument 12345 + 12 is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}} - const int &x3 [[clang::lifetime_capture_by(abcdefgh)]], // expected-error {{'lifetime_capture_by' attribute argument 'abcdefgh' is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}} - const int &x4 [[clang::lifetime_capture_by("abcdefgh")]], // expected-error {{'lifetime_capture_by' attribute argument "abcdefgh" is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}} - const int &x5 [[clang::lifetime_capture_by(this)]], // expected-error {{'lifetime_capture_by' argument references unavailable implicit 'this'}} - const int &x6 [[clang::lifetime_capture_by()]], // expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}} - const int& x7 [[clang::lifetime_capture_by(u, - x7)]], // expected-error {{'lifetime_capture_by' argument references itself}} - const S& u - ) -{ - s.captureInt(x1); -} - -struct T { - void member( - const int &x [[clang::lifetime_capture_by(s)]], - S &s, - S &t, - const int &y [[clang::lifetime_capture_by(s)]], - const int &z [[clang::lifetime_capture_by(this, x, y)]], - const int &u [[clang::lifetime_capture_by(global, x, s)]]) - { - s.captureInt(x); - } -}; - -struct ThisIsCaptured { - void capture(S& s) [[clang::lifetime_capture_by(s)]]; - void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error {{'lifetime_capture_by' attribute argument 'abcd' is not a known function parameter}} - void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error {{'lifetime_capture_by' argument references itself}} -}; - -/////////////////////////// -// Detect dangling cases. -/////////////////////////// -void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s); -void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s); -void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s); - -std::string_view substr(const std::string& s [[clang::lifetimebound]]); -std::string_view strcopy(const std::string& s); - -void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s); -void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], S&s); -void noCaptureSV(std::string_view x, S&s); - -void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s); -void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s); - -void use() { - std::string_view local_sv; - std::string local_s; - S s; - // Capture an 'int'. - int local; - captureInt(1, // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}} - s); - captureRValInt(1, s); // expected-warning {{object captured by 's'}} - captureInt(local, s); - - noCaptureInt(1, s); - noCaptureInt(local, s); - - // Capture using std::string_view. - captureSV(local_sv, s); - captureSV(std::string(), // expected-warning {{object captured by 's'}} - s); - captureSV(substr( - std::string() // expected-warning {{object captured by 's'}} - ), s); - captureSV(substr(local_s), s); - captureSV(strcopy(std::string()), s); - - captureRValSV(std::move(local_sv), s); - captureRValSV(std::string(), s); // expected-warning {{object captured by 's'}} - captureRValSV(std::string_view{"abcd"}, s); - captureRValSV(substr(local_s), s); - captureRValSV(substr(std::string()), s); // expected-warning {{object captured by 's'}} - captureRValSV(strcopy(std::string()), s); - - noCaptureSV(local_sv, s); - noCaptureSV(std::string(), s); - noCaptureSV(substr(std::string()), s); - - // Capture using std::string. - captureS(std::string(), s); // expected-warning {{object captured by 's'}} - captureS(local_s, s); - captureRValS(std::move(local_s), s); - captureRValS(std::string(), s); // expected-warning {{object captured by 's'}} - - // Member functions. - s.captureInt(1); // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}} - s.captureSV(std::string()); // expected-warning {{object captured by 's'}} - s.captureSV(substr(std::string())); // expected-warning {{object captured by 's'}} - s.captureSV(strcopy(std::string())); - - // This is captured. - ThisIsCaptured{}.capture(s); //expected-warning {{object captured by 's'}} - ThisIsCaptured TIS; - TIS.capture(s); -} -} // namespace lifetime_capture_by_usage - -// Test for templated code. -// 2 nested function calls foo(sv, bar(sv, setsv)); \ No newline at end of file _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits