Index: include/clang/AST/Type.h =================================================================== --- include/clang/AST/Type.h +++ include/clang/AST/Type.h @@ -4237,6 +4237,7 @@ attr_null_unspecified, attr_objc_kindof, attr_objc_inert_unsafe_unretained, + attr_lifetimebound, }; private: Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -141,6 +141,13 @@ isa(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; + // A single argument to an attribute class Argument { string Name = name; @@ -1211,6 +1218,13 @@ 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. Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -2362,6 +2362,22 @@ }]; } +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 = [{ Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -7854,6 +7854,13 @@ "null returned from %select{function|method}0 that requires a non-null return value">, InGroup; +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 " Index: lib/AST/Type.cpp =================================================================== --- lib/AST/Type.cpp +++ lib/AST/Type.cpp @@ -3178,6 +3178,7 @@ 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 @@ case attr_null_unspecified: case attr_objc_kindof: case attr_nocf_check: + case attr_lifetimebound: return false; case attr_pcs: Index: lib/AST/TypePrinter.cpp =================================================================== --- lib/AST/TypePrinter.cpp +++ lib/AST/TypePrinter.cpp @@ -1443,9 +1443,27 @@ 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 Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -6008,6 +6008,27 @@ << Attr; ND.dropAttr(); } + + // Check the attributes on the function type, if any. + if (const auto *FD = dyn_cast(&ND)) { + for (TypeLoc TL = FD->getTypeSourceInfo()->getTypeLoc(); + auto ATL = TL.getAsAdjusted(); + 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(FD); + if (!MD || MD->isStatic()) { + S.Diag(ATL.getAttrNameLoc(), diag::err_lifetimebound_no_object_param) + << !MD << ATL.getLocalSourceRange(); + } else if (isa(MD) || isa(MD)) { + S.Diag(ATL.getAttrNameLoc(), diag::err_lifetimebound_ctor_dtor) + << isa(MD) << ATL.getLocalSourceRange(); + } + } + } + } } static void checkDLLAttributeRedeclaration(Sema &S, NamedDecl *OldDecl, Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -6150,6 +6150,9 @@ case ParsedAttr::AT_Restrict: handleRestrictAttr(S, D, AL); break; + case ParsedAttr::AT_LifetimeBound: + handleSimpleAttribute(S, D, AL); + break; case ParsedAttr::AT_MayAlias: handleSimpleAttribute(S, D, AL); break; Index: lib/Sema/SemaInit.cpp =================================================================== --- lib/Sema/SemaInit.cpp +++ lib/Sema/SemaInit.cpp @@ -6322,12 +6322,14 @@ 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; @@ -6361,6 +6363,68 @@ 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(); + 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 Args; + + if (auto *CE = dyn_cast(Call)) { + Callee = CE->getDirectCallee(); + Args = llvm::makeArrayRef(CE->getArgs(), CE->getNumArgs()); + } else { + auto *CCE = cast(Call); + Callee = CCE->getConstructor(); + Args = llvm::makeArrayRef(CCE->getArgs(), CCE->getNumArgs()); + } + if (!Callee) + return; + + Expr *ObjectArg = nullptr; + if (isa(Call) && Callee->isCXXInstanceMember()) { + ObjectArg = Args[0]; + Args = Args.slice(1); + } else if (auto *MCE = dyn_cast(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(Callee->getNumParams(), Args.size()); + I != N; ++I) { + if (Callee->getParamDecl(I)->hasAttr()) + 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 @@ true); } + if (isa(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 @@ 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(Init)) { - Path.push_back({IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()}); - Init = DIE->getExpr(); - } + Expr *Old; + do { + Old = Init; + + // Step into CXXDefaultInitExprs so we can diagnose cases where a + // constructor inherits one as an implicit mem-initializer. + if (auto *DIE = dyn_cast(Init)) { + Path.push_back({IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()}); + Init = DIE->getExpr(); + } + + if (auto *EWC = dyn_cast(Init)) + Init = EWC->getSubExpr(); - if (auto *EWC = dyn_cast(Init)) - Init = EWC->getSubExpr(); + // Dig out the expression which constructs the extended temporary. + Init = const_cast(Init->skipRValueSubobjectAdjustments()); + + if (CXXBindTemporaryExpr *BTE = dyn_cast(Init)) + Init = BTE->getSubExpr(); + + Init = Init->IgnoreParens(); + + // Step over value-preserving rvalue casts. + if (auto *CE = dyn_cast(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(L)) { + auto *VD = dyn_cast(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(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); - // Dig out the expression which constructs the extended temporary. - Init = const_cast(Init->skipRValueSubobjectAdjustments()); + default: + return; + } - if (CXXBindTemporaryExpr *BTE = dyn_cast(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 @@ return; } - // Step over value-preserving rvalue casts. - while (auto *CE = dyn_cast(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(L)) { - auto *VD = dyn_cast(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(L)) { - if (MTE->getType().isConstQualified()) - visitLocalsRetainedByInitializer(Path, MTE->GetTemporaryExpr(), - Visit, true); - } - return false; - }); + if (isa(Init) || isa(Init)) + return visitLifetimeBoundArguments(Path, Init, Visit); - // 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(); - } - - Init = Init->IgnoreParens(); switch (Init->getStmtClass()) { case Stmt::UnaryOperatorClass: { auto *UO = cast(Init); @@ -6698,6 +6776,7 @@ 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 @@ // supporting lifetime extension. break; + case IndirectLocalPathEntry::LifetimeBoundCall: + // FIXME: Consider adding a note for this. + break; + case IndirectLocalPathEntry::DefaultInit: { auto *FD = cast(Elem.D); Diag(FD->getLocation(), diag::note_init_with_default_member_initalizer) Index: lib/Sema/SemaType.cpp =================================================================== --- lib/Sema/SemaType.cpp +++ lib/Sema/SemaType.cpp @@ -5233,6 +5233,8 @@ 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 @@ 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 @@ 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)) Index: test/SemaCXX/attr-lifetimebound.cpp =================================================================== --- test/SemaCXX/attr-lifetimebound.cpp +++ 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 ¶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 "" 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 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 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 + const V &findOrDefault(const std::map &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 m; + const std::string &v = findOrDefault(m, "foo"s, "bar"s); // expected-warning {{temporary bound to local reference 'v'}} +} Index: utils/TableGen/ClangAttrEmitter.cpp =================================================================== --- utils/TableGen/ClangAttrEmitter.cpp +++ utils/TableGen/ClangAttrEmitter.cpp @@ -3305,11 +3305,16 @@ // 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 @@ 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");