[clang] c22bb6f - [clang] Implement lifetime analysis for lifetime_capture_by(X) (#115921)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Nov 20 06:17:04 PST 2024
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 <brickner at 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}}
}
More information about the cfe-commits
mailing list