[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:
Utkarsh Saxena 2024-11-20 15:17:00 +01:00 committed by GitHub
parent 3e15bce9e1
commit c22bb6f5b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 701 additions and 167 deletions

View File

@ -3921,17 +3921,42 @@ have their lifetimes extended.
def LifetimeCaptureByDocs : Documentation { def LifetimeCaptureByDocs : Documentation {
let Category = DocCatFunction; let Category = DocCatFunction;
let Content = [{ let Content = [{
Similar to `lifetimebound`_, the ``lifetime_capture_by(X)`` attribute on a function Similar to `lifetimebound`_, the ``lifetime_capture_by(X)`` attribute on a
parameter or implicit object parameter indicates that that objects that are referred to function parameter or implicit object parameter indicates that the capturing
by that parameter may also be referred to by the capturing entity ``X``. entity ``X`` may refer to the object referred by that parameter.
By default, a reference is considered to refer to its referenced object, a Below is a list of types of the parameters and what they're considered to refer to:
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 - A reference param (of non-view type) is considered to refer to its referenced object.
simple ``struct``\s) are considered to refer to all objects that their - A pointer param (of non-view type) is considered to refer to its pointee.
transitive subobjects refer to. - 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: The capturing entity ``X`` can be one of the following:
- Another (named) function parameter. - Another (named) function parameter.
.. code-block:: c++ .. code-block:: c++
@ -3951,7 +3976,7 @@ The capturing entity ``X`` can be one of the following:
std::set<std::string_view> s; std::set<std::string_view> s;
}; };
- 'global', 'unknown' (without quotes). - `global`, `unknown`.
.. code-block:: c++ .. code-block:: c++
@ -3983,6 +4008,22 @@ The attribute supports specifying more than one capturing entities:
s2.insert(a); 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 .. _`lifetimebound`: https://clang.llvm.org/docs/AttributeReference.html#lifetimebound
}]; }];
} }

View File

@ -453,6 +453,7 @@ def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">; def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
def DanglingAssignment: DiagGroup<"dangling-assignment">; def DanglingAssignment: DiagGroup<"dangling-assignment">;
def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">; def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
def DanglingCapture : DiagGroup<"dangling-capture">;
def DanglingElse: DiagGroup<"dangling-else">; def DanglingElse: DiagGroup<"dangling-else">;
def DanglingField : DiagGroup<"dangling-field">; def DanglingField : DiagGroup<"dangling-field">;
def DanglingInitializerList : DiagGroup<"dangling-initializer-list">; def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
@ -462,6 +463,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">;
def : DiagGroup<"return-local-addr", [ReturnStackAddress]>; def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
def Dangling : DiagGroup<"dangling", [DanglingAssignment, def Dangling : DiagGroup<"dangling", [DanglingAssignment,
DanglingAssignmentGsl, DanglingAssignmentGsl,
DanglingCapture,
DanglingField, DanglingField,
DanglingInitializerList, DanglingInitializerList,
DanglingGsl, DanglingGsl,

View File

@ -10129,10 +10129,11 @@ def err_lifetimebound_ctor_dtor : Error<
"%select{constructor|destructor}0">; "%select{constructor|destructor}0">;
def err_lifetimebound_parameter_void_return_type : Error< def err_lifetimebound_parameter_void_return_type : Error<
"'lifetimebound' attribute cannot be applied to a parameter of a function " "'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< def err_lifetimebound_implicit_object_parameter_void_return_type : Error<
"'lifetimebound' attribute cannot be applied to an implicit object " "'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 // CHECK: returning address/reference of stack memory
def warn_ret_stack_addr_ref : Warning< def warn_ret_stack_addr_ref : Warning<
@ -10227,6 +10228,12 @@ def warn_dangling_pointer_assignment : Warning<
"object backing %select{|the pointer }0%1 " "object backing %select{|the pointer }0%1 "
"will be destroyed at the end of the full-expression">, "will be destroyed at the end of the full-expression">,
InGroup<DanglingAssignment>; 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 // 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. // should result in a warning, since these always evaluate to a constant.

View File

@ -2323,6 +2323,9 @@ public:
bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = false); bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = false);
bool BuiltinVectorToScalarMath(CallExpr *TheCall); 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 /// Handles the checks for format strings, non-POD arguments to vararg
/// functions, NULL arguments passed to non-NULL parameters, diagnose_if /// functions, NULL arguments passed to non-NULL parameters, diagnose_if
/// attributes and AArch64 SME attributes. /// attributes and AArch64 SME attributes.

View File

