Hi rsmith,

Specifically, when we have this situation:
  struct A {
    template <typename T> struct B {
      int m1 = sizeof(A);
    };
    B<int> m2;
  };

We can't parse m1's initializer eagerly because we need A to be
complete.  Therefore we wait until the end of A's class scope to parse
it. However, we can trigger instantiation of B before the end of A,
which will attempt to instantiate the field decls eagerly, and it would
build a bad field decl instantiation that said it had an initializer but
actually lacked one.

Fixed by doing template instantiation when building a CXXDefaultInitExpr
and erorring out if the initializer is missing for any other reason.
Fixes PR19195.

This does *not* defer all field instantiation until later, as that
causes assertions in the test suite.  Fixing that might be the right
thing to do, but I wanted feedback on the approach so far first.

http://reviews.llvm.org/D5690

Files:
  include/clang/AST/ExprCXX.h
  include/clang/Basic/DiagnosticSemaKinds.td
  include/clang/Sema/Sema.h
  lib/Sema/SemaDeclCXX.cpp
  lib/Sema/SemaInit.cpp
  lib/Sema/SemaTemplateInstantiate.cpp
  lib/Sema/SemaTemplateInstantiateDecl.cpp
  test/SemaCXX/constant-expression-cxx11.cpp
  test/SemaCXX/implicit-exception-spec.cpp
  test/SemaCXX/member-init.cpp
Index: include/clang/AST/ExprCXX.h
===================================================================
--- include/clang/AST/ExprCXX.h
+++ include/clang/AST/ExprCXX.h
@@ -967,8 +967,14 @@
   const FieldDecl *getField() const { return Field; }
 
   /// \brief Get the initialization expression that will be used.
-  const Expr *getExpr() const { return Field->getInClassInitializer(); }
-  Expr *getExpr() { return Field->getInClassInitializer(); }
+  const Expr *getExpr() const {
+    assert(Field->getInClassInitializer() && "initializer hasn't been parsed");
+    return Field->getInClassInitializer();
+  }
+  Expr *getExpr() {
+    assert(Field->getInClassInitializer() && "initializer hasn't been parsed");
+    return Field->getInClassInitializer();
+  }
 
   SourceLocation getLocStart() const LLVM_READONLY { return Loc; }
   SourceLocation getLocEnd() const LLVM_READONLY { return Loc; }
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td
+++ include/clang/Basic/DiagnosticSemaKinds.td
@@ -6185,9 +6185,9 @@
   "'constexpr' specifier">;
 def err_in_class_initializer_non_constant : Error<
   "in-class initializer for static data member is not a constant expression">;
-def err_in_class_initializer_references_def_ctor : Error<
-  "defaulted default constructor of %0 cannot be used by non-static data "
-  "member initializer which appears before end of class definition">;
+def err_in_class_initializer_not_yet_parsed : Error<
+  "cannot default initialize non-static data member %0 before the end of the "
+  "outermost containing class %1">;
 
 def ext_in_class_initializer_non_constant : Extension<
   "in-class initializer for static data member is not a constant expression; "
Index: include/clang/Sema/Sema.h
===================================================================
--- include/clang/Sema/Sema.h
+++ include/clang/Sema/Sema.h
@@ -3916,6 +3916,8 @@
                         bool IsStdInitListInitialization, bool RequiresZeroInit,
                         unsigned ConstructKind, SourceRange ParenRange);
 
+  ExprResult BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field);
+
   /// BuildCXXDefaultArgExpr - Creates a CXXDefaultArgExpr, instantiating
   /// the default expr if needed.
   ExprResult BuildCXXDefaultArgExpr(SourceLocation CallLoc,
@@ -6731,6 +6733,10 @@
                        const MultiLevelTemplateArgumentList &TemplateArgs,
                        TemplateSpecializationKind TSK);
 
