summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Smith <richard-llvm@metafoo.co.uk>2018-08-01 00:33:25 +0000
committerRichard Smith <richard-llvm@metafoo.co.uk>2018-08-01 00:33:25 +0000
commit10bf761d7edcf1da7fd9f4da71606ef3fba046bd (patch)
tree7409db8b0d416b3fc884ed5ca2c95300b8e8d164
parent0f097ac04fc21e27a382b6b896f52a12e0c02b97 (diff)
[P0936R0] add [[clang::lifetimebound]] attribute
This patch adds support for a new attribute, [[clang::lifetimebound]], that indicates that the lifetime of a function result is related to one of the function arguments. When walking an initializer to make sure that the lifetime of the initial value is at least as long as the lifetime of the initialized object, we step through parameters (including the implicit object parameter of a non-static member function) that are marked with this attribute. There's nowhere to write an attribute on the implicit object parameter, so in lieu of that, it may be applied to a function type (where it appears immediately after the cv-qualifiers and ref-qualifier, which is as close to a declaration of the implicit object parameter as we have). I'm currently modeling this in the AST as the attribute appertaining to the function type. Differential Revision: https://reviews.llvm.org/D49922 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@338464 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--include/clang/AST/Type.h1
-rw-r--r--include/clang/Basic/Attr.td14
-rw-r--r--include/clang/Basic/AttrDocs.td16
-rw-r--r--include/clang/Basic/DiagnosticSemaKinds.td7
-rw-r--r--lib/AST/Type.cpp2
-rw-r--r--lib/AST/TypePrinter.cpp20
-rw-r--r--lib/Sema/SemaDecl.cpp21
-rw-r--r--lib/Sema/SemaDeclAttr.cpp3
-rw-r--r--lib/Sema/SemaInit.cpp231
-rw-r--r--lib/Sema/SemaType.cpp21
-rw-r--r--test/SemaCXX/attr-lifetimebound.cpp115
-rw-r--r--utils/TableGen/ClangAttrEmitter.cpp9
12 files changed, 383 insertions, 77 deletions
diff --git a/include/clang/AST/Type.h b/include/clang/AST/Type.h
index 4cfa9904ab..9a8dd6faff 100644
--- a/include/clang/AST/Type.h
+++ b/include/clang/AST/Type.h
@@ -4237,6 +4237,7 @@ public:
attr_null_unspecified,
attr_objc_kindof,
attr_objc_inert_unsafe_unretained,
+ attr_lifetimebound,
};
private:
diff --git a/include/clang/Basic/Attr.td b/include/clang/Basic/Attr.td
index cb8957112b..fea8e129d7 100644
--- a/include/clang/Basic/Attr.td
+++ b/include/clang/Basic/Attr.td
@@ -141,6 +141,13 @@ def HasFunctionProto : SubsetSubject<DeclBase,
isa<BlockDecl>(S)}],
"non-K&R-style functions">;
+// A subject that matches the implicit object parameter of a non-static member
+// function. Accepted as a function type attribute on the type of such a
+// member function.
+// FIXME: This does not actually ever match currently.
+def ImplicitObjectParameter : SubsetSubject<Function, [{false}],
+ "implicit object parameters">;
+
// A single argument to an attribute
class Argument<string name, bit optional, bit fake = 0> {
string Name = name;
@@ -1211,6 +1218,13 @@ def LayoutVersion : InheritableAttr, TargetSpecificAttr<TargetMicrosoftCXXABI> {
let Documentation = [LayoutVersionDocs];
}
+def LifetimeBound : InheritableAttr {
+ let Spellings = [Clang<"lifetimebound", 0>];
+ let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
+ let Documentation = [LifetimeBoundDocs];
+ let LangOpts = [CPlusPlus];
+}
+
def TrivialABI : InheritableAttr {
// This attribute does not have a C [[]] spelling because it requires the
// CPlusPlus language option.
diff --git a/include/clang/Basic/AttrDocs.td b/include/clang/Basic/AttrDocs.td
index 5a5ab78b49..bb2993eab4 100644
--- a/include/clang/Basic/AttrDocs.td
+++ b/include/clang/Basic/AttrDocs.td
@@ -2362,6 +2362,22 @@ It is only supported when using the Microsoft C++ ABI.
}];
}
+def LifetimeBoundDocs : Documentation {
+ let Category = DocCatFunction;
+ let Content = [{
+The ``lifetimebound`` attribute indicates that a resource owned by
+a function parameter or implicit object parameter
+is retained by the return value of the annotated function
+(or, for a parameter of a constructor, in the value of the constructed object).
+It is only supported in C++.
+
+This attribute provides an experimental implementation of the facility
+described in the C++ committee paper [http://wg21.link/p0936r0](P0936R0),
+and is subject to change as the design of the corresponding functionality
+changes.
+ }];
+}
+
def TrivialABIDocs : Documentation {
let Category = DocCatVariable;
let Content = [{
diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td
index bce7731068..8eef30e710 100644
--- a/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7854,6 +7854,13 @@ def warn_null_ret : Warning<
"null returned from %select{function|method}0 that requires a non-null return value">,
InGroup<NonNull>;
+def err_lifetimebound_no_object_param : Error<
+ "'lifetimebound' attribute cannot be applied; %select{static |non-}0member "
+ "function has no implicit object parameter">;
+def err_lifetimebound_ctor_dtor : Error<
+ "'lifetimebound' attribute cannot be applied to a "
+ "%select{constructor|destructor}0">;
+
// CHECK: returning address/reference of stack memory
def warn_ret_stack_addr_ref : Warning<
"%select{address of|reference to}0 stack memory associated with "
diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp
index d219e4e72d..f79a59712a 100644
--- a/lib/AST/Type.cpp
+++ b/lib/AST/Type.cpp
@@ -3178,6 +3178,7 @@ bool AttributedType::isQualifier() const {
case AttributedType::attr_nonnull:
case AttributedType::attr_nullable:
case AttributedType::attr_null_unspecified:
+ case AttributedType::attr_lifetimebound:
return true;
// These aren't qualifiers; they rewrite the modified type to be a
@@ -3247,6 +3248,7 @@ bool AttributedType::isCallingConv() const {
case attr_null_unspecified:
case attr_objc_kindof:
case attr_nocf_check:
+ case attr_lifetimebound:
return false;
case attr_pcs:
diff --git a/lib/AST/TypePrinter.cpp b/lib/AST/TypePrinter.cpp
index bf3b453969..e032c312aa 100644
--- a/lib/AST/TypePrinter.cpp
+++ b/lib/AST/TypePrinter.cpp
@@ -1443,9 +1443,27 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
return;
}
+ if (T->getAttrKind() == AttributedType::attr_lifetimebound) {
+ OS << " [[clang::lifetimebound]]";
+ return;
+ }
+
OS << " __attribute__((";
switch (T->getAttrKind()) {
- default: llvm_unreachable("This attribute should have been handled already");
+ case AttributedType::attr_lifetimebound:
+ case AttributedType::attr_nonnull:
+ case AttributedType::attr_nullable:
+ case AttributedType::attr_null_unspecified:
+ case AttributedType::attr_objc_gc:
+ case AttributedType::attr_objc_inert_unsafe_unretained:
+ case AttributedType::attr_objc_kindof:
+ case AttributedType::attr_objc_ownership:
+ case AttributedType::attr_ptr32:
+ case AttributedType::attr_ptr64:
+ case AttributedType::attr_sptr:
+ case AttributedType::attr_uptr:
+ llvm_unreachable("This attribute should have been handled already");
+
case AttributedType::attr_address_space:
OS << "address_space(";
// FIXME: printing the raw LangAS value is wrong. This should probably
diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp
index ccdb4d288e..50ea98e7f6 100644
--- a/lib/Sema/SemaDecl.cpp
+++ b/lib/Sema/SemaDecl.cpp
@@ -6008,6 +6008,27 @@ static void checkAttributesAfterMerging(Sema &S, NamedDecl &ND) {
<< Attr;
ND.dropAttr<NotTailCalledAttr>();
}
+
+ // Check the attributes on the function type, if any.
+ if (const auto *FD = dyn_cast<FunctionDecl>(&ND)) {
+ for (TypeLoc TL = FD->getTypeSourceInfo()->getTypeLoc();
+ auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
+ TL = ATL.getModifiedLoc()) {
+ // The [[lifetimebound]] attribute can be applied to the implicit object
+ // parameter of a non-static member function (other than a ctor or dtor)
+ // by applying it to the function type.
+ if (ATL.getAttrKind() == AttributedType::attr_lifetimebound) {
+ const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+ if (!MD || MD->isStatic()) {
+ S.Diag(ATL.getAttrNameLoc(), diag::err_lifetimebound_no_object_param)
+ << !MD << ATL.getLocalSourceRange();
+ } else if (isa<CXXConstructorDecl>(MD) || isa<CXXDestructorDecl>(MD)) {
+ S.Diag(ATL.getAttrNameLoc(), diag::err_lifetimebound_ctor_dtor)
+ << isa<CXXDestructorDecl>(MD) << ATL.getLocalSourceRange();
+ }
+ }
+ }
+ }
}
static void checkDLLAttributeRedeclaration(Sema &S, NamedDecl *OldDecl,
diff --git a/lib/Sema/SemaDeclAttr.cpp b/lib/Sema/SemaDeclAttr.cpp
index f6caff6b7d..77deed6047 100644
--- a/lib/Sema/SemaDeclAttr.cpp
+++ b/lib/Sema/SemaDeclAttr.cpp
@@ -6150,6 +6150,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
case ParsedAttr::AT_Restrict:
handleRestrictAttr(S, D, AL);
break;
+ case ParsedAttr::AT_LifetimeBound:
+ handleSimpleAttribute<LifetimeBoundAttr>(S, D, AL);
+ break;
case ParsedAttr::AT_MayAlias:
handleSimpleAttribute<MayAliasAttr>(S, D, AL);
break;
diff --git a/lib/Sema/SemaInit.cpp b/lib/Sema/SemaInit.cpp
index cbb9f78e75..5fa8a3c6c4 100644
--- a/lib/Sema/SemaInit.cpp
+++ b/lib/Sema/SemaInit.cpp
@@ -6322,12 +6322,14 @@ struct IndirectLocalPathEntry {
AddressOf,
VarInit,
LValToRVal,
+ LifetimeBoundCall,
} Kind;
Expr *E;
- Decl *D = nullptr;
+ const Decl *D = nullptr;
IndirectLocalPathEntry() {}
IndirectLocalPathEntry(EntryKind K, Expr *E) : Kind(K), E(E) {}
- IndirectLocalPathEntry(EntryKind K, Expr *E, Decl *D) : Kind(K), E(E), D(D) {}
+ IndirectLocalPathEntry(EntryKind K, Expr *E, const Decl *D)
+ : Kind(K), E(E), D(D) {}
};
using IndirectLocalPath = llvm::SmallVectorImpl<IndirectLocalPathEntry>;
@@ -6361,6 +6363,68 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
Expr *Init, LocalVisitor Visit,
bool RevisitSubinits);
+static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
+ Expr *Init, ReferenceKind RK,
+ LocalVisitor Visit);
+
+static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
+ const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+ if (!TSI)
+ return false;
+ for (TypeLoc TL = TSI->getTypeLoc();
+ auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
+ TL = ATL.getModifiedLoc()) {
+ if (ATL.getAttrKind() == AttributedType::attr_lifetimebound)
+ return true;
+ }
+ return false;
+}
+
+static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call,
+ LocalVisitor Visit) {
+ const FunctionDecl *Callee;
+ ArrayRef<Expr*> Args;
+
+ if (auto *CE = dyn_cast<CallExpr>(Call)) {
+ Callee = CE->getDirectCallee();
+ Args = llvm::makeArrayRef(CE->getArgs(), CE->getNumArgs());
+ } else {
+ auto *CCE = cast<CXXConstructExpr>(Call);
+ Callee = CCE->getConstructor();
+ Args = llvm::makeArrayRef(CCE->getArgs(), CCE->getNumArgs());
+ }
+ if (!Callee)
+ return;
+
+ Expr *ObjectArg = nullptr;
+ if (isa<CXXOperatorCallExpr>(Call) && Callee->isCXXInstanceMember()) {
+ ObjectArg = Args[0];
+ Args = Args.slice(1);
+ } else if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Call)) {
+ ObjectArg = MCE->getImplicitObjectArgument();
+ }
+
+ auto VisitLifetimeBoundArg = [&](const Decl *D, Expr *Arg) {
+ Path.push_back({IndirectLocalPathEntry::LifetimeBoundCall, Arg, D});
+ if (Arg->isGLValue())
+ visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding,
+ Visit);
+ else
+ visitLocalsRetainedByInitializer(Path, Arg, Visit, true);
+ Path.pop_back();
+ };
+
+ if (ObjectArg && implicitObjectParamIsLifetimeBound(Callee))
+ VisitLifetimeBoundArg(Callee, ObjectArg);
+
+ for (unsigned I = 0,
+ N = std::min<unsigned>(Callee->getNumParams(), Args.size());
+ I != N; ++I) {
+ if (Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
+ VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
+ }
+}
+
/// Visit the locals that would be reachable through a reference bound to the
/// glvalue expression \c Init.
static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
@@ -6420,6 +6484,9 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
true);
}
+ if (isa<CallExpr>(Init))
+ return visitLifetimeBoundArguments(Path, Init, Visit);
+
switch (Init->getStmtClass()) {
case Stmt::DeclRefExprClass: {
// If we find the name of a local non-reference parameter, we could have a
@@ -6483,21 +6550,90 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
bool RevisitSubinits) {
RevertToOldSizeRAII RAII(Path);
- // Step into CXXDefaultInitExprs so we can diagnose cases where a
- // constructor inherits one as an implicit mem-initializer.
- if (auto *DIE = dyn_cast<CXXDefaultInitExpr>(Init)) {
- Path.push_back({IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()});
- Init = DIE->getExpr();
- }
+ Expr *Old;
+ do {
+ Old = Init;
- if (auto *EWC = dyn_cast<ExprWithCleanups>(Init))
- Init = EWC->getSubExpr();
+ // Step into CXXDefaultInitExprs so we can diagnose cases where a
+ // constructor inherits one as an implicit mem-initializer.
+ if (auto *DIE = dyn_cast<CXXDefaultInitExpr>(Init)) {
+ Path.push_back({IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()});
+ Init = DIE->getExpr();
+ }
- // Dig out the expression which constructs the extended temporary.
- Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments());
+ if (auto *EWC = dyn_cast<ExprWithCleanups>(Init))
+ Init = EWC->getSubExpr();
+
+ // Dig out the expression which constructs the extended temporary.
+ Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments());
+
+ if (CXXBindTemporaryExpr *BTE = dyn_cast<CXXBindTemporaryExpr>(Init))
+ Init = BTE->getSubExpr();
+
+ Init = Init->IgnoreParens();
+
+ // Step over value-preserving rvalue casts.
+ if (auto *CE = dyn_cast<CastExpr>(Init)) {
+ switch (CE->getCastKind()) {
+ case CK_LValueToRValue:
+ // If we can match the lvalue to a const object, we can look at its
+ // initializer.
+ Path.push_back({IndirectLocalPathEntry::LValToRVal, CE});
+ return visitLocalsRetainedByReferenceBinding(
+ Path, Init, RK_ReferenceBinding,
+ [&](IndirectLocalPath &Path, Local L, ReferenceKind RK) -> bool {
+ if (auto *DRE = dyn_cast<DeclRefExpr>(L)) {
+ auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
+ if (VD && VD->getType().isConstQualified() && VD->getInit() &&
+ !isVarOnPath(Path, VD)) {
+ Path.push_back({IndirectLocalPathEntry::VarInit, DRE, VD});
+ visitLocalsRetainedByInitializer(Path, VD->getInit(), Visit, true);
+ }
+ } else if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L)) {
+ if (MTE->getType().isConstQualified())
+ visitLocalsRetainedByInitializer(Path, MTE->GetTemporaryExpr(),
+ Visit, true);
+ }
+ return false;
+ });
+
+ // We assume that objects can be retained by pointers cast to integers,
+ // but not if the integer is cast to floating-point type or to _Complex.
+ // We assume that casts to 'bool' do not preserve enough information to
+ // retain a local object.
+ case CK_NoOp:
+ case CK_BitCast:
+ case CK_BaseToDerived:
+ case CK_DerivedToBase:
+ case CK_UncheckedDerivedToBase:
+ case CK_Dynamic:
+ case CK_ToUnion:
+ case CK_UserDefinedConversion:
+ case CK_ConstructorConversion:
+ case CK_IntegralToPointer:
+ case CK_PointerToIntegral:
+ case CK_VectorSplat:
+ case CK_IntegralCast:
+ case CK_CPointerToObjCPointerCast:
+ case CK_BlockPointerToObjCPointerCast:
+ case CK_AnyPointerToBlockPointerCast:
+ case CK_AddressSpaceConversion:
+ break;
+
+ case CK_ArrayToPointerDecay:
+ // Model array-to-pointer decay as taking the address of the array
+ // lvalue.
+ Path.push_back({IndirectLocalPathEntry::AddressOf, CE});
+ return visitLocalsRetainedByReferenceBinding(Path, CE->getSubExpr(),
+ RK_ReferenceBinding, Visit);
+
+ default:
+ return;
+ }
- if (CXXBindTemporaryExpr *BTE = dyn_cast<CXXBindTemporaryExpr>(Init))
- Init = BTE->getSubExpr();
+ Init = CE->getSubExpr();
+ }
+ } while (Old != Init);
// C++17 [dcl.init.list]p6:
// initializing an initializer_list object from the array extends the
@@ -6558,67 +6694,9 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
return;
}
- // Step over value-preserving rvalue casts.
- while (auto *CE = dyn_cast<CastExpr>(Init)) {
- switch (CE->getCastKind()) {
- case CK_LValueToRValue:
- // If we can match the lvalue to a const object, we can look at its
- // initializer.
- Path.push_back({IndirectLocalPathEntry::LValToRVal, CE});
- return visitLocalsRetainedByReferenceBinding(
- Path, Init, RK_ReferenceBinding,
- [&](IndirectLocalPath &Path, Local L, ReferenceKind RK) -> bool {
- if (auto *DRE = dyn_cast<DeclRefExpr>(L)) {
- auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
- if (VD && VD->getType().isConstQualified() && VD->getInit() &&
- !isVarOnPath(Path, VD)) {
- Path.push_back({IndirectLocalPathEntry::VarInit, DRE, VD});
- visitLocalsRetainedByInitializer(Path, VD->getInit(), Visit, true);
- }
- } else if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L)) {
- if (MTE->getType().isConstQualified())
- visitLocalsRetainedByInitializer(Path, MTE->GetTemporaryExpr(),
- Visit, true);
- }
- return false;
- });
-
- // We assume that objects can be retained by pointers cast to integers,
- // but not if the integer is cast to floating-point type or to _Complex.
- // We assume that casts to 'bool' do not preserve enough information to
- // retain a local object.
- case CK_NoOp:
- case CK_BitCast:
- case CK_BaseToDerived:
- case CK_DerivedToBase:
- case CK_UncheckedDerivedToBase:
- case CK_Dynamic:
- case CK_ToUnion:
- case CK_IntegralToPointer:
- case CK_PointerToIntegral:
- case CK_VectorSplat:
- case CK_IntegralCast:
- case CK_CPointerToObjCPointerCast:
- case CK_BlockPointerToObjCPointerCast:
- case CK_AnyPointerToBlockPointerCast:
- case CK_AddressSpaceConversion:
- break;
-
- case CK_ArrayToPointerDecay:
- // Model array-to-pointer decay as taking the address of the array
- // lvalue.
- Path.push_back({IndirectLocalPathEntry::AddressOf, CE});
- return visitLocalsRetainedByReferenceBinding(Path, CE->getSubExpr(),
- RK_ReferenceBinding, Visit);
-
- default:
- return;
- }
-
- Init = CE->getSubExpr();
- }
+ if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init))
+ return visitLifetimeBoundArguments(Path, Init, Visit);
- Init = Init->IgnoreParens();
switch (Init->getStmtClass()) {
case Stmt::UnaryOperatorClass: {
auto *UO = cast<UnaryOperator>(Init);
@@ -6698,6 +6776,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
switch (Path[I].Kind) {
case IndirectLocalPathEntry::AddressOf:
case IndirectLocalPathEntry::LValToRVal:
+ case IndirectLocalPathEntry::LifetimeBoundCall:
// These exist primarily to mark the path as not permitting or
// supporting lifetime extension.
break;
@@ -6876,6 +6955,10 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
// supporting lifetime extension.
break;
+ case IndirectLocalPathEntry::LifetimeBoundCall:
+ // FIXME: Consider adding a note for this.
+ break;
+
case IndirectLocalPathEntry::DefaultInit: {
auto *FD = cast<FieldDecl>(Elem.D);
Diag(FD->getLocation(), diag::note_init_with_default_member_initalizer)
diff --git a/lib/Sema/SemaType.cpp b/lib/Sema/SemaType.cpp
index a374dd8bc2..284d34b22c 100644
--- a/lib/Sema/SemaType.cpp
+++ b/lib/Sema/SemaType.cpp
@@ -5233,6 +5233,8 @@ static ParsedAttr::Kind getAttrListKind(AttributedType::Kind kind) {
return ParsedAttr::AT_ObjCKindOf;
case AttributedType::attr_ns_returns_retained:
return ParsedAttr::AT_NSReturnsRetained;
+ case AttributedType::attr_lifetimebound:
+ return ParsedAttr::AT_LifetimeBound;
}
llvm_unreachable("unexpected attribute kind!");
}
@@ -7194,6 +7196,18 @@ static void deduceOpenCLImplicitAddrSpace(TypeProcessingState &State,
T = State.getSema().Context.getAddrSpaceQualType(T, ImpAddr);
}
+static void HandleLifetimeBoundAttr(QualType &CurType,
+ const ParsedAttr &Attr,
+ Sema &S, Declarator &D) {
+ if (D.isDeclarationOfFunction()) {
+ CurType = S.Context.getAttributedType(AttributedType::attr_lifetimebound,
+ CurType, CurType);
+ } else {
+ Attr.diagnoseAppertainsTo(S, nullptr);
+ }
+}
+
+
static void processTypeAttrs(TypeProcessingState &state, QualType &type,
TypeAttrLocation TAL,
ParsedAttributesView &attrs) {
@@ -7298,6 +7312,13 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
HandleOpenCLAccessAttr(type, attr, state.getSema());
attr.setUsedAsTypeAttr();
break;
+ case ParsedAttr::AT_LifetimeBound:
+ if (TAL == TAL_DeclChunk) {
+ HandleLifetimeBoundAttr(type, attr, state.getSema(),
+ state.getDeclarator());
+ attr.setUsedAsTypeAttr();
+ }
+ break;
MS_TYPE_ATTRS_CASELIST:
if (!handleMSPointerTypeQualifierAttr(state, attr, type))
diff --git a/test/SemaCXX/attr-lifetimebound.cpp b/test/SemaCXX/attr-lifetimebound.cpp
new file mode 100644
index 0000000000..b376819ad5
--- /dev/null
+++ b/test/SemaCXX/attr-lifetimebound.cpp
@@ -0,0 +1,115 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+namespace usage_invalid {
+ // FIXME: Should we diagnose a void return type?
+ void voidreturn(int &param [[clang::lifetimebound]]);
+
+ int *not_class_member() [[clang::lifetimebound]]; // expected-error {{non-member function has no implicit object parameter}}
+ struct A {
+ A() [[clang::lifetimebound]]; // expected-error {{cannot be applied to a constructor}}
+ ~A() [[clang::lifetimebound]]; // expected-error {{cannot be applied to a destructor}}
+ static int *static_class_member() [[clang::lifetimebound]]; // expected-error {{static member function has no implicit object parameter}}
+ int not_function [[clang::lifetimebound]]; // expected-error {{only applies to parameters and implicit object parameters}}
+ int [[clang::lifetimebound]] also_not_function; // expected-error {{cannot be applied to types}}
+ };
+ int *attr_with_param(int &param [[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}}
+}
+
+namespace usage_ok {
+ struct IntRef { int *target; };
+
+ int &refparam(int &param [[clang::lifetimebound]]);
+ int &classparam(IntRef param [[clang::lifetimebound]]);
+
+ // Do not diagnose non-void return types; they can still be lifetime-bound.
+ long long ptrintcast(int &param [[clang::lifetimebound]]) {
+ return (long long)&param;
+ }
+ // Likewise.
+ int &intptrcast(long long param [[clang::lifetimebound]]) {
+ return *(int*)param;
+ }
+
+ struct A {
+ A();
+ A(int);
+ int *class_member() [[clang::lifetimebound]];
+ operator int*() [[clang::lifetimebound]];
+ };
+
+ int *p = A().class_member(); // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
+ int *q = A(); // expected-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}}
+ int *r = A(1); // expected-warning {{temporary whose address is used as value of local variable 'r' will be destroyed at the end of the full-expression}}
+}
+
+# 1 "<std>" 1 3
+namespace std {
+ using size_t = __SIZE_TYPE__;
+ struct string {
+ string();
+ string(const char*);
+
+ char &operator[](size_t) const [[clang::lifetimebound]];
+ };
+ string operator""s(const char *, size_t);
+
+ struct string_view {
+ string_view();
+ string_view(const char *p [[clang::lifetimebound]]);
+ string_view(const string &s [[clang::lifetimebound]]);
+ };
+ string_view operator""sv(const char *, size_t);
+
+ struct vector {
+ int *data();
+ size_t size();
+ };
+
+ template<typename K, typename V> struct map {};
+}
+# 68 "attr-lifetimebound.cpp" 2
+
+using std::operator""s;
+using std::operator""sv;
+
+namespace p0936r0_examples {
+ std::string_view s = "foo"s; // expected-warning {{temporary}}
+
+ std::string operator+(std::string_view s1, std::string_view s2);
+ void f() {
+ std::string_view sv = "hi";
+ std::string_view sv2 = sv + sv; // expected-warning {{temporary}}
+ sv2 = sv + sv; // FIXME: can we infer that we should warn here too?
+ }
+
+ struct X { int a, b; };
+ const int &f(const X &x [[clang::lifetimebound]]) { return x.a; }
+ const int &r = f(X()); // expected-warning {{temporary}}
+
+ char &c = std::string("hello my pretty long strong")[0]; // expected-warning {{temporary}}
+
+ struct reversed_range {
+ int *begin();
+ int *end();
+ int *p;
+ std::size_t n;
+ };
+ template <typename R> reversed_range reversed(R &&r [[clang::lifetimebound]]) {
+ return reversed_range{r.data(), r.size()};
+ }
+
+ std::vector make_vector();
+ void use_reversed_range() {
+ // FIXME: Don't expose the name of the internal range variable.
+ for (auto x : reversed(make_vector())) {} // expected-warning {{temporary bound to local reference '__range1'}}
+ }
+
+ template <typename K, typename V>
+ const V &findOrDefault(const std::map<K, V> &m [[clang::lifetimebound]],
+ const K &key,
+ const V &defvalue [[clang::lifetimebound]]);
+
+ // FIXME: Maybe weaken the wording here: "local reference 'v' could bind to temporary that will be destroyed at end of full-expression"?
+ std::map<std::string, std::string> m;
+ const std::string &v = findOrDefault(m, "foo"s, "bar"s); // expected-warning {{temporary bound to local reference 'v'}}
+}
diff --git a/utils/TableGen/ClangAttrEmitter.cpp b/utils/TableGen/ClangAttrEmitter.cpp
index 0bf7a07cf6..6b3df82580 100644
--- a/utils/TableGen/ClangAttrEmitter.cpp
+++ b/utils/TableGen/ClangAttrEmitter.cpp
@@ -3305,11 +3305,16 @@ static std::string GenerateAppertainsTo(const Record &Attr, raw_ostream &OS) {
// Otherwise, generate an appertainsTo check specific to this attribute which
// checks all of the given subjects against the Decl passed in. Return the
// name of that check to the caller.
+ //
+ // If D is null, that means the attribute was not applied to a declaration
+ // at all (for instance because it was applied to a type), or that the caller
+ // has determined that the check should fail (perhaps prior to the creation
+ // of the declaration).
std::string FnName = "check" + Attr.getName().str() + "AppertainsTo";
std::stringstream SS;
SS << "static bool " << FnName << "(Sema &S, const ParsedAttr &Attr, ";
SS << "const Decl *D) {\n";
- SS << " if (";
+ SS << " if (!D || (";
for (auto I = Subjects.begin(), E = Subjects.end(); I != E; ++I) {
// If the subject has custom code associated with it, generate a function
// for it. The function cannot be inlined into this check (yet) because it
@@ -3325,7 +3330,7 @@ static std::string GenerateAppertainsTo(const Record &Attr, raw_ostream &OS) {
if (I + 1 != E)
SS << " && ";
}
- SS << ") {\n";
+ SS << ")) {\n";
SS << " S.Diag(Attr.getLoc(), diag::";
SS << (Warn ? "warn_attribute_wrong_decl_type_str" :
"err_attribute_wrong_decl_type_str");