[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 {
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
}];
}

View File

@ -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,

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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()) {

View File

@ -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,

View File

@ -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]);
}

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
#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;

View File

@ -1,7 +1,7 @@
// RUN: %clang_cc1 -std=c++23 -verify %s
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}}
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 &param [[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}}
}