@ -45,10 +45,14 @@ enum LifetimeKind {
/// a default member initializer), the program is ill-formed. /// a default member initializer), the program is ill-formed.
LK_MemInitializer, 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 /// because the entity is a pointer and we assign the address of a temporary
/// object to it. /// object to it.
LK_Assignment, 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 = using LifetimeResult =
llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>; llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
@ -1108,13 +1112,14 @@ static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator))); isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
} }
static void checkExprLifetimeImpl(Sema &SemaRef, static void
const InitializedEntity *InitEntity, checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
const InitializedEntity *ExtendingEntity, const InitializedEntity *ExtendingEntity, LifetimeKind LK,
LifetimeKind LK, const AssignedEntity *AEntity,
const AssignedEntity *AEntity, Expr *Init) { const CapturingEntity *CapEntity, Expr *Init) {
assert((AEntity && LK == LK_Assignment) || assert(!AEntity || LK == LK_Assignment);
(InitEntity && 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 // If this entity doesn't have an interesting lifetime, don't bother looking
// for temporaries within its initializer. // for temporaries within its initializer.
if (LK == LK_FullExpression) if (LK == LK_FullExpression)
@ -1197,12 +1202,23 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
break; 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: { case LK_Assignment: {
if (!MTE || pathContainsInit(Path)) if (!MTE || pathContainsInit(Path))
return false; return false;
assert(shouldLifetimeExtendThroughPath(Path) ==
PathLifetimeKind::NoExtend &&
"No lifetime extension for assignments");
if (IsGslPtrValueFromGslTempOwner) if (IsGslPtrValueFromGslTempOwner)
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_assignment) SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_assignment)
<< AEntity->LHS << DiagRange; << AEntity->LHS << DiagRange;
@ -1411,13 +1427,23 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
}; };
llvm::SmallVector<IndirectLocalPathEntry, 8> Path; llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
if (LK == LK_Assignment && switch (LK) {
shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity)) { case LK_Assignment: {
Path.push_back( if (shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity))
{isAssignmentOperatorLifetimeBound(AEntity->AssignmentOperator) Path.push_back(
? IndirectLocalPathEntry::LifetimeBoundCall {isAssignmentOperatorLifetimeBound(AEntity->AssignmentOperator)
: IndirectLocalPathEntry::GslPointerAssignment, ? IndirectLocalPathEntry::LifetimeBoundCall
Init}); : IndirectLocalPathEntry::GslPointerAssignment,
Init});
break;
}
case LK_LifetimeCapture: {
if (isPointerLikeType(Init->getType()))
Path.push_back({IndirectLocalPathEntry::GslPointerInit, Init});
break;
}
default:
break;
} }
if (Init->isGLValue()) if (Init->isGLValue())
@ -1430,23 +1456,23 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
/*RevisitSubinits=*/!InitEntity); /*RevisitSubinits=*/!InitEntity);
} }
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity, void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
Expr *Init) { Expr *Init) {
auto LTResult = getEntityLifetime(&Entity); auto LTResult = getEntityLifetime(&Entity);
LifetimeKind LK = LTResult.getInt(); LifetimeKind LK = LTResult.getInt();
const InitializedEntity *ExtendingEntity = LTResult.getPointer(); const InitializedEntity *ExtendingEntity = LTResult.getPointer();
checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK, checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK,
/*AEntity*/ nullptr, Init); /*AEntity=*/nullptr, /*CapEntity=*/nullptr, Init);
} }
void checkExprLifetimeMustTailArg(Sema &SemaRef, void checkExprLifetimeMustTailArg(Sema &SemaRef,
const InitializedEntity &Entity, Expr *Init) { const InitializedEntity &Entity, Expr *Init) {
checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail, checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail,
/*AEntity*/ nullptr, Init); /*AEntity=*/nullptr, /*CapEntity=*/nullptr, Init);
} }
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, void checkAssignmentLifetime(Sema &SemaRef, const AssignedEntity &Entity,
Expr *Init) { Expr *Init) {
bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored( bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored(
diag::warn_dangling_pointer_assignment, SourceLocation()); diag::warn_dangling_pointer_assignment, SourceLocation());
bool RunAnalysis = (EnableDanglingPointerAssignment && bool RunAnalysis = (EnableDanglingPointerAssignment &&
@ -1458,7 +1484,20 @@ void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr, checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
/*ExtendingEntity=*/nullptr, LK_Assignment, &Entity, /*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 } // namespace clang::sema

View File

@ -25,15 +25,31 @@ struct AssignedEntity {
CXXMethodDecl *AssignmentOperator = nullptr; 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 /// Check that the lifetime of the given expr (and its subobjects) is
/// sufficient for initializing the entity, and perform lifetime extension /// sufficient for initializing the entity, and perform lifetime extension
/// (when permitted) if not. /// (when permitted) if not.
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity, void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
Expr *Init); Expr *Init);
/// Check that the lifetime of the given expr (and its subobjects) is /// Check that the lifetime of the given expr (and its subobjects) is
/// sufficient for assigning to the entity. /// 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 /// Check that the lifetime of the given expr (and its subobjects) is
/// sufficient, assuming that it is passed as an argument to a musttail /// sufficient, assuming that it is passed as an argument to a musttail

View File

@ -11,6 +11,7 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#include "CheckExprLifetime.h"
#include "clang/AST/APValue.h" #include "clang/AST/APValue.h"
#include "clang/AST/ASTContext.h" #include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h" #include "clang/AST/Attr.h"
@ -3222,6 +3223,47 @@ void Sema::CheckArgAlignment(SourceLocation Loc, NamedDecl *FDecl,
<< ParamName << (FDecl != nullptr) << 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, void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
const Expr *ThisArg, ArrayRef<const Expr *> Args, const Expr *ThisArg, ArrayRef<const Expr *> Args,
bool IsMemberFunction, SourceLocation Loc, 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) { if (FDecl || Proto) {
CheckNonNullArguments(*this, FDecl, Proto, Args, Loc); CheckNonNullArguments(*this, FDecl, Proto, Args, Loc);

View File

@ -13821,7 +13821,7 @@ QualType Sema::CheckAssignmentOperands(Expr *LHSExpr, ExprResult &RHS,
CheckForNullPointerDereference(*this, LHSExpr); CheckForNullPointerDereference(*this, LHSExpr);
AssignedEntity AE{LHSExpr}; AssignedEntity AE{LHSExpr};
checkExprLifetime(*this, AE, RHS.get()); checkAssignmentLifetime(*this, AE, RHS.get());
if (getLangOpts().CPlusPlus20 && LHSType.isVolatileQualified()) { if (getLangOpts().CPlusPlus20 && LHSType.isVolatileQualified()) {
if (CompoundType.isNull()) { if (CompoundType.isNull()) {

View File

@ -7401,7 +7401,7 @@ PerformConstructorInitialization(Sema &S,
void Sema::checkInitializerLifetime(const InitializedEntity &Entity, void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
Expr *Init) { Expr *Init) {
return sema::checkExprLifetime(*this, Entity, Init); return sema::checkInitLifetime(*this, Entity, Init);
} }
static void DiagnoseNarrowingInInitList(Sema &S, static void DiagnoseNarrowingInInitList(Sema &S,

View File

@ -14809,7 +14809,7 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
// Check for a self move. // Check for a self move.
DiagnoseSelfMove(Args[0], Args[1], OpLoc); DiagnoseSelfMove(Args[0], Args[1], OpLoc);
// lifetime check. // lifetime check.
checkExprLifetime( checkAssignmentLifetime(
*this, AssignedEntity{Args[0], dyn_cast<CXXMethodDecl>(FnDecl)}, *this, AssignedEntity{Args[0], dyn_cast<CXXMethodDecl>(FnDecl)},
Args[1]); Args[1]);
} }

View 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 {};
}

View 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

View File

@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field -Wreturn-stack-address -verify %s // RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field -Wreturn-stack-address -verify %s
#include "Inputs/lifetime-analysis.h"
struct [[gsl::Owner(int)]] MyIntOwner { struct [[gsl::Owner(int)]] MyIntOwner {
MyIntOwner(); MyIntOwner();
int &operator*(); int &operator*();
@ -129,130 +130,6 @@ void initLocalGslPtrWithTempOwner() {
global2 = MyLongOwnerWithConversion{}; // expected-warning {{object backing the pointer global2 }} 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 { struct Unannotated {
typedef std::vector<int>::iterator iterator; typedef std::vector<int>::iterator iterator;

View File

@ -1,7 +1,7 @@
// RUN: %clang_cc1 -std=c++23 -verify %s // RUN: %clang_cc1 -std=c++23 -verify %s
namespace usage_invalid { namespace usage_invalid {
void void_return(int &param [[clang::lifetimebound]]); // expected-error {{'lifetimebound' attribute cannot be applied to a parameter of a function that returns void}} void void_return(int &param [[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}} int *not_class_member() [[clang::lifetimebound]]; // expected-error {{non-member function has no implicit object parameter}}
struct A { 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 *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 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}} 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 &param [[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}} int *attr_with_param(int &param [[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}}
} }