Skip to content

Commit 8c4331c

Browse files
authored
[clang] Introduce [[clang::lifetime_capture_by(X)]] (#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.
1 parent 4a68e4c commit 8c4331c

11 files changed

+322
-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: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,6 +1889,39 @@ 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 AdditionalMembers = [{
1898+
private:
1899+
SmallVector<IdentifierInfo*, 1> ArgIdents;
1900+
SmallVector<SourceLocation, 1> ArgLocs;
1901+
1902+
public:
1903+
static constexpr int THIS = 0;
1904+
static constexpr int INVALID = -1;
1905+
static constexpr int UNKNOWN = -2;
1906+
static constexpr int GLOBAL = -3;
1907+
1908+
void setArgs(SmallVector<IdentifierInfo*>&& Idents,
1909+
SmallVector<SourceLocation>&& Locs) {
1910+
assert(Idents.size() == Locs.size());
1911+
assert(Idents.size() == params_Size);
1912+
ArgIdents = std::move(Idents);
1913+
ArgLocs = std::move(Locs);
1914+
}
1915+
1916+
ArrayRef<IdentifierInfo*> getArgIdents() const { return ArgIdents; }
1917+
ArrayRef<SourceLocation> getArgLocs() const { return ArgLocs; }
1918+
void setParamIdx(size_t Idx, int Val) {
1919+
assert(Idx < params_Size);
1920+
params_[Idx] = Val;
1921+
}
1922+
}];
1923+
}
1924+
18921925
def TrivialABI : InheritableAttr {
18931926
// This attribute does not have a C [[]] spelling because it requires the
18941927
// CPlusPlus language option.

clang/include/clang/Basic/AttrDocs.td

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3918,6 +3918,75 @@ have their lifetimes extended.
39183918
}];
39193919
}
39203920

