r338464 - [P0936R0] add [[clang::lifetimebound]] attribute

Richard Smith via cfe-commits cfe-commits at lists.llvm.org
Tue Jul 31 17:33:25 PDT 2018


Author: rsmith
Date: Tue Jul 31 17:33:25 2018
New Revision: 338464

URL: http://llvm.org/viewvc/llvm-project?rev=338464&view=rev
Log:
[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

Added:
    cfe/trunk/test/SemaCXX/attr-lifetimebound.cpp
Modified:
    cfe/trunk/include/clang/AST/Type.h
    cfe/trunk/include/clang/Basic/Attr.td
    cfe/trunk/include/clang/Basic/AttrDocs.td
    cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
    cfe/trunk/lib/AST/Type.cpp
    cfe/trunk/lib/AST/TypePrinter.cpp
    cfe/trunk/lib/Sema/SemaDecl.cpp
    cfe/trunk/lib/Sema/SemaDeclAttr.cpp
    cfe/trunk/lib/Sema/SemaInit.cpp
    cfe/trunk/lib/Sema/SemaType.cpp
    cfe/trunk/utils/TableGen/ClangAttrEmitter.cpp

Modified: cfe/trunk/include/clang/AST/Type.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/Type.h?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/include/clang/AST/Type.h (original)
+++ cfe/trunk/include/clang/AST/Type.h Tue Jul 31 17:33:25 2018
@@ -4237,6 +4237,7 @@ public:
     attr_null_unspecified,
     attr_objc_kindof,
     attr_objc_inert_unsafe_unretained,
+    attr_lifetimebound,
   };
 
 private:

Modified: cfe/trunk/include/clang/Basic/Attr.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/Attr.td?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/Attr.td (original)
+++ cfe/trunk/include/clang/Basic/Attr.td Tue Jul 31 17:33:25 2018
@@ -141,6 +141,13 @@ def HasFunctionProto : SubsetSubject<Dec
                                        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, Tar
   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.

Modified: cfe/trunk/include/clang/Basic/AttrDocs.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/AttrDocs.td?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/AttrDocs.td (original)
+++ cfe/trunk/include/clang/Basic/AttrDocs.td Tue Jul 31 17:33:25 2018
@@ -2362,6 +2362,22 @@ It is only supported when using the Micr
   }];
 }
 
+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 = [{

Modified: cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td Tue Jul 31 17:33:25 2018
@@ -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 "

Modified: cfe/trunk/lib/AST/Type.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/Type.cpp?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/lib/AST/Type.cpp (original)
+++ cfe/trunk/lib/AST/Type.cpp Tue Jul 31 17:33:25 2018
@@ -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() con
   case attr_null_unspecified:
   case attr_objc_kindof:
   case attr_nocf_check:
+  case attr_lifetimebound:
     return false;
 
   case attr_pcs:

Modified: cfe/trunk/lib/AST/TypePrinter.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/TypePrinter.cpp?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/lib/AST/TypePrinter.cpp (original)
+++ cfe/trunk/lib/AST/TypePrinter.cpp Tue Jul 31 17:33:25 2018
@@ -1443,9 +1443,27 @@ void TypePrinter::printAttributedAfter(c
     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

Modified: cfe/trunk/lib/Sema/SemaDecl.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaDecl.cpp?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaDecl.cpp (original)
+++ cfe/trunk/lib/Sema/SemaDecl.cpp Tue Jul 31 17:33:25 2018
@@ -6008,6 +6008,27 @@ static void checkAttributesAfterMerging(
             << 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,

Modified: cfe/trunk/lib/Sema/SemaDeclAttr.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaDeclAttr.cpp?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaDeclAttr.cpp (original)
+++ cfe/trunk/lib/Sema/SemaDeclAttr.cpp Tue Jul 31 17:33:25 2018
@@ -6150,6 +6150,9 @@ static void ProcessDeclAttribute(Sema &S
   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;

Modified: cfe/trunk/lib/Sema/SemaInit.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaInit.cpp?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaInit.cpp (original)
+++ cfe/trunk/lib/Sema/SemaInit.cpp Tue Jul 31 17:33:25 2018
@@ -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 visitLocalsRetainedByInitial
                                              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 visitLocalsRetainedByReferen
                                        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 visitLocalsRetainedByInitial
                                              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();
 
-  if (CXXBindTemporaryExpr *BTE = dyn_cast<CXXBindTemporaryExpr>(Init))
-    Init = BTE->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;
+      }
+
+      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 visitLocalsRetainedByInitial
     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;
-      });
+  if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(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<UnaryOperator>(Init);
@@ -6698,6 +6776,7 @@ static SourceRange nextPathEntryRange(co
     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(cons
         // 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)

Modified: cfe/trunk/lib/Sema/SemaType.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaType.cpp?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaType.cpp (original)
+++ cfe/trunk/lib/Sema/SemaType.cpp Tue Jul 31 17:33:25 2018
@@ -5233,6 +5233,8 @@ static ParsedAttr::Kind getAttrListKind(
     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 deduceOpenCLImplicitAddrSpac
   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(TypeProcess
       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))

Added: cfe/trunk/test/SemaCXX/attr-lifetimebound.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/attr-lifetimebound.cpp?rev=338464&view=auto
==============================================================================
--- cfe/trunk/test/SemaCXX/attr-lifetimebound.cpp (added)
+++ cfe/trunk/test/SemaCXX/attr-lifetimebound.cpp Tue Jul 31 17:33:25 2018
@@ -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)¶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'}}
+}

Modified: cfe/trunk/utils/TableGen/ClangAttrEmitter.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/utils/TableGen/ClangAttrEmitter.cpp?rev=338464&r1=338463&r2=338464&view=diff
==============================================================================
--- cfe/trunk/utils/TableGen/ClangAttrEmitter.cpp (original)
+++ cfe/trunk/utils/TableGen/ClangAttrEmitter.cpp Tue Jul 31 17:33:25 2018
@@ -3305,11 +3305,16 @@ static std::string GenerateAppertainsTo(
   // 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(
     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");




More information about the cfe-commits mailing list