https://github.com/usx95 created https://github.com/llvm/llvm-project/pull/111499
This implements the RFC https://discourse.llvm.org/t/rfc-introduce-clang-lifetime-capture-by-x/81371 >From b1368f676ac5f55741df021c2697d3b46fd2c92d Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Tue, 8 Oct 2024 08:19:56 +0000 Subject: [PATCH] start working on lifetime capture --- clang/include/clang/Basic/Attr.td | 35 ++++++++ .../clang/Basic/DiagnosticSemaKinds.td | 15 ++++ clang/include/clang/Sema/Sema.h | 5 ++ clang/lib/AST/TypePrinter.cpp | 1 + clang/lib/Sema/CheckExprLifetime.cpp | 26 +++++- clang/lib/Sema/CheckExprLifetime.h | 3 + clang/lib/Sema/SemaChecking.cpp | 26 ++++++ clang/lib/Sema/SemaDecl.cpp | 1 + clang/lib/Sema/SemaDeclAttr.cpp | 79 ++++++++++++++++++ clang/lib/Sema/SemaType.cpp | 13 +++ clang/test/SemaCXX/attr-lifetimebound.cpp | 80 +++++++++++++++++++ 11 files changed, 283 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 35b9716e13ff21..8307d8d19c7fec 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1869,6 +1869,41 @@ 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: + 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..94b2b9252ff834 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3382,6 +3382,17 @@ 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_init_method_bad_return_type : Error< "init methods must return an object pointer type, not %0">; def err_attribute_invalid_size : Error< @@ -10185,6 +10196,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..26b648868d7eb4 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1830,6 +1830,8 @@ class Sema final : public SemaBase { /// Add [[gsl::Pointer]] attributes for std:: types. void inferGslPointerAttribute(TypedefNameDecl *TD); + void lazyProcessLifetimeCaptureByParams(FunctionDecl *FD); + /// Add _Nullable attributes for std:: types. void inferNullableClassAttribute(CXXRecordDecl *CRD); @@ -2384,6 +2386,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..d7f447ebf0f9ed 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1966,6 +1966,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..55129c0c04ebb5 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -45,6 +45,8 @@ enum LifetimeKind { /// because the entity is a pointer and we assign the address of a temporary /// object to it. LK_Assignment, + + LK_Capture, }; using LifetimeResult = llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>; @@ -189,6 +191,7 @@ struct IndirectLocalPathEntry { VarInit, LValToRVal, LifetimeBoundCall, + LifetimeCapture, TemporaryCopy, LambdaCaptureInit, GslReferenceInit, @@ -898,6 +901,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 +932,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: @@ -961,7 +966,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *ExtendingEntity, LifetimeKind LK, const AssignedEntity *AEntity, Expr *Init) { - assert((AEntity && LK == LK_Assignment) || + assert((AEntity && (LK == LK_Assignment || LK == LK_Capture)) || (InitEntity && LK != LK_Assignment)); // If this entity doesn't have an interesting lifetime, don't bother looking // for temporaries within its initializer. @@ -1046,6 +1051,17 @@ static void checkExprLifetimeImpl(Sema &SemaRef, break; } + case LK_Capture: { + if (!MTE) + return false; + assert(shouldLifetimeExtendThroughPath(Path) == + PathLifetimeKind::NoExtend && + "No lifetime extension for in function calls"); + SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured) + << AEntity->LHS << DiagRange; + return false; + } + case LK_Assignment: { if (!MTE || pathContainsInit(Path)) return false; @@ -1199,6 +1215,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef, break; case IndirectLocalPathEntry::LifetimeBoundCall: + case IndirectLocalPathEntry::LifetimeCapture: case IndirectLocalPathEntry::TemporaryCopy: case IndirectLocalPathEntry::GslPointerInit: case IndirectLocalPathEntry::GslReferenceInit: @@ -1245,6 +1262,8 @@ static void checkExprLifetimeImpl(Sema &SemaRef, llvm::SmallVector<IndirectLocalPathEntry, 8> Path; if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity)) Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init}); + else if (LK == LK_Capture) + Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init}); if (Init->isGLValue()) visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding, @@ -1281,4 +1300,9 @@ void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, Init); } +void checkCaptureLifetime(Sema &SemaRef, const AssignedEntity &Entity, + Expr *Init) { + checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr, + /*ExtendingEntity=*/nullptr, LK_Capture, &Entity, Init); +} } // namespace clang::sema diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h index 8c8d0806dee0a3..174d86da86ddff 100644 --- a/clang/lib/Sema/CheckExprLifetime.h +++ b/clang/lib/Sema/CheckExprLifetime.h @@ -35,6 +35,9 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity, /// sufficient for assigning to the entity. void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, Expr *Init); +void checkCaptureLifetime(Sema &SemaRef, const AssignedEntity &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 99500daca295c9..3388ff5fb7310c 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,28 @@ 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 *LHS = GetArgAt(CapturingParamIdx); + Expr *RHS = GetArgAt(I + IsMemberFunction); + AssignedEntity AE{LHS}; + checkCaptureLifetime(*this, AE, RHS); + } + } +} + void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto, const Expr *ThisArg, ArrayRef<const Expr *> Args, bool IsMemberFunction, SourceLocation Loc, @@ -3244,6 +3268,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..4e49cca7f09265 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..67b27c6bbd86d6 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,81 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) { S.Context, AL, EncodingIndices.data(), EncodingIndices.size())); } +static void HandleLifetimeCaptureByAttr(Sema &S, Decl *D, + const ParsedAttr &AL) { + // Atleast one capture by is required. TODO() + if (AL.getNumArgs() == 0) { + S.Diag(AL.getLoc(), diag::err_capture_by_attribute_no_entity) + << AL.getRange(); + return; + } + + ParmVarDecl *PVD = dyn_cast<ParmVarDecl>(D); + if (!PVD) { + llvm::errs() << "Should be attached only to a function parameter;\n"; + return; + } + SmallVector<IdentifierInfo *, 1> ParamIdents; + SmallVector<SourceLocation, 1> ParamLocs; + for (unsigned I = 0; I < AL.getNumArgs(); ++I) { + if (AL.isArgIdent(I)) { + IdentifierLoc *IdLoc = AL.getArgAsIdent(I); + ParamIdents.push_back(IdLoc->Ident); + ParamLocs.push_back(IdLoc->Loc); + } else if (AL.isArgExpr(I)) { + Expr *E = AL.getArgAsExpr(I); + S.Diag(E->getExprLoc(), diag::err_capture_by_attribute_argument_unknown) + << E << E->getExprLoc(); + } + } + // Do not allow multiple attributes. + if (D->hasAttr<LifetimeCaptureByAttr>()) { + S.Diag(AL.getLoc(), diag::err_capture_by_attribute_multiple) + << AL.getRange(); + return; + } + SmallVector<int, 1> FakeParamIndices(ParamIdents.size(), -1); + LifetimeCaptureByAttr *CapturedBy = ::new (S.Context) LifetimeCaptureByAttr( + S.Context, AL, FakeParamIndices.data(), FakeParamIndices.size()); + CapturedBy->setArgs(std::move(ParamIdents), std::move(ParamLocs)); + D->addAttr(CapturedBy); +} + +void Sema::lazyProcessLifetimeCaptureByParams(FunctionDecl *FD) { + bool HasImplicitThisParam = isInstanceMethod(FD); + + llvm::StringMap<std::pair<int, QualType>> NameIdxMapping; + NameIdxMapping["global"] = {-1, {}}; + NameIdxMapping["unknown"] = {-1, {}}; + int Idx = 0; + if (HasImplicitThisParam) { + NameIdxMapping["this"] = {0, dyn_cast<CXXMethodDecl>(FD)->getThisType()}; + Idx++; + } + for (const ParmVarDecl *PVD : FD->parameters()) + NameIdxMapping[PVD->getName()] = {Idx++, PVD->getType()}; + for (ParmVarDecl *PVD : FD->parameters()) { + auto *CapturedBy = PVD->getAttr<LifetimeCaptureByAttr>(); + if (!CapturedBy) + continue; + 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.first); + } + } +} + static bool isFunctionLike(const Type &T) { // Check for explicit function types. // 'called_once' is only supported in Objective-C and it has @@ -6618,6 +6694,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/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 950bd6db0359d1..8305b24b469558 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 &Attr) { + // if (State.getDeclarator().isDeclarationOfFunction()) { + // CurType = State.getAttributedType( + // createSimpleAttr<LifetimeCaptureByAttr>(State.getSema().Context, + // 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..5de4f47ac176dd 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -307,3 +307,83 @@ void test(StatusOr<FooView> foo1, StatusOr<NonAnnotatedFooView> foo2) { foo2 = NonAnnotatedFoo(); } } // namespace GH106372 + +namespace lifetime_capture_by { + +struct S { + const int *x; + void setX(const int *x) { 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)]], + const S& u + ) +{ + s.setX(&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.setX(&x); + } +}; + +/////////////////////////// +// Detect dangling warning. +/////////////////////////// +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(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); +} +} // namespace lifetime_capture_by_usage + +// Test for templated code. \ 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