Skip to content

Commit 7bfb781

Browse files
committed
Introduce [[clang::lifetime_capture_by]]
1 parent eeb987f commit 7bfb781

19 files changed

+559
-34
lines changed

clang/include/clang/Basic/Attr.td

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,6 +1873,46 @@ def LifetimeBound : DeclOrTypeAttr {
18731873
let SimpleHandler = 1;
18741874
}
18751875

1876+
def LifetimeCaptureBy : DeclOrTypeAttr {
1877+
let Spellings = [Clang<"lifetime_capture_by", 0>];
1878+
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
1879+
let Args = [VariadicParamOrParamIdxArgument<"Params">];
1880+
let Documentation = [LifetimeBoundDocs];
1881+
let LangOpts = [CPlusPlus];
1882+
1883+
// let SimpleHandler = 1;
1884+
// let LateParsed = LateAttrParseStandard;
1885+
// let HasCustomParsing = 1;
1886+
// let ParseArgumentsAsUnevaluated = 1;
1887+
1888+
let AdditionalMembers = [{
1889+
private:
1890+
SmallVector<IdentifierInfo*, 1> ArgIdents;
1891+
SmallVector<SourceLocation, 1> ArgLocs;
1892+
1893+
public:
1894+
static constexpr int INVALID = -2;
1895+
static constexpr int UNKNOWN = -1;
1896+
static constexpr int GLOBAL = -1;
1897+
static constexpr int THIS = 0;
1898+
1899+
void setArgs(SmallVector<IdentifierInfo*, 1> Idents,
1900+
SmallVector<SourceLocation, 1> Locs) {
1901+
assert(Idents.size() == Locs.size());
1902+
assert(Idents.size() == params_Size);
1903+
ArgIdents = std::move(Idents);
1904+
ArgLocs = std::move(Locs);
1905+
}
1906+
1907+
const SmallVector<IdentifierInfo*, 1>& getArgIdents() const { return ArgIdents; }
1908+
const SmallVector<SourceLocation, 1>& getArgLocs() const { return ArgLocs; }
1909+
void setParamIdx(size_t Idx, int Val) {
1910+
assert(Idx < params_Size);
1911+
params_[Idx] = Val;
1912+
}
1913+
}];
1914+
}
1915+
18761916
def TrivialABI : InheritableAttr {
18771917
// This attribute does not have a C [[]] spelling because it requires the
18781918
// CPlusPlus language option.

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
453453
def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
454454
def DanglingAssignment: DiagGroup<"dangling-assignment">;
455455
def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
456+
def DanglingCapture : DiagGroup<"dangling-capture">;
456457
def DanglingElse: DiagGroup<"dangling-else">;
457458
def DanglingField : DiagGroup<"dangling-field">;
458459
def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3383,6 +3383,18 @@ def err_callback_callee_is_variadic : Error<
33833383
"'callback' attribute callee may not be variadic">;
33843384
def err_callback_implicit_this_not_available : Error<
33853385
"'callback' argument at position %0 references unavailable implicit 'this'">;
3386+
3387+
def err_capture_by_attribute_multiple : Error<
3388+
"multiple 'lifetime_capture' attributes specified">;
3389+
def err_capture_by_attribute_no_entity : Error<
3390+
"'lifetime_capture_by' attribute specifies no capturing entity">;
3391+
def err_capture_by_implicit_this_not_available : Error<
3392+
"'lifetime_capture_by' argument references unavailable implicit 'this'">;
3393+
def err_capture_by_attribute_argument_unknown : Error<
3394+
"'lifetime_capture_by' attribute argument %0 is not a known function parameter"
3395+
". Must be a function parameter of one of 'this', 'global' or 'unknown'">;
3396+
def err_capture_by_references_itself : Error<"'lifetime_capture_by' argument references itself">;
3397+
33863398
def err_init_method_bad_return_type : Error<
33873399
"init methods must return an object pointer type, not %0">;
33883400
def err_attribute_invalid_size : Error<
@@ -10198,6 +10210,9 @@ def warn_dangling_pointer_assignment : Warning<
1019810210
"object backing %select{|the pointer }0%1 "
1019910211
"will be destroyed at the end of the full-expression">,
1020010212
InGroup<DanglingAssignment>;
10213+
def warn_dangling_reference_captured : Warning<
10214+
"object captured by '%0' will be destroyed at the end of the full-expression">,
10215+
InGroup<DanglingCapture>;
1020110216

1020210217
// For non-floating point, expressions of the form x == x or x != x
1020310218
// should result in a warning, since these always evaluate to a constant.

clang/include/clang/Sema/Sema.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,9 +1757,16 @@ class Sema final : public SemaBase {
17571757
/// Add [[clang:::lifetimebound]] attr for std:: functions and methods.
17581758
void inferLifetimeBoundAttribute(FunctionDecl *FD);
17591759

1760+
/// Add [[clang:::lifetime_capture(this)]] to std:: methods.
1761+
void inferLifetimeCaptureByAttribute(FunctionDecl *FD);
1762+
17601763
/// Add [[gsl::Pointer]] attributes for std:: types.
17611764
void inferGslPointerAttribute(TypedefNameDecl *TD);
17621765

1766+
LifetimeCaptureByAttr *ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
1767+
StringRef ParamName);
1768+
void LazyProcessLifetimeCaptureByParams(FunctionDecl *FD);
1769+
17631770
/// Add _Nullable attributes for std:: types.
17641771
void inferNullableClassAttribute(CXXRecordDecl *CRD);
17651772

@@ -2315,6 +2322,9 @@ class Sema final : public SemaBase {
23152322
bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = false);
23162323
bool BuiltinVectorToScalarMath(CallExpr *TheCall);
23172324

2325+
void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
2326+
const Expr *ThisArg, ArrayRef<const Expr *> Args);
2327+
23182328
/// Handles the checks for format strings, non-POD arguments to vararg
23192329
/// functions, NULL arguments passed to non-NULL parameters, diagnose_if
23202330
/// attributes and AArch64 SME attributes.

