mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-29 06:06:06 +00:00
[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@google.com>
This commit is contained in:
parent
3e15bce9e1
commit
c22bb6f5b1
@ -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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
.. 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
|
||||
}];
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -2323,6 +2323,9 @@ public:
|
||||
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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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,
|
||||
|
@ -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]);
|
||||
}
|
||||
|
138
clang/test/Sema/Inputs/lifetime-analysis.h
Normal file
138
clang/test/Sema/Inputs/lifetime-analysis.h
Normal file
@ -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 {};
|
||||
}
|
368
clang/test/Sema/warn-lifetime-analysis-capture-by.cpp
Normal file
368
clang/test/Sema/warn-lifetime-analysis-capture-by.cpp
Normal file
@ -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_different {
|
||||
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_different
|
||||
|
||||
// ****************************************************************************
|
||||
// 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
|
@ -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;
|
||||
|
@ -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}}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user