+  bool InstantiateField(SourceLocation PointOfInstantiation,
+                        FieldDecl *Instantiation, FieldDecl *Pattern,
+                        const MultiLevelTemplateArgumentList &TemplateArgs);
+
   struct LateInstantiatedAttribute {
     const Attr *TmplAttr;
     LocalInstantiationScope *Scope;
Index: lib/Sema/SemaDeclCXX.cpp
===================================================================
--- lib/Sema/SemaDeclCXX.cpp
+++ lib/Sema/SemaDeclCXX.cpp
@@ -36,6 +36,7 @@
 #include "clang/Sema/ParsedTemplate.h"
 #include "clang/Sema/Scope.h"
 #include "clang/Sema/ScopeInfo.h"
+#include "clang/Sema/Template.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallString.h"
 #include <map>
@@ -3610,19 +3611,19 @@
     return false;
 
   if (Field->hasInClassInitializer() && !Info.isImplicitCopyOrMove()) {
-    Expr *DIE = CXXDefaultInitExpr::Create(SemaRef.Context,
-                                           Info.Ctor->getLocation(), Field);
+    ExprResult DIE =
+        SemaRef.BuildCXXDefaultInitExpr(Info.Ctor->getLocation(), Field);
+    if (DIE.isInvalid())
+      return true;
     CXXCtorInitializer *Init;
     if (Indirect)
-      Init = new (SemaRef.Context) CXXCtorInitializer(SemaRef.Context, Indirect,
-                                                      SourceLocation(),
-                                                      SourceLocation(), DIE,
-                                                      SourceLocation());
+      Init = new (SemaRef.Context)
+          CXXCtorInitializer(SemaRef.Context, Indirect, SourceLocation(),
+                             SourceLocation(), DIE.get(), SourceLocation());
     else
-      Init = new (SemaRef.Context) CXXCtorInitializer(SemaRef.Context, Field,
-                                                      SourceLocation(),
-                                                      SourceLocation(), DIE,
-                                                      SourceLocation());
+      Init = new (SemaRef.Context)
+          CXXCtorInitializer(SemaRef.Context, Field, SourceLocation(),
+                             SourceLocation(), DIE.get(), SourceLocation());
     return Info.addFieldInitializer(Init);
   }
 
@@ -8417,22 +8418,6 @@
     if (F->hasInClassInitializer()) {
       if (Expr *E = F->getInClassInitializer())
         ExceptSpec.CalledExpr(E);
-      else if (!F->isInvalidDecl())
-        // DR1351:
-        //   If the brace-or-equal-initializer of a non-static data member
-        //   invokes a defaulted default constructor of its class or of an
-        //   enclosing class in a potentially evaluated subexpression, the
-        //   program is ill-formed.
-        //
-        // This resolution is unworkable: the exception specification of the
-        // default constructor can be needed in an unevaluated context, in
-        // particular, in the operand of a noexcept-expression, and we can be
-        // unable to compute an exception specification for an enclosed class.
-        //
-        // We do not allow an in-class initializer to require the evaluation
-        // of the exception specification for any in-class initializer whose
-        // definition is not lexically complete.
-        Diag(Loc, diag::err_in_class_initializer_references_def_ctor) << MD;
     } else if (const RecordType *RecordTy
               = Context.getBaseElementType(F->getType())->getAs<RecordType>()) {
       CXXRecordDecl *FieldRecDecl = cast<CXXRecordDecl>(RecordTy->getDecl());
@@ -8500,9 +8485,6 @@
     if (F->hasInClassInitializer()) {
       if (Expr *E = F->getInClassInitializer())
         ExceptSpec.CalledExpr(E);
-      else if (!F->isInvalidDecl())
-        Diag(CD->getLocation(),
-             diag::err_in_class_initializer_references_def_ctor) << CD;
     } else if (const RecordType *RecordTy
               = Context.getBaseElementType(F->getType())->getAs<RecordType>()) {
       CXXRecordDecl *FieldRecDecl = cast<CXXRecordDecl>(RecordTy->getDecl());
@@ -10967,6 +10949,57 @@
       ParenRange);
 }
 
+ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
+  assert(Field->hasInClassInitializer());
+
+  // If we already have the in-class initializer nothing needs to be done.
+  if (Field->getInClassInitializer())
+    return CXXDefaultInitExpr::Create(Context, Loc, Field);
+
+  // DR1351:
+  //   If the brace-or-equal-initializer of a non-static data member
+  //   invokes a defaulted default constructor of its class or of an
+  //   enclosing class in a potentially evaluated subexpression, the
+  //   program is ill-formed.
+  //
+  // This resolution is unworkable: the exception specification of the
+  // default constructor can be needed in an unevaluated context, in
+  // particular, in the operand of a noexcept-expression, and we can be
+  // unable to compute an exception specification for an enclosed class.
+  //
+  // Any attempt to evaluate the exception specification of a defaulted default
+  // constructor before the initializer is lexically complete will ultimately
+  // come here at which point we can diagnose it.
+
+  // Maybe we haven't instantiated the in-class initializer. Go check the
+  // pattern FieldDecl to see if it has one.
+  CXXRecordDecl *ParentRD = cast<CXXRecordDecl>(Field->getParent());
+
+  if (isTemplateInstantiation(ParentRD->getTemplateSpecializationKind())) {
+    auto *InstantiatedClass = cast<ClassTemplateSpecializationDecl>(ParentRD);
+    CXXRecordDecl *ClassPattern =
+        InstantiatedClass->getSpecializedTemplate()->getTemplatedDecl();
+    DeclContext::lookup_result Lookup =
+        ClassPattern->lookup(Field->getDeclName());
+    assert(Lookup.size() == 1);
+    FieldDecl *Pattern = cast<FieldDecl>(Lookup[0]);
+    if (InstantiateField(Loc, Field, Pattern,
+                         getTemplateInstantiationArgs(Field)))
+      return ExprError();
+    return CXXDefaultInitExpr::Create(Context, Loc, Field);
+  }
+  RecordDecl *OutermostClass = ParentRD;
+  for (DeclContext *DC = OutermostClass; !DC->isTranslationUnit();
+       DC = DC->getParent()) {
+    if (auto *RD = dyn_cast<RecordDecl>(DC))
+      OutermostClass = RD;
+  }
+  Diag(Field->getLocEnd(), diag::err_in_class_initializer_not_yet_parsed)
+      << Field << OutermostClass;
+
+  return ExprError();
+}
+
 void Sema::FinalizeVarWithDestructor(VarDecl *VD, const RecordType *Record) {
   if (VD->isInvalidDecl()) return;
 
Index: lib/Sema/SemaInit.cpp
===================================================================
--- lib/Sema/SemaInit.cpp
+++ lib/Sema/SemaInit.cpp
@@ -466,11 +466,15 @@
     //   members in the aggregate, then each member not explicitly initialized
     //   shall be initialized from its brace-or-equal-initializer [...]
     if (Field->hasInClassInitializer()) {
-      Expr *DIE = CXXDefaultInitExpr::Create(SemaRef.Context, Loc, Field);
+      ExprResult DIE = SemaRef.BuildCXXDefaultInitExpr(Loc, Field);
+      if (DIE.isInvalid()) {
+        hadError = true;
+        return;
+      }
       if (Init < NumInits)
-        ILE->setInit(Init, DIE);
+        ILE->setInit(Init, DIE.get());
       else {
-        ILE->updateInit(SemaRef.Context, Init, DIE);
+        ILE->updateInit(SemaRef.Context, Init, DIE.get());
         RequiresSecondPass = true;
       }
       return;
Index: lib/Sema/SemaTemplateInstantiate.cpp
===================================================================
--- lib/Sema/SemaTemplateInstantiate.cpp
+++ lib/Sema/SemaTemplateInstantiate.cpp
@@ -1976,7 +1976,7 @@
       if (FieldDecl *Field = dyn_cast<FieldDecl>(NewMember)) {
         Fields.push_back(Field);
         FieldDecl *OldField = cast<FieldDecl>(Member);
-        if (OldField->getInClassInitializer())
+        if (OldField->hasInClassInitializer())
           FieldsWithMemberInitializers.push_back(std::make_pair(OldField,
                                                                 Field));
       } else if (EnumDecl *Enum = dyn_cast<EnumDecl>(NewMember)) {
@@ -2030,14 +2030,14 @@
       FieldDecl *NewField = FieldsWithMemberInitializers[I].second;
       Expr *OldInit = OldField->getInClassInitializer();
 
-      ActOnStartCXXInClassMemberInitializer();
-      ExprResult NewInit = SubstInitializer(OldInit, TemplateArgs,
-                                            /*CXXDirectInit=*/false);
-      Expr *Init = NewInit.get();
-      assert((!Init || !isa<ParenListExpr>(Init)) &&
-             "call-style init in class");
-      ActOnFinishCXXInClassMemberInitializer(NewField,
-        Init ? Init->getLocStart() : SourceLocation(), Init);
+      // If we haven't parsed the initializer yet, defer instantiation until
+      // later.
+      // FIXME: If we can tolerate defering some initializers, it shouldn't be
+      // hard to defer *all* field instantiation until later as described above.
+      if (OldInit) {
+        InstantiateField(Instantiation->getLocation(), NewField, OldField,
+                         TemplateArgs);
+      }
     }
   }
   // Instantiate late parsed attributes, and attach them to their decls.
@@ -2180,6 +2180,61 @@
   return Instantiation->isInvalidDecl();
 }
 