clang/lib/AST/TypePrinter.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "clang/AST/ASTContext.h"
1414
#include "clang/AST/Attr.h"
15+
#include "clang/AST/Attrs.inc"
1516
#include "clang/AST/Decl.h"
1617
#include "clang/AST/DeclBase.h"
1718
#include "clang/AST/DeclCXX.h"
@@ -25,6 +26,7 @@
2526
#include "clang/AST/TextNodeDumper.h"
2627
#include "clang/AST/Type.h"
2728
#include "clang/Basic/AddressSpaces.h"
29+
#include "clang/Basic/AttrKinds.h"
2830
#include "clang/Basic/ExceptionSpecificationType.h"
2931
#include "clang/Basic/IdentifierTable.h"
3032
#include "clang/Basic/LLVM.h"
@@ -1909,6 +1911,12 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19091911
OS << " [[clang::lifetimebound]]";
19101912
return;
19111913
}
1914+
if (T->getAttrKind() == attr::LifetimeCaptureBy) {
1915+
// FIXME: Print the attribute arguments once we have a way to retrieve these
1916+
// here.
1917+
OS << " [[clang::lifetime_capture_by(...)";
1918+
return;
1919+
}
19121920

19131921
// The printing of the address_space attribute is handled by the qualifier
19141922
// since it is still stored in the qualifier. Return early to prevent printing
@@ -1976,6 +1984,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19761984
case attr::SizedBy:
19771985
case attr::SizedByOrNull:
19781986
case attr::LifetimeBound:
1987+
case attr::LifetimeCaptureBy:
19791988
case attr::TypeNonNull:
19801989
case attr::TypeNullable:
19811990
case attr::TypeNullableResult:

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "CheckExprLifetime.h"
10+
#include "clang/AST/Attrs.inc"
1011
#include "clang/AST/Decl.h"
12+
#include "clang/AST/DeclCXX.h"
13+
#include "clang/AST/DeclTemplate.h"
1114
#include "clang/AST/Expr.h"
1215
#include "clang/Basic/DiagnosticSema.h"
1316
#include "clang/Sema/Initialization.h"
@@ -45,10 +48,14 @@ enum LifetimeKind {
4548
/// a default member initializer), the program is ill-formed.
4649
LK_MemInitializer,
4750

48-
/// The lifetime of a temporary bound to this entity probably ends too soon,
51+
/// The lifetime of a temporary bound to this entity may end too soon,
4952
/// because the entity is a pointer and we assign the address of a temporary
5053
/// object to it.
5154
LK_Assignment,
55+
56+
/// The lifetime of a temporary bound to this entity may end too soon,
57+
/// because the entity may capture the reference to a temporary object.
58+
LK_LifetimeCapture,
5259
};
5360
using LifetimeResult =
5461
llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
@@ -193,6 +200,7 @@ struct IndirectLocalPathEntry {
193200
VarInit,
194201
LValToRVal,
195202
LifetimeBoundCall,
203+
LifetimeCapture,
196204
TemporaryCopy,
197205
LambdaCaptureInit,
198206
GslReferenceInit,
@@ -249,9 +257,12 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
249257
LocalVisitor Visit);
250258

