c++: ICE with late parsing of noexcept in nested class [PR98899]
authorMarek Polacek <polacek@redhat.com>
Tue, 2 Feb 2021 04:30:05 +0000 (23:30 -0500)
committerMarek Polacek <polacek@redhat.com>
Wed, 3 Feb 2021 14:44:18 +0000 (09:44 -0500)
Here we crash with a noexcept-specifier in a nested template class,
because my handling of such deferred-parse noexcept-specifiers was
gronked when we need to instantiate a DEFERRED_PARSE before it was
actually parsed at the end of the outermost class.

In

  struct S {
    template<class> struct B {
      B() noexcept(noexcept(x));
      int x;
    };
    struct A : B<int> {
      A() : B() {}
    };
  };

we call complete_type for B<int> which triggers tsubsting S::B<int>::B()
whose noexcept-specifier still contains a DEFERRED_PARSE.  The trick is
to stash such noexcept-specifiers into DEFPARSE_INSTANTIATIONS so that
we can replace it later when we've finally parsed all deferred
noexcept-specifiers.

In passing, fix missing usage of UNPARSED_NOEXCEPT_SPEC_P.

gcc/cp/ChangeLog:

PR c++/98899
* parser.c (cp_parser_class_specifier_1): Use any possible
DEFPARSE_INSTANTIATIONS to update DEFERRED_NOEXCEPT_PATTERN.
(cp_parser_save_noexcept): Initialize DEFPARSE_INSTANTIATIONS.
* pt.c (tsubst_exception_specification): Stash new_specs into
DEFPARSE_INSTANTIATIONS.
* tree.c (fixup_deferred_exception_variants): Use
UNPARSED_NOEXCEPT_SPEC_P.

gcc/testsuite/ChangeLog:

PR c++/98899
* g++.dg/cpp0x/noexcept65.C: New test.

gcc/cp/parser.c
gcc/cp/pt.c
gcc/cp/tree.c
gcc/testsuite/g++.dg/cpp0x/noexcept65.C [new file with mode: 0644]

index abadaf9..5da8670 100644 (file)
@@ -25026,8 +25026,8 @@ cp_parser_class_specifier_1 (cp_parser* parser)
              pushed_scope = push_scope (class_type);
            }
 
-         tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl));
-         spec = TREE_PURPOSE (spec);
+         tree def_parse = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl));
+         def_parse = TREE_PURPOSE (def_parse);
 
          /* Make sure that any template parameters are in scope.  */
          maybe_begin_member_template_processing (decl);
@@ -25044,7 +25044,7 @@ cp_parser_class_specifier_1 (cp_parser* parser)
            parser->local_variables_forbidden_p |= THIS_FORBIDDEN;
 
          /* Now we can parse the noexcept-specifier.  */
-         spec = cp_parser_late_noexcept_specifier (parser, spec);
+         tree spec = cp_parser_late_noexcept_specifier (parser, def_parse);
 
          if (spec == error_mark_node)
            spec = NULL_TREE;
@@ -25052,6 +25052,12 @@ cp_parser_class_specifier_1 (cp_parser* parser)
          /* Update the fn's type directly -- it might have escaped
             beyond this decl :(  */
          fixup_deferred_exception_variants (TREE_TYPE (decl), spec);
+         /* Update any instantiations we've already created.  We must
+            keep the new noexcept-specifier wrapped in a DEFERRED_NOEXCEPT
+            so that maybe_instantiate_noexcept can tsubst the NOEXCEPT_EXPR
+            in the pattern.  */
+         for (tree i : DEFPARSE_INSTANTIATIONS (def_parse))
+           DEFERRED_NOEXCEPT_PATTERN (TREE_PURPOSE (i)) = TREE_PURPOSE (spec);
 
          /* Restore the state of local_variables_forbidden_p.  */
          parser->local_variables_forbidden_p = local_variables_forbidden_p;
@@ -26695,6 +26701,7 @@ cp_parser_save_noexcept (cp_parser *parser)
   /* Save away the noexcept-specifier; we will process it when the
      class is complete.  */
   DEFPARSE_TOKENS (expr) = cp_token_cache_new (first, last);
+  DEFPARSE_INSTANTIATIONS (expr) = nullptr;
   expr = build_tree_list (expr, NULL_TREE);
   return expr;
 }
index aa1687a..4781519 100644 (file)
@@ -15189,6 +15189,22 @@ tsubst_exception_specification (tree fntype,
             /*integral_constant_expression_p=*/true);
        }
       new_specs = build_noexcept_spec (new_specs, complain);
+      /* We've instantiated a template before a noexcept-specifier
+        contained therein has been parsed.  This can happen for
+        a nested template class:
+
+         struct S {
+           template<typename> struct B { B() noexcept(...); };
+           struct A : B<int> { ... use B() ... };
+         };
+
+        where completing B<int> will trigger instantiating the
+        noexcept, even though we only parse it at the end of S.  */
+      if (UNPARSED_NOEXCEPT_SPEC_P (specs))
+       {
+         gcc_checking_assert (defer_ok);
+         vec_safe_push (DEFPARSE_INSTANTIATIONS (expr), new_specs);
+       }
     }
   else if (specs)
     {
index 2e5a1f1..e6ced27 100644 (file)
@@ -2738,8 +2738,7 @@ fixup_deferred_exception_variants (tree type, tree raises)
   tree original = TYPE_RAISES_EXCEPTIONS (type);
   tree cr = flag_noexcept_type ? canonical_eh_spec (raises) : NULL_TREE;
 
-  gcc_checking_assert (TREE_CODE (TREE_PURPOSE (original))
-                      == DEFERRED_PARSE);
+  gcc_checking_assert (UNPARSED_NOEXCEPT_SPEC_P (original));
 
   /* Though sucky, this walk will process the canonical variants
      first.  */
diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept65.C b/gcc/testsuite/g++.dg/cpp0x/noexcept65.C
new file mode 100644 (file)
index 0000000..f593377
--- /dev/null
@@ -0,0 +1,35 @@
+// PR c++/98899
+// { dg-do compile { target c++11 } }
+
+template <int __v> struct integral_constant {
+  static constexpr int value = __v;
+};
+
+struct S {
+  template<class> struct B {
+    B() noexcept(noexcept(x));
+    int x;
+  };
+  struct A : B<int> {
+    A() : B() {}
+  };
+};
+
+struct S2 {
+  template<class> struct B {
+    B() noexcept(integral_constant<false>::value);
+  };
+  struct A : B<int> {
+    A() : B() {}
+  };
+};
+
+struct S3 {
+  template<class> struct B {
+    B() noexcept(b);
+  };
+  struct A : B<int> {
+    A() : B() {}
+  };
+  static constexpr bool b = false;
+};