+
+/// \brief Instantiate the definition of a field from the given pattern.
+///
+/// \param PointOfInstantiation The point of instantiation within the
+///        source code.
+/// \param Instantiation is the declaration whose definition is being
+///        instantiated. This will be a class of a class temploid
+///        specialization, or a local enumeration within a function temploid
+///        specialization.
+/// \param Pattern The templated declaration from which the instantiation
+///        occurs.
+/// \param TemplateArgs The template arguments to be substituted into
+///        the pattern.
+///
+/// \return \c true if an error occurred, \c false otherwise.
+bool
+Sema::InstantiateField(SourceLocation PointOfInstantiation,
+                       FieldDecl *Instantiation, FieldDecl *Pattern,
+                       const MultiLevelTemplateArgumentList &TemplateArgs) {
+  // We only need to instantiate the initializer, the rest of the field was
+  // instantiated during InstantiateClass.
+  if (!Pattern->hasInClassInitializer())
+    return false;
+  assert(Instantiation->getInClassInitStyle() ==
+             Pattern->getInClassInitStyle() &&
+         "pattern and instantiation disagree about init style");
+
+  // Error out if we haven't parsed the initializer of the pattern yet because
+  // we are waiting for the closing brace of the outer class.
+  Expr *OldInit = Pattern->getInClassInitializer();
+  if (!OldInit) {
+    // FIXME: Deduplicate code with SemaInit
+    RecordDecl *OutermostClass = Pattern->getParent();
+    for (DeclContext *DC = OutermostClass; !DC->isTranslationUnit();
+         DC = DC->getParent()) {
+      if (auto *RD = dyn_cast<RecordDecl>(DC))
+        OutermostClass = RD;
+    }
+    Diag(Pattern->getLocEnd(), diag::err_in_class_initializer_not_yet_parsed)
+        << Pattern << OutermostClass;
+    Instantiation->setInvalidDecl();
+    return true;
+  }
+
+  // Instantiate the initializer.
+  ActOnStartCXXInClassMemberInitializer();
+  ExprResult NewInit = SubstInitializer(OldInit, TemplateArgs,
+                                        /*CXXDirectInit=*/false);
+  Expr *Init = NewInit.get();
+  assert((!Init || !isa<ParenListExpr>(Init)) && "call-style init in class");
+  ActOnFinishCXXInClassMemberInitializer(
+      Instantiation, Init ? Init->getLocStart() : SourceLocation(), Init);
+  return Init == nullptr;
+}
+
 namespace {
   /// \brief A partial specialization whose template arguments have matched
   /// a given template-id.
Index: lib/Sema/SemaTemplateInstantiateDecl.cpp
===================================================================
--- lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -548,6 +548,24 @@
     return nullptr;
   }
 