251259
template <typename T> static bool isRecordWithAttr(QualType Type) {
252-
if (auto *RD = Type->getAsCXXRecordDecl())
253-
return RD->hasAttr<T>();
254-
return false;
260+
CXXRecordDecl *RD = Type.getNonReferenceType()->getAsCXXRecordDecl();
261+
if (!RD)
262+
return false;
263+
if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
264+
RD = CTSD->getSpecializedTemplate()->getTemplatedDecl();
265+
return RD->hasAttr<T>();
255266
}
256267

257268
// Decl::isInStdNamespace will return false for iterators in some STL
@@ -1049,6 +1060,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
10491060
case IndirectLocalPathEntry::AddressOf:
10501061
case IndirectLocalPathEntry::LValToRVal:
10511062
case IndirectLocalPathEntry::LifetimeBoundCall:
1063+
case IndirectLocalPathEntry::LifetimeCapture:
10521064
case IndirectLocalPathEntry::TemporaryCopy:
10531065
case IndirectLocalPathEntry::GslReferenceInit:
10541066
case IndirectLocalPathEntry::GslPointerInit:
@@ -1082,6 +1094,7 @@ static bool pathOnlyHandlesGslPointer(const IndirectLocalPath &Path) {
10821094
case IndirectLocalPathEntry::VarInit:
10831095
case IndirectLocalPathEntry::AddressOf:
10841096
case IndirectLocalPathEntry::LifetimeBoundCall:
1097+
case IndirectLocalPathEntry::LifetimeCapture:
10851098
continue;
10861099
case IndirectLocalPathEntry::GslPointerInit:
10871100
case IndirectLocalPathEntry::GslReferenceInit:
@@ -1102,21 +1115,22 @@ static bool isAssignmentOperatorLifetimeBound(CXXMethodDecl *CMD) {
11021115
}
11031116

11041117
static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
1105-
const AssignedEntity &Entity) {
1118+
const CapturingEntity &Entity) {
11061119
bool EnableGSLAssignmentWarnings = !SemaRef.getDiagnostics().isIgnored(
11071120
diag::warn_dangling_lifetime_pointer_assignment, SourceLocation());
11081121
return (EnableGSLAssignmentWarnings &&
1109-
(isRecordWithAttr<PointerAttr>(Entity.LHS->getType()) ||
1122+
(isRecordWithAttr<PointerAttr>(Entity.Expression->getType()) ||
11101123
isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
11111124
}
11121125

11131126
static void checkExprLifetimeImpl(Sema &SemaRef,
11141127
const InitializedEntity *InitEntity,
11151128
const InitializedEntity *ExtendingEntity,
11161129
LifetimeKind LK,
1117-
const AssignedEntity *AEntity, Expr *Init) {
1118-
assert((AEntity && LK == LK_Assignment) ||
1119-
(InitEntity && LK != LK_Assignment));
1130+
const CapturingEntity *CEntity, Expr *Init) {
1131+
assert(InitEntity || CEntity);
1132+
assert(!CEntity || LK == LK_Assignment || LK == LK_LifetimeCapture);
1133+
assert(!InitEntity || LK != LK_Assignment);
11201134
// If this entity doesn't have an interesting lifetime, don't bother looking
11211135
// for temporaries within its initializer.
11221136
if (LK == LK_FullExpression)
@@ -1199,6 +1213,17 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
11991213
break;
12001214
}
12011215

1216+
case LK_LifetimeCapture: {
1217+
if (!MTE)
1218+
return false;
1219+
assert(shouldLifetimeExtendThroughPath(Path) ==
1220+
PathLifetimeKind::NoExtend &&
1221+
"No lifetime extension in function calls");
1222+
SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
1223+
<< CEntity->Expression << DiagRange;
1224+
return false;
1225+
}
1226+
12021227
case LK_Assignment: {
12031228
if (!MTE || pathContainsInit(Path))
12041229
return false;
@@ -1207,10 +1232,10 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
12071232
"No lifetime extension for assignments");
12081233
if (IsGslPtrValueFromGslTempOwner)
12091234
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_assignment)
1210-
<< AEntity->LHS << DiagRange;
1235+
<< CEntity->Expression << DiagRange;
12111236
else
12121237
SemaRef.Diag(DiagLoc, diag::warn_dangling_pointer_assignment)
1213-
<< AEntity->LHS->getType()->isPointerType() << AEntity->LHS
1238+
<< CEntity->Expression->getType()->isPointerType() << CEntity->Expression
12141239
<< DiagRange;
12151240
return false;
12161241
}
@@ -1359,6 +1384,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
13591384
break;
13601385

13611386
case IndirectLocalPathEntry::LifetimeBoundCall:
1387+
case IndirectLocalPathEntry::LifetimeCapture:
13621388
case IndirectLocalPathEntry::TemporaryCopy:
13631389
case IndirectLocalPathEntry::GslPointerInit:
13641390
case IndirectLocalPathEntry::GslReferenceInit:
@@ -1412,17 +1438,27 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
14121438
return false;
14131439
};
14141440

