https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/115921
>From 2cef37ecdb81452a8f5882dfe765167c1e45b7b6 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Wed, 13 Nov 2024 10:24:33 +0000 Subject: [PATCH 1/3] Implement semantics for lifetime analysis --- clang/include/clang/Basic/DiagnosticGroups.td | 2 + .../clang/Basic/DiagnosticSemaKinds.td | 7 +- clang/include/clang/Sema/Sema.h | 3 + clang/lib/Sema/CheckExprLifetime.cpp | 66 ++++++++--- clang/lib/Sema/CheckExprLifetime.h | 13 +++ clang/lib/Sema/SemaChecking.cpp | 47 +++++++- .../Sema/warn-lifetime-analysis-nocfg.cpp | 105 ++++++++++++++++++ clang/test/SemaCXX/attr-lifetimebound.cpp | 4 +- 8 files changed, 228 insertions(+), 19 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 72eada50a56cc9..df9bf94b5d0398 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -453,6 +453,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">; @@ -462,6 +463,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">; def : DiagGroup<"return-local-addr", [ReturnStackAddress]>; def Dangling : DiagGroup<"dangling", [DanglingAssignment, DanglingAssignmentGsl, + DanglingCapture, DanglingField, DanglingInitializerList, DanglingGsl, diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 2f5d672e2f0035..58745d450ed63f 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10132,10 +10132,10 @@ def err_lifetimebound_ctor_dtor : Error< "%select{constructor|destructor}0">; def err_lifetimebound_parameter_void_return_type : Error< "'lifetimebound' attribute cannot be applied to a parameter of a function " - "that returns void">; + "that returns void; did you mean 'lifetime_capture_by(X)'">; def err_lifetimebound_implicit_object_parameter_void_return_type : Error< "'lifetimebound' attribute cannot be applied to an implicit object " - "parameter of a function that returns void">; + "parameter of a function that returns void; did you mean 'lifetime_capture_by(X)'">; // CHECK: returning address/reference of stack memory def warn_ret_stack_addr_ref : Warning< @@ -10230,6 +10230,9 @@ def warn_dangling_pointer_assignment : Warning< "object backing %select{|the pointer }0%1 " "will be destroyed at the end of the full-expression">, InGroup<DanglingAssignment>; +def warn_dangling_reference_captured : Warning< + "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/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d6f3508a5243f3..6ea6c67447b6f0 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2323,6 +2323,9 @@ class Sema final : public SemaBase { bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = false); 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/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index a1a402b4a2b530..81e26f48fb8851 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -45,10 +45,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 may end too soon, + /// because the entity may capture the reference to a temporary object. + LK_LifetimeCapture, }; using LifetimeResult = llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>; @@ -193,6 +197,7 @@ struct IndirectLocalPathEntry { VarInit, LValToRVal, LifetimeBoundCall, + LifetimeCapture, TemporaryCopy, LambdaCaptureInit, GslReferenceInit, @@ -249,9 +254,10 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path, LocalVisitor Visit); template <typename T> static bool isRecordWithAttr(QualType Type) { - if (auto *RD = Type->getAsCXXRecordDecl()) - return RD->hasAttr<T>(); - return false; + CXXRecordDecl *RD = Type.getNonReferenceType()->getAsCXXRecordDecl(); + if (!RD) + return false; + return RD->hasAttr<T>(); } // Decl::isInStdNamespace will return false for iterators in some STL @@ -1049,6 +1055,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: @@ -1082,6 +1089,7 @@ static bool pathOnlyHandlesGslPointer(const IndirectLocalPath &Path) { case IndirectLocalPathEntry::VarInit: case IndirectLocalPathEntry::AddressOf: case IndirectLocalPathEntry::LifetimeBoundCall: + case IndirectLocalPathEntry::LifetimeCapture: continue; case IndirectLocalPathEntry::GslPointerInit: case IndirectLocalPathEntry::GslReferenceInit: @@ -1110,12 +1118,13 @@ static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef, isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator))); } -static void checkExprLifetimeImpl(Sema &SemaRef, - const InitializedEntity *InitEntity, - const InitializedEntity *ExtendingEntity, - LifetimeKind LK, - const AssignedEntity *AEntity, Expr *Init) { +static void +checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity, + const InitializedEntity *ExtendingEntity, LifetimeKind LK, + const AssignedEntity *AEntity, + const CapturingEntity *CapEntity, Expr *Init) { assert((AEntity && LK == LK_Assignment) || + (CapEntity && LK == LK_LifetimeCapture) || (InitEntity && LK != LK_Assignment)); // If this entity doesn't have an interesting lifetime, don't bother looking // for temporaries within its initializer. @@ -1199,6 +1208,17 @@ static void checkExprLifetimeImpl(Sema &SemaRef, break; } + case LK_LifetimeCapture: { + if (!MTE) + return false; + assert(shouldLifetimeExtendThroughPath(Path) == + PathLifetimeKind::NoExtend && + "No lifetime extension in function calls"); + SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured) + << CapEntity->Entity << DiagRange; + return false; + } + case LK_Assignment: { if (!MTE || pathContainsInit(Path)) return false; @@ -1359,6 +1379,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef, break; case IndirectLocalPathEntry::LifetimeBoundCall: + case IndirectLocalPathEntry::LifetimeCapture: case IndirectLocalPathEntry::TemporaryCopy: case IndirectLocalPathEntry::GslPointerInit: case IndirectLocalPathEntry::GslReferenceInit: @@ -1411,7 +1432,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef, // warnings or errors on inner temporaries within this one's initializer. return false; }; - + bool HasReferenceBinding = Init->isGLValue(); llvm::SmallVector<IndirectLocalPathEntry, 8> Path; if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity)) { @@ -1420,9 +1441,18 @@ static void checkExprLifetimeImpl(Sema &SemaRef, ? IndirectLocalPathEntry::LifetimeBoundCall : IndirectLocalPathEntry::GslPointerAssignment, Init}); + } else if (LK == LK_LifetimeCapture) { + Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init}); + if (isRecordWithAttr<PointerAttr>(Init->getType())) + HasReferenceBinding = false; + // Skip the top MTE if it is a temporary object of the pointer-like type + // itself. + if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(Init); + MTE && isPointerLikeType(Init->getType())) + Init = MTE->getSubExpr(); } - if (Init->isGLValue()) + if (HasReferenceBinding) visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding, TemporaryVisitor); else @@ -1438,13 +1468,13 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity, LifetimeKind LK = LTResult.getInt(); const InitializedEntity *ExtendingEntity = LTResult.getPointer(); checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK, - /*AEntity*/ nullptr, Init); + /*AEntity*/ nullptr, /*CapEntity=*/nullptr, Init); } void checkExprLifetimeMustTailArg(Sema &SemaRef, const InitializedEntity &Entity, Expr *Init) { checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail, - /*AEntity*/ nullptr, Init); + /*AEntity*/ nullptr, /*CapEntity=*/nullptr, Init); } void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, @@ -1460,7 +1490,15 @@ void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr, /*ExtendingEntity=*/nullptr, LK_Assignment, &Entity, - Init); + /*CapEntity=*/nullptr, Init); +} + +void checkExprLifetime(Sema &SemaRef, const CapturingEntity &Entity, + Expr *Init) { + return checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr, + /*ExtendingEntity=*/nullptr, LK_LifetimeCapture, + /*AEntity=*/nullptr, + /*CapEntity=*/&Entity, Init); } } // namespace clang::sema diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h index 903f312f3533e5..e109b73b280e9e 100644 --- a/clang/lib/Sema/CheckExprLifetime.h +++ b/clang/lib/Sema/CheckExprLifetime.h @@ -25,6 +25,16 @@ struct AssignedEntity { CXXMethodDecl *AssignmentOperator = nullptr; }; +struct CapturingEntity { + // In an function call involving a lifetime capture, this would be the + // argument capturing the lifetime of another argument. + // void addToSet(std::string_view sv [[clang::lifetime_capture_by(setsv)]], + // set<std::string_view>& setsv); + // set<std::string_view> setsv; + // addToSet(std::string(), setsv); // Here 'setsv' is the 'Entity'. + Expr *Entity = 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. @@ -35,6 +45,9 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity, /// sufficient for assigning to the entity. void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, Expr *Init); +void checkExprLifetime(Sema &SemaRef, const CapturingEntity &Entity, + Expr *Init); + /// 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. diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 96008b14225a4c..27edb56df3abc7 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// +#include "CheckExprLifetime.h" #include "clang/AST/APValue.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" @@ -3229,6 +3230,49 @@ 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' outlives the 'Capturing' entity. + checkExprLifetime(*this, CE, Captured); + } + } + // Check when the 'this' object is 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}; + checkExprLifetime(*this, CE, Captured); + } + } + } +} + void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto, const Expr *ThisArg, ArrayRef<const Expr *> Args, bool IsMemberFunction, SourceLocation Loc, @@ -3269,7 +3313,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/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp index 6a2af01ea5116c..49bbc05c009ff9 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -793,3 +793,108 @@ 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); +const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]); +const std::string* getPointerNoLB(const std::string& s); +void capturePointer(const 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 lifetimebound pointer. + capturePointer(getPointerLB(std::string()), s); // expected-warning {{object captured by 's'}} + capturePointer(getPointerLB(*getPointerLB(std::string())), s); // expected-warning {{object captured by 's'}} + capturePointer(getPointerNoLB(std::string()), 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'}} + 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); +} +class [[gsl::Pointer()]] my_string_view : public std::string_view {}; +class my_string_view_not_pointer : public std::string_view {}; +std::optional<std::string_view> getOptionalSV(); +std::optional<std::string> getOptionalS(); +std::optional<my_string_view> getOptionalMySV(); +std::optional<my_string_view_not_pointer> getOptionalMySVNotP(); +my_string_view getMySV(); +my_string_view_not_pointer getMySVNotP(); + +template<class T> +struct MySet { +void insert(T&& t [[clang::lifetime_capture_by(this)]]); +void insert(const T& t [[clang::lifetime_capture_by(this)]]); +}; +void user_defined_containers() { + MySet<int> set_of_int; + set_of_int.insert(1); // expected-warning {{object captured by 'set_of_int' will be destroyed}} + MySet<std::string_view> set_of_sv; + set_of_sv.insert(std::string()); // expected-warning {{object captured by 'set_of_sv' will be destroyed}} +} +} // namespace lifetime_capture_by +// 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-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp index 81e9193cf76a04..f89b556f5bba08 100644 --- a/clang/test/SemaCXX/attr-lifetimebound.cpp +++ b/clang/test/SemaCXX/attr-lifetimebound.cpp @@ -1,7 +1,7 @@ // RUN: %clang_cc1 -std=c++23 -verify %s namespace usage_invalid { - void void_return(int ¶m [[clang::lifetimebound]]); // expected-error {{'lifetimebound' attribute cannot be applied to a parameter of a function that returns void}} + void void_return(int ¶m [[clang::lifetimebound]]); // expected-error {{'lifetimebound' attribute cannot be applied to a parameter of a function that returns void; did you mean 'lifetime_capture_by(X)'}} int *not_class_member() [[clang::lifetimebound]]; // expected-error {{non-member function has no implicit object parameter}} struct A { @@ -11,7 +11,7 @@ namespace usage_invalid { int *explicit_object(this A&) [[clang::lifetimebound]]; // expected-error {{explicit object member function has no implicit object parameter}} int not_function [[clang::lifetimebound]]; // expected-error {{only applies to parameters and implicit object parameters}} int [[clang::lifetimebound]] also_not_function; // expected-error {{cannot be applied to types}} - void void_return_member() [[clang::lifetimebound]]; // expected-error {{'lifetimebound' attribute cannot be applied to an implicit object parameter of a function that returns void}} + void void_return_member() [[clang::lifetimebound]]; // expected-error {{'lifetimebound' attribute cannot be applied to an implicit object parameter of a function that returns void; did you mean 'lifetime_capture_by(X)'}} }; int *attr_with_param(int ¶m [[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}} } >From 8522c58c9778463e0e67b1bf49dc26408d906d50 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Wed, 13 Nov 2024 14:48:58 +0000 Subject: [PATCH 2/3] add tests for template containers and implement capture by global/unknown --- .../clang/Basic/DiagnosticSemaKinds.td | 2 +- clang/lib/Sema/CheckExprLifetime.cpp | 8 +- clang/lib/Sema/CheckExprLifetime.h | 6 +- clang/lib/Sema/SemaChecking.cpp | 6 +- .../Sema/warn-lifetime-analysis-nocfg.cpp | 171 ++++++++++++++---- 5 files changed, 155 insertions(+), 38 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 58745d450ed63f..62c1fc7e001d46 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10231,7 +10231,7 @@ 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 '%0' will be destroyed at the end of the full-expression">, + "object whose reference is captured%select{| by '%1'}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 diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index 81e26f48fb8851..7f52055d4a9c61 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -1214,8 +1214,12 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity, assert(shouldLifetimeExtendThroughPath(Path) == PathLifetimeKind::NoExtend && "No lifetime extension in function calls"); - SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured) - << CapEntity->Entity << DiagRange; + if (CapEntity->Entity != nullptr) + SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured) + << true << CapEntity->Entity << DiagRange; + else + SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured) + << false << "<ignore>" << DiagRange; return false; } diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h index e109b73b280e9e..bcc29f502708c6 100644 --- a/clang/lib/Sema/CheckExprLifetime.h +++ b/clang/lib/Sema/CheckExprLifetime.h @@ -26,12 +26,14 @@ struct AssignedEntity { }; struct CapturingEntity { - // In an function call involving a lifetime capture, this would be the - // argument capturing the lifetime of another argument. + // In an function call involving a lifetime capture, this would be the + // argument capturing the lifetime of another argument. // void addToSet(std::string_view sv [[clang::lifetime_capture_by(setsv)]], // set<std::string_view>& setsv); // set<std::string_view> setsv; // addToSet(std::string(), setsv); // Here 'setsv' is the 'Entity'. + // + // This is 'nullptr' when the capturing entity is 'global' or 'unknown'. Expr *Entity = nullptr; }; diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 27edb56df3abc7..9f8174d206a80a 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -16,6 +16,7 @@ #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" @@ -3233,7 +3234,10 @@ void Sema::CheckArgAlignment(SourceLocation Loc, NamedDecl *FDecl, void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction, const Expr *ThisArg, ArrayRef<const Expr *> Args) { - auto GetArgAt = [&](int Idx) { + auto GetArgAt = [&](int Idx) -> Expr * { + if (Idx == LifetimeCaptureByAttr::GLOBAL || + Idx == LifetimeCaptureByAttr::UNKNOWN) + return nullptr; if (IsMemberFunction && Idx == 0) return const_cast<Expr *>(ThisArg); return const_cast<Expr *>(Args[Idx - int(IsMemberFunction)]); diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp index 49bbc05c009ff9..9627054167b78f 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field -Wreturn-stack-address -verify %s +// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wdangling -Wdangling-field -Wreturn-stack-address -verify %s struct [[gsl::Owner(int)]] MyIntOwner { MyIntOwner(); int &operator*(); @@ -252,6 +252,19 @@ struct reference_wrapper { template<typename T> reference_wrapper<T> ref(T& t) noexcept; + +struct false_type { + static constexpr bool value = false; + constexpr operator bool() const noexcept { return value; } +}; +struct true_type { + static constexpr bool value = true; + constexpr operator bool() const noexcept { return value; } +}; + +template<class T> struct is_pointer : false_type {}; +template<class T> struct is_pointer<T*> : true_type {}; +template<class T> struct is_pointer<T* const> : true_type {}; } struct Unannotated { @@ -806,95 +819,189 @@ struct S { 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); + +const std::string& getLB(const std::string& s[[clang::lifetimebound]]); +const std::string& getLB(std::string_view sv[[clang::lifetimebound]]); const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]); const std::string* getPointerNoLB(const std::string& s); + void capturePointer(const 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 captureByGlobal(std::string_view s [[clang::lifetime_capture_by(global)]]); +void captureByUnknown(std::string_view s [[clang::lifetime_capture_by(unknown)]]); + 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}} + captureInt(1, // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}} s); - captureRValInt(1, s); // expected-warning {{object captured by 's'}} + captureRValInt(1, s); // expected-warning {{object whose reference is captured by 's'}} captureInt(local, s); noCaptureInt(1, s); noCaptureInt(local, s); - // Capture lifetimebound pointer. - capturePointer(getPointerLB(std::string()), s); // expected-warning {{object captured by 's'}} - capturePointer(getPointerLB(*getPointerLB(std::string())), s); // expected-warning {{object captured by 's'}} - capturePointer(getPointerNoLB(std::string()), s); - // Capture using std::string_view. captureSV(local_sv, s); - captureSV(std::string(), // expected-warning {{object captured by 's'}} + captureSV(std::string(), // expected-warning {{object whose reference is captured by 's'}} s); captureSV(substr( - std::string() // expected-warning {{object captured by 's'}} + std::string() // expected-warning {{object whose reference is 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(), s); // expected-warning {{object whose reference is 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(substr(std::string()), s); // expected-warning {{object whose reference is 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(std::string(), s); // expected-warning {{object whose reference is captured by 's'}} captureS(local_s, s); captureRValS(std::move(local_s), s); - captureRValS(std::string(), s); // expected-warning {{object captured by 's'}} + captureRValS(std::string(), s); // expected-warning {{object whose reference is captured by 's'}} + + // Capture with lifetimebound. + captureSV(getLB(std::string()), s); // expected-warning {{object whose reference is captured by 's'}} + captureSV(getLB(substr(std::string())), s); // expected-warning {{object whose reference is captured by 's'}} + captureSV(getLB(getLB( + std::string() // expected-warning {{object whose reference is captured by 's'}} + )), s); + capturePointer(getPointerLB(std::string()), s); // expected-warning {{object whose reference is captured by 's'}} + capturePointer(getPointerLB(*getPointerLB( + std::string() // expected-warning {{object whose reference is captured by 's'}} + )), s); + capturePointer(getPointerNoLB(std::string()), s); // Member functions. - s.captureInt(1); // expected-warning {{object captured by 's'}} - s.captureSV(std::string()); // expected-warning {{object captured by 's'}} - s.captureSV(substr(std::string())); // expected-warning {{object captured by 's'}} + s.captureInt(1); // expected-warning {{object whose reference is captured by 's'}} + s.captureSV(std::string()); // expected-warning {{object whose reference is captured by 's'}} + s.captureSV(substr(std::string())); // expected-warning {{object whose reference is captured by 's'}} s.captureSV(strcopy(std::string())); // 'this' is captured. - ThisIsCaptured{}.capture(s); // expected-warning {{object captured by 's'}} + ThisIsCaptured{}.capture(s); // expected-warning {{object whose reference is captured by 's'}} ThisIsCaptured TIS; TIS.capture(s); + + // capture by global. + captureByGlobal(std::string()); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}} + captureByGlobal(substr(std::string())); // expected-warning {{captured}} + captureByGlobal(local_s); + captureByGlobal(local_sv); + + // // capture by unknown. + captureByGlobal(std::string()); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}} + captureByGlobal(substr(std::string())); // expected-warning {{captured}} + captureByGlobal(local_s); + captureByGlobal(local_sv); } -class [[gsl::Pointer()]] my_string_view : public std::string_view {}; -class my_string_view_not_pointer : public std::string_view {}; -std::optional<std::string_view> getOptionalSV(); -std::optional<std::string> getOptionalS(); -std::optional<my_string_view> getOptionalMySV(); -std::optional<my_string_view_not_pointer> getOptionalMySVNotP(); -my_string_view getMySV(); -my_string_view_not_pointer getMySVNotP(); +template<typename T> struct IsPointerLikeTypeImpl : std::false_type {}; +template<> struct IsPointerLikeTypeImpl<std::string_view> : std::true_type {}; +template<typename T> concept IsPointerLikeType = std::is_pointer<T>::value || IsPointerLikeTypeImpl<T>::value; + +// Templated containers having no distinction between pointer-like and other element type. template<class T> struct MySet { -void insert(T&& t [[clang::lifetime_capture_by(this)]]); -void insert(const T& t [[clang::lifetime_capture_by(this)]]); + void insert(T&& t [[clang::lifetime_capture_by(this)]]); + void insert(const T& t [[clang::lifetime_capture_by(this)]]); }; void user_defined_containers() { MySet<int> set_of_int; - set_of_int.insert(1); // expected-warning {{object captured by 'set_of_int' will be destroyed}} + set_of_int.insert(1); // expected-warning {{object whose reference is captured by 'set_of_int' will be destroyed}} MySet<std::string_view> set_of_sv; - set_of_sv.insert(std::string()); // expected-warning {{object captured by 'set_of_sv' will be destroyed}} + set_of_sv.insert(std::string()); // expected-warning {{object whose reference is captured by 'set_of_sv' will be destroyed}} +} + +// Templated containers having **which distinguishes** between pointer-like and other element type. +template<class T> +struct MyVector { + void push_back(T&& t [[clang::lifetime_capture_by(this)]]) requires IsPointerLikeType<T>; + void push_back(const T& t [[clang::lifetime_capture_by(this)]]) requires IsPointerLikeType<T>; + + void push_back(T&& t) requires (!IsPointerLikeType<T>); + void push_back(const T& t) requires (!IsPointerLikeType<T>); +}; + +// Container of pointers. +struct [[gsl::Pointer()]] MyStringView : public std::string_view { + MyStringView(); + MyStringView(std::string_view&&); + MyStringView(const MyStringView&); + MyStringView(const std::string&); +}; +template<> struct IsPointerLikeTypeImpl<MyStringView> : std::true_type {}; + +std::optional<std::string_view> getOptionalSV(); +std::optional<std::string> getOptionalS(); +std::optional<MyStringView> getOptionalMySV(); +MyStringView getMySV(); + +class MyStringViewNotPointer : public std::string_view {}; +std::optional<MyStringViewNotPointer> getOptionalMySVNotP(); +MyStringViewNotPointer getMySVNotP(); + +void container_of_pointers() { + std::string local; + MyVector<std::string> vs; + vs.push_back(std::string()); // Ok. + + MyVector<std::string_view> vsv; + vsv.push_back(std::string()); // expected-warning {{object whose reference is captured by 'vsv'}} + vsv.push_back(substr(std::string())); // expected-warning {{object whose reference is captured by 'vsv'}} + + MyVector<const std::string*> vp; + vp.push_back(getPointerLB(std::string())); // expected-warning {{object whose reference is captured by 'vp'}} + vp.push_back(getPointerLB(*getPointerLB(std::string()))); // expected-warning {{object whose reference is captured by 'vp'}} + vp.push_back(getPointerLB(local)); + vp.push_back(getPointerNoLB(std::string())); + + // User-defined [[gsl::Pointer]] + vsv.push_back(getMySV()); + vsv.push_back(getMySVNotP()); + + // Vector of user defined gsl::Pointer. + MyVector<MyStringView> vmysv; + vmysv.push_back(getMySV()); + vmysv.push_back(MyStringView{}); + vmysv.push_back(std::string_view{}); + vmysv.push_back(std::string{}); // expected-warning {{object whose reference is captured by 'vmysv'}} + vmysv.push_back(substr(std::string{})); // expected-warning {{object whose reference is captured by 'vmysv'}} + vmysv.push_back(getLB(substr(std::string{}))); // expected-warning {{object whose reference is captured by 'vmysv'}} + vmysv.push_back(strcopy(getLB(substr(std::string{})))); + + // With std::optional container. + std::optional<std::string_view> optional; + vsv.push_back(optional.value()); + vsv.push_back(getOptionalS().value()); // expected-warning {{object whose reference is captured by 'vsv'}} + vsv.push_back(getOptionalSV().value()); + vsv.push_back(getOptionalMySV().value()); + + // (maybe) FIXME: We may choose to diagnose the following case. + // This happens because 'MyStringViewNotPointer' is not marked as a [[gsl::Pointer]] but is derived from one. + vsv.push_back(getOptionalMySVNotP().value()); // expected-warning {{object whose reference is captured by 'vsv'}} } } // namespace lifetime_capture_by -// Test for templated code. -// 2 nested function calls foo(sv, bar(sv, setsv)); \ No newline at end of file >From f349aa811fe312247e3bebb064b374cf454ed798 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Wed, 13 Nov 2024 14:58:46 +0000 Subject: [PATCH 3/3] simply some code --- clang/lib/Sema/CheckExprLifetime.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index 7f52055d4a9c61..d5918c85ae3fa4 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -254,10 +254,9 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path, LocalVisitor Visit); template <typename T> static bool isRecordWithAttr(QualType Type) { - CXXRecordDecl *RD = Type.getNonReferenceType()->getAsCXXRecordDecl(); - if (!RD) - return false; - return RD->hasAttr<T>(); + if (auto *RD = Type.getNonReferenceType()->getAsCXXRecordDecl()) + return RD->hasAttr<T>(); + return false; } // Decl::isInStdNamespace will return false for iterators in some STL _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits