mirror of
https://github.com/llvm/llvm-project.git
synced 2025-05-01 14:16:09 +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 {
|
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
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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()) {
|
||||||
|
@ -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,
|
||||||
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
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
|
// 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;
|
||||||
|
@ -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 ¶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}}
|
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 ¶m [[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}}
|
int *attr_with_param(int ¶m [[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user