1441+
bool HasReferenceBinding = Init->isGLValue();
14151442
llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
14161443
if (LK == LK_Assignment &&
1417-
shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity)) {
1444+
shouldRunGSLAssignmentAnalysis(SemaRef, *CEntity)) {
14181445
Path.push_back(
1419-
{isAssignmentOperatorLifetimeBound(AEntity->AssignmentOperator)
1446+
{isAssignmentOperatorLifetimeBound(CEntity->AssignmentOperator)
14201447
? IndirectLocalPathEntry::LifetimeBoundCall
14211448
: IndirectLocalPathEntry::GslPointerAssignment,
14221449
Init});
1450+
} else if (LK == LK_LifetimeCapture) {
1451+
Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init});
1452+
if (isRecordWithAttr<PointerAttr>(Init->getType()))
1453+
HasReferenceBinding = false;
1454+
// Skip the top MaterializeTemoraryExpr if it is temporary object of the
1455+
// pointer-like type itself.
1456+
if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(Init);
1457+
MTE && isPointerLikeType(Init->getType()))
1458+
Init = MTE->getSubExpr();
14231459
}
14241460

1425-
if (Init->isGLValue())
1461+
if (HasReferenceBinding)
14261462
visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding,
14271463
TemporaryVisitor);
14281464
else
@@ -1432,7 +1468,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
14321468
/*RevisitSubinits=*/!InitEntity);
14331469
}
14341470

1435-
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
1471+
void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
14361472
Expr *Init) {
14371473
auto LTResult = getEntityLifetime(&Entity);
14381474
LifetimeKind LK = LTResult.getInt();
@@ -1447,20 +1483,26 @@ void checkExprLifetimeMustTailArg(Sema &SemaRef,
14471483
/*AEntity*/ nullptr, Init);
14481484
}
14491485

1450-
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
1451-
Expr *Init) {
1486+
void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
1487+
Expr *RHS) {
14521488
bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored(
14531489
diag::warn_dangling_pointer_assignment, SourceLocation());
14541490
bool RunAnalysis = (EnableDanglingPointerAssignment &&
1455-
Entity.LHS->getType()->isPointerType()) ||
1491+
Entity.Expression->getType()->isPointerType()) ||
14561492
shouldRunGSLAssignmentAnalysis(SemaRef, Entity);
14571493

14581494
if (!RunAnalysis)
14591495
return;
14601496

14611497
checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
14621498
/*ExtendingEntity=*/nullptr, LK_Assignment, &Entity,
1463-
Init);
1499+
RHS);
14641500
}
14651501

1502+
void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity,
1503+
Expr *Captured) {
1504+
checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
1505+
/*ExtendingEntity=*/nullptr, LK_LifetimeCapture,
1506+
&Entity, Captured);
1507+
}
14661508
} // namespace clang::sema

0 commit comments

Comments
 (0)