Skip to content

Commit 2cef37e

Browse files
committed
Implement semantics for lifetime analysis
1 parent c7df106 commit 2cef37e

File tree

8 files changed

+228
-19
lines changed

8 files changed

+228
-19
lines changed

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 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">;
@@ -462,6 +463,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">;
462463
def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
463464
def Dangling : DiagGroup<"dangling", [DanglingAssignment,
464465
DanglingAssignmentGsl,
466+
DanglingCapture,
465467
DanglingField,
466468
DanglingInitializerList,
467469
DanglingGsl,

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10132,10 +10132,10 @@ def err_lifetimebound_ctor_dtor : Error<
1013210132
"%select{constructor|destructor}0">;
1013310133
def err_lifetimebound_parameter_void_return_type : Error<
1013410134
"'lifetimebound' attribute cannot be applied to a parameter of a function "
10135-
"that returns void">;
10135+
"that returns void; did you mean 'lifetime_capture_by(X)'">;
1013610136
def err_lifetimebound_implicit_object_parameter_void_return_type : Error<
1013710137
"'lifetimebound' attribute cannot be applied to an implicit object "
10138-
"parameter of a function that returns void">;
10138+
"parameter of a function that returns void; did you mean 'lifetime_capture_by(X)'">;
1013910139

1014010140
// CHECK: returning address/reference of stack memory
1014110141
def warn_ret_stack_addr_ref : Warning<
@@ -10230,6 +10230,9 @@ def warn_dangling_pointer_assignment : Warning<
1023010230
"object backing %select{|the pointer }0%1 "
1023110231
"will be destroyed at the end of the full-expression">,
1023210232
InGroup<DanglingAssignment>;
10233+
def warn_dangling_reference_captured : Warning<
10234+
"object captured by '%0' will be destroyed at the end of the full-expression">,
10235+
InGroup<DanglingCapture>;
1023310236

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

clang/include/clang/Sema/Sema.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2323,6 +2323,9 @@ class Sema final : public SemaBase {
23232323
bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = false);
23242324
bool BuiltinVectorToScalarMath(CallExpr *TheCall);
23252325

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

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,14 @@ enum LifetimeKind {
4545
/// a default member initializer), the program is ill-formed.
4646
LK_MemInitializer,
4747

48-
/// The lifetime of a temporary bound to this entity probably ends too soon,
48+
/// The lifetime of a temporary bound to this entity may end too soon,
4949
/// because the entity is a pointer and we assign the address of a temporary
5050
/// object to it.
5151
LK_Assignment,
52+
53+
/// The lifetime of a temporary bound to this entity may end too soon,
54+
/// because the entity may capture the reference to a temporary object.
55+
LK_LifetimeCapture,
5256
};
5357
using LifetimeResult =
5458
llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
@@ -193,6 +197,7 @@ struct IndirectLocalPathEntry {
193197
VarInit,
194198
LValToRVal,
195199
LifetimeBoundCall,
200+
LifetimeCapture,
196201
TemporaryCopy,
197202
LambdaCaptureInit,
198203
GslReferenceInit,
@@ -249,9 +254,10 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
249254
LocalVisitor Visit);
250255

251256
template <typename T> static bool isRecordWithAttr(QualType Type) {
252-
if (auto *RD = Type->getAsCXXRecordDecl())
253-
return RD->hasAttr<T>();
254-
return false;
257+
CXXRecordDecl *RD = Type.getNonReferenceType()->getAsCXXRecordDecl();
258+
if (!RD)
259+
return false;
260+
return RD->hasAttr<T>();
255261
}
256262

257263
// Decl::isInStdNamespace will return false for iterators in some STL
@@ -1049,6 +1055,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
10491055
case IndirectLocalPathEntry::AddressOf:
10501056
case IndirectLocalPathEntry::LValToRVal:
10511057
case IndirectLocalPathEntry::LifetimeBoundCall:
1058+
case IndirectLocalPathEntry::LifetimeCapture:
10521059
case IndirectLocalPathEntry::TemporaryCopy:
10531060
case IndirectLocalPathEntry::GslReferenceInit:
10541061
case IndirectLocalPathEntry::GslPointerInit:
@@ -1082,6 +1089,7 @@ static bool pathOnlyHandlesGslPointer(const IndirectLocalPath &Path) {
10821089
case IndirectLocalPathEntry::VarInit:
10831090
case IndirectLocalPathEntry::AddressOf:
10841091
case IndirectLocalPathEntry::LifetimeBoundCall:
1092+
case IndirectLocalPathEntry::LifetimeCapture:
10851093
continue;
10861094
case IndirectLocalPathEntry::GslPointerInit:
10871095
case IndirectLocalPathEntry::GslReferenceInit:
@@ -1110,12 +1118,13 @@ static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
11101118
isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
11111119
}
11121120

1113-
static void checkExprLifetimeImpl(Sema &SemaRef,
1114-
const InitializedEntity *InitEntity,
1115-
const InitializedEntity *ExtendingEntity,
1116-
LifetimeKind LK,
1117-
const AssignedEntity *AEntity, Expr *Init) {
1121+
static void
1122+
checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
1123+
const InitializedEntity *ExtendingEntity, LifetimeKind LK,
1124+
const AssignedEntity *AEntity,
1125+
const CapturingEntity *CapEntity, Expr *Init) {
11181126
assert((AEntity && LK == LK_Assignment) ||
1127+
(CapEntity && LK == LK_LifetimeCapture) ||
11191128
(InitEntity && LK != LK_Assignment));
11201129
// If this entity doesn't have an interesting lifetime, don't bother looking
11211130
// for temporaries within its initializer.
@@ -1199,6 +1208,17 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
11991208
break;
12001209
}
12011210

1211+
case LK_LifetimeCapture: {
1212+
if (!MTE)
1213+
return false;
1214+
assert(shouldLifetimeExtendThroughPath(Path) ==
1215+
PathLifetimeKind::NoExtend &&
1216+
"No lifetime extension in function calls");
1217+
SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
1218+
<< CapEntity->Entity << DiagRange;
1219+
return false;
1220+
}
1221+
12021222
case LK_Assignment: {
12031223
if (!MTE || pathContainsInit(Path))
12041224
return false;
@@ -1359,6 +1379,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
13591379
break;
13601380

13611381
case IndirectLocalPathEntry::LifetimeBoundCall:
1382+
case IndirectLocalPathEntry::LifetimeCapture:
13621383
case IndirectLocalPathEntry::TemporaryCopy:
13631384
case IndirectLocalPathEntry::GslPointerInit:
13641385
case IndirectLocalPathEntry::GslReferenceInit:
@@ -1411,7 +1432,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
14111432
// warnings or errors on inner temporaries within this one's initializer.
14121433
return false;
14131434
};
1414-
1435+
bool HasReferenceBinding = Init->isGLValue();
14151436
llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
14161437
if (LK == LK_Assignment &&
14171438
shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity)) {
@@ -1420,9 +1441,18 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
14201441
? IndirectLocalPathEntry::LifetimeBoundCall
14211442
: IndirectLocalPathEntry::GslPointerAssignment,
14221443
Init});
1444+
} else if (LK == LK_LifetimeCapture) {
1445+
Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init});
1446+
if (isRecordWithAttr<PointerAttr>(Init->getType()))
1447+
HasReferenceBinding = false;
1448+
// Skip the top MTE if it is a temporary object of the pointer-like type
1449+
// itself.
1450+
if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(Init);
1451+
MTE && isPointerLikeType(Init->getType()))
1452+
Init = MTE->getSubExpr();
14231453
}
14241454

