Skip to content

Commit 83f0c8f

Browse files
committed
Introduce [[clang::lifetime_capture_by]]
1 parent d822c09 commit 83f0c8f

File tree

9 files changed

+268
-0
lines changed

9 files changed

+268
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,9 @@ Attribute Changes in Clang
449449
- Fix a bug where clang doesn't automatically apply the ``[[gsl::Owner]]`` or
450450
``[[gsl::Pointer]]`` to STL explicit template specialization decls. (#GH109442)
451451

452+
- Clang now supports ``[[clang::lifetime_capture_by(X)]]``. Similar to lifetimebound, this can be
453+
used to specify when a reference to a function parameter is captured by another capturing entity ``X``.
454+
452455
Improvements to Clang's diagnostics
453456
-----------------------------------
454457

clang/include/clang/Basic/Attr.td

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,6 +1889,40 @@ def LifetimeBound : DeclOrTypeAttr {
18891889
let SimpleHandler = 1;
18901890
}
18911891

1892+
def LifetimeCaptureBy : DeclOrTypeAttr {
1893+
let Spellings = [Clang<"lifetime_capture_by", 0>];
1894+
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
1895+
let Args = [VariadicParamOrParamIdxArgument<"Params">];
1896+
let Documentation = [LifetimeCaptureByDocs];
1897+
let LangOpts = [CPlusPlus];
1898+
let AdditionalMembers = [{
1899+
private:
1900+
SmallVector<IdentifierInfo*, 1> ArgIdents;
1901+
SmallVector<SourceLocation, 1> ArgLocs;
1902+
1903+
public:
1904+
static constexpr int THIS = 0;
1905+
static constexpr int INVALID = -1;
1906+
static constexpr int UNKNOWN = -2;
1907+
static constexpr int GLOBAL = -3;
1908+
1909+
void setArgs(SmallVector<IdentifierInfo*, 1>&& Idents,
1910+
SmallVector<SourceLocation, 1>&& Locs) {
1911+
assert(Idents.size() == Locs.size());
1912+
assert(Idents.size() == params_Size);
1913+
ArgIdents = std::move(Idents);
1914+
ArgLocs = std::move(Locs);
1915+
}
1916+
1917+
ArrayRef<IdentifierInfo*> getArgIdents() const { return ArgIdents; }
1918+
ArrayRef<SourceLocation> getArgLocs() const { return ArgLocs; }
1919+
void setParamIdx(size_t Idx, int Val) {
1920+
assert(Idx < params_Size);
1921+
params_[Idx] = Val;
1922+
}
1923+
}];
1924+
}
1925+
18921926
def TrivialABI : InheritableAttr {
18931927
// This attribute does not have a C [[]] spelling because it requires the
18941928
// CPlusPlus language option.

clang/include/clang/Basic/AttrDocs.td

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3967,6 +3967,69 @@ Attribute ``trivial_abi`` has no effect in the following cases:
39673967
}];
39683968
}
39693969

3970+
3971+
def LifetimeCaptureByDocs : Documentation {
3972+
let Category = DocCatFunction;
3973+
let Content = [{
3974+
The ``lifetime_capture_by(X)`` attribute on a function parameter or implicit object
3975+
parameter indicates that references to arguments passed to such parameters may be
3976+
captured by the capturing entity ``X``.
3977+
3978+
The capturing entity ``X`` can be one of the following:
3979+
- Another (named) function parameter.
3980+
3981+
.. code-block:: c++
3982+
3983+
void addToSet(std::string_view a [[clang::lifetime_capture_by(s)]], std::set<std::string_view>& s) {
3984+
s.insert(a);
3985+
}
3986+
3987+
- ``this`` (in case of member functions).
3988+
3989+
.. code-block:: c++
3990+
3991+
class S {
3992+
void addToSet(std::string_view a [[clang::lifetime_capture_by(this)]]) {
3993+
s.insert(a);
3994+
}
3995+
std::set<std::string_view> s;
3996+
};
3997+
3998+
- 'global', 'unknown' (without quotes).
3999+
4000+
.. code-block:: c++
4001+
4002+
std::set<std::string_view> s;
4003+
void addToSet(std::string_view a [[clang::lifetime_capture_by(global)]]) {
4004+
s.insert(a);
4005+
}
4006+
void addSomewhere(std::string_view a [[clang::lifetime_capture_by(unknown)]]);
4007+
4008+
The attribute can be applied to the implicit ``this`` parameter of a member
4009+
function by writing the attribute after the function type:
4010+
4011+
.. code-block:: c++
4012+
4013+
struct S {
4014+
const char *data(std::set<S*>& s) [[clang::lifetime_capture_by(s)]] {
4015+
s.insert(this);
4016+
}
4017+
};
4018+
4019+
The attribute supports specifying more than one capturing entities:
4020+
4021+
.. code-block:: c++
4022+
4023+
void addToSets(std::string_view a [[clang::lifetime_capture_by(s1, s2)]],
4024+
std::set<std::string_view>& s1,
4025+
std::set<std::string_view>& s2) {
4026+
s1.insert(a);
4027+
s2.insert(a);
4028+
}
4029+
4030+
.. _`lifetimebound`: https://clang.llvm.org/docs/AttributeReference.html#lifetimebound
4031+
}];
4032+
}
39704033
def MSInheritanceDocs : Documentation {
39714034
let Category = DocCatDecl;
39724035
let Heading = "__single_inheritance, __multiple_inheritance, __virtual_inheritance";

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 12 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<

clang/include/clang/Sema/Sema.h

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

1763+
LifetimeCaptureByAttr *ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
1764+
StringRef ParamName);
1765+
void LazyProcessLifetimeCaptureByParams(FunctionDecl *FD);
1766+
17631767
/// Add _Nullable attributes for std:: types.
17641768
void inferNullableClassAttribute(CXXRecordDecl *CRD);
17651769

clang/lib/AST/TypePrinter.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "clang/AST/TextNodeDumper.h"
2626
#include "clang/AST/Type.h"
2727
#include "clang/Basic/AddressSpaces.h"
28+
#include "clang/Basic/AttrKinds.h"
2829
#include "clang/Basic/ExceptionSpecificationType.h"
2930
#include "clang/Basic/IdentifierTable.h"
3031
#include "clang/Basic/LLVM.h"
@@ -1909,6 +1910,12 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19091910
OS << " [[clang::lifetimebound]]";
19101911
return;
19111912
}
1913+
if (T->getAttrKind() == attr::LifetimeCaptureBy) {
1914+
// FIXME: Print the attribute arguments once we have a way to retrieve these
1915+
// here.
1916+
OS << " [[clang::lifetime_capture_by(...)";
1917+
return;
1918+
}
19121919

19131920
// The printing of the address_space attribute is handled by the qualifier
19141921
// since it is still stored in the qualifier. Return early to prevent printing
@@ -1976,6 +1983,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19761983
case attr::SizedBy:
19771984
case attr::SizedByOrNull:
19781985
case attr::LifetimeBound:
1986+
case attr::LifetimeCaptureBy:
19791987
case attr::TypeNonNull:
19801988
case attr::TypeNullable:
19811989
case attr::TypeNullableResult:

clang/lib/Sema/SemaDecl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16687,6 +16687,7 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) {
1668716687
}
1668816688
}
1668916689

16690+
LazyProcessLifetimeCaptureByParams(FD);
1669016691
inferLifetimeBoundAttribute(FD);
1669116692
AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD);
1669216693

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "clang/AST/ASTContext.h"
1515
#include "clang/AST/ASTMutationListener.h"
1616
#include "clang/AST/CXXInheritance.h"
17+
#include "clang/AST/Decl.h"
1718
#include "clang/AST/DeclCXX.h"
1819
#include "clang/AST/DeclObjC.h"
1920
#include "clang/AST/DeclTemplate.h"
@@ -3867,6 +3868,105 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
38673868
S.Context, AL, EncodingIndices.data(), EncodingIndices.size()));
38683869
}
38693870

