From 25fdd0d6df44044a8b505e6fcd07270e2e279b06 Mon Sep 17 00:00:00 2001 From: Marek Polacek Date: Mon, 1 Feb 2021 23:30:05 -0500 Subject: [PATCH] c++: ICE with late parsing of noexcept in nested class [PR98899] 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 struct B { B() noexcept(noexcept(x)); int x; }; struct A : B { A() : B() {} }; }; we call complete_type for B which triggers tsubsting S::B::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 | 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 struct B { B() noexcept(...); }; + struct A : B { ... use B() ... }; + }; + + where completing B 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 struct integral_constant { + static constexpr int value = __v; +}; + +struct S { + template struct B { + B() noexcept(noexcept(x)); + int x; + }; + struct A : B { + A() : B() {} + }; +}; + +struct S2 { + template struct B { + B() noexcept(integral_constant::value); + }; + struct A : B { + A() : B() {} + }; +}; + +struct S3 { + template struct B { + B() noexcept(b); + }; + struct A : B { + A() : B() {} + }; + static constexpr bool b = false; +}; -- 2.30.2