1425-
if (Init->isGLValue())
1455+
if (HasReferenceBinding)
14261456
visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding,
14271457
TemporaryVisitor);
14281458
else
@@ -1438,13 +1468,13 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
14381468
LifetimeKind LK = LTResult.getInt();
14391469
const InitializedEntity *ExtendingEntity = LTResult.getPointer();
14401470
checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK,
1441-
/*AEntity*/ nullptr, Init);
1471+
/*AEntity*/ nullptr, /*CapEntity=*/nullptr, Init);
14421472
}
14431473

14441474
void checkExprLifetimeMustTailArg(Sema &SemaRef,
14451475
const InitializedEntity &Entity, Expr *Init) {
14461476
checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail,
1447-
/*AEntity*/ nullptr, Init);
1477+
/*AEntity*/ nullptr, /*CapEntity=*/nullptr, Init);
14481478
}
14491479

14501480
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
@@ -1460,7 +1490,15 @@ void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
14601490

14611491
checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
14621492
/*ExtendingEntity=*/nullptr, LK_Assignment, &Entity,
1463-
Init);
1493+
/*CapEntity=*/nullptr, Init);
1494+
}
1495+
1496+
void checkExprLifetime(Sema &SemaRef, const CapturingEntity &Entity,
1497+
Expr *Init) {
1498+
return checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
1499+
/*ExtendingEntity=*/nullptr, LK_LifetimeCapture,
1500+
/*AEntity=*/nullptr,
1501+
/*CapEntity=*/&Entity, Init);
14641502
}
14651503

