Skip to content

Commit ad89af1

Browse files
authored
Merge pull request #9595 from swiftlang/gaborh/cherry-pick-lifetime-capture-by
Reapply "[clang] Introduce [[clang::lifetime_capture_by(X)]]
2 parents fcc20a2 + c3b99be commit ad89af1

11 files changed

+324
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,9 @@ Attribute Changes in Clang
636636
- The ``hybrid_patchable`` attribute is now supported on ARM64EC targets. It can be used to specify
637637
that a function requires an additional x86-64 thunk, which may be patched at runtime.
638638

639+
- Clang now supports ``[[clang::lifetime_capture_by(X)]]``. Similar to lifetimebound, this can be
640+
used to specify when a reference to a function parameter is captured by another capturing entity ``X``.
641+
639642
Improvements to Clang's diagnostics
640643
-----------------------------------
641644
- Clang now emits an error instead of a warning for ``-Wundefined-internal``

clang/include/clang/Basic/Attr.td

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,6 +1840,37 @@ def LifetimeBound : DeclOrTypeAttr {
18401840
let SimpleHandler = 1;
18411841
}
18421842

1843+
def LifetimeCaptureBy : DeclOrTypeAttr {
1844+
let Spellings = [Clang<"lifetime_capture_by", 0>];
1845+
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
1846+
let Args = [VariadicParamOrParamIdxArgument<"Params">];
1847+
let Documentation = [LifetimeCaptureByDocs];
1848+
let AdditionalMembers = [{
1849+
private:
1850+
ArrayRef<IdentifierInfo*> ArgIdents;
1851+
ArrayRef<SourceLocation> ArgLocs;
1852+
1853+
public:
1854+
static constexpr int THIS = 0;
1855+
static constexpr int INVALID = -1;
1856+
static constexpr int UNKNOWN = -2;
1857+
static constexpr int GLOBAL = -3;
1858+
1859+
void setArgs(ArrayRef<IdentifierInfo*> Idents, ArrayRef<SourceLocation> Locs) {
1860+
assert(Idents.size() == params_Size);
1861+
assert(Locs.size() == params_Size);
1862+
ArgIdents = Idents;
1863+
ArgLocs = Locs;
1864+
}
1865+
auto getArgIdents() const { return ArgIdents; }
1866+
auto getArgLocs() const { return ArgLocs; }
1867+
void setParamIdx(size_t Idx, int Val) {
1868+
assert(Idx < params_Size);
1869+
params_[Idx] = Val;
1870+
}
1871+
}];
1872+
}
1873+
18431874
def TrivialABI : InheritableAttr {
18441875
// This attribute does not have a C [[]] spelling because it requires the
18451876
// CPlusPlus language option.

clang/include/clang/Basic/AttrDocs.td

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3813,6 +3813,75 @@ have their lifetimes extended.
38133813
}];
38143814
}
38153815

