Skip to content

Reapply "[clang] Introduce [[clang::lifetime_capture_by(X)]] #115823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,9 @@ Attribute Changes in Clang
- Fix a bug where clang doesn't automatically apply the ``[[gsl::Owner]]`` or
``[[gsl::Pointer]]`` to STL explicit template specialization decls. (#GH109442)

- Clang now supports ``[[clang::lifetime_capture_by(X)]]``. Similar to lifetimebound, this can be
used to specify when a reference to a function parameter is captured by another capturing entity ``X``.

Improvements to Clang's diagnostics
-----------------------------------

Expand Down
37 changes: 37 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,43 @@ def LifetimeBound : DeclOrTypeAttr {
let SimpleHandler = 1;
}

def LifetimeCaptureBy : DeclOrTypeAttr {
let Spellings = [Clang<"lifetime_capture_by", 0>];
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
let Args = [VariadicParamOrParamIdxArgument<"Params">];
let Documentation = [LifetimeCaptureByDocs];
let AdditionalMembers = [{
private:
MutableArrayRef<IdentifierInfo*> ArgIdents;
MutableArrayRef<SourceLocation> ArgLocs;

public:
static constexpr int THIS = 0;
static constexpr int INVALID = -1;
static constexpr int UNKNOWN = -2;
static constexpr int GLOBAL = -3;

void CreateArgs(ASTContext &Ctx) {
ArgIdents =
MutableArrayRef<IdentifierInfo *>(new (Ctx) IdentifierInfo *[params_Size], params_Size);
ArgLocs =
MutableArrayRef<SourceLocation>(new (Ctx) SourceLocation[params_Size], params_Size);
}
auto getArgIdents() const {
assert(ArgIdents.size() == params_Size);
return ArgIdents;
}
auto getArgLocs() const {
assert(ArgLocs.size() == params_Size);
return ArgLocs;
}
void setParamIdx(size_t Idx, int Val) {
assert(Idx < params_Size);
params_[Idx] = Val;
}
}];
}

def TrivialABI : InheritableAttr {
// This attribute does not have a C [[]] spelling because it requires the
// CPlusPlus language option.
Expand Down
69 changes: 69 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -3918,6 +3918,75 @@ have their lifetimes extended.
}];
}

def LifetimeCaptureByDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Similar to `lifetimebound`_, the ``lifetime_capture_by(X)`` attribute on a function
parameter or implicit object parameter indicates that that objects that are referred to
by that parameter may also be referred to by the capturing entity ``X``.

By default, a reference is considered to refer to its referenced object, a
pointer is considered to refer to its pointee, a ``std::initializer_list<T>``
is considered to refer to its underlying array, and aggregates (arrays and
simple ``struct``\s) are considered to refer to all objects that their
transitive subobjects refer to.

The capturing entity ``X`` can be one of the following:
- Another (named) function parameter.

.. code-block:: c++

void addToSet(std::string_view a [[clang::lifetime_capture_by(s)]], std::set<std::string_view>& s) {
s.insert(a);
}

- ``this`` (in case of member functions).

.. code-block:: c++

class S {
void addToSet(std::string_view a [[clang::lifetime_capture_by(this)]]) {
s.insert(a);
}
std::set<std::string_view> s;
};

- 'global', 'unknown' (without quotes).

.. code-block:: c++

std::set<std::string_view> s;
void addToSet(std::string_view a [[clang::lifetime_capture_by(global)]]) {
s.insert(a);
}
void addSomewhere(std::string_view a [[clang::lifetime_capture_by(unknown)]]);

The attribute can be applied to the implicit ``this`` parameter of a member
function by writing the attribute after the function type:

.. code-block:: c++

struct S {
const char *data(std::set<S*>& s) [[clang::lifetime_capture_by(s)]] {
s.insert(this);
}
};

The attribute supports specifying more than one capturing entities:

.. code-block:: c++

void addToSets(std::string_view a [[clang::lifetime_capture_by(s1, s2)]],
std::set<std::string_view>& s1,
std::set<std::string_view>& s2) {
s1.insert(a);
s2.insert(a);
}

.. _`lifetimebound`: https://clang.llvm.org/docs/AttributeReference.html#lifetimebound
}];
}

def TrivialABIDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
Expand Down
14 changes: 14 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3383,6 +3383,20 @@ def err_callback_callee_is_variadic : Error<
"'callback' attribute callee may not be variadic">;
def err_callback_implicit_this_not_available : Error<
"'callback' argument at position %0 references unavailable implicit 'this'">;

def err_capture_by_attribute_multiple : Error<
"multiple 'lifetime_capture' attributes specified">;
def err_capture_by_attribute_no_entity : Error<
"'lifetime_capture_by' attribute specifies no capturing entity">;
def err_capture_by_implicit_this_not_available : Error<
"'lifetime_capture_by' argument references unavailable implicit 'this'">;
def err_capture_by_attribute_argument_unknown : Error<
"'lifetime_capture_by' attribute argument %0 is not a known function parameter"
"; must be a function parameter, 'this', 'global' or 'unknown'">;
def err_capture_by_references_itself : Error<"'lifetime_capture_by' argument references itself">;
def err_capture_by_param_uses_reserved_name : Error<
"parameter cannot be named '%select{global|unknown}0' while using 'lifetime_capture_by(%select{global|unknown}0)'">;

