Skip to content

Commit 4951a7b

Browse files
committed
start working on lifetime capture
1 parent 09284e7 commit 4951a7b

14 files changed

+387
-24
lines changed

clang/include/clang/Basic/Attr.td

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,6 +1869,46 @@ def LifetimeBound : DeclOrTypeAttr {
18691869
let SimpleHandler = 1;
18701870
}
18711871

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

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3382,6 +3382,18 @@ def err_callback_callee_is_variadic : Error<
33823382
"'callback' attribute callee may not be variadic">;
33833383
def err_callback_implicit_this_not_available : Error<
33843384
"'callback' argument at position %0 references unavailable implicit 'this'">;
3385+
3386+
def err_capture_by_attribute_multiple : Error<
3387+
"multiple 'lifetime_capture' attributes specified">;
3388+
def err_capture_by_attribute_no_entity : Error<
3389+
"'lifetime_capture_by' attribute specifies no capturing entity">;
3390+
def err_capture_by_implicit_this_not_available : Error<
3391+
"'lifetime_capture_by' argument references unavailable implicit 'this'">;
3392+
def err_capture_by_attribute_argument_unknown : Error<
3393+
"'lifetime_capture_by' attribute argument %0 is not a known function parameter"
3394+
". Must be a function parameter of one of 'this', 'global' or 'unknown'">;
3395+
def err_capture_by_references_itself : Error<"'lifetime_capture_by' argument references itself">;
3396+
33853397
def err_init_method_bad_return_type : Error<
33863398
"init methods must return an object pointer type, not %0">;
33873399
def err_attribute_invalid_size : Error<
@@ -10185,6 +10197,10 @@ def warn_dangling_pointer_assignment : Warning<
1018510197
"object backing the pointer %0 "
1018610198
"will be destroyed at the end of the full-expression">,
1018710199
InGroup<DanglingAssignment>;
10200+
def warn_dangling_reference_captured : Warning<
10201+
"object captured by the '%0' "
10202+
"will be destroyed at the end of the full-expression">,
10203+
InGroup<DanglingAssignment>;
1018810204

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

clang/include/clang/Sema/Sema.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,10 @@ class Sema final : public SemaBase {
18301830
/// Add [[gsl::Pointer]] attributes for std:: types.
18311831
void inferGslPointerAttribute(TypedefNameDecl *TD);
18321832

1833+
LifetimeCaptureByAttr *ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
1834+
StringRef ParamName);
1835+
void LazyProcessLifetimeCaptureByParams(FunctionDecl *FD);
1836+
18331837
/// Add _Nullable attributes for std:: types.
18341838
void inferNullableClassAttribute(CXXRecordDecl *CRD);
18351839

@@ -2384,6 +2388,9 @@ class Sema final : public SemaBase {
23842388
bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res);
23852389
bool BuiltinVectorToScalarMath(CallExpr *TheCall);
23862390

2391+
void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
2392+
const Expr *ThisArg, ArrayRef<const Expr *> Args);
2393+
23872394
/// Handles the checks for format strings, non-POD arguments to vararg
23882395
/// functions, NULL arguments passed to non-NULL parameters, diagnose_if
23892396
/// attributes and AArch64 SME attributes.

clang/lib/AST/TypePrinter.cpp

Lines changed: 21 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"
@@ -1907,6 +1909,24 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19071909
OS << " [[clang::lifetimebound]]";
19081910
return;
19091911
}
1912+
if (T->getAttrKind() == attr::LifetimeCaptureBy) {
1913+
OS << " [[clang::lifetime_capture_by(...)";
1914+
// const LifetimeCaptureByAttr* A= T->getAs<LifetimeCaptureByAttr>();
1915+
// bool valid = true;
1916+
// for (int I : A->params())
1917+
// valid &= I != -2;
1918+
// if (valid) {
1919+
// OS << "invalid)";
1920+
// return;
1921+
// }
1922+
// for (size_t I = 0; I < A->params_size(); ++I) {
1923+
// OS << A->getArgIdents()[I]->getName()
1924+
// << "(idx: " << *(A->params_begin() + I) << ")";
1925+
// if (I != A->params_size() - 1)
1926+
// OS << ", ";
1927+
// }
1928+
return;
1929+
}
19101930