3816+
def LifetimeCaptureByDocs : Documentation {
3817+
let Category = DocCatFunction;
3818+
let Content = [{
3819+
Similar to `lifetimebound`_, the ``lifetime_capture_by(X)`` attribute on a function
3820+
parameter or implicit object parameter indicates that that objects that are referred to
3821+
by that parameter may also be referred to by the capturing entity ``X``.
3822+
3823+
By default, a reference is considered to refer to its referenced object, a
3824+
pointer is considered to refer to its pointee, a ``std::initializer_list<T>``
3825+
is considered to refer to its underlying array, and aggregates (arrays and
3826+
simple ``struct``\s) are considered to refer to all objects that their
3827+
transitive subobjects refer to.
3828+
3829+
The capturing entity ``X`` can be one of the following:
3830+
- Another (named) function parameter.
3831+
3832+
.. code-block:: c++
3833+
3834+
void addToSet(std::string_view a [[clang::lifetime_capture_by(s)]], std::set<std::string_view>& s) {
3835+
s.insert(a);
3836+
}
3837+
3838+
- ``this`` (in case of member functions).
3839+
3840+
.. code-block:: c++
3841+
3842+
class S {
3843+
void addToSet(std::string_view a [[clang::lifetime_capture_by(this)]]) {
3844+
s.insert(a);
3845+
}
3846+
std::set<std::string_view> s;
3847+
};
3848+
3849+
- 'global', 'unknown' (without quotes).
3850+
3851+
.. code-block:: c++
3852+
3853+
std::set<std::string_view> s;
3854+
void addToSet(std::string_view a [[clang::lifetime_capture_by(global)]]) {
3855+
s.insert(a);
3856+
}
3857+
void addSomewhere(std::string_view a [[clang::lifetime_capture_by(unknown)]]);
3858+
3859+
The attribute can be applied to the implicit ``this`` parameter of a member
3860+
function by writing the attribute after the function type:
3861+
3862+
.. code-block:: c++
3863+
3864+
struct S {
3865+
const char *data(std::set<S*>& s) [[clang::lifetime_capture_by(s)]] {
3866+
s.insert(this);
3867+
}
3868+
};
3869+
3870+
The attribute supports specifying more than one capturing entities:
3871+
3872+
.. code-block:: c++
3873+
3874+
void addToSets(std::string_view a [[clang::lifetime_capture_by(s1, s2)]],
3875+
std::set<std::string_view>& s1,
3876+
std::set<std::string_view>& s2) {
3877+
s1.insert(a);
3878+
s2.insert(a);
3879+
}
3880+
3881+
.. _`lifetimebound`: https://clang.llvm.org/docs/AttributeReference.html#lifetimebound
3882+
}];
3883+
}
3884+
38163885
def TrivialABIDocs : Documentation {
38173886
let Category = DocCatDecl;
38183887
let Content = [{

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3386,6 +3386,20 @@ def err_callback_callee_is_variadic : Error<
33863386
"'callback' attribute callee may not be variadic">;
33873387
def err_callback_implicit_this_not_available : Error<
33883388
"'callback' argument at position %0 references unavailable implicit 'this'">;
3389+
3390+
def err_capture_by_attribute_multiple : Error<
3391+
"multiple 'lifetime_capture' attributes specified">;
3392+
def err_capture_by_attribute_no_entity : Error<
3393+
"'lifetime_capture_by' attribute specifies no capturing entity">;
3394+
def err_capture_by_implicit_this_not_available : Error<
3395+
"'lifetime_capture_by' argument references unavailable implicit 'this'">;
3396+
def err_capture_by_attribute_argument_unknown : Error<
3397+
"'lifetime_capture_by' attribute argument %0 is not a known function parameter"
3398+
"; must be a function parameter, 'this', 'global' or 'unknown'">;
3399+
def err_capture_by_references_itself : Error<"'lifetime_capture_by' argument references itself">;
3400+
def err_capture_by_param_uses_reserved_name : Error<
3401+
"parameter cannot be named '%select{global|unknown}0' while using 'lifetime_capture_by(%select{global|unknown}0)'">;
3402+
33893403
def err_init_method_bad_return_type : Error<
33903404
"init methods must return an object pointer type, not %0">;
33913405
def err_attribute_invalid_size : Error<

clang/include/clang/Sema/Sema.h

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

1765+
LifetimeCaptureByAttr *ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
1766+
StringRef ParamName);
1767+
// Processes the argument 'X' in [[clang::lifetime_capture_by(X)]]. Since 'X'
1768+
// can be the name of a function parameter, we need to parse the function
1769+
// declaration and rest of the parameters before processesing 'X'. Therefore
1770+
// do this lazily instead of processing while parsing the annotation itself.
1771+
void LazyProcessLifetimeCaptureByParams(FunctionDecl *FD);
1772+
17651773
/// Add _Nullable attributes for std:: types.
17661774
void inferNullableClassAttribute(CXXRecordDecl *CRD);
17671775

clang/lib/AST/TypePrinter.cpp

Lines changed: 10 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"
@@ -1896,6 +1897,14 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
18961897
OS << " [[clang::lifetimebound]]";
18971898
return;
18981899
}
1900+
if (T->getAttrKind() == attr::LifetimeCaptureBy) {
1901+
OS << " [[clang::lifetime_capture_by(";
1902+
if (auto *attr = dyn_cast_or_null<LifetimeCaptureByAttr>(T->getAttr()))
1903+
llvm::interleaveComma(attr->getArgIdents(), OS,
1904+
[&](auto it) { OS << it->getName(); });
1905+
OS << ")]]";
1906+
return;
1907+
}
18991908

19001909
// The printing of the address_space attribute is handled by the qualifier
19011910
// since it is still stored in the qualifier. Return early to prevent printing
@@ -1957,6 +1966,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19571966
case attr::SizedBy:
19581967
case attr::SizedByOrNull:
19591968
case attr::LifetimeBound:
1969+
case attr::LifetimeCaptureBy:
19601970
case attr::TypeNonNull:
19611971
case attr::TypeNullable:
19621972
case attr::TypeNullableResult:

clang/lib/Sema/SemaDecl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16591,6 +16591,8 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) {
1659116591
}
1659216592
}
1659316593

