Index: include/clang/AST/Type.h =================================================================== --- include/clang/AST/Type.h +++ include/clang/AST/Type.h @@ -4234,6 +4234,7 @@ attr_null_unspecified, attr_objc_kindof, attr_objc_inert_unsafe_unretained, + attr_lifetimebound, }; private: Index: include/clang/AST/TypeLoc.h =================================================================== --- include/clang/AST/TypeLoc.h +++ include/clang/AST/TypeLoc.h @@ -93,8 +93,8 @@ } /// Convert to the specified TypeLoc type, returning a null TypeLoc if - /// this TypeLock is not of the desired type. It will consider type - /// adjustments from a type that wad written as a T to another type that is + /// this TypeLoc is not of the desired type. It will consider type + /// adjustments from a type that was written as a T to another type that is /// still canonically a T (ignores parens, attributes, elaborated types, etc). template T getAsAdjusted() const; 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 ``lifetime_bound`` 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 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 @@ -7851,6 +7851,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 @@ -3174,6 +3174,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 @@ -3243,6 +3244,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 @@ -1489,36 +1507,6 @@ break; } - case AttributedType::attr_objc_gc: { - OS << "objc_gc("; - - QualType tmp = T->getEquivalentType(); - while (tmp.getObjCGCAttr() == Qualifiers::GCNone) { - QualType next = tmp->getPointeeType(); - if (next == tmp) break; - tmp = next; - } - - if (tmp.isObjCGCWeak()) - OS << "weak"; - else - OS << "strong"; - OS << ')'; - break; - } - - case AttributedType::attr_objc_ownership: - OS << "objc_ownership("; - switch (T->getEquivalentType().getObjCLifetime()) { - case Qualifiers::OCL_None: llvm_unreachable("no ownership!"); - case Qualifiers::OCL_ExplicitNone: OS << "none"; break; - case Qualifiers::OCL_Strong: OS << "strong"; break; - case Qualifiers::OCL_Weak: OS << "weak"; break; - case Qualifiers::OCL_Autoreleasing: OS << "autoreleasing"; break; - } - OS << ')'; - break; - case AttributedType::attr_ns_returns_retained: OS << "ns_returns_retained"; break; Index: lib/Parse/ParseDeclCXX.cpp =================================================================== --- lib/Parse/ParseDeclCXX.cpp +++ lib/Parse/ParseDeclCXX.cpp @@ -3808,6 +3808,7 @@ case ParsedAttr::AT_Deprecated: case ParsedAttr::AT_FallThrough: case ParsedAttr::AT_CXX11NoReturn: + case ParsedAttr::AT_LifetimeBound: return true; case ParsedAttr::AT_WarnUnusedResult: return !ScopeName && AttrName->getName().equals("nodiscard"); 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 (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 dtor) by + // applying it to the function type. + if (ATL.getAttrKind() == AttributedType::attr_lifetimebound) { + 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,6 +6322,7 @@ AddressOf, VarInit, LValToRVal, + LifetimeBoundCall, } Kind; Expr *E; Decl *D = nullptr; @@ -6361,6 +6362,68 @@ Expr *Init, LocalVisitor Visit, bool RevisitSubinits); +static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path, + Expr *Init, ReferenceKind RK, + LocalVisitor Visit); + +static bool implicitObjectParamIsLifetimeBound(FunctionDecl *FD) { + 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) { + 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 = [&](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 +6483,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,22 +6549,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; - if (auto *EWC = dyn_cast(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(Init)) { + Path.push_back({IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()}); + Init = DIE->getExpr(); + } - // Dig out the expression which constructs the extended temporary. - Init = const_cast(Init->skipRValueSubobjectAdjustments()); + if (auto *EWC = dyn_cast(Init)) + Init = EWC->getSubExpr(); - if (CXXBindTemporaryExpr *BTE = dyn_cast(Init)) - Init = BTE->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()) { + 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); + + default: + return; + } + + Init = CE->getSubExpr(); + } + } while (Old != Init); + // C++17 [dcl.init.list]p6: // initializing an initializer_list object from the array extends the // lifetime of the array exactly like binding a reference to a temporary. @@ -6558,66 +6692,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()) { - 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); @@ -6697,6 +6774,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; @@ -6875,6 +6953,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/SemaOverload.cpp =================================================================== --- lib/Sema/SemaOverload.cpp +++ lib/Sema/SemaOverload.cpp @@ -5230,7 +5230,9 @@ if (!Context.hasSameType(From->getType(), DestType)) From = ImpCastExprToType(From, DestType, CK_NoOp, From->getValueKind()).get(); - return From; + if (From->getType()->isPointerType()) + return From; + return TemporaryMaterializationConversion(From); } /// TryContextuallyConvertToBool - Attempt to contextually convert the 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: 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");