3921+
def LifetimeCaptureByDocs : Documentation {
3922+
let Category = DocCatFunction;
3923+
let Content = [{
3924+
Similar to `lifetimebound`_, the ``lifetime_capture_by(X)`` attribute on a function
3925+
parameter or implicit object parameter indicates that that objects that are referred to
3926+
by that parameter may also be referred to by the capturing entity ``X``.
3927+
3928+
By default, a reference is considered to refer to its referenced object, a
3929+
pointer is considered to refer to its pointee, a ``std::initializer_list<T>``
3930+
is considered to refer to its underlying array, and aggregates (arrays and
3931+
simple ``struct``\s) are considered to refer to all objects that their
3932+
transitive subobjects refer to.
3933+
3934+
The capturing entity ``X`` can be one of the following:
3935+
- Another (named) function parameter.
3936+
3937+
.. code-block:: c++
3938+
3939+
void addToSet(std::string_view a [[clang::lifetime_capture_by(s)]], std::set<std::string_view>& s) {
3940+
s.insert(a);
3941+
}
3942+
3943+
- ``this`` (in case of member functions).
3944+
3945+
.. code-block:: c++
3946+
3947+
class S {
3948+
void addToSet(std::string_view a [[clang::lifetime_capture_by(this)]]) {
3949+
s.insert(a);
3950+
}
3951+
std::set<std::string_view> s;
3952+
};
3953+
3954+
- 'global', 'unknown' (without quotes).
3955+
3956+
.. code-block:: c++
3957+
3958+
std::set<std::string_view> s;
3959+
void addToSet(std::string_view a [[clang::lifetime_capture_by(global)]]) {
3960+
s.insert(a);
3961+
}
3962+
void addSomewhere(std::string_view a [[clang::lifetime_capture_by(unknown)]]);
3963+
3964+
The attribute can be applied to the implicit ``this`` parameter of a member
3965+
function by writing the attribute after the function type:
3966+
3967+
.. code-block:: c++
3968+
3969+
struct S {
3970+
const char *data(std::set<S*>& s) [[clang::lifetime_capture_by(s)]] {
3971+
s.insert(this);
3972+
}
3973+
};
3974+
3975+
The attribute supports specifying more than one capturing entities:
3976+
3977+
.. code-block:: c++
3978+
3979+
void addToSets(std::string_view a [[clang::lifetime_capture_by(s1, s2)]],
3980+
std::set<std::string_view>& s1,
3981+
std::set<std::string_view>& s2) {
3982+
s1.insert(a);
3983+
s2.insert(a);
3984+
}
3985+
3986+
.. _`lifetimebound`: https://clang.llvm.org/docs/AttributeReference.html#lifetimebound
3987+
}];
3988+
}
3989+
39213990
def TrivialABIDocs : Documentation {
39223991
let Category = DocCatDecl;
39233992
let Content = [{

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3383,6 +3383,20 @@ 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, 'this', 'global' or 'unknown'">;
3396+
def err_capture_by_references_itself : Error<"'lifetime_capture_by' argument references itself">;
3397+
def err_capture_by_param_uses_reserved_name : Error<
3398+
"parameter cannot be named '%select{global|unknown}0' while using 'lifetime_capture_by(%select{global|unknown}0)'">;
3399+
33863400
def err_init_method_bad_return_type : Error<
33873401
"init methods must return an object pointer type, not %0">;
33883402
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
@@ -1760,6 +1760,14 @@ 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+
// Processes the argument 'X' in [[clang::lifetime_capture_by(X)]]. Since 'X'
1766+
// can be the name of a function parameter, we need to parse the function
1767+
// declaration and rest of the parameters before processesing 'X'. Therefore
1768+
// do this lazily instead of processing while parsing the annotation itself.
1769+
void LazyProcessLifetimeCaptureByParams(FunctionDecl *FD);
1770+
17631771
/// Add _Nullable attributes for std:: types.
17641772
void inferNullableClassAttribute(CXXRecordDecl *CRD);
17651773

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"
@@ -1909,6 +1910,19 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19091910
OS << " [[clang::lifetimebound]]";
19101911
return;
19111912
}
1913+
if (T->getAttrKind() == attr::LifetimeCaptureBy) {
1914+
OS << " [[clang::lifetime_capture_by(";
1915+
if (auto *attr = dyn_cast_or_null<LifetimeCaptureByAttr>(T->getAttr())) {
1916+
auto Idents = attr->getArgIdents();
1917+
for (unsigned I = 0; I < Idents.size(); ++I) {
1918+
OS << Idents[I]->getName();
1919+
if (I != Idents.size() - 1)
1920+
OS << ", ";
1921+
}
1922+
}
1923+
OS << ")]]";
1924+
return;
1925+
}
19121926

19131927
// The printing of the address_space attribute is handled by the qualifier
19141928
// since it is still stored in the qualifier. Return early to prevent printing
@@ -1976,6 +1990,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19761990
case attr::SizedBy:
19771991
case attr::SizedByOrNull:
19781992
case attr::LifetimeBound:
1993+
case attr::LifetimeCaptureBy:
19791994
case attr::TypeNonNull:
19801995
case attr::TypeNullable:
19811996
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: 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"
@@ -3867,6 +3868,113 @@ 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 DisallowReservedParams = [&](StringRef Reserved) {
3934+
for (const ParmVarDecl *PVD : FD->parameters())
3935+
if (PVD->getName() == Reserved)
3936+
Diag(PVD->getLocation(), diag::err_capture_by_param_uses_reserved_name)
3937+
<< (PVD->getName() == "unknown");
3938+
};
3939+
auto HandleCaptureBy = [&](LifetimeCaptureByAttr *CapturedBy) {
3940+
if (!CapturedBy)
3941+
return;
3942+
const auto &Entities = CapturedBy->getArgIdents();
3943+
for (size_t I = 0; I < Entities.size(); ++I) {
3944+
StringRef Name = Entities[I]->getName();
3945+
auto It = NameIdxMapping.find(Name);
3946+
if (It == NameIdxMapping.end()) {
3947+
auto Loc = CapturedBy->getArgLocs()[I];
3948+
if (!HasImplicitThisParam && Name == "this")
3949+
Diag(Loc, diag::err_capture_by_implicit_this_not_available) << Loc;
3950+
else
3951+
Diag(Loc, diag::err_capture_by_attribute_argument_unknown)
3952+
<< Entities[I] << Loc;
3953+
continue;
3954+
}
3955+
if (Name == "unknown" || Name == "global")
3956+
DisallowReservedParams(Name);
3957+
CapturedBy->setParamIdx(I, It->second);
3958+
}
3959+
};
3960+
for (ParmVarDecl *PVD : FD->parameters())
3961+
HandleCaptureBy(PVD->getAttr<LifetimeCaptureByAttr>());
3962+
if (!HasImplicitThisParam)
3963+
return;
3964+
TypeSourceInfo *TSI = FD->getTypeSourceInfo();
3965+
if (!TSI)
3966+
return;
3967+
AttributedTypeLoc ATL;
3968+
for (TypeLoc TL = TSI->getTypeLoc();
3969+
(ATL = TL.getAsAdjusted<AttributedTypeLoc>());
3970+
TL = ATL.getModifiedLoc()) {
3971+
auto *A = ATL.getAttrAs<LifetimeCaptureByAttr>();
3972+
if (!A)
3973+
continue;
3974+
HandleCaptureBy(const_cast<LifetimeCaptureByAttr *>(A));
3975+
}
3976+
}
3977+
38703978
static bool isFunctionLike(const Type &T) {
38713979
// Check for explicit function types.
38723980
// 'called_once' is only supported in Objective-C and it has
@@ -6644,6 +6752,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
66446752
case ParsedAttr::AT_Callback:
66456753
handleCallbackAttr(S, D, AL);
66466754
break;
6755+
case ParsedAttr::AT_LifetimeCaptureBy:
6756+
HandleLifetimeCaptureByAttr(S, D, AL);
6757+
break;
66476758
case ParsedAttr::AT_CalledOnce:
66486759
handleCalledOnceAttr(S, D, AL);
66496760
break;

clang/lib/Sema/SemaType.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8609,6 +8609,15 @@ static void HandleLifetimeBoundAttr(TypeProcessingState &State,
86098609
}
86108610
}
86118611

8612+
static void HandleLifetimeCaptureByAttr(TypeProcessingState &State,
8613+
QualType &CurType, ParsedAttr &PA) {
8614+
if (State.getDeclarator().isDeclarationOfFunction()) {
8615+
auto *Attr = State.getSema().ParseLifetimeCaptureByAttr(PA, "this");
8616+
if (Attr)
8617+
CurType = State.getAttributedType(Attr, CurType, CurType);
8618+
}
8619+
}
8620+
86128621
static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
86138622
QualType &CurType,
86148623
const ParsedAttr &Attr, Sema &S) {
@@ -8770,6 +8779,10 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
87708779
if (TAL == TAL_DeclChunk)
87718780
HandleLifetimeBoundAttr(state, type, attr);
87728781
break;
8782+
case ParsedAttr::AT_LifetimeCaptureBy:
8783+
if (TAL == TAL_DeclChunk)
8784+
HandleLifetimeCaptureByAttr(state, type, attr);
8785+
break;
87738786

87748787
case ParsedAttr::AT_NoDeref: {
87758788
// 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)