16594+
LazyProcessLifetimeCaptureByParams(FD);
16595+
1659416596
AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD);
1659516597

1659616598
// If C++ exceptions are enabled but we are told extern "C" functions cannot

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 118 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"
@@ -64,6 +65,7 @@
6465
#include "llvm/ADT/StringExtras.h"
6566
#include "llvm/Demangle/Demangle.h"
6667
#include "llvm/IR/Assumptions.h"
68+
#include "llvm/IR/DerivedTypes.h"
6769
#include "llvm/MC/MCSectionMachO.h"
6870
#include "llvm/Support/Error.h"
6971
#include "llvm/Support/MathExtras.h"
@@ -3753,6 +3755,119 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
37533755
S.Context, AL, EncodingIndices.data(), EncodingIndices.size()));
37543756
}
37553757

3758+
LifetimeCaptureByAttr *Sema::ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
3759+
StringRef ParamName) {
3760+
// Atleast one capture by is required.
3761+
if (AL.getNumArgs() == 0) {
3762+
Diag(AL.getLoc(), diag::err_capture_by_attribute_no_entity)
3763+
<< AL.getRange();
3764+
return nullptr;
3765+
}
3766+
unsigned N = AL.getNumArgs();
3767+
auto ParamIdents =
3768+
MutableArrayRef<IdentifierInfo *>(new (Context) IdentifierInfo *[N], N);
3769+
auto ParamLocs =
3770+
MutableArrayRef<SourceLocation>(new (Context) SourceLocation[N], N);
3771+
bool IsValid = true;
3772+
for (unsigned I = 0; I < N; ++I) {
3773+
if (AL.isArgExpr(I)) {
3774+
Expr *E = AL.getArgAsExpr(I);
3775+
Diag(E->getExprLoc(), diag::err_capture_by_attribute_argument_unknown)
3776+
<< E << E->getExprLoc();
3777+
IsValid = false;
3778+
continue;
3779+
}
3780+
assert(AL.isArgIdent(I));
3781+
IdentifierLoc *IdLoc = AL.getArgAsIdent(I);
3782+
if (IdLoc->Ident->getName() == ParamName) {
3783+
Diag(IdLoc->Loc, diag::err_capture_by_references_itself) << IdLoc->Loc;
3784+
IsValid = false;
3785+
continue;
3786+
}
3787+
ParamIdents[I] = IdLoc->Ident;
3788+
ParamLocs[I] = IdLoc->Loc;
3789+
}
3790+
if (!IsValid)
3791+
return nullptr;
3792+
SmallVector<int> FakeParamIndices(N, LifetimeCaptureByAttr::INVALID);
3793+
auto *CapturedBy =
3794+
LifetimeCaptureByAttr::Create(Context, FakeParamIndices.data(), N, AL);
3795+
CapturedBy->setArgs(ParamIdents, ParamLocs);
3796+
return CapturedBy;
3797+
}
3798+
3799+
static void handleLifetimeCaptureByAttr(Sema &S, Decl *D,
3800+
const ParsedAttr &AL) {
3801+
// Do not allow multiple attributes.
3802+
if (D->hasAttr<LifetimeCaptureByAttr>()) {
3803+
S.Diag(AL.getLoc(), diag::err_capture_by_attribute_multiple)
3804+
<< AL.getRange();
3805+
return;
3806+
}
3807+
auto *PVD = dyn_cast<ParmVarDecl>(D);
3808+
assert(PVD);
3809+
auto *CaptureByAttr = S.ParseLifetimeCaptureByAttr(AL, PVD->getName());
3810+
if (CaptureByAttr)
3811+
D->addAttr(CaptureByAttr);
3812+
}
3813+
3814+
void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) {
3815+
bool HasImplicitThisParam = isInstanceMethod(FD);
3816+
SmallVector<LifetimeCaptureByAttr *, 1> Attrs;
3817+
for (ParmVarDecl *PVD : FD->parameters())
3818+
if (auto *A = PVD->getAttr<LifetimeCaptureByAttr>())
3819+
Attrs.push_back(A);
3820+
if (HasImplicitThisParam) {
3821+
TypeSourceInfo *TSI = FD->getTypeSourceInfo();
3822+
if (!TSI)
3823+
return;
3824+
AttributedTypeLoc ATL;
3825+
for (TypeLoc TL = TSI->getTypeLoc();
3826+
(ATL = TL.getAsAdjusted<AttributedTypeLoc>());
3827+
TL = ATL.getModifiedLoc()) {
3828+
if (auto *A = ATL.getAttrAs<LifetimeCaptureByAttr>())
3829+
Attrs.push_back(const_cast<LifetimeCaptureByAttr *>(A));
3830+
}
3831+
}
3832+
if (Attrs.empty())
3833+
return;
3834+
llvm::StringMap<int> NameIdxMapping = {
3835+
{"global", LifetimeCaptureByAttr::GLOBAL},
3836+
{"unknown", LifetimeCaptureByAttr::UNKNOWN}};
3837+
int Idx = 0;
3838+
if (HasImplicitThisParam) {
3839+
NameIdxMapping["this"] = 0;
3840+
Idx++;
3841+
}
3842+
for (const ParmVarDecl *PVD : FD->parameters())
3843+
NameIdxMapping[PVD->getName()] = Idx++;
3844+
auto DisallowReservedParams = [&](StringRef Reserved) {
3845+
for (const ParmVarDecl *PVD : FD->parameters())
3846+
if (PVD->getName() == Reserved)
3847+
Diag(PVD->getLocation(), diag::err_capture_by_param_uses_reserved_name)
3848+
<< (PVD->getName() == "unknown");
3849+
};
3850+
for (auto *CapturedBy : Attrs) {
3851+
const auto &Entities = CapturedBy->getArgIdents();
3852+
for (size_t I = 0; I < Entities.size(); ++I) {
3853+
StringRef Name = Entities[I]->getName();
3854+
auto It = NameIdxMapping.find(Name);
3855+
if (It == NameIdxMapping.end()) {
3856+
auto Loc = CapturedBy->getArgLocs()[I];
3857+
if (!HasImplicitThisParam && Name == "this")
3858+
Diag(Loc, diag::err_capture_by_implicit_this_not_available) << Loc;
3859+
else
3860+
Diag(Loc, diag::err_capture_by_attribute_argument_unknown)
3861+
<< Entities[I] << Loc;
3862+
continue;
3863+
}
3864+
if (Name == "unknown" || Name == "global")
3865+
DisallowReservedParams(Name);
3866+
CapturedBy->setParamIdx(I, It->second);
3867+
}
3868+
}
3869+
}
3870+
37563871
static bool isFunctionLike(const Type &T) {
37573872
// Check for explicit function types.
37583873
// 'called_once' is only supported in Objective-C and it has
@@ -6538,6 +6653,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
65386653
case ParsedAttr::AT_Callback:
65396654
handleCallbackAttr(S, D, AL);
65406655
break;
6656+
case ParsedAttr::AT_LifetimeCaptureBy:
6657+
handleLifetimeCaptureByAttr(S, D, AL);
6658+
break;
65416659
case ParsedAttr::AT_CalledOnce:
65426660
handleCalledOnceAttr(S, D, AL);
65436661
break;

clang/lib/Sema/SemaType.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8637,6 +8637,15 @@ static void HandleLifetimeBoundAttr(TypeProcessingState &State,
86378637
}
86388638
}
86398639

