Skip to content

Commit ecd775c

Browse files
usx95Gabor Horvath
authored andcommitted
[clang] Introduce [[clang::lifetime_capture_by(X)]] (llvm#111499)
This implements the RFC https://discourse.llvm.org/t/rfc-introduce-clang-lifetime-capture-by-x/81371 In this PR, we introduce `[[clang::lifetime_capture_by(X)]]` attribute as discussed in the RFC. As an implementation detail of this attribute, we store and use param indices instead of raw param expressions. The parameter indices are computed lazily at the end of function declaration since the function decl (and therefore the subsequent parameters) are not visible yet while parsing a parameter annotation. In subsequent PR, we will infer this attribute for STL containers and perform lifetime analysis to detect dangling cases. Cherry-picked from 8c4331c
1 parent 9aaee4d commit ecd775c

File tree

11 files changed

+323
-0
lines changed

11 files changed

+323
-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: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,6 +1841,39 @@ def LifetimeBound : DeclOrTypeAttr {
18411841
let SimpleHandler = 1;
18421842
}
18431843

1844+
def LifetimeCaptureBy : DeclOrTypeAttr {
1845+
let Spellings = [Clang<"lifetime_capture_by", 0>];
1846+
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
1847+
let Args = [VariadicParamOrParamIdxArgument<"Params">];
1848+
let Documentation = [LifetimeCaptureByDocs];
1849+
let AdditionalMembers = [{
1850+
private:
1851+
SmallVector<IdentifierInfo*, 1> ArgIdents;
1852+
SmallVector<SourceLocation, 1> ArgLocs;
1853+
1854+
public:
1855+
static constexpr int THIS = 0;
1856+
static constexpr int INVALID = -1;
1857+
static constexpr int UNKNOWN = -2;
1858+
static constexpr int GLOBAL = -3;
1859+
1860+
void setArgs(SmallVector<IdentifierInfo*>&& Idents,
1861+
SmallVector<SourceLocation>&& Locs) {
1862+
assert(Idents.size() == Locs.size());
1863+
assert(Idents.size() == params_Size);
1864+
ArgIdents = std::move(Idents);
1865+
ArgLocs = std::move(Locs);
1866+
}
1867+
1868+
ArrayRef<IdentifierInfo*> getArgIdents() const { return ArgIdents; }
1869+
ArrayRef<SourceLocation> getArgLocs() const { return ArgLocs; }
1870+
void setParamIdx(size_t Idx, int Val) {
1871+
assert(Idx < params_Size);
1872+
params_[Idx] = Val;
1873+
}
1874+
}];
1875+
}
1876+
18441877
def TrivialABI : InheritableAttr {
18451878
// This attribute does not have a C [[]] spelling because it requires the
18461879
// 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: 15 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,19 @@ 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+
auto Idents = attr->getArgIdents();
1904+
for (unsigned I = 0; I < Idents.size(); ++I) {
1905+
OS << Idents[I]->getName();
1906+
if (I != Idents.size() - 1)
1907+
OS << ", ";
1908+
}
1909+
}
1910+
OS << ")]]";
1911+
return;
1912+
}
18991913

19001914
// The printing of the address_space attribute is handled by the qualifier
19011915
// since it is still stored in the qualifier. Return early to prevent printing
@@ -1957,6 +1971,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19571971
case attr::SizedBy:
19581972
case attr::SizedByOrNull:
19591973
case attr::LifetimeBound:
1974+
case attr::LifetimeCaptureBy:
19601975
case attr::TypeNonNull:
19611976
case attr::TypeNullable:
19621977
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: 111 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"
@@ -3753,6 +3754,113 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
37533754
S.Context, AL, EncodingIndices.data(), EncodingIndices.size()));
37543755
}
37553756

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