diff mbox series

c++: ICE with late parsing of noexcept in nested class [PR98899]

Message ID 20210202220902.347739-1-polacek@redhat.com
State New
Headers show
Series c++: ICE with late parsing of noexcept in nested class [PR98899] | expand

Commit Message

Marek Polacek Feb. 2, 2021, 10:09 p.m. UTC
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.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/10?

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                         | 13 ++++++---
 gcc/cp/pt.c                             | 16 +++++++++++
 gcc/cp/tree.c                           |  3 +--
 gcc/testsuite/g++.dg/cpp0x/noexcept65.C | 35 +++++++++++++++++++++++++
 4 files changed, 62 insertions(+), 5 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept65.C


base-commit: 8e4a738d2540ab6aff77506d368bf4e3fa6963bd

Comments

Jason Merrill Feb. 3, 2021, 1:46 p.m. UTC | #1
On 2/2/21 5:09 PM, Marek Polacek wrote:
> 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.
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/10?

OK.

> 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                         | 13 ++++++---
>   gcc/cp/pt.c                             | 16 +++++++++++
>   gcc/cp/tree.c                           |  3 +--
>   gcc/testsuite/g++.dg/cpp0x/noexcept65.C | 35 +++++++++++++++++++++++++
>   4 files changed, 62 insertions(+), 5 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept65.C
> 
> diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
> index abadaf972d6..5da8670f0e2 100644
> --- a/gcc/cp/parser.c
> +++ b/gcc/cp/parser.c
> @@ -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;
>   }
> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> index aa1687a9f2a..4781519d00f 100644
> --- a/gcc/cp/pt.c
> +++ b/gcc/cp/pt.c
> @@ -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)
>       {
> diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c
> index 2e5a1f198e8..e6ced274959 100644
> --- a/gcc/cp/tree.c
> +++ b/gcc/cp/tree.c
> @@ -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
> index 00000000000..f59337777de
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/noexcept65.C
> @@ -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;
> +};
> 
> base-commit: 8e4a738d2540ab6aff77506d368bf4e3fa6963bd
>
diff mbox series

Patch

diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index abadaf972d6..5da8670f0e2 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -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;
 }
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index aa1687a9f2a..4781519d00f 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -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)
     {
diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c
index 2e5a1f198e8..e6ced274959 100644
--- a/gcc/cp/tree.c
+++ b/gcc/cp/tree.c
@@ -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
index 00000000000..f59337777de
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/noexcept65.C
@@ -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;
+};