8640+
static void HandleLifetimeCaptureByAttr(TypeProcessingState &State,
8641+
QualType &CurType, ParsedAttr &PA) {
8642+
if (State.getDeclarator().isDeclarationOfFunction()) {
8643+
auto *Attr = State.getSema().ParseLifetimeCaptureByAttr(PA, "this");
8644+
if (Attr)
8645+
CurType = State.getAttributedType(Attr, CurType, CurType);
8646+
}
8647+
}
8648+
86408649
static void HandleHLSLParamModifierAttr(QualType &CurType,
86418650
const ParsedAttr &Attr, Sema &S) {
86428651
// Don't apply this attribute to template dependent types. It is applied on
@@ -8798,6 +8807,10 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
87988807
if (TAL == TAL_DeclChunk)
87998808
HandleLifetimeBoundAttr(state, type, attr);
88008809
break;
8810+
case ParsedAttr::AT_LifetimeCaptureBy:
8811+
if (TAL == TAL_DeclChunk)
8812+
HandleLifetimeCaptureByAttr(state, type, attr);
8813+
break;
88018814

88028815
case ParsedAttr::AT_NoDeref: {
88038816
// FIXME: `noderef` currently doesn't work correctly in [[]] syntax.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %clang_cc1 %s -ast-dump | FileCheck %s
2+
3+
// Verify that we print the [[clang::lifetime_capture_by(X)]] attribute.
4+
5+
struct S {
6+
void foo(int &a, int &b) [[clang::lifetime_capture_by(a, b, global)]];
7+
};
8+
9+
// CHECK: CXXMethodDecl {{.*}}clang::lifetime_capture_by(a, b, global)

0 commit comments

Comments
 (0)