def err_init_method_bad_return_type : Error<
"init methods must return an object pointer type, not %0">;
def err_attribute_invalid_size : Error<
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,14 @@ class Sema final : public SemaBase {
/// Add [[gsl::Pointer]] attributes for std:: types.
void inferGslPointerAttribute(TypedefNameDecl *TD);

LifetimeCaptureByAttr *ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
StringRef ParamName);
// Processes the argument 'X' in [[clang::lifetime_capture_by(X)]]. Since 'X'
// can be the name of a function parameter, we need to parse the function
// declaration and rest of the parameters before processesing 'X'. Therefore
// do this lazily instead of processing while parsing the annotation itself.
void LazyProcessLifetimeCaptureByParams(FunctionDecl *FD);

/// Add _Nullable attributes for std:: types.
void inferNullableClassAttribute(CXXRecordDecl *CRD);

Expand Down
10 changes: 10 additions & 0 deletions clang/lib/AST/TypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "clang/AST/TextNodeDumper.h"
#include "clang/AST/Type.h"
#include "clang/Basic/AddressSpaces.h"
#include "clang/Basic/AttrKinds.h"
#include "clang/Basic/ExceptionSpecificationType.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/LLVM.h"
Expand Down Expand Up @@ -1909,6 +1910,14 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
OS << " [[clang::lifetimebound]]";
return;
}
if (T->getAttrKind() == attr::LifetimeCaptureBy) {
OS << " [[clang::lifetime_capture_by(";
if (auto *attr = dyn_cast_or_null<LifetimeCaptureByAttr>(T->getAttr()))
llvm::interleaveComma(attr->getArgIdents(), OS,
[&](auto it) { OS << it->getName(); });
OS << ")]]";
return;
}

// The printing of the address_space attribute is handled by the qualifier
// since it is still stored in the qualifier. Return early to prevent printing
Expand Down Expand Up @@ -1976,6 +1985,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::SizedBy:
case attr::SizedByOrNull:
case attr::LifetimeBound:
case attr::LifetimeCaptureBy:
case attr::TypeNonNull:
case attr::TypeNullable:
case attr::TypeNullableResult:
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16687,6 +16687,7 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) {
}
}

LazyProcessLifetimeCaptureByParams(FD);
inferLifetimeBoundAttribute(FD);
AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD);

Expand Down
114 changes: 114 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTMutationListener.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
Expand Down Expand Up @@ -64,6 +65,7 @@
#include "llvm/ADT/StringExtras.h"
#include "llvm/Demangle/Demangle.h"
#include "llvm/IR/Assumptions.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/MC/MCSectionMachO.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MathExtras.h"
Expand Down Expand Up @@ -3867,6 +3869,115 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
S.Context, AL, EncodingIndices.data(), EncodingIndices.size()));
}

LifetimeCaptureByAttr *Sema::ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
StringRef ParamName) {
// Atleast one capture by is required.
if (AL.getNumArgs() == 0) {
Diag(AL.getLoc(), diag::err_capture_by_attribute_no_entity)
<< AL.getRange();
return nullptr;
}
unsigned N = AL.getNumArgs();
SmallVector<int> FakeParamIndices(N, LifetimeCaptureByAttr::INVALID);
auto *CapturedBy = ::new (Context)
LifetimeCaptureByAttr(Context, AL, FakeParamIndices.data(), N);
CapturedBy->CreateArgs(Context);
MutableArrayRef<SourceLocation> ParamLocs = CapturedBy->getArgLocs();
MutableArrayRef<IdentifierInfo *> ParamIdents = CapturedBy->getArgIdents();
bool IsValid = true;
for (unsigned I = 0; I < N; ++I) {
if (AL.isArgExpr(I)) {
Expr *E = AL.getArgAsExpr(I);
Diag(E->getExprLoc(), diag::err_capture_by_attribute_argument_unknown)
<< E << E->getExprLoc();
IsValid = false;
continue;
}
assert(AL.isArgIdent(I));
IdentifierLoc *IdLoc = AL.getArgAsIdent(I);
if (IdLoc->Ident->getName() == ParamName) {
Diag(IdLoc->Loc, diag::err_capture_by_references_itself) << IdLoc->Loc;
IsValid = false;
continue;
}
ParamIdents[I] = IdLoc->Ident;
ParamLocs[I] = IdLoc->Loc;
}
return IsValid ? CapturedBy : nullptr;
}

static void handleLifetimeCaptureByAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
// Do not allow multiple attributes.
if (D->hasAttr<LifetimeCaptureByAttr>()) {
S.Diag(AL.getLoc(), diag::err_capture_by_attribute_multiple)
<< AL.getRange();
return;
}
auto *PVD = dyn_cast<ParmVarDecl>(D);
assert(PVD);
auto *CaptureByAttr = S.ParseLifetimeCaptureByAttr(AL, PVD->getName());
if (CaptureByAttr)
D->addAttr(CaptureByAttr);
}

void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) {
bool HasImplicitThisParam = isInstanceMethod(FD);
SmallVector<LifetimeCaptureByAttr *, 1> Attrs;
for (ParmVarDecl *PVD : FD->parameters())
if (auto *A = PVD->getAttr<LifetimeCaptureByAttr>())
Attrs.push_back(A);
if (HasImplicitThisParam) {
TypeSourceInfo *TSI = FD->getTypeSourceInfo();
if (!TSI)
return;
AttributedTypeLoc ATL;
for (TypeLoc TL = TSI->getTypeLoc();
(ATL = TL.getAsAdjusted<AttributedTypeLoc>());
TL = ATL.getModifiedLoc()) {
if (auto *A = ATL.getAttrAs<LifetimeCaptureByAttr>())
Attrs.push_back(const_cast<LifetimeCaptureByAttr *>(A));
}
}
if (Attrs.empty())
return;
llvm::StringMap<int> NameIdxMapping = {
{"global", LifetimeCaptureByAttr::GLOBAL},
{"unknown", LifetimeCaptureByAttr::UNKNOWN}};
int Idx = 0;
if (HasImplicitThisParam) {
NameIdxMapping["this"] = 0;
Idx++;
}
for (const ParmVarDecl *PVD : FD->parameters())
NameIdxMapping[PVD->getName()] = Idx++;
auto DisallowReservedParams = [&](StringRef Reserved) {
for (const ParmVarDecl *PVD : FD->parameters())
if (PVD->getName() == Reserved)
Diag(PVD->getLocation(), diag::err_capture_by_param_uses_reserved_name)
<< (PVD->getName() == "unknown");
};
for (auto *CapturedBy : Attrs) {
const auto &Entities = CapturedBy->getArgIdents();
for (size_t I = 0; I < Entities.size(); ++I) {
StringRef Name = Entities[I]->getName();
auto It = NameIdxMapping.find(Name);
if (It == NameIdxMapping.end()) {
auto Loc = CapturedBy->getArgLocs()[I];
if (!HasImplicitThisParam && Name == "this")
Diag(Loc, diag::err_capture_by_implicit_this_not_available) << Loc;
else
Diag(Loc, diag::err_capture_by_attribute_argument_unknown)
<< Entities[I] << Loc;
continue;
}
if (Name == "unknown" || Name == "global")
DisallowReservedParams(Name);
CapturedBy->setParamIdx(I, It->second);
}
}
}

static bool isFunctionLike(const Type &T) {
// Check for explicit function types.
// 'called_once' is only supported in Objective-C and it has
Expand Down Expand Up @@ -6644,6 +6755,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_Callback:
handleCallbackAttr(S, D, AL);
break;
case ParsedAttr::AT_LifetimeCaptureBy:
handleLifetimeCaptureByAttr(S, D, AL);
break;
case ParsedAttr::AT_CalledOnce:
handleCalledOnceAttr(S, D, AL);
break;
Expand Down
13 changes: 13 additions & 0 deletions clang/lib/Sema/SemaType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8609,6 +8609,15 @@ static void HandleLifetimeBoundAttr(TypeProcessingState &State,
}
}

static void HandleLifetimeCaptureByAttr(TypeProcessingState &State,
QualType &CurType, ParsedAttr &PA) {
if (State.getDeclarator().isDeclarationOfFunction()) {
auto *Attr = State.getSema().ParseLifetimeCaptureByAttr(PA, "this");
if (Attr)
CurType = State.getAttributedType(Attr, CurType, CurType);
}
}

static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
QualType &CurType,
const ParsedAttr &Attr, Sema &S) {
Expand Down Expand Up @@ -8770,6 +8779,10 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
if (TAL == TAL_DeclChunk)
HandleLifetimeBoundAttr(state, type, attr);
break;
case ParsedAttr::AT_LifetimeCaptureBy:
if (TAL == TAL_DeclChunk)
HandleLifetimeCaptureByAttr(state, type, attr);
break;

case ParsedAttr::AT_NoDeref: {
// FIXME: `noderef` currently doesn't work correctly in [[]] syntax.
Expand Down
9 changes: 9 additions & 0 deletions clang/test/AST/attr-lifetime-capture-by.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// RUN: %clang_cc1 %s -ast-dump | FileCheck %s

// Verify that we print the [[clang::lifetime_capture_by(X)]] attribute.

struct S {
void foo(int &a, int &b) [[clang::lifetime_capture_by(a, b, global)]];
};

// CHECK: CXXMethodDecl {{.*}}clang::lifetime_capture_by(a, b, global)
Loading
Loading