19111931
// The printing of the address_space attribute is handled by the qualifier
19121932
// since it is still stored in the qualifier. Return early to prevent printing
@@ -1966,6 +1986,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19661986
case attr::SizedBy:
19671987
case attr::SizedByOrNull:
19681988
case attr::LifetimeBound:
1989+
case attr::LifetimeCaptureBy:
19691990
case attr::TypeNonNull:
19701991
case attr::TypeNullable:
19711992
case attr::TypeNullableResult:

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,14 @@ enum LifetimeKind {
4141
/// a default member initializer), the program is ill-formed.
4242
LK_MemInitializer,
4343

44-
/// The lifetime of a temporary bound to this entity probably ends too soon,
44+
/// The lifetime of a temporary bound to this entity may end too soon,
4545
/// because the entity is a pointer and we assign the address of a temporary
4646
/// object to it.
4747
LK_Assignment,
48+
49+
/// The lifetime of a temporary bound to this entity probably ends too soon,
50+
/// because the entity may capture the reference to a temporary object.
51+
LK_LifetimeCapture,
4852
};
4953
using LifetimeResult =
5054
llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
@@ -189,6 +193,7 @@ struct IndirectLocalPathEntry {
189193
VarInit,
190194
LValToRVal,
191195
LifetimeBoundCall,
196+
LifetimeCapture,
192197
TemporaryCopy,
193198
LambdaCaptureInit,
194199
GslReferenceInit,
@@ -898,6 +903,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
898903
case IndirectLocalPathEntry::AddressOf:
899904
case IndirectLocalPathEntry::LValToRVal:
900905
case IndirectLocalPathEntry::LifetimeBoundCall:
906+
case IndirectLocalPathEntry::LifetimeCapture:
901907
case IndirectLocalPathEntry::TemporaryCopy:
902908
case IndirectLocalPathEntry::GslReferenceInit:
903909
case IndirectLocalPathEntry::GslPointerInit:
@@ -928,6 +934,7 @@ static bool pathOnlyHandlesGslPointer(IndirectLocalPath &Path) {
928934
case IndirectLocalPathEntry::VarInit:
929935
case IndirectLocalPathEntry::AddressOf:
930936
case IndirectLocalPathEntry::LifetimeBoundCall:
937+
case IndirectLocalPathEntry::LifetimeCapture:
931938
continue;
932939
case IndirectLocalPathEntry::GslPointerInit:
933940
case IndirectLocalPathEntry::GslReferenceInit:
@@ -948,21 +955,22 @@ static bool isAssignmentOperatorLifetimeBound(CXXMethodDecl *CMD) {
948955
}
949956

950957
static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
951-
const AssignedEntity &Entity) {
958+
const CapturingEntity &Entity) {
952959
bool EnableGSLAssignmentWarnings = !SemaRef.getDiagnostics().isIgnored(
953960
diag::warn_dangling_lifetime_pointer_assignment, SourceLocation());
954961
return (EnableGSLAssignmentWarnings &&
955-
(isRecordWithAttr<PointerAttr>(Entity.LHS->getType()) ||
962+
(isRecordWithAttr<PointerAttr>(Entity.Expression->getType()) ||
956963
isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
957964
}
958965

959966
static void checkExprLifetimeImpl(Sema &SemaRef,
960967
const InitializedEntity *InitEntity,
961968
const InitializedEntity *ExtendingEntity,
962969
LifetimeKind LK,
963-
const AssignedEntity *AEntity, Expr *Init) {
964-
assert((AEntity && LK == LK_Assignment) ||
965-
(InitEntity && LK != LK_Assignment));
970+
const CapturingEntity *CEntity, Expr *Init) {
971+
assert(InitEntity || CEntity);
972+
assert(!CEntity || LK == LK_Assignment || LK == LK_LifetimeCapture);
973+
assert(!InitEntity || LK != LK_Assignment);
966974
// If this entity doesn't have an interesting lifetime, don't bother looking
967975
// for temporaries within its initializer.
968976
if (LK == LK_FullExpression)
@@ -1046,6 +1054,17 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
10461054
break;
10471055
}
10481056

1057+
case LK_LifetimeCapture: {
1058+
if (!MTE)
1059+
return false;
1060+
assert(shouldLifetimeExtendThroughPath(Path) ==
1061+
PathLifetimeKind::NoExtend &&
1062+
"No lifetime extension for in function calls");
1063+
SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
1064+
<< CEntity->Expression << DiagRange;
1065+
return false;
1066+
}
1067+
10491068
case LK_Assignment: {
10501069
if (!MTE || pathContainsInit(Path))
10511070
return false;
@@ -1056,7 +1075,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
10561075
IsGslPtrValueFromGslTempOwner
10571076
? diag::warn_dangling_lifetime_pointer_assignment
10581077
: diag::warn_dangling_pointer_assignment)
1059-
<< AEntity->LHS << DiagRange;
1078+
<< CEntity->Expression << DiagRange;
10601079
return false;
10611080
}
10621081
case LK_MemInitializer: {
@@ -1199,6 +1218,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
11991218
break;
12001219

12011220
case IndirectLocalPathEntry::LifetimeBoundCall:
1221+
case IndirectLocalPathEntry::LifetimeCapture:
12021222
case IndirectLocalPathEntry::TemporaryCopy:
12031223
case IndirectLocalPathEntry::GslPointerInit:
12041224
case IndirectLocalPathEntry::GslReferenceInit:
@@ -1243,8 +1263,10 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
12431263
};
12441264

12451265
llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
1246-
if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity))
1266+
if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *CEntity))
12471267
Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init});
1268+
else if (LK == LK_LifetimeCapture)
1269+
Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init});
12481270

12491271
if (Init->isGLValue())
12501272
visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding,
@@ -1256,7 +1278,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
12561278
/*RevisitSubinits=*/!InitEntity);
12571279
}
12581280

