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 |
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 --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; +};