14661504
} // namespace clang::sema

clang/lib/Sema/CheckExprLifetime.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ struct AssignedEntity {
2525
CXXMethodDecl *AssignmentOperator = nullptr;
2626
};
2727

28+
struct CapturingEntity {
29+
// In an function call involving a lifetime capture, this would be the
30+
// argument capturing the lifetime of another argument.
31+
// void addToSet(std::string_view sv [[clang::lifetime_capture_by(setsv)]],
32+
// set<std::string_view>& setsv);
33+
// set<std::string_view> setsv;
34+
// addToSet(std::string(), setsv); // Here 'setsv' is the 'Entity'.
35+
Expr *Entity = nullptr;
36+
};
37+
2838
/// Check that the lifetime of the given expr (and its subobjects) is
2939
/// sufficient for initializing the entity, and perform lifetime extension
3040
/// (when permitted) if not.
@@ -35,6 +45,9 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
3545
/// sufficient for assigning to the entity.
3646
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, Expr *Init);
3747

48+
void checkExprLifetime(Sema &SemaRef, const CapturingEntity &Entity,
49+
Expr *Init);
50+
3851
/// Check that the lifetime of the given expr (and its subobjects) is
3952
/// sufficient, assuming that it is passed as an argument to a musttail
4053
/// function.

clang/lib/Sema/SemaChecking.cpp

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//
1212
//===----------------------------------------------------------------------===//
1313

14+
#include "CheckExprLifetime.h"
1415
#include "clang/AST/APValue.h"
1516
#include "clang/AST/ASTContext.h"
1617
#include "clang/AST/Attr.h"
@@ -3229,6 +3230,49 @@ void Sema::CheckArgAlignment(SourceLocation Loc, NamedDecl *FDecl,
32293230
<< ParamName << (FDecl != nullptr) << FDecl;
32303231
}
32313232

3233+
void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
3234+
const Expr *ThisArg,
3235+
ArrayRef<const Expr *> Args) {
3236+
auto GetArgAt = [&](int Idx) {
3237+
if (IsMemberFunction && Idx == 0)
3238+
return const_cast<Expr *>(ThisArg);
3239+
return const_cast<Expr *>(Args[Idx - int(IsMemberFunction)]);
3240+
};
3241+
for (unsigned I = 0; I < FD->getNumParams(); ++I) {
3242+
auto *CapturedByAttr =
3243+
FD->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
3244+
if (!CapturedByAttr)
3245+
continue;
3246+
for (int CapturingParamIdx : CapturedByAttr->params()) {
3247+
Expr *Capturing = GetArgAt(CapturingParamIdx);
3248+
Expr *Captured = GetArgAt(I + IsMemberFunction);
3249+
CapturingEntity CE{Capturing};
3250+
// Ensure that 'Captured' outlives the 'Capturing' entity.
3251+
checkExprLifetime(*this, CE, Captured);
3252+
}
3253+
}
3254+
// Check when the 'this' object is captured.
3255+
if (IsMemberFunction) {
3256+
TypeSourceInfo *TSI = FD->getTypeSourceInfo();
3257+
if (!TSI)
3258+
return;
3259+
AttributedTypeLoc ATL;
3260+
for (TypeLoc TL = TSI->getTypeLoc();
3261+
(ATL = TL.getAsAdjusted<AttributedTypeLoc>());
3262+
TL = ATL.getModifiedLoc()) {
3263+
auto *CapturedByAttr = ATL.getAttrAs<LifetimeCaptureByAttr>();
3264+
if (!CapturedByAttr)
3265+
continue;
3266+
Expr *Captured = GetArgAt(0);
3267+
for (int CapturingParamIdx : CapturedByAttr->params()) {
3268+
Expr *Capturing = GetArgAt(CapturingParamIdx);
3269+
CapturingEntity CE{Capturing};
3270+
checkExprLifetime(*this, CE, Captured);
3271+
}
3272+
}
3273+
}
3274+
}
3275+
32323276
void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
32333277
const Expr *ThisArg, ArrayRef<const Expr *> Args,
32343278
bool IsMemberFunction, SourceLocation Loc,
@@ -3269,7 +3313,8 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
32693313
}
32703314
}
32713315
}
3272-
3316+
if (FD)
3317+
checkLifetimeCaptureBy(FD, IsMemberFunction, ThisArg, Args);
32733318
if (FDecl || Proto) {
32743319
CheckNonNullArguments(*this, FDecl, Proto, Args, Loc);
32753320

0 commit comments

Comments
 (0)