+#if 0
+  // Instantiate the in-class initializer if present.
+  llvm::errs() << "VisitFieldDecl\n";
+  D->dump();
+  if (D->hasInClassInitializer()) {
+    Expr *Init = D->getInClassInitializer();
+    llvm::errs() << "Init\n";
+    Init->dump();
+    SemaRef.ActOnStartCXXInClassMemberInitializer();
+    bool IsDirectInit = D->getInClassInitStyle() == ICIS_ListInit;
+    Init = SemaRef.SubstInitializer(Init, TemplateArgs, IsDirectInit).get();
+    llvm::errs() << "instantiated Init\n";
+    Init->dump();
+    SemaRef.ActOnFinishCXXInClassMemberInitializer(Field, Init->getLocStart(),
+                                                   Init);
+  }
+#endif
+
   SemaRef.InstantiateAttrs(TemplateArgs, D, Field, LateAttrs, StartingScope);
 
   if (Field->hasAttrs())
Index: test/SemaCXX/constant-expression-cxx11.cpp
===================================================================
--- test/SemaCXX/constant-expression-cxx11.cpp
+++ test/SemaCXX/constant-expression-cxx11.cpp
@@ -1842,8 +1842,9 @@
 namespace BadDefaultInit {
   template<int N> struct X { static const int n = N; };
 
-  struct A { // expected-note {{subexpression}}
-    int k = X<A().k>::n; // expected-error {{defaulted default constructor of 'A' cannot be used}} expected-error {{not a constant expression}} expected-note {{in call to 'A()'}}
+  struct A {
+    int k = // expected-error {{cannot default initialize non-static data member 'k' before the end of the outermost containing class 'A'}}
+        X<A().k>::n; // expected-error {{not a constant expression}} expected-note {{implicit default constructor for 'BadDefaultInit::A' first required here}}
   };
 
   // FIXME: The "constexpr constructor must initialize all members" diagnostic
Index: test/SemaCXX/implicit-exception-spec.cpp
===================================================================
--- test/SemaCXX/implicit-exception-spec.cpp
+++ test/SemaCXX/implicit-exception-spec.cpp
@@ -17,31 +17,32 @@
   // is false.
   bool ThrowSomething() noexcept(false);
   struct ConstExpr {
-    bool b = noexcept(ConstExpr()) && ThrowSomething(); // expected-error {{cannot be used by non-static data member initializer}}
+    bool b = noexcept(ConstExpr()) && ThrowSomething(); // expected-error {{cannot default initialize non-static data member}}
+  // expected-note@-1 {{implicit default constructor for 'InClassInitializers::ConstExpr' first required here}}
   };
-  // We can use it now.
-  bool w = noexcept(ConstExpr());
 
   // Much more obviously broken: we can't parse the initializer without already
   // knowing whether it produces a noexcept expression.
   struct TemplateArg {
-    int n = ExceptionIf<noexcept(TemplateArg())>::f(); // expected-error {{cannot be used by non-static data member initializer}}
+    int n = ExceptionIf<noexcept(TemplateArg())>::f(); // expected-error {{cannot default initialize non-static data member}}
+    // expected-note@-1 {{implicit default constructor for 'InClassInitializers::TemplateArg' first required here}}
   };
-  bool x = noexcept(TemplateArg());
 
   // And within a nested class.
-  struct Nested { // expected-error {{cannot be used by non-static data member initializer}}
+  struct Nested { // expected-note {{implicit default constructor for 'InClassInitializers::Nested::Inner' first required here}}
     struct Inner {
+      // expected-error@+1 {{cannot default initialize non-static data member}}
       int n = ExceptionIf<noexcept(Nested())>::f(); // expected-note {{implicit default constructor for 'InClassInitializers::Nested' first required here}}
     } inner;
   };
 
-  struct Nested2 {
+  struct Nested2 { // expected-error {{implicit default constructor for 'InClassInitializers::Nested2' must explicitly initialize the member 'inner' which does not have a default constructor}}
     struct Inner;
-    int n = Inner().n; // expected-error {{cannot be used by non-static data member initializer}}
-    struct Inner {
-      int n = ExceptionIf<noexcept(Nested2())>::f();
-    } inner;
+    int n = Inner().n; // expected-note {{implicit default constructor for 'InClassInitializers::Nested2::Inner' first required here}}
+    struct Inner { // expected-note {{declared here}}
+      int n = ExceptionIf<noexcept(Nested2())>::f(); // expected-error {{cannot default initialize non-static data member}}
+      // expected-note@-1 {{implicit default constructor for 'InClassInitializers::Nested2' first required here}}
+    } inner; // expected-note {{member is declared here}}
   };
 }
 
Index: test/SemaCXX/member-init.cpp
===================================================================
--- test/SemaCXX/member-init.cpp
+++ test/SemaCXX/member-init.cpp
@@ -14,7 +14,10 @@
 bool b();
 int k;
 struct Recurse {
-  int &n = b() ? Recurse().n : k; // expected-error {{defaulted default constructor of 'Recurse' cannot be used by non-static data member initializer which appears before end of class definition}}
+  int &n = // expected-error {{cannot default initialize non-static data member 'n' before the end of the outermost containing class 'Recurse'}}
+      b() ?
+      Recurse().n : // expected-note {{implicit default constructor for 'Recurse' first required here}}
+      k;
 };
 
 struct UnknownBound {
@@ -110,3 +113,35 @@
 
   struct Y { int b = f(); };
 }
+
+namespace template_valid {
+// Valid, we shouldn't build a CXXDefaultInitExpr until A's ctor definition.
+struct A {
+  A();
+  template <typename T>
+  struct B { int m1 = sizeof(A) + sizeof(T); };
+  B<int> m2;
+};
+A::A() {}
+}
+
+namespace template_default_ctor {
+struct A {
+  template <typename T>
+  struct B {
+    int m1 = 0; // expected-error {{cannot default initialize non-static data member 'm1' before the end of the outermost containing class 'A'}}
+  };
+  // expected-note@+1 {{implicit default constructor for 'template_default_ctor::A::B<int>' first required here}}
+  enum { NOE = noexcept(B<int>()) };
+};
+}
+
+namespace default_ctor {
+struct A {
+  struct B {
+    int m1 = 0; // expected-error {{cannot default initialize non-static data member 'm1' before the end of the outermost containing class 'A'}}
+  };
+  // expected-note@+1 {{implicit default constructor for 'default_ctor::A::B' first required here}}
+  enum { NOE = noexcept(B()) };
+};
+}
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to