Author: Utkarsh Saxena Date: 2024-11-20T15:17:00+01:00 New Revision: c22bb6f5b1b43484b47dd896a147bf54f8f44c9a
URL: https://github.com/llvm/llvm-project/commit/c22bb6f5b1b43484b47dd896a147bf54f8f44c9a DIFF: https://github.com/llvm/llvm-project/commit/c22bb6f5b1b43484b47dd896a147bf54f8f44c9a.diff LOG: [clang] Implement lifetime analysis for lifetime_capture_by(X) (#115921) This PR uses the existing lifetime analysis for the `capture_by` attribute. The analysis is behind `-Wdangling-capture` warning and is disabled by default for now. Once it is found to be stable, it will be default enabled. Planned followup: - add implicit inference of this attribute on STL container methods like `std::vector::push_back`. - (consider) warning if capturing `X` cannot capture anything. It should be a reference, pointer or a view type. - refactoring temporary visitors and other related handlers. - start discussing `__global` vs `global` in the annotation in a separate PR. --------- Co-authored-by: Boaz Brickner <brick...@google.com> Added: clang/test/Sema/Inputs/lifetime-analysis.h clang/test/Sema/warn-lifetime-analysis-capture-by.cpp Modified: clang/include/clang/Basic/AttrDocs.td clang/include/clang/Basic/DiagnosticGroups.td clang/include/clang/Basic/DiagnosticSemaKinds.td clang/include/clang/Sema/Sema.h clang/lib/Sema/CheckExprLifetime.cpp clang/lib/Sema/CheckExprLifetime.h clang/lib/Sema/SemaChecking.cpp clang/lib/Sema/SemaExpr.cpp clang/lib/Sema/SemaInit.cpp clang/lib/Sema/SemaOverload.cpp clang/test/Sema/warn-lifetime-analysis-nocfg.cpp clang/test/SemaCXX/attr-lifetimebound.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 2fdceca163ee63..94d6d15365cef6 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -3921,17 +3921,42 @@ have their lifetimes extended. def LifetimeCaptureByDocs : Documentation { let Category = DocCatFunction; let Content = [{ - Similar to `lifetimebound`_, the ``lifetime_capture_by(X)`` attribute on a function -parameter or implicit object parameter indicates that that objects that are referred to -by that parameter may also be referred to by the capturing entity ``X``. +Similar to `lifetimebound`_, the ``lifetime_capture_by(X)`` attribute on a +function parameter or implicit object parameter indicates that the capturing +entity ``X`` may refer to the object referred by that parameter. + +Below is a list of types of the parameters and what they're considered to refer to: + +- A reference param (of non-view type) is considered to refer to its referenced object. +- A pointer param (of non-view type) is considered to refer to its pointee. +- View type param (type annotated with ``[[gsl::Pointer()]]``) is considered to refer + to its pointee (gsl owner). This holds true even if the view type appears as a reference + in the parameter. For example, both ``std::string_view`` and + ``const std::string_view &`` are considered to refer to a ``std::string``. +- A ``std::initializer_list<T>`` is considered to refer to its underlying array. +- Aggregates (arrays and simple ``struct``\s) are considered to refer to all + objects that their transitive subobjects refer to. + +Clang would diagnose when a temporary object is used as an argument to such an +annotated parameter. +In this case, the capturing entity ``X`` could capture a dangling reference to this +temporary object. -By default, a reference is considered to refer to its referenced object, a -pointer is considered to refer to its pointee, a ``std::initializer_list<T>`` -is considered to refer to its underlying array, and aggregates (arrays and -simple ``struct``\s) are considered to refer to all objects that their -transitive subobjects refer to. +.. code-block:: c++ + + void addToSet(std::string_view a [[clang::lifetime_capture_by(s)]], std::set<std::string_view>& s) { + s.insert(a); + } + void use() { + std::set<std::string_view> s; + addToSet(std::string(), s); // Warning: object whose reference is captured by 's' will be destroyed at the end of the full-expression. + // ^^^^^^^^^^^^^ + std::string local; + addToSet(local, s); // Ok. + } The capturing entity ``X`` can be one of the following: + - Another (named) function parameter. .. code-block:: c++ @@ -3951,7 +3976,7 @@ The capturing entity ``X`` can be one of the following: std::set<std::string_view> s; }; -- 'global', 'unknown' (without quotes). +- `global`, `unknown`. .. code-block:: c++ @@ -3983,6 +4008,22 @@ The attribute supports specifying more than one capturing entities: s2.insert(a); } +Limitation: The capturing entity ``X`` is not used by the analysis and is +used for documentation purposes only. This is because the analysis is +statement-local and only detects use of a temporary as an argument to the +annotated parameter. + +.. code-block:: c++ + + void addToSet(std::string_view a [[clang::lifetime_capture_by(s)]], std::set<std::string_view>& s); + void use() { + std::set<std::string_view> s; + if (foo()) { + std::string str; + addToSet(str, s); // Not detected. + } + } + .. _`lifetimebound`: https://clang.llvm.org/docs/AttributeReference.html#lifetimebound }]; } 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 dfb90501ce72d7..157d77b38b354e 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10129,10 +10129,11 @@ 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< @@ -10227,6 +10228,12 @@ 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 whose reference is captured by '%0' will be destroyed at the end of " + "the full-expression">, InGroup<DanglingCapture>, DefaultIgnore; +def warn_dangling_reference_captured_by_unknown : Warning< + "object whose reference is captured will be destroyed at the end of " + "the full-expression">, InGroup<DanglingCapture>, DefaultIgnore; // 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 2dbd9862802e7a..8886e5e307ddf8 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>; @@ -1108,13 +1112,14 @@ 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) { - assert((AEntity && LK == LK_Assignment) || - (InitEntity && LK != LK_Assignment)); +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); + assert(!CapEntity || LK == LK_LifetimeCapture); + assert(!InitEntity || (LK != LK_Assignment && LK != LK_LifetimeCapture)); // If this entity doesn't have an interesting lifetime, don't bother looking // for temporaries within its initializer. if (LK == LK_FullExpression) @@ -1197,12 +1202,23 @@ static void checkExprLifetimeImpl(Sema &SemaRef, break; } + case LK_LifetimeCapture: { + // The captured entity has lifetime beyond the full-expression, + // and the capturing entity does too, so don't warn. + if (!MTE) + return false; + if (CapEntity->Entity) + SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured) + << CapEntity->Entity << DiagRange; + else + SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured_by_unknown) + << DiagRange; + return false; + } + case LK_Assignment: { if (!MTE || pathContainsInit(Path)) return false; - assert(shouldLifetimeExtendThroughPath(Path) == - PathLifetimeKind::NoExtend && - "No lifetime extension for assignments"); if (IsGslPtrValueFromGslTempOwner) SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_assignment) << AEntity->LHS << DiagRange; @@ -1411,13 +1427,23 @@ static void checkExprLifetimeImpl(Sema &SemaRef, }; llvm::SmallVector<IndirectLocalPathEntry, 8> Path; - if (LK == LK_Assignment && - shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity)) { - Path.push_back( - {isAssignmentOperatorLifetimeBound(AEntity->AssignmentOperator) - ? IndirectLocalPathEntry::LifetimeBoundCall - : IndirectLocalPathEntry::GslPointerAssignment, - Init}); + switch (LK) { + case LK_Assignment: { + if (shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity)) + Path.push_back( + {isAssignmentOperatorLifetimeBound(AEntity->AssignmentOperator) + ? IndirectLocalPathEntry::LifetimeBoundCall + : IndirectLocalPathEntry::GslPointerAssignment, + Init}); + break; + } + case LK_LifetimeCapture: { + if (isPointerLikeType(Init->getType())) + Path.push_back({IndirectLocalPathEntry::GslPointerInit, Init}); + break; + } + default: + break; } if (Init->isGLValue()) @@ -1430,23 +1456,23 @@ 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(); 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, - Expr *Init) { +void checkAssignmentLifetime(Sema &SemaRef, const AssignedEntity &Entity, + Expr *Init) { bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored( diag::warn_dangling_pointer_assignment, SourceLocation()); bool RunAnalysis = (EnableDanglingPointerAssignment && @@ -1458,7 +1484,20 @@ void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr, /*ExtendingEntity=*/nullptr, LK_Assignment, &Entity, - Init); + /*CapEntity=*/nullptr, Init); +} + +void checkCaptureByLifetime(Sema &SemaRef, const CapturingEntity &Entity, + Expr *Init) { + if (SemaRef.getDiagnostics().isIgnored(diag::warn_dangling_reference_captured, + SourceLocation()) && + SemaRef.getDiagnostics().isIgnored( + diag::warn_dangling_reference_captured_by_unknown, SourceLocation())) + return; + 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..38b7061988dc78 100644 --- a/clang/lib/Sema/CheckExprLifetime.h +++ b/clang/lib/Sema/CheckExprLifetime.h @@ -25,15 +25,31 @@ 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'. + // + // This is 'nullptr' when the capturing entity is 'global' or 'unknown'. + 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. -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 AssignedEntity &Entity, + Expr *Init); + +void checkCaptureByLifetime(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 diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 2d4a7cd287b70d..2fd990750ed212 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" @@ -3222,6 +3223,47 @@ 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) { + if (!FD || Args.empty()) + return; + auto GetArgAt = [&](int Idx) -> const Expr * { + if (Idx == LifetimeCaptureByAttr::GLOBAL || + Idx == LifetimeCaptureByAttr::UNKNOWN) + return nullptr; + if (IsMemberFunction && Idx == 0) + return ThisArg; + return Args[Idx - IsMemberFunction]; + }; + auto HandleCaptureByAttr = [&](const LifetimeCaptureByAttr *Attr, + unsigned ArgIdx) { + if (!Attr) + return; + Expr *Captured = const_cast<Expr *>(GetArgAt(ArgIdx)); + for (int CapturingParamIdx : Attr->params()) { + Expr *Capturing = const_cast<Expr *>(GetArgAt(CapturingParamIdx)); + CapturingEntity CE{Capturing}; + // Ensure that 'Captured' outlives the 'Capturing' entity. + checkCaptureByLifetime(*this, CE, Captured); + } + }; + for (unsigned I = 0; I < FD->getNumParams(); ++I) + HandleCaptureByAttr(FD->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>(), + I + IsMemberFunction); + // Check when the implicit object param 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()) + HandleCaptureByAttr(ATL.getAttrAs<LifetimeCaptureByAttr>(), 0); + } +} + void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto, const Expr *ThisArg, ArrayRef<const Expr *> Args, bool IsMemberFunction, SourceLocation Loc, @@ -3262,7 +3304,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/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index dcf495b700540f..6c7472ce92703b 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -13821,7 +13821,7 @@ QualType Sema::CheckAssignmentOperands(Expr *LHSExpr, ExprResult &RHS, CheckForNullPointerDereference(*this, LHSExpr); AssignedEntity AE{LHSExpr}; - checkExprLifetime(*this, AE, RHS.get()); + 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 1e98a074894ad0..7c03a12e812809 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -7401,7 +7401,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 a239f2c6e88e4b..e4bf9aa521224b 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -14809,7 +14809,7 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc, // Check for a self move. DiagnoseSelfMove(Args[0], Args[1], OpLoc); // lifetime check. - checkExprLifetime( + checkAssignmentLifetime( *this, AssignedEntity{Args[0], dyn_cast<CXXMethodDecl>(FnDecl)}, Args[1]); } diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h new file mode 100644 index 00000000000000..41d1e2f074cc83 --- /dev/null +++ b/clang/test/Sema/Inputs/lifetime-analysis.h @@ -0,0 +1,138 @@ + +namespace __gnu_cxx { +template <typename T> +struct basic_iterator { + basic_iterator operator++(); + T& operator*() const; + T* operator->() const; +}; + +template<typename T> +bool operator!=(basic_iterator<T>, basic_iterator<T>); +} + +namespace std { +template<typename T> struct remove_reference { typedef T type; }; +template<typename T> struct remove_reference<T &> { typedef T type; }; +template<typename T> struct remove_reference<T &&> { typedef T type; }; + +template<typename T> +typename remove_reference<T>::type &&move(T &&t) noexcept; + +template <typename C> +auto data(const C &c) -> decltype(c.data()); + +template <typename C> +auto begin(C &c) -> decltype(c.begin()); + +template<typename T, int N> +T *begin(T (&array)[N]); + +using size_t = decltype(sizeof(0)); + +template<typename T> +struct initializer_list { + const T* ptr; size_t sz; +}; +template<typename T> class allocator {}; +template <typename T, typename Alloc = allocator<T>> +struct vector { + typedef __gnu_cxx::basic_iterator<T> iterator; + iterator begin(); + iterator end(); + const T *data() const; + vector(); + vector(initializer_list<T> __l, + const Alloc& alloc = Alloc()); + + template<typename InputIterator> + vector(InputIterator first, InputIterator __last); + + T &at(int n); +}; + +template<typename T> +struct basic_string_view { + basic_string_view(); + basic_string_view(const T *); + const T *begin() const; +}; +using string_view = basic_string_view<char>; + +template<class _Mystr> struct iter { + iter& operator-=(int); + + iter operator-(int _Off) const { + iter _Tmp = *this; + return _Tmp -= _Off; + } +}; + +template<typename T> +struct basic_string { + basic_string(); + basic_string(const T *); + const T *c_str() const; + operator basic_string_view<T> () const; + using const_iterator = iter<T>; +}; +using string = basic_string<char>; + +template<typename T> +struct unique_ptr { + T &operator*(); + T *get() const; +}; + +template<typename T> +struct optional { + optional(); + optional(const T&); + + template<typename U = T> + optional(U&& t); + + template<typename U> + optional(optional<U>&& __t); + + T &operator*() &; + T &&operator*() &&; + T &value() &; + T &&value() &&; +}; +template<typename T> +optional<__decay(T)> make_optional(T&&); + + +template<typename T> +struct stack { + T &top(); +}; + +struct any {}; + +template<typename T> +T any_cast(const any& operand); + +template<typename T> +struct reference_wrapper { + template<typename U> + reference_wrapper(U &&); +}; + +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 {}; +} diff --git a/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp b/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp new file mode 100644 index 00000000000000..b3fde386b8616c --- /dev/null +++ b/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp @@ -0,0 +1,368 @@ +// RUN: %clang_cc1 --std=c++20 -fsyntax-only -verify -Wdangling-capture %s + +#include "Inputs/lifetime-analysis.h" + +// **************************************************************************** +// Capture an integer +// **************************************************************************** +namespace capture_int { +struct X {} x; +void captureInt(const int &i [[clang::lifetime_capture_by(x)]], X &x); +void captureRValInt(int &&i [[clang::lifetime_capture_by(x)]], X &x); +void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X &x); + +void use() { + int local; + captureInt(1, // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + x); + captureRValInt(1, x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + captureInt(local, x); + noCaptureInt(1, x); + noCaptureInt(local, x); +} +} // namespace capture_int + +// **************************************************************************** +// Capture std::string (gsl owner types) +// **************************************************************************** +namespace capture_string { +struct X {} x; +void captureString(const std::string &s [[clang::lifetime_capture_by(x)]], X &x); +void captureRValString(std::string &&s [[clang::lifetime_capture_by(x)]], X &x); + +void use() { + std::string local_string; + captureString(std::string(), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + captureString(local_string, x); + captureRValString(std::move(local_string), x); + captureRValString(std::string(), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} +} +} // namespace capture_string + +// **************************************************************************** +// Capture std::string_view (gsl pointer types) +// **************************************************************************** +namespace capture_string_view { +struct X {} x; +void captureStringView(std::string_view s [[clang::lifetime_capture_by(x)]], X &x); +void captureRValStringView(std::string_view &&sv [[clang::lifetime_capture_by(x)]], X &x); +void noCaptureStringView(std::string_view sv, X &x); + +std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]); +std::string_view getNotLifetimeBoundView(const std::string& s); +const std::string& getLifetimeBoundString(const std::string &s [[clang::lifetimebound]]); +const std::string& getLifetimeBoundString(std::string_view sv [[clang::lifetimebound]]); + +void use() { + std::string_view local_string_view; + std::string local_string; + captureStringView(local_string_view, x); + captureStringView(std::string(), // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + x); + + captureStringView(getLifetimeBoundView(local_string), x); + captureStringView(getNotLifetimeBoundView(std::string()), x); + captureRValStringView(std::move(local_string_view), x); + captureRValStringView(std::string(), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + captureRValStringView(std::string_view{"abcd"}, x); + + noCaptureStringView(local_string_view, x); + noCaptureStringView(std::string(), x); + + // With lifetimebound functions. + captureStringView(getLifetimeBoundView( + std::string() // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + ), x); + captureRValStringView(getLifetimeBoundView(local_string), x); + captureRValStringView(getLifetimeBoundView(std::string()), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + captureRValStringView(getNotLifetimeBoundView(std::string()), x); + noCaptureStringView(getLifetimeBoundView(std::string()), x); + captureStringView(getLifetimeBoundString(std::string()), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + captureStringView(getLifetimeBoundString(getLifetimeBoundView(std::string())), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + captureStringView(getLifetimeBoundString(getLifetimeBoundString( + std::string() // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + )), x); +} +} // namespace capture_string_view + +// **************************************************************************** +// Capture pointer (eg: std::string*) +// **************************************************************************** +const std::string* getLifetimeBoundPointer(const std::string &s [[clang::lifetimebound]]); +const std::string* getNotLifetimeBoundPointer(const std::string &s); + +namespace capture_pointer { +struct X {} x; +void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], X &x); +void use() { + capturePointer(getLifetimeBoundPointer(std::string()), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + capturePointer(getLifetimeBoundPointer(*getLifetimeBoundPointer( + std::string() // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + )), x); + capturePointer(getNotLifetimeBoundPointer(std::string()), x); + +} +} // namespace capture_pointer + +// **************************************************************************** +// Arrays and initializer lists. +// **************************************************************************** +namespace init_lists { +struct X {} x; +void captureVector(const std::vector<int> &a [[clang::lifetime_capture_by(x)]], X &x); +void captureArray(int array [[clang::lifetime_capture_by(x)]] [2], X &x); +void captureInitList(std::initializer_list<int> abc [[clang::lifetime_capture_by(x)]], X &x); + + +std::initializer_list<int> getLifetimeBoundInitList(std::initializer_list<int> abc [[clang::lifetimebound]]); + +void use() { + captureVector({1, 2, 3}, x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + captureVector(std::vector<int>{}, x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + std::vector<int> local_vector; + captureVector(local_vector, x); + int local_array[2]; + captureArray(local_array, x); + captureInitList({1, 2}, x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + captureInitList(getLifetimeBoundInitList({1, 2}), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} +} +} // namespace init_lists + +// **************************************************************************** +// Implicit object param 'this' is captured +// **************************************************************************** +namespace this_is_captured { +struct X {} x; +struct S { + void capture(X &x) [[clang::lifetime_capture_by(x)]]; +}; +void use() { + S{}.capture(x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + S s; + s.capture(x); +} +} // namespace this_is_captured + +// **************************************************************************** +// Capture by Global and Unknown. +// **************************************************************************** +namespace capture_by_global_unknown { +void captureByGlobal(std::string_view s [[clang::lifetime_capture_by(global)]]); +void captureByUnknown(std::string_view s [[clang::lifetime_capture_by(unknown)]]); + +std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]); + +void use() { + std::string_view local_string_view; + std::string local_string; + // capture by global. + captureByGlobal(std::string()); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}} + captureByGlobal(getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}} + captureByGlobal(local_string); + captureByGlobal(local_string_view); + + // capture by unknown. + captureByUnknown(std::string()); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}} + captureByUnknown(getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}} + captureByUnknown(local_string); + captureByUnknown(local_string_view); +} +} // namespace capture_by_global_unknown + +// **************************************************************************** +// Member functions: Capture by 'this' +// **************************************************************************** +namespace capture_by_this { +struct S { + void captureInt(const int& x [[clang::lifetime_capture_by(this)]]); + void captureView(std::string_view sv [[clang::lifetime_capture_by(this)]]); +}; +std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]); +std::string_view getNotLifetimeBoundView(const std::string& s); +const std::string& getLifetimeBoundString(const std::string &s [[clang::lifetimebound]]); + +void use() { + S s; + s.captureInt(1); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}} + s.captureView(std::string()); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}} + s.captureView(getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}} + s.captureView(getLifetimeBoundString(std::string())); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}} + s.captureView(getNotLifetimeBoundView(std::string())); +} +} // namespace capture_by_this + +// **************************************************************************** +// Struct with field as a reference +// **************************************************************************** +namespace reference_field { +struct X {} x; +struct Foo { + const int& b; +}; +void captureField(Foo param [[clang::lifetime_capture_by(x)]], X &x); +void use() { + captureField(Foo{ + 1 // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + }, x); + int local; + captureField(Foo{local}, x); +} +} // namespace reference_field + +// **************************************************************************** +// Capture default argument. +// **************************************************************************** +namespace default_arg { +struct X {} x; +void captureDefaultArg(X &x, std::string_view s [[clang::lifetime_capture_by(x)]] = std::string()); + +std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]); + +void useCaptureDefaultArg() { + X x; + captureDefaultArg(x); // FIXME: Diagnose temporary default arg. + captureDefaultArg(x, std::string("temp")); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + captureDefaultArg(x, getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + std::string local; + captureDefaultArg(x, local); +} +} // namespace default_arg + +// **************************************************************************** +// Container: *No* distinction between pointer-like and other element type +// **************************************************************************** +namespace containers_no_distinction { +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 whose reference is captured by 'set_of_int' will be destroyed at the end of the full-expression}} + MySet<std::string_view> set_of_sv; + set_of_sv.insert(std::string()); // expected-warning {{object whose reference is captured by 'set_of_sv' will be destroyed at the end of the full-expression}} + set_of_sv.insert(std::string_view()); +} +} // namespace containers_no_distinction + +// **************************************************************************** +// Container: Different for pointer-like and other element type. +// **************************************************************************** +namespace conatiners_with_ diff erent { +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; + +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>); +}; + +std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]); + +void use_container() { + std::string local; + + MyVector<std::string> vector_of_string; + vector_of_string.push_back(std::string()); // Ok. + + MyVector<std::string_view> vector_of_view; + vector_of_view.push_back(std::string()); // expected-warning {{object whose reference is captured by 'vector_of_view' will be destroyed at the end of the full-expression}} + vector_of_view.push_back(getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured by 'vector_of_view' will be destroyed at the end of the full-expression}} + + MyVector<const std::string*> vector_of_pointer; + vector_of_pointer.push_back(getLifetimeBoundPointer(std::string())); // expected-warning {{object whose reference is captured by 'vector_of_pointer' will be destroyed at the end of the full-expression}} + vector_of_pointer.push_back(getLifetimeBoundPointer(*getLifetimeBoundPointer(std::string()))); // expected-warning {{object whose reference is captured by 'vector_of_pointer' will be destroyed at the end of the full-expression}} + vector_of_pointer.push_back(getLifetimeBoundPointer(local)); + vector_of_pointer.push_back(getNotLifetimeBoundPointer(std::string())); +} + +// **************************************************************************** +// Container: For user defined view types +// **************************************************************************** +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(); + +std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]); +std::string_view getNotLifetimeBoundView(const std::string& s); +const std::string& getLifetimeBoundString(const std::string &s [[clang::lifetimebound]]); +const std::string& getLifetimeBoundString(std::string_view sv [[clang::lifetimebound]]); + +void use_my_view() { + std::string local; + MyVector<MyStringView> vector_of_my_view; + vector_of_my_view.push_back(getMySV()); + vector_of_my_view.push_back(MyStringView{}); + vector_of_my_view.push_back(std::string_view{}); + vector_of_my_view.push_back(std::string{}); // expected-warning {{object whose reference is captured by 'vector_of_my_view' will be destroyed at the end of the full-expression}} + vector_of_my_view.push_back(getLifetimeBoundView(std::string{})); // expected-warning {{object whose reference is captured by 'vector_of_my_view' will be destroyed at the end of the full-expression}} + vector_of_my_view.push_back(getLifetimeBoundString(getLifetimeBoundView(std::string{}))); // expected-warning {{object whose reference is captured by 'vector_of_my_view' will be destroyed at the end of the full-expression}} + vector_of_my_view.push_back(getNotLifetimeBoundView(getLifetimeBoundString(getLifetimeBoundView(std::string{})))); + + // Use with container of other view types. + MyVector<std::string_view> vector_of_view; + vector_of_view.push_back(getMySV()); + vector_of_view.push_back(getMySVNotP()); +} + +// **************************************************************************** +// Container: Use with std::optional<view> (owner<pointer> types) +// **************************************************************************** +void use_with_optional_view() { + MyVector<std::string_view> vector_of_view; + + std::optional<std::string_view> optional_of_view; + vector_of_view.push_back(optional_of_view.value()); + vector_of_view.push_back(getOptionalS().value()); // expected-warning {{object whose reference is captured by 'vector_of_view' will be destroyed at the end of the full-expression}} + + vector_of_view.push_back(getOptionalSV().value()); + vector_of_view.push_back(getOptionalMySV().value()); + vector_of_view.push_back(getOptionalMySVNotP().value()); +} +} // namespace conatiners_with_ diff erent + +// **************************************************************************** +// Capture 'temporary' views +// **************************************************************************** +namespace temporary_views { +void capture1(std::string_view s [[clang::lifetime_capture_by(x)]], std::vector<std::string_view>& x); + +// Intended to capture the "string_view" itself +void capture2(const std::string_view& s [[clang::lifetime_capture_by(x)]], std::vector<std::string_view*>& x); +// Intended to capture the pointee of the "string_view" +void capture3(const std::string_view& s [[clang::lifetime_capture_by(x)]], std::vector<std::string_view>& x); + +void use() { + std::vector<std::string_view> x1; + capture1(std::string(), x1); // expected-warning {{object whose reference is captured by 'x1' will be destroyed at the end of the full-expression}} + capture1(std::string_view(), x1); + + std::vector<std::string_view*> x2; + // Clang considers 'const std::string_view&' to refer to the owner + // 'std::string' and not 'std::string_view'. Therefore no diagnostic here. + capture2(std::string_view(), x2); + capture2(std::string(), x2); // expected-warning {{object whose reference is captured by 'x2' will be destroyed at the end of the full-expression}} + + std::vector<std::string_view> x3; + capture3(std::string_view(), x3); + capture3(std::string(), x3); // expected-warning {{object whose reference is captured by 'x3' will be destroyed at the end of the full-expression}} +} +} // namespace temporary_views diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp index 6a2af01ea5116c..c18ecd86ad06f0 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field -Wreturn-stack-address -verify %s +#include "Inputs/lifetime-analysis.h" struct [[gsl::Owner(int)]] MyIntOwner { MyIntOwner(); int &operator*(); @@ -129,130 +130,6 @@ void initLocalGslPtrWithTempOwner() { global2 = MyLongOwnerWithConversion{}; // expected-warning {{object backing the pointer global2 }} } -namespace __gnu_cxx { -template <typename T> -struct basic_iterator { - basic_iterator operator++(); - T& operator*() const; - T* operator->() const; -}; - -template<typename T> -bool operator!=(basic_iterator<T>, basic_iterator<T>); -} - -namespace std { -template<typename T> struct remove_reference { typedef T type; }; -template<typename T> struct remove_reference<T &> { typedef T type; }; -template<typename T> struct remove_reference<T &&> { typedef T type; }; - -template<typename T> -typename remove_reference<T>::type &&move(T &&t) noexcept; - -template <typename C> -auto data(const C &c) -> decltype(c.data()); - -template <typename C> -auto begin(C &c) -> decltype(c.begin()); - -template<typename T, int N> -T *begin(T (&array)[N]); - -using size_t = decltype(sizeof(0)); - -template<typename T> -struct initializer_list { - const T* ptr; size_t sz; -}; -template<typename T> class allocator {}; -template <typename T, typename Alloc = allocator<T>> -struct vector { - typedef __gnu_cxx::basic_iterator<T> iterator; - iterator begin(); - iterator end(); - const T *data() const; - vector(); - vector(initializer_list<T> __l, - const Alloc& alloc = Alloc()); - - template<typename InputIterator> - vector(InputIterator first, InputIterator __last); - - T &at(int n); -}; - -template<typename T> -struct basic_string_view { - basic_string_view(); - basic_string_view(const T *); - const T *begin() const; -}; -using string_view = basic_string_view<char>; - -template<class _Mystr> struct iter { - iter& operator-=(int); - - iter operator-(int _Off) const { - iter _Tmp = *this; - return _Tmp -= _Off; - } -}; - -template<typename T> -struct basic_string { - basic_string(); - basic_string(const T *); - const T *c_str() const; - operator basic_string_view<T> () const; - using const_iterator = iter<T>; -}; -using string = basic_string<char>; - -template<typename T> -struct unique_ptr { - T &operator*(); - T *get() const; -}; - -template<typename T> -struct optional { - optional(); - optional(const T&); - - template<typename U = T> - optional(U&& t); - - template<typename U> - optional(optional<U>&& __t); - - T &operator*() &; - T &&operator*() &&; - T &value() &; - T &&value() &&; -}; -template<typename T> -optional<__decay(T)> make_optional(T&&); - - -template<typename T> -struct stack { - T &top(); -}; - -struct any {}; - -template<typename T> -T any_cast(const any& operand); - -template<typename T> -struct reference_wrapper { - template<typename U> - reference_wrapper(U &&); -}; - -template<typename T> -reference_wrapper<T> ref(T& t) noexcept; -} struct Unannotated { typedef std::vector<int>::iterator iterator; 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}} } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits