c++: GC during late parsing collects live data [PR91416]

Message ID 20210403015346.107092-1-polacek@redhat.com
State New
Headers show
Series
  • c++: GC during late parsing collects live data [PR91416]
Related show

Commit Message

Andrew MacLeod via Gcc-patches April 3, 2021, 1:53 a.m.
Coming back to
<https://gcc.gnu.org/pipermail/gcc-patches/2019-August/527699.html>:

This is a crash that points to a GC problem.  Consider this test:

  __attribute__ ((unused)) struct S {
    S() { }
  } s;

We're parsing a simple-declaration.  While parsing the decl specs, we parse
the attribute, which means creating a TREE_LIST using ggc_alloc_*.

A function body is a complete-class context so when parsing the
member-specification of this class-specifier, we parse the bodies of the
functions we'd queued in cp_parser_late_parsing_for_member.  This then
leads to this call chain:
cp_parser_function_definition_after_declarator -> expand_or_defer_fn ->
expand_or_defer_fn_1 -> maybe_clone_body -> expand_or_defer_fn ->
cgraph_node::finalize_function -> ggc_collect.

In this test, the ggc_collect call collects the TREE_LIST we had
allocated, and a crash duly ensues.

I couldn't do what Richard suggested, that is, attach the attribute list
to struct S, because we don't pass decl_specs from cp_parser_type_specifier
down to cp_parser_class_specifier.  Therefore I've attempted to do "push the
decl_specifiers onto a vec that is a GC root", except I couldn't really push
the decl_specifiers, because first I'd have to mark cp_decl_specifier_seq with
GTY(()) and even that wouldn't be enough for me to be able to create

  static GTY(()) vec<cp_decl_specifier_seq *, va_gc>

But here we only care about cp_decl_specifier_seq::attributes, so the
patch is just this.  I've also extended the test so now we test a nested
class too.

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

gcc/cp/ChangeLog:

	PR c++/91416
	* parser.c: Create a GC root for attributes in a decl specifier.
	(cp_parser_declaration): Free it.
	(cp_parser_type_specifier): Push ->attributes onto it.

gcc/testsuite/ChangeLog:

	PR c++/91416
	* g++.dg/other/gc7.C: New test.
---
 gcc/cp/parser.c                  |  7 +++++++
 gcc/testsuite/g++.dg/other/gc7.C | 16 ++++++++++++++++
 2 files changed, 23 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/other/gc7.C


base-commit: b7c1f3d66cfc171bc4e779068530101fb2f197f1
-- 
2.30.2

Comments

Andrew MacLeod via Gcc-patches April 3, 2021, 2:47 p.m. | #1
On 4/2/21 9:53 PM, Marek Polacek wrote:
> Coming back to

> <https://gcc.gnu.org/pipermail/gcc-patches/2019-August/527699.html>:

> 

> This is a crash that points to a GC problem.  Consider this test:

> 

>    __attribute__ ((unused)) struct S {

>      S() { }

>    } s;

> 

> We're parsing a simple-declaration.  While parsing the decl specs, we parse

> the attribute, which means creating a TREE_LIST using ggc_alloc_*.

> 

> A function body is a complete-class context so when parsing the

> member-specification of this class-specifier, we parse the bodies of the

> functions we'd queued in cp_parser_late_parsing_for_member.  This then

> leads to this call chain:

> cp_parser_function_definition_after_declarator -> expand_or_defer_fn ->

> expand_or_defer_fn_1 -> maybe_clone_body -> expand_or_defer_fn ->

> cgraph_node::finalize_function -> ggc_collect.

> 

> In this test, the ggc_collect call collects the TREE_LIST we had

> allocated, and a crash duly ensues.

> 

> I couldn't do what Richard suggested, that is, attach the attribute list

> to struct S, because we don't pass decl_specs from cp_parser_type_specifier

> down to cp_parser_class_specifier.  Therefore I've attempted to do "push the

> decl_specifiers onto a vec that is a GC root", except I couldn't really push

> the decl_specifiers, because first I'd have to mark cp_decl_specifier_seq with

> GTY(()) and even that wouldn't be enough for me to be able to create

> 

>    static GTY(()) vec<cp_decl_specifier_seq *, va_gc>

> 

> But here we only care about cp_decl_specifier_seq::attributes, so the

> patch is just this.  I've also extended the test so now we test a nested

> class too.

> 

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

> 

> gcc/cp/ChangeLog:

> 

> 	PR c++/91416

> 	* parser.c: Create a GC root for attributes in a decl specifier.

> 	(cp_parser_declaration): Free it.

> 	(cp_parser_type_specifier): Push ->attributes onto it.

> 

> gcc/testsuite/ChangeLog:

> 

> 	PR c++/91416

> 	* g++.dg/other/gc7.C: New test.

> ---

>   gcc/cp/parser.c                  |  7 +++++++

>   gcc/testsuite/g++.dg/other/gc7.C | 16 ++++++++++++++++

>   2 files changed, 23 insertions(+)

>   create mode 100644 gcc/testsuite/g++.dg/other/gc7.C

> 

> diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c

> index 808e5b61b1e..31da9f56b1b 100644

> --- a/gcc/cp/parser.c

> +++ b/gcc/cp/parser.c

> @@ -13957,6 +13957,11 @@ cp_parser_declaration_seq_opt (cp_parser* parser)

>       }

>   }

>   

> +/* Preserve the attributes across a garbage collect (by making it a GC

> +   root), which can occur when parsing a member function.  */

> +

> +static GTY(()) vec<tree, va_gc> *cp_parser_decl_specs_attrs;

> +

>   /* Parse a declaration.

>   

>      declaration:

> @@ -14140,6 +14145,7 @@ cp_parser_declaration (cp_parser* parser, tree prefix_attrs)

>   

>     /* Free any declarators allocated.  */

>     obstack_free (&declarator_obstack, p);

> +  vec_free (cp_parser_decl_specs_attrs);

>   }

>   

>   /* Parse a namespace-scope declaration.  */

> @@ -18438,6 +18444,7 @@ cp_parser_type_specifier (cp_parser* parser,

>         /* Parse tentatively so that we can back up if we don't find a

>   	 class-specifier.  */

>         cp_parser_parse_tentatively (parser);

> +      vec_safe_push (cp_parser_decl_specs_attrs, decl_specs->attributes);


This looks like any time we see 'class' in a function we'll add another 
entry to the vec, that accumulates until we finish the enclosing 
top-level declaration.  Let's pop the entry again after the call to 
cp_parser_class_specifier, and only push/pop if there are actually any 
attributes.

>         /* Look for the class-specifier.  */

>         type_spec = cp_parser_class_specifier (parser);

>         invoke_plugin_callbacks (PLUGIN_FINISH_TYPE, type_spec);

> diff --git a/gcc/testsuite/g++.dg/other/gc7.C b/gcc/testsuite/g++.dg/other/gc7.C

> new file mode 100644

> index 00000000000..ab436bac72f

> --- /dev/null

> +++ b/gcc/testsuite/g++.dg/other/gc7.C

> @@ -0,0 +1,16 @@

> +// PR c++/91416 - GC during late parsing collects live data.

> +// { dg-do compile }

> +// { dg-options "--param ggc-min-heapsize=0 --param ggc-min-expand=0" }

> +

> +__attribute__ ((unused)) struct S {

> +  S() { }

> +} s;

> +

> +__attribute__ ((unused)) struct X {

> +  void fn ()

> +  {

> +    __attribute__ ((unused)) struct N {

> +	N() { }

> +    } n;

> +  }

> +} x;

> 

> base-commit: b7c1f3d66cfc171bc4e779068530101fb2f197f1

>
Andrew MacLeod via Gcc-patches April 3, 2021, 9:35 p.m. | #2
On Sat, Apr 03, 2021 at 10:47:13AM -0400, Jason Merrill wrote:
> On 4/2/21 9:53 PM, Marek Polacek wrote:

> > Coming back to

> > <https://gcc.gnu.org/pipermail/gcc-patches/2019-August/527699.html>:

> > 

> > This is a crash that points to a GC problem.  Consider this test:

> > 

> >    __attribute__ ((unused)) struct S {

> >      S() { }

> >    } s;

> > 

> > We're parsing a simple-declaration.  While parsing the decl specs, we parse

> > the attribute, which means creating a TREE_LIST using ggc_alloc_*.

> > 

> > A function body is a complete-class context so when parsing the

> > member-specification of this class-specifier, we parse the bodies of the

> > functions we'd queued in cp_parser_late_parsing_for_member.  This then

> > leads to this call chain:

> > cp_parser_function_definition_after_declarator -> expand_or_defer_fn ->

> > expand_or_defer_fn_1 -> maybe_clone_body -> expand_or_defer_fn ->

> > cgraph_node::finalize_function -> ggc_collect.

> > 

> > In this test, the ggc_collect call collects the TREE_LIST we had

> > allocated, and a crash duly ensues.

> > 

> > I couldn't do what Richard suggested, that is, attach the attribute list

> > to struct S, because we don't pass decl_specs from cp_parser_type_specifier

> > down to cp_parser_class_specifier.  Therefore I've attempted to do "push the

> > decl_specifiers onto a vec that is a GC root", except I couldn't really push

> > the decl_specifiers, because first I'd have to mark cp_decl_specifier_seq with

> > GTY(()) and even that wouldn't be enough for me to be able to create

> > 

> >    static GTY(()) vec<cp_decl_specifier_seq *, va_gc>

> > 

> > But here we only care about cp_decl_specifier_seq::attributes, so the

> > patch is just this.  I've also extended the test so now we test a nested

> > class too.

> > 

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

> > 

> > gcc/cp/ChangeLog:

> > 

> > 	PR c++/91416

> > 	* parser.c: Create a GC root for attributes in a decl specifier.

> > 	(cp_parser_declaration): Free it.

> > 	(cp_parser_type_specifier): Push ->attributes onto it.

> > 

> > gcc/testsuite/ChangeLog:

> > 

> > 	PR c++/91416

> > 	* g++.dg/other/gc7.C: New test.

> > ---

> >   gcc/cp/parser.c                  |  7 +++++++

> >   gcc/testsuite/g++.dg/other/gc7.C | 16 ++++++++++++++++

> >   2 files changed, 23 insertions(+)

> >   create mode 100644 gcc/testsuite/g++.dg/other/gc7.C

> > 

> > diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c

> > index 808e5b61b1e..31da9f56b1b 100644

> > --- a/gcc/cp/parser.c

> > +++ b/gcc/cp/parser.c

> > @@ -13957,6 +13957,11 @@ cp_parser_declaration_seq_opt (cp_parser* parser)

> >       }

> >   }

> > +/* Preserve the attributes across a garbage collect (by making it a GC

> > +   root), which can occur when parsing a member function.  */

> > +

> > +static GTY(()) vec<tree, va_gc> *cp_parser_decl_specs_attrs;

> > +

> >   /* Parse a declaration.

> >      declaration:

> > @@ -14140,6 +14145,7 @@ cp_parser_declaration (cp_parser* parser, tree prefix_attrs)

> >     /* Free any declarators allocated.  */

> >     obstack_free (&declarator_obstack, p);

> > +  vec_free (cp_parser_decl_specs_attrs);

> >   }

> >   /* Parse a namespace-scope declaration.  */

> > @@ -18438,6 +18444,7 @@ cp_parser_type_specifier (cp_parser* parser,

> >         /* Parse tentatively so that we can back up if we don't find a

> >   	 class-specifier.  */

> >         cp_parser_parse_tentatively (parser);

> > +      vec_safe_push (cp_parser_decl_specs_attrs, decl_specs->attributes);

> 

> This looks like any time we see 'class' in a function we'll add another

> entry to the vec, that accumulates until we finish the enclosing top-level

> declaration.  Let's pop the entry again after the call to

> cp_parser_class_specifier, and only push/pop if there are actually any

> attributes.


Ah, yeah, push/pop works here too, thanks.

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

-- >8 --
Coming back to
<https://gcc.gnu.org/pipermail/gcc-patches/2019-August/527699.html>:

This is a crash that points to a GC problem.  Consider this test:

  __attribute__ ((unused)) struct S {
    S() { }
  } s;

We're parsing a simple-declaration.  While parsing the decl specs, we parse
the attribute, which means creating a TREE_LIST using ggc_alloc_*.

A function body is a complete-class context so when parsing the
member-specification of this class-specifier, we parse the bodies of the
functions we'd queued in cp_parser_late_parsing_for_member.  This then
leads to this call chain:
cp_parser_function_definition_after_declarator -> expand_or_defer_fn ->
expand_or_defer_fn_1 -> maybe_clone_body -> expand_or_defer_fn ->
cgraph_node::finalize_function -> ggc_collect.

In this test, the ggc_collect call collects the TREE_LIST we had
allocated, and a crash duly ensues.

I couldn't do what Richard suggested, that is, attach the attribute list
to struct S, because we don't pass decl_specs from cp_parser_type_specifier
down to cp_parser_class_specifier.  Therefore I've attempted to do "push the
decl_specifiers onto a vec that is a GC root", except I couldn't really push
the decl_specifiers, because first I'd have to mark cp_decl_specifier_seq with
GTY(()) and even that wouldn't be enough for me to be able to create

  static GTY(()) vec<cp_decl_specifier_seq *, va_gc>

But here we only care about cp_decl_specifier_seq::attributes, so the
patch is just this.  I've also extended the test so now we test a nested
class too.

gcc/cp/ChangeLog:

	PR c++/91416
	* parser.c: Create a GC root for attributes in a decl specifier.
	(cp_parser_type_specifier): Push/pop ->attributes onto/from it.

gcc/testsuite/ChangeLog:

	PR c++/91416
	* g++.dg/other/gc7.C: New test.
---
 gcc/cp/parser.c                  |  9 +++++++++
 gcc/testsuite/g++.dg/other/gc7.C | 16 ++++++++++++++++
 2 files changed, 25 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/other/gc7.C

diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 808e5b61b1e..c915d6415de 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -18346,6 +18346,11 @@ cp_parser_explicit_specialization (cp_parser* parser)
   --parser->num_template_parameter_lists;
 }
 
+/* Preserve the attributes across a garbage collect (by making it a GC
+   root), which can occur when parsing a member function.  */
+
+static GTY(()) vec<tree, va_gc> *cp_parser_decl_specs_attrs;
+
 /* Parse a type-specifier.
 
    type-specifier:
@@ -18438,8 +18443,12 @@ cp_parser_type_specifier (cp_parser* parser,
       /* Parse tentatively so that we can back up if we don't find a
 	 class-specifier.  */
       cp_parser_parse_tentatively (parser);
+      if (decl_specs->attributes)
+	vec_safe_push (cp_parser_decl_specs_attrs, decl_specs->attributes);
       /* Look for the class-specifier.  */
       type_spec = cp_parser_class_specifier (parser);
+      if (decl_specs->attributes)
+	cp_parser_decl_specs_attrs->pop ();
       invoke_plugin_callbacks (PLUGIN_FINISH_TYPE, type_spec);
       /* If that worked, we're done.  */
       if (cp_parser_parse_definitely (parser))
diff --git a/gcc/testsuite/g++.dg/other/gc7.C b/gcc/testsuite/g++.dg/other/gc7.C
new file mode 100644
index 00000000000..ab436bac72f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/other/gc7.C
@@ -0,0 +1,16 @@
+// PR c++/91416 - GC during late parsing collects live data.
+// { dg-do compile }
+// { dg-options "--param ggc-min-heapsize=0 --param ggc-min-expand=0" }
+
+__attribute__ ((unused)) struct S {
+  S() { }
+} s;
+
+__attribute__ ((unused)) struct X {
+  void fn ()
+  {
+    __attribute__ ((unused)) struct N {
+	N() { }
+    } n;
+  }
+} x;

base-commit: fc27115d6107f219e6f3dc610c99210005fe9dc5
-- 
2.30.2
Andrew MacLeod via Gcc-patches April 3, 2021, 9:55 p.m. | #3
On 4/3/21 5:35 PM, Marek Polacek wrote:
> On Sat, Apr 03, 2021 at 10:47:13AM -0400, Jason Merrill wrote:

>> On 4/2/21 9:53 PM, Marek Polacek wrote:

>>> Coming back to

>>> <https://gcc.gnu.org/pipermail/gcc-patches/2019-August/527699.html>:

>>>

>>> This is a crash that points to a GC problem.  Consider this test:

>>>

>>>     __attribute__ ((unused)) struct S {

>>>       S() { }

>>>     } s;

>>>

>>> We're parsing a simple-declaration.  While parsing the decl specs, we parse

>>> the attribute, which means creating a TREE_LIST using ggc_alloc_*.

>>>

>>> A function body is a complete-class context so when parsing the

>>> member-specification of this class-specifier, we parse the bodies of the

>>> functions we'd queued in cp_parser_late_parsing_for_member.  This then

>>> leads to this call chain:

>>> cp_parser_function_definition_after_declarator -> expand_or_defer_fn ->

>>> expand_or_defer_fn_1 -> maybe_clone_body -> expand_or_defer_fn ->

>>> cgraph_node::finalize_function -> ggc_collect.

>>>

>>> In this test, the ggc_collect call collects the TREE_LIST we had

>>> allocated, and a crash duly ensues.

>>>

>>> I couldn't do what Richard suggested, that is, attach the attribute list

>>> to struct S, because we don't pass decl_specs from cp_parser_type_specifier

>>> down to cp_parser_class_specifier.  Therefore I've attempted to do "push the

>>> decl_specifiers onto a vec that is a GC root", except I couldn't really push

>>> the decl_specifiers, because first I'd have to mark cp_decl_specifier_seq with

>>> GTY(()) and even that wouldn't be enough for me to be able to create

>>>

>>>     static GTY(()) vec<cp_decl_specifier_seq *, va_gc>

>>>

>>> But here we only care about cp_decl_specifier_seq::attributes, so the

>>> patch is just this.  I've also extended the test so now we test a nested

>>> class too.

>>>

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

>>>

>>> gcc/cp/ChangeLog:

>>>

>>> 	PR c++/91416

>>> 	* parser.c: Create a GC root for attributes in a decl specifier.

>>> 	(cp_parser_declaration): Free it.

>>> 	(cp_parser_type_specifier): Push ->attributes onto it.

>>>

>>> gcc/testsuite/ChangeLog:

>>>

>>> 	PR c++/91416

>>> 	* g++.dg/other/gc7.C: New test.

>>> ---

>>>    gcc/cp/parser.c                  |  7 +++++++

>>>    gcc/testsuite/g++.dg/other/gc7.C | 16 ++++++++++++++++

>>>    2 files changed, 23 insertions(+)

>>>    create mode 100644 gcc/testsuite/g++.dg/other/gc7.C

>>>

>>> diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c

>>> index 808e5b61b1e..31da9f56b1b 100644

>>> --- a/gcc/cp/parser.c

>>> +++ b/gcc/cp/parser.c

>>> @@ -13957,6 +13957,11 @@ cp_parser_declaration_seq_opt (cp_parser* parser)

>>>        }

>>>    }

>>> +/* Preserve the attributes across a garbage collect (by making it a GC

>>> +   root), which can occur when parsing a member function.  */

>>> +

>>> +static GTY(()) vec<tree, va_gc> *cp_parser_decl_specs_attrs;

>>> +

>>>    /* Parse a declaration.

>>>       declaration:

>>> @@ -14140,6 +14145,7 @@ cp_parser_declaration (cp_parser* parser, tree prefix_attrs)

>>>      /* Free any declarators allocated.  */

>>>      obstack_free (&declarator_obstack, p);

>>> +  vec_free (cp_parser_decl_specs_attrs);

>>>    }

>>>    /* Parse a namespace-scope declaration.  */

>>> @@ -18438,6 +18444,7 @@ cp_parser_type_specifier (cp_parser* parser,

>>>          /* Parse tentatively so that we can back up if we don't find a

>>>    	 class-specifier.  */

>>>          cp_parser_parse_tentatively (parser);

>>> +      vec_safe_push (cp_parser_decl_specs_attrs, decl_specs->attributes);

>>

>> This looks like any time we see 'class' in a function we'll add another

>> entry to the vec, that accumulates until we finish the enclosing top-level

>> declaration.  Let's pop the entry again after the call to

>> cp_parser_class_specifier, and only push/pop if there are actually any

>> attributes.

> 

> Ah, yeah, push/pop works here too, thanks.

> 

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


OK for trunk and 10 after 10.3.

> -- >8 --

> Coming back to

> <https://gcc.gnu.org/pipermail/gcc-patches/2019-August/527699.html>:

> 

> This is a crash that points to a GC problem.  Consider this test:

> 

>    __attribute__ ((unused)) struct S {

>      S() { }

>    } s;

> 

> We're parsing a simple-declaration.  While parsing the decl specs, we parse

> the attribute, which means creating a TREE_LIST using ggc_alloc_*.

> 

> A function body is a complete-class context so when parsing the

> member-specification of this class-specifier, we parse the bodies of the

> functions we'd queued in cp_parser_late_parsing_for_member.  This then

> leads to this call chain:

> cp_parser_function_definition_after_declarator -> expand_or_defer_fn ->

> expand_or_defer_fn_1 -> maybe_clone_body -> expand_or_defer_fn ->

> cgraph_node::finalize_function -> ggc_collect.

> 

> In this test, the ggc_collect call collects the TREE_LIST we had

> allocated, and a crash duly ensues.

> 

> I couldn't do what Richard suggested, that is, attach the attribute list

> to struct S, because we don't pass decl_specs from cp_parser_type_specifier

> down to cp_parser_class_specifier.  Therefore I've attempted to do "push the

> decl_specifiers onto a vec that is a GC root", except I couldn't really push

> the decl_specifiers, because first I'd have to mark cp_decl_specifier_seq with

> GTY(()) and even that wouldn't be enough for me to be able to create

> 

>    static GTY(()) vec<cp_decl_specifier_seq *, va_gc>

> 

> But here we only care about cp_decl_specifier_seq::attributes, so the

> patch is just this.  I've also extended the test so now we test a nested

> class too.

> 

> gcc/cp/ChangeLog:

> 

> 	PR c++/91416

> 	* parser.c: Create a GC root for attributes in a decl specifier.

> 	(cp_parser_type_specifier): Push/pop ->attributes onto/from it.

> 

> gcc/testsuite/ChangeLog:

> 

> 	PR c++/91416

> 	* g++.dg/other/gc7.C: New test.

> ---

>   gcc/cp/parser.c                  |  9 +++++++++

>   gcc/testsuite/g++.dg/other/gc7.C | 16 ++++++++++++++++

>   2 files changed, 25 insertions(+)

>   create mode 100644 gcc/testsuite/g++.dg/other/gc7.C

> 

> diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c

> index 808e5b61b1e..c915d6415de 100644

> --- a/gcc/cp/parser.c

> +++ b/gcc/cp/parser.c

> @@ -18346,6 +18346,11 @@ cp_parser_explicit_specialization (cp_parser* parser)

>     --parser->num_template_parameter_lists;

>   }

>   

> +/* Preserve the attributes across a garbage collect (by making it a GC

> +   root), which can occur when parsing a member function.  */

> +

> +static GTY(()) vec<tree, va_gc> *cp_parser_decl_specs_attrs;

> +

>   /* Parse a type-specifier.

>   

>      type-specifier:

> @@ -18438,8 +18443,12 @@ cp_parser_type_specifier (cp_parser* parser,

>         /* Parse tentatively so that we can back up if we don't find a

>   	 class-specifier.  */

>         cp_parser_parse_tentatively (parser);

> +      if (decl_specs->attributes)

> +	vec_safe_push (cp_parser_decl_specs_attrs, decl_specs->attributes);

>         /* Look for the class-specifier.  */

>         type_spec = cp_parser_class_specifier (parser);

> +      if (decl_specs->attributes)

> +	cp_parser_decl_specs_attrs->pop ();

>         invoke_plugin_callbacks (PLUGIN_FINISH_TYPE, type_spec);

>         /* If that worked, we're done.  */

>         if (cp_parser_parse_definitely (parser))

> diff --git a/gcc/testsuite/g++.dg/other/gc7.C b/gcc/testsuite/g++.dg/other/gc7.C

> new file mode 100644

> index 00000000000..ab436bac72f

> --- /dev/null

> +++ b/gcc/testsuite/g++.dg/other/gc7.C

> @@ -0,0 +1,16 @@

> +// PR c++/91416 - GC during late parsing collects live data.

> +// { dg-do compile }

> +// { dg-options "--param ggc-min-heapsize=0 --param ggc-min-expand=0" }

> +

> +__attribute__ ((unused)) struct S {

> +  S() { }

> +} s;

> +

> +__attribute__ ((unused)) struct X {

> +  void fn ()

> +  {

> +    __attribute__ ((unused)) struct N {

> +	N() { }

> +    } n;

> +  }

> +} x;

> 

> base-commit: fc27115d6107f219e6f3dc610c99210005fe9dc5

>

Patch

diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 808e5b61b1e..31da9f56b1b 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -13957,6 +13957,11 @@  cp_parser_declaration_seq_opt (cp_parser* parser)
     }
 }
 
+/* Preserve the attributes across a garbage collect (by making it a GC
+   root), which can occur when parsing a member function.  */
+
+static GTY(()) vec<tree, va_gc> *cp_parser_decl_specs_attrs;
+
 /* Parse a declaration.
 
    declaration:
@@ -14140,6 +14145,7 @@  cp_parser_declaration (cp_parser* parser, tree prefix_attrs)
 
   /* Free any declarators allocated.  */
   obstack_free (&declarator_obstack, p);
+  vec_free (cp_parser_decl_specs_attrs);
 }
 
 /* Parse a namespace-scope declaration.  */
@@ -18438,6 +18444,7 @@  cp_parser_type_specifier (cp_parser* parser,
       /* Parse tentatively so that we can back up if we don't find a
 	 class-specifier.  */
       cp_parser_parse_tentatively (parser);
+      vec_safe_push (cp_parser_decl_specs_attrs, decl_specs->attributes);
       /* Look for the class-specifier.  */
       type_spec = cp_parser_class_specifier (parser);
       invoke_plugin_callbacks (PLUGIN_FINISH_TYPE, type_spec);
diff --git a/gcc/testsuite/g++.dg/other/gc7.C b/gcc/testsuite/g++.dg/other/gc7.C
new file mode 100644
index 00000000000..ab436bac72f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/other/gc7.C
@@ -0,0 +1,16 @@ 
+// PR c++/91416 - GC during late parsing collects live data.
+// { dg-do compile }
+// { dg-options "--param ggc-min-heapsize=0 --param ggc-min-expand=0" }
+
+__attribute__ ((unused)) struct S {
+  S() { }
+} s;
+
+__attribute__ ((unused)) struct X {
+  void fn ()
+  {
+    __attribute__ ((unused)) struct N {
+	N() { }
+    } n;
+  }
+} x;