C++ PATCH for c++/91416 - GC during late parsing collects live data

Message ID 20190811171206.GB14737@redhat.com
State New
Headers show
Series
  • C++ PATCH for c++/91416 - GC during late parsing collects live data
Related show

Commit Message

Marek Polacek Aug. 11, 2019, 5:12 p.m.
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.  We can't avoid late parsing of members in this context,
so my fix is to bump function_depth, exactly following cp_parser_lambda_body.
Since we are performing late parsing, we know we have to be nested in a class.
(We still ggc_collect later, in c_parse_final_cleanups.)


But here's the thing.  This goes back to ancient r104500, at least.  How has
this not broken before?  All you need to trigger it is to enable GC checking
and have a class with a ctor/member function, that has an attribute.  You'd
think that since we've got hundreds of those in the testsuite, at least one of
them would trigger this crash.  Or that there'd be several reports about this in
the BZ already.  Yet I didn't find any.  Truly, I'm perplexed.

Anyway, bootstrapped/regtested on x86_64-linux, ok for trunk?  How about 9.3?
I vote yes.

2019-08-11  Marek Polacek  <polacek@redhat.com>

	PR c++/91416 - GC during late parsing collects live data.
	* parser.c (cp_parser_late_parsing_for_member): Increment function_depth
	around call to cp_parser_function_definition_after_declarator.

	* g++.dg/other/gc6.C: New test.

Comments

Richard Biener Aug. 12, 2019, 8:37 a.m. | #1
On Sun, Aug 11, 2019 at 7:12 PM Marek Polacek <polacek@redhat.com> wrote:
>

> 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.  We can't avoid late parsing of members in this context,

> so my fix is to bump function_depth, exactly following cp_parser_lambda_body.

> Since we are performing late parsing, we know we have to be nested in a class.

> (We still ggc_collect later, in c_parse_final_cleanups.)


So the struct S itself is properly referenced by a GC root?  If so why not
attach the attribute list to the tentative struct instead?  Or do we
fear we have
other non-rooted data live at the point we collect?  If so shouldn't we instead
bump function_depth when parsing a declaration in general?

>

> But here's the thing.  This goes back to ancient r104500, at least.  How has

> this not broken before?  All you need to trigger it is to enable GC checking

> and have a class with a ctor/member function, that has an attribute.  You'd

> think that since we've got hundreds of those in the testsuite, at least one of

> them would trigger this crash.  Or that there'd be several reports about this in

> the BZ already.  Yet I didn't find any.  Truly, I'm perplexed.

>

> Anyway, bootstrapped/regtested on x86_64-linux, ok for trunk?  How about 9.3?

> I vote yes.

>

> 2019-08-11  Marek Polacek  <polacek@redhat.com>

>

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

>         * parser.c (cp_parser_late_parsing_for_member): Increment function_depth

>         around call to cp_parser_function_definition_after_declarator.

>

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

>

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

> index b56cc6924f4..0d4d32e9670 100644

> --- gcc/cp/parser.c

> +++ gcc/cp/parser.c

> @@ -28934,6 +28934,8 @@ cp_parser_late_parsing_for_member (cp_parser* parser, tree member_function)

>        function_scope = current_function_decl;

>        if (function_scope)

>         push_function_context ();

> +      else

> +       ++function_depth;

>

>        /* Push the body of the function onto the lexer stack.  */

>        cp_parser_push_lexer_for_tokens (parser, tokens);

> @@ -28966,6 +28968,9 @@ cp_parser_late_parsing_for_member (cp_parser* parser, tree member_function)

>        /* Leave the scope of the containing function.  */

>        if (function_scope)

>         pop_function_context ();

> +      else

> +       --function_depth;

> +

>        cp_parser_pop_lexer (parser);

>      }

>

> diff --git gcc/testsuite/g++.dg/other/gc6.C gcc/testsuite/g++.dg/other/gc6.C

> new file mode 100644

> index 00000000000..385be5c945e

> --- /dev/null

> +++ gcc/testsuite/g++.dg/other/gc6.C

> @@ -0,0 +1,7 @@

> +// 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;
Jason Merrill Aug. 13, 2019, 3:04 p.m. | #2
On 8/12/19 4:37 AM, Richard Biener wrote:
> On Sun, Aug 11, 2019 at 7:12 PM Marek Polacek <polacek@redhat.com> wrote:

>>

>> 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.  We can't avoid late parsing of members in this context,

>> so my fix is to bump function_depth, exactly following cp_parser_lambda_body.

>> Since we are performing late parsing, we know we have to be nested in a class.

>> (We still ggc_collect later, in c_parse_final_cleanups.)

> 

> So the struct S itself is properly referenced by a GC root?  If so why not

> attach the attribute list to the tentative struct instead?  Or do we

> fear we have

> other non-rooted data live at the point we collect?  If so shouldn't we instead

> bump function_depth when parsing a declaration in general?


It's already a significant issue for C++ that we only collect between 
function declarations, I'm concerned that this change will cause more 
memory problems.

Perhaps if we start parsing a class-specifier we could push the 
decl_specifiers onto a vec that is a GC root?

Jason

>> But here's the thing.  This goes back to ancient r104500, at least.  How has

>> this not broken before?  All you need to trigger it is to enable GC checking

>> and have a class with a ctor/member function, that has an attribute.


...and is defined in the declaration of something else?  Does it happen 
without declaring the variable 's'?

Jason

Patch

diff --git gcc/cp/parser.c gcc/cp/parser.c
index b56cc6924f4..0d4d32e9670 100644
--- gcc/cp/parser.c
+++ gcc/cp/parser.c
@@ -28934,6 +28934,8 @@  cp_parser_late_parsing_for_member (cp_parser* parser, tree member_function)
       function_scope = current_function_decl;
       if (function_scope)
 	push_function_context ();
+      else
+	++function_depth;
 
       /* Push the body of the function onto the lexer stack.  */
       cp_parser_push_lexer_for_tokens (parser, tokens);
@@ -28966,6 +28968,9 @@  cp_parser_late_parsing_for_member (cp_parser* parser, tree member_function)
       /* Leave the scope of the containing function.  */
       if (function_scope)
 	pop_function_context ();
+      else
+	--function_depth;
+
       cp_parser_pop_lexer (parser);
     }
 
diff --git gcc/testsuite/g++.dg/other/gc6.C gcc/testsuite/g++.dg/other/gc6.C
new file mode 100644
index 00000000000..385be5c945e
--- /dev/null
+++ gcc/testsuite/g++.dg/other/gc6.C
@@ -0,0 +1,7 @@ 
+// 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;