3871+
LifetimeCaptureByAttr *Sema::ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
3872+
StringRef ParamName) {
3873+
// Atleast one capture by is required.
3874+
if (AL.getNumArgs() == 0) {
3875+
Diag(AL.getLoc(), diag::err_capture_by_attribute_no_entity)
3876+
<< AL.getRange();
3877+
return nullptr;
3878+
}
3879+
SmallVector<IdentifierInfo *, 1> ParamIdents;
3880+
SmallVector<SourceLocation, 1> ParamLocs;
3881+
for (unsigned I = 0; I < AL.getNumArgs(); ++I) {
3882+
if (AL.isArgExpr(I)) {
3883+
Expr *E = AL.getArgAsExpr(I);
3884+
Diag(E->getExprLoc(), diag::err_capture_by_attribute_argument_unknown)
3885+
<< E << E->getExprLoc();
3886+
continue;
3887+
}
3888+
assert(AL.isArgIdent(I));
3889+
IdentifierLoc *IdLoc = AL.getArgAsIdent(I);
3890+
if (IdLoc->Ident->getName() == ParamName) {
3891+
Diag(IdLoc->Loc, diag::err_capture_by_references_itself) << IdLoc->Loc;
3892+
continue;
3893+
}
3894+
ParamIdents.push_back(IdLoc->Ident);
3895+
ParamLocs.push_back(IdLoc->Loc);
3896+
}
3897+
SmallVector<int, 1> FakeParamIndices(ParamIdents.size(),
3898+
LifetimeCaptureByAttr::INVALID);
3899+
LifetimeCaptureByAttr *CapturedBy = ::new (Context) LifetimeCaptureByAttr(
3900+
Context, AL, FakeParamIndices.data(), FakeParamIndices.size());
3901+
CapturedBy->setArgs(std::move(ParamIdents), std::move(ParamLocs));
3902+
return CapturedBy;
3903+
}
3904+
3905+
static void HandleLifetimeCaptureByAttr(Sema &S, Decl *D,
3906+
const ParsedAttr &AL) {
3907+
// Do not allow multiple attributes.
3908+
if (D->hasAttr<LifetimeCaptureByAttr>()) {
3909+
S.Diag(AL.getLoc(), diag::err_capture_by_attribute_multiple)
3910+
<< AL.getRange();
3911+
return;
3912+
}
3913+
auto *PVD = dyn_cast<ParmVarDecl>(D);
3914+
assert(PVD);
3915+
auto *CaptureByAttr = S.ParseLifetimeCaptureByAttr(AL, PVD->getName());
3916+
if (CaptureByAttr)
3917+
D->addAttr(CaptureByAttr);
3918+
}
3919+
3920+
void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) {
3921+
bool HasImplicitThisParam = isInstanceMethod(FD);
3922+
3923+
llvm::StringMap<int> NameIdxMapping;
3924+
NameIdxMapping["global"] = LifetimeCaptureByAttr::GLOBAL;
3925+
NameIdxMapping["unknown"] = LifetimeCaptureByAttr::UNKNOWN;
3926+
int Idx = 0;
3927+
if (HasImplicitThisParam) {
3928+
NameIdxMapping["this"] = 0;
3929+
Idx++;
3930+
}
3931+
for (const ParmVarDecl *PVD : FD->parameters())
3932+
NameIdxMapping[PVD->getName()] = Idx++;
3933+
auto HandleCaptureBy = [&](LifetimeCaptureByAttr *CapturedBy) {
3934+
if (!CapturedBy)
3935+
return;
3936+
const auto &Entities = CapturedBy->getArgIdents();
3937+
for (size_t I = 0; I < Entities.size(); ++I) {
3938+
StringRef Name = Entities[I]->getName();
3939+
auto It = NameIdxMapping.find(Name);
3940+
if (It == NameIdxMapping.end()) {
3941+
auto Loc = CapturedBy->getArgLocs()[I];
3942+
if (!HasImplicitThisParam && Name == "this")
3943+
Diag(Loc, diag::err_capture_by_implicit_this_not_available) << Loc;
3944+
else
3945+
Diag(Loc, diag::err_capture_by_attribute_argument_unknown)
3946+
<< Entities[I] << Loc;
3947+
continue;
3948+
}
3949+
CapturedBy->setParamIdx(I, It->second);
3950+
}
3951+
};
3952+
for (ParmVarDecl *PVD : FD->parameters())
3953+
HandleCaptureBy(PVD->getAttr<LifetimeCaptureByAttr>());
3954+
if (!HasImplicitThisParam)
3955+
return;
3956+
TypeSourceInfo *TSI = FD->getTypeSourceInfo();
3957+
if (!TSI)
3958+
return;
3959+
AttributedTypeLoc ATL;
3960+
for (TypeLoc TL = TSI->getTypeLoc();
3961+
(ATL = TL.getAsAdjusted<AttributedTypeLoc>());
3962+
TL = ATL.getModifiedLoc()) {
3963+
auto *A = ATL.getAttrAs<LifetimeCaptureByAttr>();
3964+
if (!A)
3965+
continue;
3966+
HandleCaptureBy(const_cast<LifetimeCaptureByAttr *>(A));
3967+
}
3968+
}
3969+
38703970
static bool isFunctionLike(const Type &T) {
38713971
// Check for explicit function types.
38723972
// 'called_once' is only supported in Objective-C and it has
@@ -6644,6 +6744,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
66446744
case ParsedAttr::AT_Callback:
66456745
handleCallbackAttr(S, D, AL);
66466746
break;
6747+
case ParsedAttr::AT_LifetimeCaptureBy:
6748+
HandleLifetimeCaptureByAttr(S, D, AL);
6749+
break;
66476750
case ParsedAttr::AT_CalledOnce:
66486751
handleCalledOnceAttr(S, D, AL);
66496752
break;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %clang_cc1 -std=c++23 -verify %s
2+
3+
struct S {
4+
const int *x;
5+
void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x = &x; }
6+
};
7+
8+
///////////////////////////
9+
// Test for valid usages.
10+
///////////////////////////
11+
[[clang::lifetime_capture_by(unknown)]] // expected-error {{'lifetime_capture_by' attribute only applies to parameters and implicit object parameters}}
12+
void nonMember(
13+
const int &x1 [[clang::lifetime_capture_by(s, t)]],
14+
S &s,
15+
S &t,
16+
const int &x2 [[clang::lifetime_capture_by(12345 + 12)]], // expected-error {{'lifetime_capture_by' attribute argument 12345 + 12 is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
17+
const int &x3 [[clang::lifetime_capture_by(abcdefgh)]], // expected-error {{'lifetime_capture_by' attribute argument 'abcdefgh' is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
18+
const int &x4 [[clang::lifetime_capture_by("abcdefgh")]], // expected-error {{'lifetime_capture_by' attribute argument "abcdefgh" is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
19+
const int &x5 [[clang::lifetime_capture_by(this)]], // expected-error {{'lifetime_capture_by' argument references unavailable implicit 'this'}}
20+
const int &x6 [[clang::lifetime_capture_by()]], // expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}}
21+
const int& x7 [[clang::lifetime_capture_by(u,
22+
x7)]], // expected-error {{'lifetime_capture_by' argument references itself}}
23+
const S& u
24+
)
25+
{
26+
s.captureInt(x1);
27+
}
28+
29+
struct T {
30+
void member(
31+
const int &x [[clang::lifetime_capture_by(s)]],
32+
S &s,
33+
S &t,
34+
const int &y [[clang::lifetime_capture_by(s)]],
35+
const int &z [[clang::lifetime_capture_by(this, x, y)]],
36+
const int &u [[clang::lifetime_capture_by(global, x, s)]])
37+
{
38+
s.captureInt(x);
39+
}
40+
};

0 commit comments

Comments
 (0)