attr_null_unspecified,
attr_objc_kindof,
attr_objc_inert_unsafe_unretained,
+ attr_lifetimebound,
};
private:
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;
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.
}];
}
+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 = [{
"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 "
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
case attr_null_unspecified:
case attr_objc_kindof:
case attr_nocf_check:
+ case attr_lifetimebound:
return false;
case attr_pcs:
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
<< 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,
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;
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>;
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,
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
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
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);
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;
// 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)
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!");
}
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) {
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))
--- /dev/null
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+namespace usage_invalid {
+ // FIXME: Should we diagnose a void return type?
+ void voidreturn(int ¶m [[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 ¶m [[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}}
+}
+
+namespace usage_ok {
+ struct IntRef { int *target; };
+
+ int &refparam(int ¶m [[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 ¶m [[clang::lifetimebound]]) {
+ return (long long)¶m;
+ }
+ // 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'}}
+}
// 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
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");