1259-
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
1281+
void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
12601282
Expr *Init) {
12611283
auto LTResult = getEntityLifetime(&Entity);
12621284
LifetimeKind LK = LTResult.getInt();
@@ -1265,20 +1287,26 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
12651287
/*AEntity*/ nullptr, Init);
12661288
}
12671289

1268-
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
1269-
Expr *Init) {
1290+
void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
1291+
Expr *RHS) {
12701292
bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored(
12711293
diag::warn_dangling_pointer_assignment, SourceLocation());
12721294
bool RunAnalysis = (EnableDanglingPointerAssignment &&
1273-
Entity.LHS->getType()->isPointerType()) ||
1295+
Entity.Expression->getType()->isPointerType()) ||
12741296
shouldRunGSLAssignmentAnalysis(SemaRef, Entity);
12751297

12761298
if (!RunAnalysis)
12771299
return;
12781300

12791301
checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
12801302
/*ExtendingEntity=*/nullptr, LK_Assignment, &Entity,
1281-
Init);
1303+
RHS);
12821304
}
12831305

1306+
void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity,
1307+
Expr *Captured) {
1308+
checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
1309+
/*ExtendingEntity=*/nullptr, LK_LifetimeCapture,
1310+
&Entity, Captured);
1311+
}
12841312
} // namespace clang::sema

clang/lib/Sema/CheckExprLifetime.h

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,36 @@
1818

1919
namespace clang::sema {
2020

21-
/// Describes an entity that is being assigned.
22-
struct AssignedEntity {
23-
// The left-hand side expression of the assignment.
24-
Expr *LHS = nullptr;
21+
struct CapturingEntity {
22+
// The expression of the entity which captures another entity.
23+
// For example:
24+
// 1. In an assignment, this would be the left-hand side expression.
25+
// std::string_view sv;
26+
// sv = std::string(); // Here 'sv' is the 'Entity'.
27+
//
28+
// 2. In an function call involving a lifetime capture, this would be the
29+
// argument capturing the lifetime of another argument.
30+
// void addToSet(std::string_view s [[clang::lifetime_capture_by(sv)]],
31+
// set<std::string_view>& setsv);
32+
// set<std::string_view> ssv;
33+
// addToSet(std::string(), ssv); // Here 'ssv' is the 'Entity'.
34+
Expr *Expression = nullptr;
2535
CXXMethodDecl *AssignmentOperator = nullptr;
2636
};
2737

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.
31-
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
41+
void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
3242
Expr *Init);
3343

3444
/// Check that the lifetime of the given expr (and its subobjects) is
3545
/// sufficient for assigning to the entity.
36-
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, Expr *Init);
46+
void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
47+
Expr *RHS);
48+
49+
void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity,
50+
Expr *Captured);
3751

3852
} // namespace clang::sema
3953

clang/lib/Sema/SemaChecking.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
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"
1718
#include "clang/AST/AttrIterator.h"
19+
#include "clang/AST/Attrs.inc"
1820
#include "clang/AST/CharUnits.h"
1921
#include "clang/AST/Decl.h"
2022
#include "clang/AST/DeclBase.h"
@@ -3203,6 +3205,29 @@ void Sema::CheckArgAlignment(SourceLocation Loc, NamedDecl *FDecl,
32033205
<< ParamName << (FDecl != nullptr) << FDecl;
32043206
}
32053207

3208+
void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
3209+
const Expr *ThisArg,
3210+
ArrayRef<const Expr *> Args) {
3211+
auto GetArgAt = [&](int Idx) {
3212+
if (IsMemberFunction && Idx == 0)
3213+
return const_cast<Expr *>(ThisArg);
3214+
return const_cast<Expr *>(Args[Idx - int(IsMemberFunction)]);
3215+
};
3216+
for (unsigned I = 0; I < FD->getNumParams(); ++I) {
3217+
auto *CapturedByAttr =
3218+
FD->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
3219+
if (!CapturedByAttr)
3220+
continue;
3221+
for (int CapturingParamIdx : CapturedByAttr->params()) {
3222+
Expr *Capturing = GetArgAt(CapturingParamIdx);
3223+
Expr *Captured = GetArgAt(I + IsMemberFunction);
3224+
CapturingEntity CE{Capturing};
3225+
// Ensure that 'Captured' lives atleast as long as the 'Capturing' entity.
3226+
checkCaptureLifetime(*this, CE, Captured);
3227+
}
3228+
}
3229+
}
3230+
32063231
void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
32073232
const Expr *ThisArg, ArrayRef<const Expr *> Args,
32083233
bool IsMemberFunction, SourceLocation Loc,
@@ -3244,6 +3269,8 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
32443269
}
32453270
}
32463271

3272+
if (FD)
3273+
checkLifetimeCaptureBy(FD, IsMemberFunction, ThisArg, Args);
32473274
if (FDecl || Proto) {
32483275
CheckNonNullArguments(*this, FDecl, Proto, Args, Loc);
32493276

0 commit comments

Comments
 (0)