Message ID | 20181219202731.GL21364@redhat.com |
---|---|
State | New |
Headers | show |
Series | C++ PATCH to implement deferred parsing of noexcept-specifiers (c++/86476, c++/52869) | expand |
Ping. On Wed, Dec 19, 2018 at 03:27:31PM -0500, Marek Polacek wrote: > Prompted by Jon's observation in 52869, I noticed that we don't treat > a noexcept-specifier as a complete-class context of a class ([class.mem]/6). > As with member function bodies, default arguments, and NSDMIs, names used in > a noexcept-specifier of a member-function can be declared later in the class > body, so we need to wait and parse them at the end of the class. > For that, I've made use of DEFAULT_ARG (now best to be renamed to UNPARSED_ARG). > > This wasn't as easy as I'd anticipated, because I needed to make sure to > * handle well accessing function parameters in the noexcept-specifier, > hence the maybe_{begin,end}_member_function_processing business, > * not regress diagnostic. See e.g. noexcept38.C for detecting "looser > throw specifier", or noexcept39.C, friend decls and redeclaration. > This is handled by functions like noexcept_override_late_checks and > check_redeclaration_exception_specification. I hope that's it. > > Compiling libstdc++ was a fairly good stress test, and I've added a bunch > of reduced testcases I've collected along the way. > > I also noticed we're not properly detecting using 'this' in static member > functions; tracked in 88548. > > Bootstrapped/regtested on x86_64-linux, ok for trunk? > > 2018-12-19 Marek Polacek <polacek@redhat.com> > > PR c++/86476 - noexcept-specifier is a complete-class context. > PR c++/52869 > * cp-tree.def (DEFAULT_ARG): Update commentary. > * cp-tree.h (UNPARSED_NOEXCEPT_SPEC_P): New macro. > (check_redeclaration_exception_specification): Declare. > (maybe_check_throw_specifier): Declare. > * decl.c (check_redeclaration_exception_specification): No longer > static. Handle UNPARSED_NOEXCEPT_SPEC_P. > * except.c (nothrow_spec_p): Accept DEFAULT_ARG in assert. > * parser.c (cp_parser_noexcept_specification_opt, > cp_parser_late_noexcept_specifier, noexcept_override_late_checks): > Forward-declare. > (unparsed_noexcepts): New macro. > (push_unparsed_function_queues): Update initializer. > (cp_parser_init_declarator): Maybe save the noexcept-specifier to > post process. > (maybe_begin_member_function_processing): New. > (maybe_end_member_function_processing): New. > (cp_parser_class_specifier_1): Implement delayed parsing of > noexcept-specifiers. > (cp_parser_member_declaration): Maybe save the noexcept-specifier to > post process. > (cp_parser_save_noexcept): New. > (cp_parser_late_noexcept_specifier): New. > (noexcept_override_late_checks): New. > (cp_parser_noexcept_specification_opt): Call cp_parser_save_noexcept > instead of the normal processing if needed. > (cp_parser_save_member_function_body): Maybe save the > noexcept-specifier to post process. > * parser.h (cp_unparsed_functions_entry): Add new field to carry > a noexcept-specifier. > * pt.c (dependent_type_p_r): Handle unparsed noexcept expression. > * search.c (maybe_check_throw_specifier): New function, broken out > of... > (check_final_overrider): ...here. Call maybe_check_throw_specifier. > * tree.c (canonical_eh_spec): Handle UNPARSED_NOEXCEPT_SPEC_P. > (cp_tree_equal): Handle DEFAULT_ARG. > * typeck2.c (merge_exception_specifiers): If an unparsed noexcept > expression has been passed, return it instead of merging it. > > * g++.dg/cpp0x/noexcept34.C: New test. > * g++.dg/cpp0x/noexcept35.C: New test. > * g++.dg/cpp0x/noexcept36.C: New test. > * g++.dg/cpp0x/noexcept37.C: New test. > * g++.dg/cpp0x/noexcept38.C: New test. > * g++.dg/cpp0x/noexcept39.C: New test. > > diff --git gcc/cp/cp-tree.def gcc/cp/cp-tree.def > index 43d90eb1efb..aa8b752d8f4 100644 > --- gcc/cp/cp-tree.def > +++ gcc/cp/cp-tree.def > @@ -209,7 +209,9 @@ DEFTREECODE (USING_STMT, "using_stmt", tcc_statement, 1) > > /* An un-parsed default argument. Holds a vector of input tokens and > a vector of places where the argument was instantiated before > - parsing had occurred. */ > + parsing had occurred. This is also used for delayed NSDMIs and > + noexcept-specifier parsing. For a noexcept-specifier, the vector > + holds a function declaration used for late checking. */ > DEFTREECODE (DEFAULT_ARG, "default_arg", tcc_exceptional, 0) > > /* An uninstantiated/unevaluated noexcept-specification. For the > diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h > index 1d806b782bd..bd3cd200fcb 100644 > --- gcc/cp/cp-tree.h > +++ gcc/cp/cp-tree.h > @@ -1193,6 +1193,9 @@ struct GTY (()) tree_default_arg { > #define UNEVALUATED_NOEXCEPT_SPEC_P(NODE) \ > (DEFERRED_NOEXCEPT_SPEC_P (NODE) \ > && DEFERRED_NOEXCEPT_PATTERN (TREE_PURPOSE (NODE)) == NULL_TREE) > +#define UNPARSED_NOEXCEPT_SPEC_P(NODE) \ > + ((NODE) && (TREE_PURPOSE (NODE)) \ > + && (TREE_CODE (TREE_PURPOSE (NODE)) == DEFAULT_ARG)) > > struct GTY (()) tree_deferred_noexcept { > struct tree_base base; > @@ -6430,6 +6433,8 @@ extern bool check_array_designated_initializer (constructor_elt *, > unsigned HOST_WIDE_INT); > extern bool check_for_uninitialized_const_var (tree, bool, tsubst_flags_t); > extern tree build_explicit_specifier (tree, tsubst_flags_t); > +extern void check_redeclaration_exception_specification > + (tree, tree); > > /* in decl2.c */ > extern void record_mangling (tree, bool); > @@ -6894,6 +6899,7 @@ extern tree copied_binfo (tree, tree); > extern tree original_binfo (tree, tree); > extern int shared_member_p (tree); > extern bool any_dependent_bases_p (tree = current_nonlambda_class_type ()); > +extern bool maybe_check_throw_specifier (tree, tree); > > /* The representation of a deferred access check. */ > > diff --git gcc/cp/decl.c gcc/cp/decl.c > index d6028e3608c..74444db8623 100644 > --- gcc/cp/decl.c > +++ gcc/cp/decl.c > @@ -1140,7 +1140,7 @@ warn_extern_redeclared_static (tree newdecl, tree olddecl) > function templates. If their exception specifications do not > match, issue a diagnostic. */ > > -static void > +void > check_redeclaration_exception_specification (tree new_decl, > tree old_decl) > { > @@ -1152,6 +1152,16 @@ check_redeclaration_exception_specification (tree new_decl, > && UNEVALUATED_NOEXCEPT_SPEC_P (old_exceptions)) > return; > > + /* We can't compare unparsed noexcept-specifiers. Save the old decl > + and check this again after we've parsed the noexcept-specifiers > + for real. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > + { > + vec_safe_push (DEFARG_INSTANTIATIONS (TREE_PURPOSE (new_exceptions)), > + copy_decl (old_decl)); > + return; > + } > + > if (!type_dependent_expression_p (old_decl)) > { > maybe_instantiate_noexcept (new_decl); > diff --git gcc/cp/except.c gcc/cp/except.c > index b04eb0c5332..92fc39d3968 100644 > --- gcc/cp/except.c > +++ gcc/cp/except.c > @@ -1245,6 +1245,7 @@ nothrow_spec_p (const_tree spec) > || TREE_VALUE (spec) > || spec == noexcept_false_spec > || TREE_PURPOSE (spec) == error_mark_node > + || TREE_CODE (TREE_PURPOSE (spec)) == DEFAULT_ARG > || processing_template_decl); > > return false; > diff --git gcc/cp/parser.c gcc/cp/parser.c > index b57e35d04c5..267b3518156 100644 > --- gcc/cp/parser.c > +++ gcc/cp/parser.c > @@ -247,6 +247,12 @@ static void cp_lexer_stop_debugging > > static cp_token_cache *cp_token_cache_new > (cp_token *, cp_token *); > +static tree cp_parser_noexcept_specification_opt > + (cp_parser *, bool, bool *, bool); > +static tree cp_parser_late_noexcept_specifier > + (cp_parser *, tree); > +static void noexcept_override_late_checks > + (tree, tree); > > static void cp_parser_initial_pragma > (cp_token *); > @@ -1968,11 +1974,14 @@ cp_parser_context_new (cp_parser_context* next) > parser->unparsed_queues->last ().nsdmis > #define unparsed_classes \ > parser->unparsed_queues->last ().classes > +#define unparsed_noexcepts \ > + parser->unparsed_queues->last ().noexcepts > > static void > push_unparsed_function_queues (cp_parser *parser) > { > - cp_unparsed_functions_entry e = {NULL, make_tree_vector (), NULL, NULL}; > + cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL, > + NULL }; > vec_safe_push (parser->unparsed_queues, e); > } > > @@ -20315,7 +20324,13 @@ cp_parser_init_declarator (cp_parser* parser, > /*asmspec=*/NULL_TREE, > attr_chainon (attributes, prefix_attributes)); > if (decl && TREE_CODE (decl) == FUNCTION_DECL) > - cp_parser_save_default_args (parser, decl); > + { > + cp_parser_save_default_args (parser, decl); > + /* Remember if there is a noexcept-specifier to post process. */ > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > + vec_safe_push (unparsed_noexcepts, decl); > + } > cp_finalize_omp_declare_simd (parser, decl); > cp_finalize_oacc_routine (parser, decl, false); > } > @@ -23058,6 +23073,45 @@ cp_parser_class_name (cp_parser *parser, > return decl; > } > > +/* Make sure that any member-function parameters are in scope. > + For instance, a function's noexcept-specifier can use the function's > + parameters: > + > + struct S { > + void fn (int p) noexcept(noexcept(p)); > + }; > + > + so we need to make sure name lookup can find them. This is used > + when we delay parsing of the noexcept-specifier. */ > + > +static void > +maybe_begin_member_function_processing (tree decl) > +{ > + begin_scope (sk_function_parms, decl); > + tree args = DECL_ARGUMENTS (decl); > + args = nreverse (args); > + > + tree next; > + for (tree parm = args; parm; parm = next) > + { > + next = DECL_CHAIN (parm); > + if (TREE_CODE (parm) == PARM_DECL) > + pushdecl (parm); > + } > + /* Get the decls in their original chain order and record in the > + function. This is all and only the PARM_DECLs that were > + pushed into scope by the loop above. */ > + DECL_ARGUMENTS (decl) = get_local_decls (); > +} > + > +/* Undo the effects of maybe_begin_member_function_processing. */ > + > +static void > +maybe_end_member_function_processing (void) > +{ > + pop_bindings_and_leave_scope (); > +} > + > /* Parse a class-specifier. > > class-specifier: > @@ -23368,6 +23422,56 @@ cp_parser_class_specifier_1 (cp_parser* parser) > vec_safe_truncate (unparsed_classes, 0); > after_nsdmi_defaulted_late_checks (type); > > + /* If there are noexcept-specifiers that have not yet been processed, > + take care of them now. */ > + class_type = NULL_TREE; > + pushed_scope = NULL_TREE; > + FOR_EACH_VEC_SAFE_ELT (unparsed_noexcepts, ix, decl) > + { > + if (class_type != DECL_CONTEXT (decl)) > + { > + if (pushed_scope) > + pop_scope (pushed_scope); > + class_type = DECL_CONTEXT (decl); > + pushed_scope = push_scope (class_type); > + } > + > + /* Make sure that any template parameters are in scope. */ > + maybe_begin_member_template_processing (decl); > + > + /* Make sure that any member-function parameters are in scope. */ > + maybe_begin_member_function_processing (decl); > + > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > + spec = TREE_PURPOSE (spec); > + vec<tree, va_gc> *p = DEFARG_INSTANTIATIONS (spec); > + tree old_decl = (p ? (*p)[0] : NULL_TREE); > + > + /* Now we can parse the noexcept-specifier. */ > + spec = cp_parser_late_noexcept_specifier (parser, spec); > + if (spec != error_mark_node) > + TREE_TYPE (decl) = build_exception_variant (TREE_TYPE (decl), spec); > + > + /* If we've stashed an old declaration, it means we need to > + perform late redeclaration checking. */ > + if (old_decl) > + check_redeclaration_exception_specification (decl, old_decl); > + > + /* The finish_struct call above performed various override checking, > + but it skipped unparsed noexcept-specifier operands. Now that we > + have resolved them, check again. */ > + noexcept_override_late_checks (type, decl); > + > + /* Remove any member-function parameters from the symbol table. */ > + maybe_end_member_function_processing (); > + > + /* Remove any template parameters from the symbol table. */ > + maybe_end_member_template_processing (); > + } > + vec_safe_truncate (unparsed_noexcepts, 0); > + if (pushed_scope) > + pop_scope (pushed_scope); > + > /* Now parse the body of the functions. */ > if (flag_openmp) > { > @@ -24537,6 +24641,12 @@ cp_parser_member_declaration (cp_parser* parser) > else > decl = finish_fully_implicit_template (parser, decl); > } > + if (decl && TREE_CODE (decl) == FUNCTION_DECL) > + { > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > + vec_safe_push (unparsed_noexcepts, decl); > + } > } > > cp_finalize_omp_declare_simd (parser, decl); > @@ -24919,6 +25029,89 @@ cp_parser_base_specifier (cp_parser* parser) > > /* Exception handling [gram.exception] */ > > +/* Save the tokens that make up the noexcept-specifier for a member-function. > + Returns a DEFAULT_ARG. */ > + > +static tree > +cp_parser_save_noexcept (cp_parser *parser) > +{ > + cp_token *first = parser->lexer->next_token; > + /* We want everything up to, including, the final ')'. */ > + cp_parser_cache_group (parser, CPP_CLOSE_PAREN, /*depth=*/0); > + cp_token *last = parser->lexer->next_token; > + > + /* As with default arguments and NSDMIs, make us of DEFAULT_ARG > + to carry the information we will need. */ > + tree expr = make_node (DEFAULT_ARG); > + /* Save away the noexcept-specifier; we will process it when the > + class is complete. */ > + DEFARG_TOKENS (expr) = cp_token_cache_new (first, last); > + DEFARG_INSTANTIATIONS (expr) = NULL; > + expr = build_tree_list (expr, NULL_TREE); > + return expr; > +} > + > +/* Used for late processing of noexcept-specifiers of member-functions. > + DEFAULT_ARG is the unparsed operand of a noexcept-specifier which > + we saved for later; parse it now. */ > + > +static tree > +cp_parser_late_noexcept_specifier (cp_parser *parser, tree default_arg) > +{ > + /* Make sure we've gotten something that hasn't been parsed yet. */ > + gcc_assert (TREE_CODE (default_arg) == DEFAULT_ARG); > + > + push_unparsed_function_queues (parser); > + > + /* Push the saved tokens for the noexcept-specifier onto the parser's > + lexer stack. */ > + cp_token_cache *tokens = DEFARG_TOKENS (default_arg); > + cp_parser_push_lexer_for_tokens (parser, tokens); > + > + /* Parse the cached noexcept-specifier. */ > + tree parsed_arg > + = cp_parser_noexcept_specification_opt (parser, > + /*require_constexpr=*/true, > + NULL, > + /*return_cond=*/false); > + > + /* Revert to the main lexer. */ > + cp_parser_pop_lexer (parser); > + > + /* Restore the queue. */ > + pop_unparsed_function_queues (parser); > + > + /* And we're done. */ > + return parsed_arg; > +} > + > +/* Perform late checking of overriding function with respect to their > + noexcept-specifiers. TYPE is the class and FNDECL is the function > + that potentially overrides some virtual function with the same > + signature. */ > + > +static void > +noexcept_override_late_checks (tree type, tree fndecl) > +{ > + tree binfo = TYPE_BINFO (type); > + tree base_binfo; > + > + if (DECL_STATIC_FUNCTION_P (fndecl)) > + return; > + > + for (int i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i) > + { > + tree basetype = BINFO_TYPE (base_binfo); > + > + if (!TYPE_POLYMORPHIC_P (basetype)) > + continue; > + > + tree fn = look_for_overrides_here (basetype, fndecl); > + if (fn) > + maybe_check_throw_specifier (fndecl, fn); > + } > +} > + > /* Parse an (optional) noexcept-specification. > > noexcept-specification: > @@ -24947,6 +25140,18 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, > if (cp_parser_is_keyword (token, RID_NOEXCEPT)) > { > tree expr; > + > + /* [class.mem]/6 says that a noexcept-specifer (within the > + member-specification of the class) is a complete-class context of > + a class. So, if the noexcept-specifier has the optional expression, > + just save the tokens, and reparse this after we're done with the > + class. */ > + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_OPEN_PAREN > + && at_class_scope_p () > + && TYPE_BEING_DEFINED (current_class_type) > + && !LAMBDA_TYPE_P (current_class_type)) > + return cp_parser_save_noexcept (parser); > + > cp_lexer_consume_token (parser->lexer); > > if (cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) > @@ -28120,7 +28325,12 @@ cp_parser_save_member_function_body (cp_parser* parser, > return error_mark_node; > } > > - /* Remember it, if there default args to post process. */ > + /* Remember if there is a noexcept-specifier to post process. */ > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fn)); > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > + vec_safe_push (unparsed_noexcepts, fn); > + > + /* Remember it, if there are default args to post process. */ > cp_parser_save_default_args (parser, fn); > > /* Save away the tokens that make up the body of the > diff --git gcc/cp/parser.h gcc/cp/parser.h > index 8bfa3f3b9c4..df0c6c1960e 100644 > --- gcc/cp/parser.h > +++ gcc/cp/parser.h > @@ -166,6 +166,9 @@ struct GTY(()) cp_unparsed_functions_entry { > /* Nested classes go in this vector, so that we can do some final > processing after parsing any NSDMIs. */ > vec<tree, va_gc> *classes; > + > + /* Functions with noexcept-specifiers that require post-processing. */ > + vec<tree, va_gc> *noexcepts; > }; > > > diff --git gcc/cp/pt.c gcc/cp/pt.c > index e99de71ea9e..382a14f7d6a 100644 > --- gcc/cp/pt.c > +++ gcc/cp/pt.c > @@ -24988,8 +24988,9 @@ dependent_type_p_r (tree type) > if (tree noex = TREE_PURPOSE (spec)) > /* Treat DEFERRED_NOEXCEPT as non-dependent, since it doesn't > affect overload resolution and treating it as dependent breaks > - things. */ > + things. Same for an unparsed noexcept expression. */ > if (TREE_CODE (noex) != DEFERRED_NOEXCEPT > + && TREE_CODE (noex) != DEFAULT_ARG > && value_dependent_expression_p (noex)) > return true; > return false; > diff --git gcc/cp/search.c gcc/cp/search.c > index d700fe328f4..3e6494dd98f 100644 > --- gcc/cp/search.c > +++ gcc/cp/search.c > @@ -1850,6 +1850,38 @@ locate_field_accessor (tree basetype_path, tree field_decl, bool const_p) > NULL, &lfd); > } > > +/* Check throw specifier of OVERRIDER is at least as strict as > + the one of BASEFN. */ > + > +bool > +maybe_check_throw_specifier (tree overrider, tree basefn) > +{ > + maybe_instantiate_noexcept (basefn); > + maybe_instantiate_noexcept (overrider); > + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > + > + if (DECL_INVALID_OVERRIDER_P (overrider)) > + return true; > + > + /* Can't check this yet. Pretend this is fine and let > + noexcept_override_late_checks check this later. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) > + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) > + return true; > + > + if (!comp_except_specs (base_throw, over_throw, ce_derived)) > + { > + auto_diagnostic_group d; > + error ("looser throw specifier for %q+#F", overrider); > + inform (DECL_SOURCE_LOCATION (basefn), > + "overridden function is %q#F", basefn); > + DECL_INVALID_OVERRIDER_P (overrider) = 1; > + return false; > + } > + return true; > +} > + > /* Check that virtual overrider OVERRIDER is acceptable for base function > BASEFN. Issue diagnostic, and return zero, if unacceptable. */ > > @@ -1860,7 +1892,6 @@ check_final_overrider (tree overrider, tree basefn) > tree base_type = TREE_TYPE (basefn); > tree over_return = fndecl_declared_return_type (overrider); > tree base_return = fndecl_declared_return_type (basefn); > - tree over_throw, base_throw; > > int fail = 0; > > @@ -1944,21 +1975,8 @@ check_final_overrider (tree overrider, tree basefn) > return 0; > } > > - /* Check throw specifier is at least as strict. */ > - maybe_instantiate_noexcept (basefn); > - maybe_instantiate_noexcept (overrider); > - base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > - over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > - > - if (!comp_except_specs (base_throw, over_throw, ce_derived)) > - { > - auto_diagnostic_group d; > - error ("looser throw specifier for %q+#F", overrider); > - inform (DECL_SOURCE_LOCATION (basefn), > - "overridden function is %q#F", basefn); > - DECL_INVALID_OVERRIDER_P (overrider) = 1; > - return 0; > - } > + if (!maybe_check_throw_specifier (overrider, basefn)) > + return 0; > > /* Check for conflicting type attributes. But leave transaction_safe for > set_one_vmethod_tm_attributes. */ > diff --git gcc/cp/tree.c gcc/cp/tree.c > index 97074dfab56..a7d1e58c73a 100644 > --- gcc/cp/tree.c > +++ gcc/cp/tree.c > @@ -2542,6 +2542,7 @@ canonical_eh_spec (tree raises) > if (raises == NULL_TREE) > return raises; > else if (DEFERRED_NOEXCEPT_SPEC_P (raises) > + || UNPARSED_NOEXCEPT_SPEC_P (raises) > || uses_template_parms (raises) > || uses_template_parms (TREE_PURPOSE (raises))) > /* Keep a dependent or deferred exception specification. */ > @@ -3653,6 +3654,7 @@ cp_tree_equal (tree t1, tree t2) > case TEMPLATE_DECL: > case IDENTIFIER_NODE: > case SSA_NAME: > + case DEFAULT_ARG: > return false; > > case BASELINK: > diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c > index 64e36efd17e..6fec77d8269 100644 > --- gcc/cp/typeck2.c > +++ gcc/cp/typeck2.c > @@ -2302,6 +2302,12 @@ merge_exception_specifiers (tree list, tree add) > { > tree noex, orig_list; > > + /* We don't want to lose the unparsed operand lest we miss diagnostics. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (list)) > + return list; > + else if (UNPARSED_NOEXCEPT_SPEC_P (add)) > + return add; > + > /* No exception-specifier or noexcept(false) are less strict than > anything else. Prefer the newer variant (LIST). */ > if (!list || list == noexcept_false_spec) > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept34.C gcc/testsuite/g++.dg/cpp0x/noexcept34.C > new file mode 100644 > index 00000000000..43b38c2446f > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept34.C > @@ -0,0 +1,147 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +#define SA(X) static_assert(X, #X) > + > +struct S { > + void f1() noexcept(noexcept(i)) { } > + void f2() noexcept(noexcept(fn())) { } > + void f3() noexcept(noexcept(fnx())) { } > + void f4() noexcept(noexcept(i)); > + void f5() noexcept(noexcept(fn())); > + void f6() noexcept(noexcept(fnx())); > + > + void f7() noexcept(1); > + void f8() noexcept(0); > + void f9() noexcept(b); > + void f10() noexcept(!b); > + > + int i; > + static constexpr auto b = true; > + void fny() noexcept(noexcept(fn())); > + void fn(); > + void fnx() noexcept; > +}; > + > +S s; > +SA(noexcept(s.f1())); > +SA(!noexcept(s.f2())); > +SA(noexcept(s.f3())); > +SA(noexcept(s.f4())); > +SA(!noexcept(s.f5())); > +SA(noexcept(s.f6())); > +SA(noexcept(s.f7())); > +SA(!noexcept(s.f8())); > +SA(noexcept(s.f9())); > +SA(!noexcept(s.f10())); > + > +struct S2 { > + struct V { > + void f1() noexcept(noexcept(fn())); > + void f2() noexcept(noexcept(fnx())); > + void f3() noexcept(noexcept(fn())) { } > + void f4() noexcept(noexcept(fnx())) { } > + void fn(); > + void fnx() noexcept; > + } v; > + void fn(); > + void fnx(); > +}; > + > +S2 s2; > +SA(!noexcept(s2.v.f1())); > +SA(noexcept(s2.v.f2())); > +SA(!noexcept(s2.v.f3())); > +SA(noexcept(s2.v.f4())); > + > +struct S3 { > + void f1() noexcept(noexcept(fn())); > + void f2() noexcept(noexcept(fnx())); > + void fn(); > + void fnx() noexcept; > +}; > + > +void > +S3::f1() noexcept(noexcept(fn())) > +{ > +} > + > +void > +S3::f2() noexcept(noexcept(fnx())) > +{ > +} > + > +struct S4 { > + int f1 (int p) noexcept(noexcept(p)) { return p; } > + int f2 (int p) noexcept(noexcept(p)); > + int f3 (int p = 10) noexcept(noexcept(p)); > + int f4 () noexcept(noexcept(S4{})); > +}; > + > +S4 s4; > +SA(noexcept(s4.f1(1))); > +SA(noexcept(s4.f2(1))); > +SA(noexcept(s4.f3())); > +SA(noexcept(s4.f4())); > + > +template<typename T> > +struct S5 { > + void f1() noexcept(noexcept(i)) { } > + void f2() noexcept(noexcept(fn())) { } > + void f3() noexcept(noexcept(fnx())) { } > + void f4() noexcept(noexcept(i)); > + void f5() noexcept(noexcept(fn())); > + void f6() noexcept(noexcept(fnx())); > + > + int i; > + void fny() noexcept(noexcept(fn())); > + void fn(); > + void fnx() noexcept; > +}; > + > +S5<int> s5; > +SA(noexcept(s5.f1())); > +SA(!noexcept(s5.f2())); > +SA(noexcept(s5.f3())); > +SA(noexcept(s5.f4())); > +SA(!noexcept(s5.f5())); > +SA(noexcept(s5.f6())); > + > +template<typename T> > +struct S6 { > + void f1() noexcept(noexcept(x)); > + T x; > +}; > + > +struct S7 { > + template<typename U> > + void f1 () noexcept(noexcept(U(1))) { } > + > + template<int N> > + void f2() noexcept(noexcept(N)); > + > + template <typename _Up> > + void f3(_Up __p) noexcept(noexcept(__p)); > +}; > + > +void glob(); > +void globx() noexcept; > +struct S8 { > + void f1 () noexcept(noexcept(glob())); > + void f2 () noexcept(noexcept(globx())); > +}; > + > +S8 s8; > +SA(!noexcept(s8.f1())); > +SA(noexcept(s8.f2())); > + > +struct W { > + constexpr operator bool(); > +}; > + > +template<typename T> > +struct S9 { > + S9() noexcept(noexcept(w)) { } > + S9 &operator=(S9 &&) noexcept(T::X); > + W w; > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept35.C gcc/testsuite/g++.dg/cpp0x/noexcept35.C > new file mode 100644 > index 00000000000..b3859de9ebc > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept35.C > @@ -0,0 +1,26 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +struct S { > + void f1() noexcept(noexcept(fn())); > + void f2() noexcept(noexcept(fnx())); > + void fn(); > + void fnx() noexcept; > +}; > + > +void > +S::f1() noexcept // { dg-error "different exception specifier" } > +{ > +} > + > +void > +S::f2() // { dg-error "different exception specifier" } > +{ > +} > + > +struct S2 { > + void f1() noexcept(noexcept(nosuchfn())); // { dg-error "not declared in this scope" } > + void f2() noexcept(noexcept(nothere)); // { dg-error "not declared in this scope" } > + void f3() noexcept(noexcept(nosuchfn())) { } // { dg-error "not declared in this scope" } > + void f4() noexcept(noexcept(nothere)) { } // { dg-error "not declared in this scope" } > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept36.C gcc/testsuite/g++.dg/cpp0x/noexcept36.C > new file mode 100644 > index 00000000000..12c6d364099 > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept36.C > @@ -0,0 +1,9 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +template <typename _Alloc> class A { > + typedef _Alloc _Alloc_traits; > + A &operator=(A &&) noexcept(_Alloc_traits::_S_nothrow_move); > + A &m_fn1(A &&) noexcept(_Alloc_traits::_S_nothrow_move); > + void m_fn2(A<char>) {} > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept37.C gcc/testsuite/g++.dg/cpp0x/noexcept37.C > new file mode 100644 > index 00000000000..a81032f28e9 > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept37.C > @@ -0,0 +1,14 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +void fn1(void()); > +template <typename> class A { > + void _M_local_data(); > + A() noexcept(_M_local_data); > +}; > + > +class B { > + void _S_initialize(); > + static void _S_initialize_once(); > +}; > +void B::_S_initialize() { fn1(_S_initialize_once); } > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept38.C gcc/testsuite/g++.dg/cpp0x/noexcept38.C > new file mode 100644 > index 00000000000..9e5545bc022 > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept38.C > @@ -0,0 +1,23 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +struct A > +{ > + virtual void f(); > + virtual void g() noexcept; > + virtual void h() noexcept(false); > +}; > + > +struct B : A > +{ > + void f() noexcept(true); > + void g() noexcept(true); > + void h() noexcept(true); > +}; > + > +struct D : A > +{ > + void f() noexcept(false); > + void g() noexcept(false); // { dg-error "looser throw specifier" } > + void h() noexcept(false); > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept39.C gcc/testsuite/g++.dg/cpp0x/noexcept39.C > new file mode 100644 > index 00000000000..da7490d651c > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept39.C > @@ -0,0 +1,28 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +void f() noexcept(false); > +void g() noexcept(true); > +void h() noexcept; > + > +struct B { > + friend void f() noexcept(false); > + friend void g() noexcept(false); // { dg-error "different exception specifier" } > + friend void h() noexcept(false); // { dg-error "different exception specifier" } > +}; > + > +struct C { > + friend void f() noexcept(true); // { dg-error "different exception specifier" } > + friend void g() noexcept(true); // { dg-error "different exception specifier" } > + friend void h() noexcept(true); // { dg-error "different exception specifier" } > +}; > + > +void o() noexcept(false); > +void p() noexcept(true); > +void q() noexcept; > + > +struct D { > + friend void o() noexcept(true); // { dg-error "different exception specifier" } > + friend void p() noexcept(true); > + friend void q() noexcept(true); > +}; Marek
On 12/19/18 3:27 PM, Marek Polacek wrote: > Prompted by Jon's observation in 52869, I noticed that we don't treat > a noexcept-specifier as a complete-class context of a class ([class.mem]/6). > As with member function bodies, default arguments, and NSDMIs, names used in > a noexcept-specifier of a member-function can be declared later in the class > body, so we need to wait and parse them at the end of the class. > For that, I've made use of DEFAULT_ARG (now best to be renamed to UNPARSED_ARG). Or DEFERRED_PARSE, yes. > + /* We can't compare unparsed noexcept-specifiers. Save the old decl > + and check this again after we've parsed the noexcept-specifiers > + for real. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > + { > + vec_safe_push (DEFARG_INSTANTIATIONS (TREE_PURPOSE (new_exceptions)), > + copy_decl (old_decl)); > + return; > + } Why copy_decl? It seems wasteful to allocate a vec to hold this single decl; let's make the last field of tree_default_arg a union instead. And add a new macro for the single decl case. I notice that default_arg currently uses tree_common for some reason, and we ought to be able to save two words by switching to tree_base > @@ -1245,6 +1245,7 @@ nothrow_spec_p (const_tree spec) > || TREE_VALUE (spec) > || spec == noexcept_false_spec > || TREE_PURPOSE (spec) == error_mark_node > + || TREE_CODE (TREE_PURPOSE (spec)) == DEFAULT_ARG Maybe use UNPARSED_NOEXCEPT_SPEC_P here? > +/* Make sure that any member-function parameters are in scope. > + For instance, a function's noexcept-specifier can use the function's > + parameters: > + > + struct S { > + void fn (int p) noexcept(noexcept(p)); > + }; > + > + so we need to make sure name lookup can find them. This is used > + when we delay parsing of the noexcept-specifier. */ > + > +static void > +maybe_begin_member_function_processing (tree decl) This name is pretty misleading. How about inject_parm_decls, to go with inject_this_parameter? > +/* Undo the effects of maybe_begin_member_function_processing. */ > + > +static void > +maybe_end_member_function_processing (void) And then perhaps pop_injected_parms. > +/* Check throw specifier of OVERRIDER is at least as strict as > + the one of BASEFN. */ > + > +bool > +maybe_check_throw_specifier (tree overrider, tree basefn) > +{ > + maybe_instantiate_noexcept (basefn); > + maybe_instantiate_noexcept (overrider); > + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > + > + if (DECL_INVALID_OVERRIDER_P (overrider)) > + return true; > + > + /* Can't check this yet. Pretend this is fine and let > + noexcept_override_late_checks check this later. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) > + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) > + return true; > + > + if (!comp_except_specs (base_throw, over_throw, ce_derived)) > + { > + auto_diagnostic_group d; > + error ("looser throw specifier for %q+#F", overrider); Since we're touching this diagnostic, let's correct it now to "exception specification". And add "on overriding virtual function". Jason
Coming back to this. I didn't think this was suitable for GCC 9. On Mon, Jan 07, 2019 at 10:44:37AM -0500, Jason Merrill wrote: > On 12/19/18 3:27 PM, Marek Polacek wrote: > > Prompted by Jon's observation in 52869, I noticed that we don't treat > > a noexcept-specifier as a complete-class context of a class ([class.mem]/6). > > As with member function bodies, default arguments, and NSDMIs, names used in > > a noexcept-specifier of a member-function can be declared later in the class > > body, so we need to wait and parse them at the end of the class. > > For that, I've made use of DEFAULT_ARG (now best to be renamed to UNPARSED_ARG). > > Or DEFERRED_PARSE, yes. I didn't change the name but I'm happy to do it as a follow up. > > + /* We can't compare unparsed noexcept-specifiers. Save the old decl > > + and check this again after we've parsed the noexcept-specifiers > > + for real. */ > > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > > + { > > + vec_safe_push (DEFARG_INSTANTIATIONS (TREE_PURPOSE (new_exceptions)), > > + copy_decl (old_decl)); > > + return; > > + } > > Why copy_decl? This is so that we don't lose the diagnostic in noexcept46.C. If I don't use copy_decl then the tree is shared and subsequent changes to it make us not detect discrepancies like noexcept(false) vs. noexcept(true) on the same decl. > It seems wasteful to allocate a vec to hold this single decl; let's make the > last field of tree_default_arg a union instead. And add a new macro for the > single decl case. Done. But that required also adding GTY markers *and* a new BOOL_BITFIELD for the sake of GTY((desc)). > I notice that default_arg currently uses tree_common for some reason, and we > ought to be able to save two words by switching to tree_base Done. > > @@ -1245,6 +1245,7 @@ nothrow_spec_p (const_tree spec) > > || TREE_VALUE (spec) > > || spec == noexcept_false_spec > > || TREE_PURPOSE (spec) == error_mark_node > > + || TREE_CODE (TREE_PURPOSE (spec)) == DEFAULT_ARG > > Maybe use UNPARSED_NOEXCEPT_SPEC_P here? Done. > > +/* Make sure that any member-function parameters are in scope. > > + For instance, a function's noexcept-specifier can use the function's > > + parameters: > > + > > + struct S { > > + void fn (int p) noexcept(noexcept(p)); > > + }; > > + > > + so we need to make sure name lookup can find them. This is used > > + when we delay parsing of the noexcept-specifier. */ > > + > > +static void > > +maybe_begin_member_function_processing (tree decl) > > This name is pretty misleading. How about inject_parm_decls, to go with > inject_this_parameter? Done. > > +/* Undo the effects of maybe_begin_member_function_processing. */ > > + > > +static void > > +maybe_end_member_function_processing (void) > > And then perhaps pop_injected_parms. Done. > > +/* Check throw specifier of OVERRIDER is at least as strict as > > + the one of BASEFN. */ > > + > > +bool > > +maybe_check_throw_specifier (tree overrider, tree basefn) > > +{ > > + maybe_instantiate_noexcept (basefn); > > + maybe_instantiate_noexcept (overrider); > > + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > > + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > > + > > + if (DECL_INVALID_OVERRIDER_P (overrider)) > > + return true; > > + > > + /* Can't check this yet. Pretend this is fine and let > > + noexcept_override_late_checks check this later. */ > > + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) > > + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) > > + return true; > > + > > + if (!comp_except_specs (base_throw, over_throw, ce_derived)) > > + { > > + auto_diagnostic_group d; > > + error ("looser throw specifier for %q+#F", overrider); > > Since we're touching this diagnostic, let's correct it now to "exception > specification". And add "on overriding virtual function". Ok, changed to the more up-to-date term. Two further changes were required since my changes to detecting 'this' for static member functions: 1) use THIS_FORBIDDEN if needed when parsing the delayed noexcept, 2) careful about friend member functions -- its DECL_CONTEXT is not the containing class, need to use DECL_FRIEND_CONTEXT. Both of these points are tested in g++.dg/cpp0x/this1.C. Bootstrapped/regtested on x86_64-linux, ok for trunk? 2019-05-10 Marek Polacek <polacek@redhat.com> PR c++/86476 - noexcept-specifier is a complete-class context. PR c++/52869 * cp-tree.def (DEFAULT_ARG): Update commentary. * cp-tree.h (DEFARG_DECL, DEFARG_NOEXCEPT_P, UNPARSED_NOEXCEPT_SPEC_P): New macros. (tree_default_arg): Add a tree field, make the last two fields into a union. Add GTY markers. (check_redeclaration_exception_specification): Declare. (maybe_check_throw_specifier): Declare. * decl.c (check_redeclaration_exception_specification): No longer static. Handle UNPARSED_NOEXCEPT_SPEC_P. * except.c (nothrow_spec_p): Accept DEFAULT_ARG in the assert. * parser.c (cp_parser_noexcept_specification_opt, cp_parser_late_noexcept_specifier, noexcept_override_late_checks): Forward-declare. (unparsed_noexcepts): New macro. (push_unparsed_function_queues): Update initializer. (cp_parser_init_declarator): Maybe save the noexcept-specifier to post process. (inject_parm_decls): New. (pop_injected_parms): New. (cp_parser_class_specifier_1): Implement delayed parsing of noexcept-specifiers. (cp_parser_member_declaration): Maybe save the noexcept-specifier to post process. (cp_parser_save_noexcept): New. (cp_parser_late_noexcept_specifier): New. (noexcept_override_late_checks): New. (cp_parser_noexcept_specification_opt): Call cp_parser_save_noexcept instead of the normal processing if needed. (cp_parser_save_member_function_body): Maybe save the noexcept-specifier to post process. * parser.h (cp_unparsed_functions_entry): Add new field to carry a noexcept-specifier. * pt.c (dependent_type_p_r): Handle unparsed noexcept expression. * search.c (maybe_check_throw_specifier): New function, broken out of... (check_final_overrider): ...here. Call maybe_check_throw_specifier. * tree.c (canonical_eh_spec): Handle UNPARSED_NOEXCEPT_SPEC_P. (cp_tree_equal): Handle DEFAULT_ARG. * typeck2.c (merge_exception_specifiers): If an unparsed noexcept expression has been passed, return it instead of merging it. * g++.dg/cpp0x/noexcept41.C: New test. * g++.dg/cpp0x/noexcept42.C: New test. * g++.dg/cpp0x/noexcept43.C: New test. * g++.dg/cpp0x/noexcept44.C: New test. * g++.dg/cpp0x/noexcept45.C: New test. * g++.dg/cpp0x/noexcept46.C: New test. * g++.dg/eh/shadow1.C: Adjust dg-error. diff --git gcc/cp/cp-tree.def gcc/cp/cp-tree.def index 03c105b5c4c..33eb5d25efe 100644 --- gcc/cp/cp-tree.def +++ gcc/cp/cp-tree.def @@ -209,7 +209,9 @@ DEFTREECODE (USING_STMT, "using_stmt", tcc_statement, 1) /* An un-parsed default argument. Holds a vector of input tokens and a vector of places where the argument was instantiated before - parsing had occurred. */ + parsing had occurred. This is also used for delayed NSDMIs and + noexcept-specifier parsing. For a noexcept-specifier, we use a tree + holding a function declaration used for late checking. */ DEFTREECODE (DEFAULT_ARG, "default_arg", tcc_exceptional, 0) /* An uninstantiated/unevaluated noexcept-specification. For the diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h index f253857b02a..ef14a011293 100644 --- gcc/cp/cp-tree.h +++ gcc/cp/cp-tree.h @@ -1178,12 +1178,20 @@ enum cp_identifier_kind { #define DEFARG_TOKENS(NODE) \ (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->tokens) #define DEFARG_INSTANTIATIONS(NODE) \ - (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->instantiations) + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->u.instantiations) +#define DEFARG_DECL(NODE) \ + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->u.decl) +#define DEFARG_NOEXCEPT_P(NODE) \ + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->noexcept_p) struct GTY (()) tree_default_arg { - struct tree_common common; + struct tree_base base; struct cp_token_cache *tokens; - vec<tree, va_gc> *instantiations; + BOOL_BITFIELD noexcept_p : 1; + union { + vec<tree, va_gc>* GTY((tag ("0"))) instantiations; + tree GTY((tag ("1"))) decl; + } GTY((desc ("%1.noexcept_p"))) u; }; @@ -1197,6 +1205,9 @@ struct GTY (()) tree_default_arg { #define UNEVALUATED_NOEXCEPT_SPEC_P(NODE) \ (DEFERRED_NOEXCEPT_SPEC_P (NODE) \ && DEFERRED_NOEXCEPT_PATTERN (TREE_PURPOSE (NODE)) == NULL_TREE) +#define UNPARSED_NOEXCEPT_SPEC_P(NODE) \ + ((NODE) && (TREE_PURPOSE (NODE)) \ + && (TREE_CODE (TREE_PURPOSE (NODE)) == DEFAULT_ARG)) struct GTY (()) tree_deferred_noexcept { struct tree_base base; @@ -6464,6 +6475,8 @@ extern bool check_array_designated_initializer (constructor_elt *, unsigned HOST_WIDE_INT); extern bool check_for_uninitialized_const_var (tree, bool, tsubst_flags_t); extern tree build_explicit_specifier (tree, tsubst_flags_t); +extern void check_redeclaration_exception_specification + (tree, tree); /* in decl2.c */ extern void record_mangling (tree, bool); @@ -6929,6 +6942,7 @@ extern tree copied_binfo (tree, tree); extern tree original_binfo (tree, tree); extern int shared_member_p (tree); extern bool any_dependent_bases_p (tree = current_nonlambda_class_type ()); +extern bool maybe_check_throw_specifier (tree, tree); /* The representation of a deferred access check. */ diff --git gcc/cp/decl.c gcc/cp/decl.c index 36014dc628e..a2effa13623 100644 --- gcc/cp/decl.c +++ gcc/cp/decl.c @@ -1139,7 +1139,7 @@ warn_extern_redeclared_static (tree newdecl, tree olddecl) function templates. If their exception specifications do not match, issue a diagnostic. */ -static void +void check_redeclaration_exception_specification (tree new_decl, tree old_decl) { @@ -1151,6 +1151,15 @@ check_redeclaration_exception_specification (tree new_decl, && UNEVALUATED_NOEXCEPT_SPEC_P (old_exceptions)) return; + /* We can't compare unparsed noexcept-specifiers. Save the old decl + and check this again after we've parsed the noexcept-specifiers + for real. */ + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) + { + DEFARG_DECL (TREE_PURPOSE (new_exceptions)) = copy_decl (old_decl); + return; + } + if (!type_dependent_expression_p (old_decl)) { maybe_instantiate_noexcept (new_decl); diff --git gcc/cp/except.c gcc/cp/except.c index afc261073d7..208c9c1461d 100644 --- gcc/cp/except.c +++ gcc/cp/except.c @@ -1248,6 +1248,7 @@ nothrow_spec_p (const_tree spec) || TREE_VALUE (spec) || spec == noexcept_false_spec || TREE_PURPOSE (spec) == error_mark_node + || UNPARSED_NOEXCEPT_SPEC_P (spec) || processing_template_decl); return false; diff --git gcc/cp/parser.c gcc/cp/parser.c index 12beadf5096..41197ab3486 100644 --- gcc/cp/parser.c +++ gcc/cp/parser.c @@ -247,6 +247,12 @@ static void cp_lexer_stop_debugging static cp_token_cache *cp_token_cache_new (cp_token *, cp_token *); +static tree cp_parser_noexcept_specification_opt + (cp_parser *, bool, bool *, bool); +static tree cp_parser_late_noexcept_specifier + (cp_parser *, tree); +static void noexcept_override_late_checks + (tree, tree); static void cp_parser_initial_pragma (cp_token *); @@ -1974,11 +1980,14 @@ cp_parser_context_new (cp_parser_context* next) parser->unparsed_queues->last ().nsdmis #define unparsed_classes \ parser->unparsed_queues->last ().classes +#define unparsed_noexcepts \ + parser->unparsed_queues->last ().noexcepts static void push_unparsed_function_queues (cp_parser *parser) { - cp_unparsed_functions_entry e = {NULL, make_tree_vector (), NULL, NULL}; + cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL, + NULL }; vec_safe_push (parser->unparsed_queues, e); } @@ -20515,7 +20524,13 @@ cp_parser_init_declarator (cp_parser* parser, /*asmspec=*/NULL_TREE, attr_chainon (attributes, prefix_attributes)); if (decl && TREE_CODE (decl) == FUNCTION_DECL) - cp_parser_save_default_args (parser, decl); + { + cp_parser_save_default_args (parser, decl); + /* Remember if there is a noexcept-specifier to post process. */ + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) + vec_safe_push (unparsed_noexcepts, decl); + } cp_finalize_omp_declare_simd (parser, decl); cp_finalize_oacc_routine (parser, decl, false); } @@ -23334,6 +23349,45 @@ cp_parser_class_name (cp_parser *parser, return decl; } +/* Make sure that any member-function parameters are in scope. + For instance, a function's noexcept-specifier can use the function's + parameters: + + struct S { + void fn (int p) noexcept(noexcept(p)); + }; + + so we need to make sure name lookup can find them. This is used + when we delay parsing of the noexcept-specifier. */ + +static void +inject_parm_decls (tree decl) +{ + begin_scope (sk_function_parms, decl); + tree args = DECL_ARGUMENTS (decl); + args = nreverse (args); + + tree next; + for (tree parm = args; parm; parm = next) + { + next = DECL_CHAIN (parm); + if (TREE_CODE (parm) == PARM_DECL) + pushdecl (parm); + } + /* Get the decls in their original chain order and record in the + function. This is all and only the PARM_DECLs that were + pushed into scope by the loop above. */ + DECL_ARGUMENTS (decl) = get_local_decls (); +} + +/* Undo the effects of inject_parm_decls. */ + +static void +pop_injected_parms (void) +{ + pop_bindings_and_leave_scope (); +} + /* Parse a class-specifier. class-specifier: @@ -23644,6 +23698,66 @@ cp_parser_class_specifier_1 (cp_parser* parser) vec_safe_truncate (unparsed_classes, 0); after_nsdmi_defaulted_late_checks (type); + /* If there are noexcept-specifiers that have not yet been processed, + take care of them now. */ + class_type = NULL_TREE; + pushed_scope = NULL_TREE; + FOR_EACH_VEC_SAFE_ELT (unparsed_noexcepts, ix, decl) + { + tree ctx = (DECL_FRIEND_P (decl) ? DECL_FRIEND_CONTEXT (decl) + : DECL_CONTEXT (decl)); + if (class_type != ctx) + { + if (pushed_scope) + pop_scope (pushed_scope); + class_type = ctx; + pushed_scope = push_scope (class_type); + } + + /* Make sure that any template parameters are in scope. */ + maybe_begin_member_template_processing (decl); + + /* Make sure that any member-function parameters are in scope. */ + inject_parm_decls (decl); + + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + spec = TREE_PURPOSE (spec); + tree old_decl = DEFARG_DECL (spec); + + /* 'this' is not allowed in static member functions. */ + unsigned char local_variables_forbidden_p + = parser->local_variables_forbidden_p; + if (DECL_THIS_STATIC (decl)) + parser->local_variables_forbidden_p |= THIS_FORBIDDEN; + + /* Now we can parse the noexcept-specifier. */ + spec = cp_parser_late_noexcept_specifier (parser, spec); + + /* Restore the state of local_variables_forbidden_p. */ + parser->local_variables_forbidden_p = local_variables_forbidden_p; + if (spec != error_mark_node) + TREE_TYPE (decl) = build_exception_variant (TREE_TYPE (decl), spec); + + /* If we've stashed an old declaration, it means we need to + perform late redeclaration checking. */ + if (old_decl) + check_redeclaration_exception_specification (decl, old_decl); + + /* The finish_struct call above performed various override checking, + but it skipped unparsed noexcept-specifier operands. Now that we + have resolved them, check again. */ + noexcept_override_late_checks (type, decl); + + /* Remove any member-function parameters from the symbol table. */ + pop_injected_parms (); + + /* Remove any template parameters from the symbol table. */ + maybe_end_member_template_processing (); + } + vec_safe_truncate (unparsed_noexcepts, 0); + if (pushed_scope) + pop_scope (pushed_scope); + /* Now parse the body of the functions. */ if (flag_openmp) { @@ -24817,6 +24931,12 @@ cp_parser_member_declaration (cp_parser* parser) else decl = finish_fully_implicit_template (parser, decl); } + if (decl && TREE_CODE (decl) == FUNCTION_DECL) + { + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) + vec_safe_push (unparsed_noexcepts, decl); + } } cp_finalize_omp_declare_simd (parser, decl); @@ -25199,6 +25319,90 @@ cp_parser_base_specifier (cp_parser* parser) /* Exception handling [gram.exception] */ +/* Save the tokens that make up the noexcept-specifier for a member-function. + Returns a DEFAULT_ARG. */ + +static tree +cp_parser_save_noexcept (cp_parser *parser) +{ + cp_token *first = parser->lexer->next_token; + /* We want everything up to, including, the final ')'. */ + cp_parser_cache_group (parser, CPP_CLOSE_PAREN, /*depth=*/0); + cp_token *last = parser->lexer->next_token; + + /* As with default arguments and NSDMIs, make use of DEFAULT_ARG + to carry the information we will need. */ + tree expr = make_node (DEFAULT_ARG); + /* Save away the noexcept-specifier; we will process it when the + class is complete. */ + DEFARG_TOKENS (expr) = cp_token_cache_new (first, last); + DEFARG_DECL (expr) = NULL_TREE; + DEFARG_NOEXCEPT_P (expr) = true; + expr = build_tree_list (expr, NULL_TREE); + return expr; +} + +/* Used for late processing of noexcept-specifiers of member-functions. + DEFAULT_ARG is the unparsed operand of a noexcept-specifier which + we saved for later; parse it now. */ + +static tree +cp_parser_late_noexcept_specifier (cp_parser *parser, tree default_arg) +{ + /* Make sure we've gotten something that hasn't been parsed yet. */ + gcc_assert (TREE_CODE (default_arg) == DEFAULT_ARG); + + push_unparsed_function_queues (parser); + + /* Push the saved tokens for the noexcept-specifier onto the parser's + lexer stack. */ + cp_token_cache *tokens = DEFARG_TOKENS (default_arg); + cp_parser_push_lexer_for_tokens (parser, tokens); + + /* Parse the cached noexcept-specifier. */ + tree parsed_arg + = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + NULL, + /*return_cond=*/false); + + /* Revert to the main lexer. */ + cp_parser_pop_lexer (parser); + + /* Restore the queue. */ + pop_unparsed_function_queues (parser); + + /* And we're done. */ + return parsed_arg; +} + +/* Perform late checking of overriding function with respect to their + noexcept-specifiers. TYPE is the class and FNDECL is the function + that potentially overrides some virtual function with the same + signature. */ + +static void +noexcept_override_late_checks (tree type, tree fndecl) +{ + tree binfo = TYPE_BINFO (type); + tree base_binfo; + + if (DECL_STATIC_FUNCTION_P (fndecl)) + return; + + for (int i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i) + { + tree basetype = BINFO_TYPE (base_binfo); + + if (!TYPE_POLYMORPHIC_P (basetype)) + continue; + + tree fn = look_for_overrides_here (basetype, fndecl); + if (fn) + maybe_check_throw_specifier (fndecl, fn); + } +} + /* Parse an (optional) noexcept-specification. noexcept-specification: @@ -25227,6 +25431,18 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, if (cp_parser_is_keyword (token, RID_NOEXCEPT)) { tree expr; + + /* [class.mem]/6 says that a noexcept-specifer (within the + member-specification of the class) is a complete-class context of + a class. So, if the noexcept-specifier has the optional expression, + just save the tokens, and reparse this after we're done with the + class. */ + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_OPEN_PAREN + && at_class_scope_p () + && TYPE_BEING_DEFINED (current_class_type) + && !LAMBDA_TYPE_P (current_class_type)) + return cp_parser_save_noexcept (parser); + cp_lexer_consume_token (parser->lexer); if (cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) @@ -28427,7 +28643,12 @@ cp_parser_save_member_function_body (cp_parser* parser, return error_mark_node; } - /* Remember it, if there default args to post process. */ + /* Remember if there is a noexcept-specifier to post process. */ + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fn)); + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) + vec_safe_push (unparsed_noexcepts, fn); + + /* Remember it, if there are default args to post process. */ cp_parser_save_default_args (parser, fn); /* Save away the tokens that make up the body of the diff --git gcc/cp/parser.h gcc/cp/parser.h index c03a9d87af5..2890788f489 100644 --- gcc/cp/parser.h +++ gcc/cp/parser.h @@ -166,6 +166,9 @@ struct GTY(()) cp_unparsed_functions_entry { /* Nested classes go in this vector, so that we can do some final processing after parsing any NSDMIs. */ vec<tree, va_gc> *classes; + + /* Functions with noexcept-specifiers that require post-processing. */ + vec<tree, va_gc> *noexcepts; }; diff --git gcc/cp/pt.c gcc/cp/pt.c index d6976e08690..c00d14fd954 100644 --- gcc/cp/pt.c +++ gcc/cp/pt.c @@ -25308,8 +25308,9 @@ dependent_type_p_r (tree type) if (tree noex = TREE_PURPOSE (spec)) /* Treat DEFERRED_NOEXCEPT as non-dependent, since it doesn't affect overload resolution and treating it as dependent breaks - things. */ + things. Same for an unparsed noexcept expression. */ if (TREE_CODE (noex) != DEFERRED_NOEXCEPT + && TREE_CODE (noex) != DEFAULT_ARG && value_dependent_expression_p (noex)) return true; return false; diff --git gcc/cp/search.c gcc/cp/search.c index 4c3fffda717..5a3a0cf2824 100644 --- gcc/cp/search.c +++ gcc/cp/search.c @@ -1863,6 +1863,39 @@ locate_field_accessor (tree basetype_path, tree field_decl, bool const_p) NULL, &lfd); } +/* Check throw specifier of OVERRIDER is at least as strict as + the one of BASEFN. */ + +bool +maybe_check_throw_specifier (tree overrider, tree basefn) +{ + maybe_instantiate_noexcept (basefn); + maybe_instantiate_noexcept (overrider); + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); + + if (DECL_INVALID_OVERRIDER_P (overrider)) + return true; + + /* Can't check this yet. Pretend this is fine and let + noexcept_override_late_checks check this later. */ + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) + return true; + + if (!comp_except_specs (base_throw, over_throw, ce_derived)) + { + auto_diagnostic_group d; + error ("looser exception specification on overriding virtual function " + "%q+#F", overrider); + inform (DECL_SOURCE_LOCATION (basefn), + "overridden function is %q#F", basefn); + DECL_INVALID_OVERRIDER_P (overrider) = 1; + return false; + } + return true; +} + /* Check that virtual overrider OVERRIDER is acceptable for base function BASEFN. Issue diagnostic, and return zero, if unacceptable. */ @@ -1873,7 +1906,6 @@ check_final_overrider (tree overrider, tree basefn) tree base_type = TREE_TYPE (basefn); tree over_return = fndecl_declared_return_type (overrider); tree base_return = fndecl_declared_return_type (basefn); - tree over_throw, base_throw; int fail = 0; @@ -1957,21 +1989,8 @@ check_final_overrider (tree overrider, tree basefn) return 0; } - /* Check throw specifier is at least as strict. */ - maybe_instantiate_noexcept (basefn); - maybe_instantiate_noexcept (overrider); - base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); - over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); - - if (!comp_except_specs (base_throw, over_throw, ce_derived)) - { - auto_diagnostic_group d; - error ("looser throw specifier for %q+#F", overrider); - inform (DECL_SOURCE_LOCATION (basefn), - "overridden function is %q#F", basefn); - DECL_INVALID_OVERRIDER_P (overrider) = 1; - return 0; - } + if (!maybe_check_throw_specifier (overrider, basefn)) + return 0; /* Check for conflicting type attributes. But leave transaction_safe for set_one_vmethod_tm_attributes. */ diff --git gcc/cp/tree.c gcc/cp/tree.c index 718eed349c6..bc0080d6720 100644 --- gcc/cp/tree.c +++ gcc/cp/tree.c @@ -2550,6 +2550,7 @@ canonical_eh_spec (tree raises) if (raises == NULL_TREE) return raises; else if (DEFERRED_NOEXCEPT_SPEC_P (raises) + || UNPARSED_NOEXCEPT_SPEC_P (raises) || uses_template_parms (raises) || uses_template_parms (TREE_PURPOSE (raises))) /* Keep a dependent or deferred exception specification. */ @@ -3662,6 +3663,7 @@ cp_tree_equal (tree t1, tree t2) case IDENTIFIER_NODE: case SSA_NAME: case USING_DECL: + case DEFAULT_ARG: return false; case BASELINK: diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c index df002a1664c..8cbc48fb44f 100644 --- gcc/cp/typeck2.c +++ gcc/cp/typeck2.c @@ -2393,6 +2393,12 @@ merge_exception_specifiers (tree list, tree add) if (list == error_mark_node || add == error_mark_node) return error_mark_node; + /* We don't want to lose the unparsed operand lest we miss diagnostics. */ + if (UNPARSED_NOEXCEPT_SPEC_P (list)) + return list; + else if (UNPARSED_NOEXCEPT_SPEC_P (add)) + return add; + /* No exception-specifier or noexcept(false) are less strict than anything else. Prefer the newer variant (LIST). */ if (!list || list == noexcept_false_spec) diff --git gcc/testsuite/g++.dg/cpp0x/noexcept41.C gcc/testsuite/g++.dg/cpp0x/noexcept41.C new file mode 100644 index 00000000000..43b38c2446f --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept41.C @@ -0,0 +1,147 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert(X, #X) + +struct S { + void f1() noexcept(noexcept(i)) { } + void f2() noexcept(noexcept(fn())) { } + void f3() noexcept(noexcept(fnx())) { } + void f4() noexcept(noexcept(i)); + void f5() noexcept(noexcept(fn())); + void f6() noexcept(noexcept(fnx())); + + void f7() noexcept(1); + void f8() noexcept(0); + void f9() noexcept(b); + void f10() noexcept(!b); + + int i; + static constexpr auto b = true; + void fny() noexcept(noexcept(fn())); + void fn(); + void fnx() noexcept; +}; + +S s; +SA(noexcept(s.f1())); +SA(!noexcept(s.f2())); +SA(noexcept(s.f3())); +SA(noexcept(s.f4())); +SA(!noexcept(s.f5())); +SA(noexcept(s.f6())); +SA(noexcept(s.f7())); +SA(!noexcept(s.f8())); +SA(noexcept(s.f9())); +SA(!noexcept(s.f10())); + +struct S2 { + struct V { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void f3() noexcept(noexcept(fn())) { } + void f4() noexcept(noexcept(fnx())) { } + void fn(); + void fnx() noexcept; + } v; + void fn(); + void fnx(); +}; + +S2 s2; +SA(!noexcept(s2.v.f1())); +SA(noexcept(s2.v.f2())); +SA(!noexcept(s2.v.f3())); +SA(noexcept(s2.v.f4())); + +struct S3 { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void fn(); + void fnx() noexcept; +}; + +void +S3::f1() noexcept(noexcept(fn())) +{ +} + +void +S3::f2() noexcept(noexcept(fnx())) +{ +} + +struct S4 { + int f1 (int p) noexcept(noexcept(p)) { return p; } + int f2 (int p) noexcept(noexcept(p)); + int f3 (int p = 10) noexcept(noexcept(p)); + int f4 () noexcept(noexcept(S4{})); +}; + +S4 s4; +SA(noexcept(s4.f1(1))); +SA(noexcept(s4.f2(1))); +SA(noexcept(s4.f3())); +SA(noexcept(s4.f4())); + +template<typename T> +struct S5 { + void f1() noexcept(noexcept(i)) { } + void f2() noexcept(noexcept(fn())) { } + void f3() noexcept(noexcept(fnx())) { } + void f4() noexcept(noexcept(i)); + void f5() noexcept(noexcept(fn())); + void f6() noexcept(noexcept(fnx())); + + int i; + void fny() noexcept(noexcept(fn())); + void fn(); + void fnx() noexcept; +}; + +S5<int> s5; +SA(noexcept(s5.f1())); +SA(!noexcept(s5.f2())); +SA(noexcept(s5.f3())); +SA(noexcept(s5.f4())); +SA(!noexcept(s5.f5())); +SA(noexcept(s5.f6())); + +template<typename T> +struct S6 { + void f1() noexcept(noexcept(x)); + T x; +}; + +struct S7 { + template<typename U> + void f1 () noexcept(noexcept(U(1))) { } + + template<int N> + void f2() noexcept(noexcept(N)); + + template <typename _Up> + void f3(_Up __p) noexcept(noexcept(__p)); +}; + +void glob(); +void globx() noexcept; +struct S8 { + void f1 () noexcept(noexcept(glob())); + void f2 () noexcept(noexcept(globx())); +}; + +S8 s8; +SA(!noexcept(s8.f1())); +SA(noexcept(s8.f2())); + +struct W { + constexpr operator bool(); +}; + +template<typename T> +struct S9 { + S9() noexcept(noexcept(w)) { } + S9 &operator=(S9 &&) noexcept(T::X); + W w; +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept42.C gcc/testsuite/g++.dg/cpp0x/noexcept42.C new file mode 100644 index 00000000000..b3859de9ebc --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept42.C @@ -0,0 +1,26 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +struct S { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void fn(); + void fnx() noexcept; +}; + +void +S::f1() noexcept // { dg-error "different exception specifier" } +{ +} + +void +S::f2() // { dg-error "different exception specifier" } +{ +} + +struct S2 { + void f1() noexcept(noexcept(nosuchfn())); // { dg-error "not declared in this scope" } + void f2() noexcept(noexcept(nothere)); // { dg-error "not declared in this scope" } + void f3() noexcept(noexcept(nosuchfn())) { } // { dg-error "not declared in this scope" } + void f4() noexcept(noexcept(nothere)) { } // { dg-error "not declared in this scope" } +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept43.C gcc/testsuite/g++.dg/cpp0x/noexcept43.C new file mode 100644 index 00000000000..12c6d364099 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept43.C @@ -0,0 +1,9 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +template <typename _Alloc> class A { + typedef _Alloc _Alloc_traits; + A &operator=(A &&) noexcept(_Alloc_traits::_S_nothrow_move); + A &m_fn1(A &&) noexcept(_Alloc_traits::_S_nothrow_move); + void m_fn2(A<char>) {} +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept44.C gcc/testsuite/g++.dg/cpp0x/noexcept44.C new file mode 100644 index 00000000000..a81032f28e9 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept44.C @@ -0,0 +1,14 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +void fn1(void()); +template <typename> class A { + void _M_local_data(); + A() noexcept(_M_local_data); +}; + +class B { + void _S_initialize(); + static void _S_initialize_once(); +}; +void B::_S_initialize() { fn1(_S_initialize_once); } diff --git gcc/testsuite/g++.dg/cpp0x/noexcept45.C gcc/testsuite/g++.dg/cpp0x/noexcept45.C new file mode 100644 index 00000000000..39df4a6571e --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept45.C @@ -0,0 +1,23 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +struct A +{ + virtual void f(); + virtual void g() noexcept; + virtual void h() noexcept(false); +}; + +struct B : A +{ + void f() noexcept(true); + void g() noexcept(true); + void h() noexcept(true); +}; + +struct D : A +{ + void f() noexcept(false); + void g() noexcept(false); // { dg-error "looser exception specification" } + void h() noexcept(false); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept46.C gcc/testsuite/g++.dg/cpp0x/noexcept46.C new file mode 100644 index 00000000000..da7490d651c --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept46.C @@ -0,0 +1,28 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +void f() noexcept(false); +void g() noexcept(true); +void h() noexcept; + +struct B { + friend void f() noexcept(false); + friend void g() noexcept(false); // { dg-error "different exception specifier" } + friend void h() noexcept(false); // { dg-error "different exception specifier" } +}; + +struct C { + friend void f() noexcept(true); // { dg-error "different exception specifier" } + friend void g() noexcept(true); // { dg-error "different exception specifier" } + friend void h() noexcept(true); // { dg-error "different exception specifier" } +}; + +void o() noexcept(false); +void p() noexcept(true); +void q() noexcept; + +struct D { + friend void o() noexcept(true); // { dg-error "different exception specifier" } + friend void p() noexcept(true); + friend void q() noexcept(true); +}; diff --git gcc/testsuite/g++.dg/eh/shadow1.C gcc/testsuite/g++.dg/eh/shadow1.C index 0ba6145ef0c..6bccc704d49 100644 --- gcc/testsuite/g++.dg/eh/shadow1.C +++ gcc/testsuite/g++.dg/eh/shadow1.C @@ -18,7 +18,7 @@ struct D : private B // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } struct E : public D { - virtual void V () throw (D); // { dg-error "looser throw" "" { target { ! c++17 } } } + virtual void V () throw (D); // { dg-error "looser exception" "" { target { ! c++17 } } } }; // { dg-error "dynamic exception specification" "" { target c++17 } .-1 } // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } B* foo (D *);
Ping. On Fri, May 10, 2019 at 03:21:33PM -0400, Marek Polacek wrote: > Coming back to this. I didn't think this was suitable for GCC 9. > > On Mon, Jan 07, 2019 at 10:44:37AM -0500, Jason Merrill wrote: > > On 12/19/18 3:27 PM, Marek Polacek wrote: > > > Prompted by Jon's observation in 52869, I noticed that we don't treat > > > a noexcept-specifier as a complete-class context of a class ([class.mem]/6). > > > As with member function bodies, default arguments, and NSDMIs, names used in > > > a noexcept-specifier of a member-function can be declared later in the class > > > body, so we need to wait and parse them at the end of the class. > > > For that, I've made use of DEFAULT_ARG (now best to be renamed to UNPARSED_ARG). > > > > Or DEFERRED_PARSE, yes. > > I didn't change the name but I'm happy to do it as a follow up. > > > > + /* We can't compare unparsed noexcept-specifiers. Save the old decl > > > + and check this again after we've parsed the noexcept-specifiers > > > + for real. */ > > > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > > > + { > > > + vec_safe_push (DEFARG_INSTANTIATIONS (TREE_PURPOSE (new_exceptions)), > > > + copy_decl (old_decl)); > > > + return; > > > + } > > > > Why copy_decl? > > This is so that we don't lose the diagnostic in noexcept46.C. If I don't use > copy_decl then the tree is shared and subsequent changes to it make us not > detect discrepancies like noexcept(false) vs. noexcept(true) on the same decl. > > > It seems wasteful to allocate a vec to hold this single decl; let's make the > > last field of tree_default_arg a union instead. And add a new macro for the > > single decl case. > > Done. But that required also adding GTY markers *and* a new BOOL_BITFIELD for > the sake of GTY((desc)). > > > I notice that default_arg currently uses tree_common for some reason, and we > > ought to be able to save two words by switching to tree_base > > Done. > > > > @@ -1245,6 +1245,7 @@ nothrow_spec_p (const_tree spec) > > > || TREE_VALUE (spec) > > > || spec == noexcept_false_spec > > > || TREE_PURPOSE (spec) == error_mark_node > > > + || TREE_CODE (TREE_PURPOSE (spec)) == DEFAULT_ARG > > > > Maybe use UNPARSED_NOEXCEPT_SPEC_P here? > > Done. > > > > +/* Make sure that any member-function parameters are in scope. > > > + For instance, a function's noexcept-specifier can use the function's > > > + parameters: > > > + > > > + struct S { > > > + void fn (int p) noexcept(noexcept(p)); > > > + }; > > > + > > > + so we need to make sure name lookup can find them. This is used > > > + when we delay parsing of the noexcept-specifier. */ > > > + > > > +static void > > > +maybe_begin_member_function_processing (tree decl) > > > > This name is pretty misleading. How about inject_parm_decls, to go with > > inject_this_parameter? > > Done. > > > > +/* Undo the effects of maybe_begin_member_function_processing. */ > > > + > > > +static void > > > +maybe_end_member_function_processing (void) > > > > And then perhaps pop_injected_parms. > > Done. > > > > +/* Check throw specifier of OVERRIDER is at least as strict as > > > + the one of BASEFN. */ > > > + > > > +bool > > > +maybe_check_throw_specifier (tree overrider, tree basefn) > > > +{ > > > + maybe_instantiate_noexcept (basefn); > > > + maybe_instantiate_noexcept (overrider); > > > + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > > > + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > > > + > > > + if (DECL_INVALID_OVERRIDER_P (overrider)) > > > + return true; > > > + > > > + /* Can't check this yet. Pretend this is fine and let > > > + noexcept_override_late_checks check this later. */ > > > + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) > > > + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) > > > + return true; > > > + > > > + if (!comp_except_specs (base_throw, over_throw, ce_derived)) > > > + { > > > + auto_diagnostic_group d; > > > + error ("looser throw specifier for %q+#F", overrider); > > > > Since we're touching this diagnostic, let's correct it now to "exception > > specification". And add "on overriding virtual function". > > Ok, changed to the more up-to-date term. > > Two further changes were required since my changes to detecting 'this' for > static member functions: > 1) use THIS_FORBIDDEN if needed when parsing the delayed noexcept, > 2) careful about friend member functions -- its DECL_CONTEXT is not the > containing class, need to use DECL_FRIEND_CONTEXT. > > Both of these points are tested in g++.dg/cpp0x/this1.C. > > Bootstrapped/regtested on x86_64-linux, ok for trunk? > > 2019-05-10 Marek Polacek <polacek@redhat.com> > > PR c++/86476 - noexcept-specifier is a complete-class context. > PR c++/52869 > * cp-tree.def (DEFAULT_ARG): Update commentary. > * cp-tree.h (DEFARG_DECL, DEFARG_NOEXCEPT_P, UNPARSED_NOEXCEPT_SPEC_P): > New macros. > (tree_default_arg): Add a tree field, make the last two fields into a > union. Add GTY markers. > (check_redeclaration_exception_specification): Declare. > (maybe_check_throw_specifier): Declare. > * decl.c (check_redeclaration_exception_specification): No longer > static. Handle UNPARSED_NOEXCEPT_SPEC_P. > * except.c (nothrow_spec_p): Accept DEFAULT_ARG in the assert. > * parser.c (cp_parser_noexcept_specification_opt, > cp_parser_late_noexcept_specifier, noexcept_override_late_checks): > Forward-declare. > (unparsed_noexcepts): New macro. > (push_unparsed_function_queues): Update initializer. > (cp_parser_init_declarator): Maybe save the noexcept-specifier to > post process. > (inject_parm_decls): New. > (pop_injected_parms): New. > (cp_parser_class_specifier_1): Implement delayed parsing of > noexcept-specifiers. > (cp_parser_member_declaration): Maybe save the noexcept-specifier to > post process. > (cp_parser_save_noexcept): New. > (cp_parser_late_noexcept_specifier): New. > (noexcept_override_late_checks): New. > (cp_parser_noexcept_specification_opt): Call cp_parser_save_noexcept > instead of the normal processing if needed. > (cp_parser_save_member_function_body): Maybe save the > noexcept-specifier to post process. > * parser.h (cp_unparsed_functions_entry): Add new field to carry > a noexcept-specifier. > * pt.c (dependent_type_p_r): Handle unparsed noexcept expression. > * search.c (maybe_check_throw_specifier): New function, broken out > of... > (check_final_overrider): ...here. Call maybe_check_throw_specifier. > * tree.c (canonical_eh_spec): Handle UNPARSED_NOEXCEPT_SPEC_P. > (cp_tree_equal): Handle DEFAULT_ARG. > * typeck2.c (merge_exception_specifiers): If an unparsed noexcept > expression has been passed, return it instead of merging it. > > * g++.dg/cpp0x/noexcept41.C: New test. > * g++.dg/cpp0x/noexcept42.C: New test. > * g++.dg/cpp0x/noexcept43.C: New test. > * g++.dg/cpp0x/noexcept44.C: New test. > * g++.dg/cpp0x/noexcept45.C: New test. > * g++.dg/cpp0x/noexcept46.C: New test. > * g++.dg/eh/shadow1.C: Adjust dg-error. > > diff --git gcc/cp/cp-tree.def gcc/cp/cp-tree.def > index 03c105b5c4c..33eb5d25efe 100644 > --- gcc/cp/cp-tree.def > +++ gcc/cp/cp-tree.def > @@ -209,7 +209,9 @@ DEFTREECODE (USING_STMT, "using_stmt", tcc_statement, 1) > > /* An un-parsed default argument. Holds a vector of input tokens and > a vector of places where the argument was instantiated before > - parsing had occurred. */ > + parsing had occurred. This is also used for delayed NSDMIs and > + noexcept-specifier parsing. For a noexcept-specifier, we use a tree > + holding a function declaration used for late checking. */ > DEFTREECODE (DEFAULT_ARG, "default_arg", tcc_exceptional, 0) > > /* An uninstantiated/unevaluated noexcept-specification. For the > diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h > index f253857b02a..ef14a011293 100644 > --- gcc/cp/cp-tree.h > +++ gcc/cp/cp-tree.h > @@ -1178,12 +1178,20 @@ enum cp_identifier_kind { > #define DEFARG_TOKENS(NODE) \ > (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->tokens) > #define DEFARG_INSTANTIATIONS(NODE) \ > - (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->instantiations) > + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->u.instantiations) > +#define DEFARG_DECL(NODE) \ > + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->u.decl) > +#define DEFARG_NOEXCEPT_P(NODE) \ > + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->noexcept_p) > > struct GTY (()) tree_default_arg { > - struct tree_common common; > + struct tree_base base; > struct cp_token_cache *tokens; > - vec<tree, va_gc> *instantiations; > + BOOL_BITFIELD noexcept_p : 1; > + union { > + vec<tree, va_gc>* GTY((tag ("0"))) instantiations; > + tree GTY((tag ("1"))) decl; > + } GTY((desc ("%1.noexcept_p"))) u; > }; > > > @@ -1197,6 +1205,9 @@ struct GTY (()) tree_default_arg { > #define UNEVALUATED_NOEXCEPT_SPEC_P(NODE) \ > (DEFERRED_NOEXCEPT_SPEC_P (NODE) \ > && DEFERRED_NOEXCEPT_PATTERN (TREE_PURPOSE (NODE)) == NULL_TREE) > +#define UNPARSED_NOEXCEPT_SPEC_P(NODE) \ > + ((NODE) && (TREE_PURPOSE (NODE)) \ > + && (TREE_CODE (TREE_PURPOSE (NODE)) == DEFAULT_ARG)) > > struct GTY (()) tree_deferred_noexcept { > struct tree_base base; > @@ -6464,6 +6475,8 @@ extern bool check_array_designated_initializer (constructor_elt *, > unsigned HOST_WIDE_INT); > extern bool check_for_uninitialized_const_var (tree, bool, tsubst_flags_t); > extern tree build_explicit_specifier (tree, tsubst_flags_t); > +extern void check_redeclaration_exception_specification > + (tree, tree); > > /* in decl2.c */ > extern void record_mangling (tree, bool); > @@ -6929,6 +6942,7 @@ extern tree copied_binfo (tree, tree); > extern tree original_binfo (tree, tree); > extern int shared_member_p (tree); > extern bool any_dependent_bases_p (tree = current_nonlambda_class_type ()); > +extern bool maybe_check_throw_specifier (tree, tree); > > /* The representation of a deferred access check. */ > > diff --git gcc/cp/decl.c gcc/cp/decl.c > index 36014dc628e..a2effa13623 100644 > --- gcc/cp/decl.c > +++ gcc/cp/decl.c > @@ -1139,7 +1139,7 @@ warn_extern_redeclared_static (tree newdecl, tree olddecl) > function templates. If their exception specifications do not > match, issue a diagnostic. */ > > -static void > +void > check_redeclaration_exception_specification (tree new_decl, > tree old_decl) > { > @@ -1151,6 +1151,15 @@ check_redeclaration_exception_specification (tree new_decl, > && UNEVALUATED_NOEXCEPT_SPEC_P (old_exceptions)) > return; > > + /* We can't compare unparsed noexcept-specifiers. Save the old decl > + and check this again after we've parsed the noexcept-specifiers > + for real. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > + { > + DEFARG_DECL (TREE_PURPOSE (new_exceptions)) = copy_decl (old_decl); > + return; > + } > + > if (!type_dependent_expression_p (old_decl)) > { > maybe_instantiate_noexcept (new_decl); > diff --git gcc/cp/except.c gcc/cp/except.c > index afc261073d7..208c9c1461d 100644 > --- gcc/cp/except.c > +++ gcc/cp/except.c > @@ -1248,6 +1248,7 @@ nothrow_spec_p (const_tree spec) > || TREE_VALUE (spec) > || spec == noexcept_false_spec > || TREE_PURPOSE (spec) == error_mark_node > + || UNPARSED_NOEXCEPT_SPEC_P (spec) > || processing_template_decl); > > return false; > diff --git gcc/cp/parser.c gcc/cp/parser.c > index 12beadf5096..41197ab3486 100644 > --- gcc/cp/parser.c > +++ gcc/cp/parser.c > @@ -247,6 +247,12 @@ static void cp_lexer_stop_debugging > > static cp_token_cache *cp_token_cache_new > (cp_token *, cp_token *); > +static tree cp_parser_noexcept_specification_opt > + (cp_parser *, bool, bool *, bool); > +static tree cp_parser_late_noexcept_specifier > + (cp_parser *, tree); > +static void noexcept_override_late_checks > + (tree, tree); > > static void cp_parser_initial_pragma > (cp_token *); > @@ -1974,11 +1980,14 @@ cp_parser_context_new (cp_parser_context* next) > parser->unparsed_queues->last ().nsdmis > #define unparsed_classes \ > parser->unparsed_queues->last ().classes > +#define unparsed_noexcepts \ > + parser->unparsed_queues->last ().noexcepts > > static void > push_unparsed_function_queues (cp_parser *parser) > { > - cp_unparsed_functions_entry e = {NULL, make_tree_vector (), NULL, NULL}; > + cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL, > + NULL }; > vec_safe_push (parser->unparsed_queues, e); > } > > @@ -20515,7 +20524,13 @@ cp_parser_init_declarator (cp_parser* parser, > /*asmspec=*/NULL_TREE, > attr_chainon (attributes, prefix_attributes)); > if (decl && TREE_CODE (decl) == FUNCTION_DECL) > - cp_parser_save_default_args (parser, decl); > + { > + cp_parser_save_default_args (parser, decl); > + /* Remember if there is a noexcept-specifier to post process. */ > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > + vec_safe_push (unparsed_noexcepts, decl); > + } > cp_finalize_omp_declare_simd (parser, decl); > cp_finalize_oacc_routine (parser, decl, false); > } > @@ -23334,6 +23349,45 @@ cp_parser_class_name (cp_parser *parser, > return decl; > } > > +/* Make sure that any member-function parameters are in scope. > + For instance, a function's noexcept-specifier can use the function's > + parameters: > + > + struct S { > + void fn (int p) noexcept(noexcept(p)); > + }; > + > + so we need to make sure name lookup can find them. This is used > + when we delay parsing of the noexcept-specifier. */ > + > +static void > +inject_parm_decls (tree decl) > +{ > + begin_scope (sk_function_parms, decl); > + tree args = DECL_ARGUMENTS (decl); > + args = nreverse (args); > + > + tree next; > + for (tree parm = args; parm; parm = next) > + { > + next = DECL_CHAIN (parm); > + if (TREE_CODE (parm) == PARM_DECL) > + pushdecl (parm); > + } > + /* Get the decls in their original chain order and record in the > + function. This is all and only the PARM_DECLs that were > + pushed into scope by the loop above. */ > + DECL_ARGUMENTS (decl) = get_local_decls (); > +} > + > +/* Undo the effects of inject_parm_decls. */ > + > +static void > +pop_injected_parms (void) > +{ > + pop_bindings_and_leave_scope (); > +} > + > /* Parse a class-specifier. > > class-specifier: > @@ -23644,6 +23698,66 @@ cp_parser_class_specifier_1 (cp_parser* parser) > vec_safe_truncate (unparsed_classes, 0); > after_nsdmi_defaulted_late_checks (type); > > + /* If there are noexcept-specifiers that have not yet been processed, > + take care of them now. */ > + class_type = NULL_TREE; > + pushed_scope = NULL_TREE; > + FOR_EACH_VEC_SAFE_ELT (unparsed_noexcepts, ix, decl) > + { > + tree ctx = (DECL_FRIEND_P (decl) ? DECL_FRIEND_CONTEXT (decl) > + : DECL_CONTEXT (decl)); > + if (class_type != ctx) > + { > + if (pushed_scope) > + pop_scope (pushed_scope); > + class_type = ctx; > + pushed_scope = push_scope (class_type); > + } > + > + /* Make sure that any template parameters are in scope. */ > + maybe_begin_member_template_processing (decl); > + > + /* Make sure that any member-function parameters are in scope. */ > + inject_parm_decls (decl); > + > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > + spec = TREE_PURPOSE (spec); > + tree old_decl = DEFARG_DECL (spec); > + > + /* 'this' is not allowed in static member functions. */ > + unsigned char local_variables_forbidden_p > + = parser->local_variables_forbidden_p; > + if (DECL_THIS_STATIC (decl)) > + parser->local_variables_forbidden_p |= THIS_FORBIDDEN; > + > + /* Now we can parse the noexcept-specifier. */ > + spec = cp_parser_late_noexcept_specifier (parser, spec); > + > + /* Restore the state of local_variables_forbidden_p. */ > + parser->local_variables_forbidden_p = local_variables_forbidden_p; > + if (spec != error_mark_node) > + TREE_TYPE (decl) = build_exception_variant (TREE_TYPE (decl), spec); > + > + /* If we've stashed an old declaration, it means we need to > + perform late redeclaration checking. */ > + if (old_decl) > + check_redeclaration_exception_specification (decl, old_decl); > + > + /* The finish_struct call above performed various override checking, > + but it skipped unparsed noexcept-specifier operands. Now that we > + have resolved them, check again. */ > + noexcept_override_late_checks (type, decl); > + > + /* Remove any member-function parameters from the symbol table. */ > + pop_injected_parms (); > + > + /* Remove any template parameters from the symbol table. */ > + maybe_end_member_template_processing (); > + } > + vec_safe_truncate (unparsed_noexcepts, 0); > + if (pushed_scope) > + pop_scope (pushed_scope); > + > /* Now parse the body of the functions. */ > if (flag_openmp) > { > @@ -24817,6 +24931,12 @@ cp_parser_member_declaration (cp_parser* parser) > else > decl = finish_fully_implicit_template (parser, decl); > } > + if (decl && TREE_CODE (decl) == FUNCTION_DECL) > + { > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > + vec_safe_push (unparsed_noexcepts, decl); > + } > } > > cp_finalize_omp_declare_simd (parser, decl); > @@ -25199,6 +25319,90 @@ cp_parser_base_specifier (cp_parser* parser) > > /* Exception handling [gram.exception] */ > > +/* Save the tokens that make up the noexcept-specifier for a member-function. > + Returns a DEFAULT_ARG. */ > + > +static tree > +cp_parser_save_noexcept (cp_parser *parser) > +{ > + cp_token *first = parser->lexer->next_token; > + /* We want everything up to, including, the final ')'. */ > + cp_parser_cache_group (parser, CPP_CLOSE_PAREN, /*depth=*/0); > + cp_token *last = parser->lexer->next_token; > + > + /* As with default arguments and NSDMIs, make use of DEFAULT_ARG > + to carry the information we will need. */ > + tree expr = make_node (DEFAULT_ARG); > + /* Save away the noexcept-specifier; we will process it when the > + class is complete. */ > + DEFARG_TOKENS (expr) = cp_token_cache_new (first, last); > + DEFARG_DECL (expr) = NULL_TREE; > + DEFARG_NOEXCEPT_P (expr) = true; > + expr = build_tree_list (expr, NULL_TREE); > + return expr; > +} > + > +/* Used for late processing of noexcept-specifiers of member-functions. > + DEFAULT_ARG is the unparsed operand of a noexcept-specifier which > + we saved for later; parse it now. */ > + > +static tree > +cp_parser_late_noexcept_specifier (cp_parser *parser, tree default_arg) > +{ > + /* Make sure we've gotten something that hasn't been parsed yet. */ > + gcc_assert (TREE_CODE (default_arg) == DEFAULT_ARG); > + > + push_unparsed_function_queues (parser); > + > + /* Push the saved tokens for the noexcept-specifier onto the parser's > + lexer stack. */ > + cp_token_cache *tokens = DEFARG_TOKENS (default_arg); > + cp_parser_push_lexer_for_tokens (parser, tokens); > + > + /* Parse the cached noexcept-specifier. */ > + tree parsed_arg > + = cp_parser_noexcept_specification_opt (parser, > + /*require_constexpr=*/true, > + NULL, > + /*return_cond=*/false); > + > + /* Revert to the main lexer. */ > + cp_parser_pop_lexer (parser); > + > + /* Restore the queue. */ > + pop_unparsed_function_queues (parser); > + > + /* And we're done. */ > + return parsed_arg; > +} > + > +/* Perform late checking of overriding function with respect to their > + noexcept-specifiers. TYPE is the class and FNDECL is the function > + that potentially overrides some virtual function with the same > + signature. */ > + > +static void > +noexcept_override_late_checks (tree type, tree fndecl) > +{ > + tree binfo = TYPE_BINFO (type); > + tree base_binfo; > + > + if (DECL_STATIC_FUNCTION_P (fndecl)) > + return; > + > + for (int i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i) > + { > + tree basetype = BINFO_TYPE (base_binfo); > + > + if (!TYPE_POLYMORPHIC_P (basetype)) > + continue; > + > + tree fn = look_for_overrides_here (basetype, fndecl); > + if (fn) > + maybe_check_throw_specifier (fndecl, fn); > + } > +} > + > /* Parse an (optional) noexcept-specification. > > noexcept-specification: > @@ -25227,6 +25431,18 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, > if (cp_parser_is_keyword (token, RID_NOEXCEPT)) > { > tree expr; > + > + /* [class.mem]/6 says that a noexcept-specifer (within the > + member-specification of the class) is a complete-class context of > + a class. So, if the noexcept-specifier has the optional expression, > + just save the tokens, and reparse this after we're done with the > + class. */ > + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_OPEN_PAREN > + && at_class_scope_p () > + && TYPE_BEING_DEFINED (current_class_type) > + && !LAMBDA_TYPE_P (current_class_type)) > + return cp_parser_save_noexcept (parser); > + > cp_lexer_consume_token (parser->lexer); > > if (cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) > @@ -28427,7 +28643,12 @@ cp_parser_save_member_function_body (cp_parser* parser, > return error_mark_node; > } > > - /* Remember it, if there default args to post process. */ > + /* Remember if there is a noexcept-specifier to post process. */ > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fn)); > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > + vec_safe_push (unparsed_noexcepts, fn); > + > + /* Remember it, if there are default args to post process. */ > cp_parser_save_default_args (parser, fn); > > /* Save away the tokens that make up the body of the > diff --git gcc/cp/parser.h gcc/cp/parser.h > index c03a9d87af5..2890788f489 100644 > --- gcc/cp/parser.h > +++ gcc/cp/parser.h > @@ -166,6 +166,9 @@ struct GTY(()) cp_unparsed_functions_entry { > /* Nested classes go in this vector, so that we can do some final > processing after parsing any NSDMIs. */ > vec<tree, va_gc> *classes; > + > + /* Functions with noexcept-specifiers that require post-processing. */ > + vec<tree, va_gc> *noexcepts; > }; > > > diff --git gcc/cp/pt.c gcc/cp/pt.c > index d6976e08690..c00d14fd954 100644 > --- gcc/cp/pt.c > +++ gcc/cp/pt.c > @@ -25308,8 +25308,9 @@ dependent_type_p_r (tree type) > if (tree noex = TREE_PURPOSE (spec)) > /* Treat DEFERRED_NOEXCEPT as non-dependent, since it doesn't > affect overload resolution and treating it as dependent breaks > - things. */ > + things. Same for an unparsed noexcept expression. */ > if (TREE_CODE (noex) != DEFERRED_NOEXCEPT > + && TREE_CODE (noex) != DEFAULT_ARG > && value_dependent_expression_p (noex)) > return true; > return false; > diff --git gcc/cp/search.c gcc/cp/search.c > index 4c3fffda717..5a3a0cf2824 100644 > --- gcc/cp/search.c > +++ gcc/cp/search.c > @@ -1863,6 +1863,39 @@ locate_field_accessor (tree basetype_path, tree field_decl, bool const_p) > NULL, &lfd); > } > > +/* Check throw specifier of OVERRIDER is at least as strict as > + the one of BASEFN. */ > + > +bool > +maybe_check_throw_specifier (tree overrider, tree basefn) > +{ > + maybe_instantiate_noexcept (basefn); > + maybe_instantiate_noexcept (overrider); > + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > + > + if (DECL_INVALID_OVERRIDER_P (overrider)) > + return true; > + > + /* Can't check this yet. Pretend this is fine and let > + noexcept_override_late_checks check this later. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) > + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) > + return true; > + > + if (!comp_except_specs (base_throw, over_throw, ce_derived)) > + { > + auto_diagnostic_group d; > + error ("looser exception specification on overriding virtual function " > + "%q+#F", overrider); > + inform (DECL_SOURCE_LOCATION (basefn), > + "overridden function is %q#F", basefn); > + DECL_INVALID_OVERRIDER_P (overrider) = 1; > + return false; > + } > + return true; > +} > + > /* Check that virtual overrider OVERRIDER is acceptable for base function > BASEFN. Issue diagnostic, and return zero, if unacceptable. */ > > @@ -1873,7 +1906,6 @@ check_final_overrider (tree overrider, tree basefn) > tree base_type = TREE_TYPE (basefn); > tree over_return = fndecl_declared_return_type (overrider); > tree base_return = fndecl_declared_return_type (basefn); > - tree over_throw, base_throw; > > int fail = 0; > > @@ -1957,21 +1989,8 @@ check_final_overrider (tree overrider, tree basefn) > return 0; > } > > - /* Check throw specifier is at least as strict. */ > - maybe_instantiate_noexcept (basefn); > - maybe_instantiate_noexcept (overrider); > - base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > - over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > - > - if (!comp_except_specs (base_throw, over_throw, ce_derived)) > - { > - auto_diagnostic_group d; > - error ("looser throw specifier for %q+#F", overrider); > - inform (DECL_SOURCE_LOCATION (basefn), > - "overridden function is %q#F", basefn); > - DECL_INVALID_OVERRIDER_P (overrider) = 1; > - return 0; > - } > + if (!maybe_check_throw_specifier (overrider, basefn)) > + return 0; > > /* Check for conflicting type attributes. But leave transaction_safe for > set_one_vmethod_tm_attributes. */ > diff --git gcc/cp/tree.c gcc/cp/tree.c > index 718eed349c6..bc0080d6720 100644 > --- gcc/cp/tree.c > +++ gcc/cp/tree.c > @@ -2550,6 +2550,7 @@ canonical_eh_spec (tree raises) > if (raises == NULL_TREE) > return raises; > else if (DEFERRED_NOEXCEPT_SPEC_P (raises) > + || UNPARSED_NOEXCEPT_SPEC_P (raises) > || uses_template_parms (raises) > || uses_template_parms (TREE_PURPOSE (raises))) > /* Keep a dependent or deferred exception specification. */ > @@ -3662,6 +3663,7 @@ cp_tree_equal (tree t1, tree t2) > case IDENTIFIER_NODE: > case SSA_NAME: > case USING_DECL: > + case DEFAULT_ARG: > return false; > > case BASELINK: > diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c > index df002a1664c..8cbc48fb44f 100644 > --- gcc/cp/typeck2.c > +++ gcc/cp/typeck2.c > @@ -2393,6 +2393,12 @@ merge_exception_specifiers (tree list, tree add) > if (list == error_mark_node || add == error_mark_node) > return error_mark_node; > > + /* We don't want to lose the unparsed operand lest we miss diagnostics. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (list)) > + return list; > + else if (UNPARSED_NOEXCEPT_SPEC_P (add)) > + return add; > + > /* No exception-specifier or noexcept(false) are less strict than > anything else. Prefer the newer variant (LIST). */ > if (!list || list == noexcept_false_spec) > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept41.C gcc/testsuite/g++.dg/cpp0x/noexcept41.C > new file mode 100644 > index 00000000000..43b38c2446f > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept41.C > @@ -0,0 +1,147 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +#define SA(X) static_assert(X, #X) > + > +struct S { > + void f1() noexcept(noexcept(i)) { } > + void f2() noexcept(noexcept(fn())) { } > + void f3() noexcept(noexcept(fnx())) { } > + void f4() noexcept(noexcept(i)); > + void f5() noexcept(noexcept(fn())); > + void f6() noexcept(noexcept(fnx())); > + > + void f7() noexcept(1); > + void f8() noexcept(0); > + void f9() noexcept(b); > + void f10() noexcept(!b); > + > + int i; > + static constexpr auto b = true; > + void fny() noexcept(noexcept(fn())); > + void fn(); > + void fnx() noexcept; > +}; > + > +S s; > +SA(noexcept(s.f1())); > +SA(!noexcept(s.f2())); > +SA(noexcept(s.f3())); > +SA(noexcept(s.f4())); > +SA(!noexcept(s.f5())); > +SA(noexcept(s.f6())); > +SA(noexcept(s.f7())); > +SA(!noexcept(s.f8())); > +SA(noexcept(s.f9())); > +SA(!noexcept(s.f10())); > + > +struct S2 { > + struct V { > + void f1() noexcept(noexcept(fn())); > + void f2() noexcept(noexcept(fnx())); > + void f3() noexcept(noexcept(fn())) { } > + void f4() noexcept(noexcept(fnx())) { } > + void fn(); > + void fnx() noexcept; > + } v; > + void fn(); > + void fnx(); > +}; > + > +S2 s2; > +SA(!noexcept(s2.v.f1())); > +SA(noexcept(s2.v.f2())); > +SA(!noexcept(s2.v.f3())); > +SA(noexcept(s2.v.f4())); > + > +struct S3 { > + void f1() noexcept(noexcept(fn())); > + void f2() noexcept(noexcept(fnx())); > + void fn(); > + void fnx() noexcept; > +}; > + > +void > +S3::f1() noexcept(noexcept(fn())) > +{ > +} > + > +void > +S3::f2() noexcept(noexcept(fnx())) > +{ > +} > + > +struct S4 { > + int f1 (int p) noexcept(noexcept(p)) { return p; } > + int f2 (int p) noexcept(noexcept(p)); > + int f3 (int p = 10) noexcept(noexcept(p)); > + int f4 () noexcept(noexcept(S4{})); > +}; > + > +S4 s4; > +SA(noexcept(s4.f1(1))); > +SA(noexcept(s4.f2(1))); > +SA(noexcept(s4.f3())); > +SA(noexcept(s4.f4())); > + > +template<typename T> > +struct S5 { > + void f1() noexcept(noexcept(i)) { } > + void f2() noexcept(noexcept(fn())) { } > + void f3() noexcept(noexcept(fnx())) { } > + void f4() noexcept(noexcept(i)); > + void f5() noexcept(noexcept(fn())); > + void f6() noexcept(noexcept(fnx())); > + > + int i; > + void fny() noexcept(noexcept(fn())); > + void fn(); > + void fnx() noexcept; > +}; > + > +S5<int> s5; > +SA(noexcept(s5.f1())); > +SA(!noexcept(s5.f2())); > +SA(noexcept(s5.f3())); > +SA(noexcept(s5.f4())); > +SA(!noexcept(s5.f5())); > +SA(noexcept(s5.f6())); > + > +template<typename T> > +struct S6 { > + void f1() noexcept(noexcept(x)); > + T x; > +}; > + > +struct S7 { > + template<typename U> > + void f1 () noexcept(noexcept(U(1))) { } > + > + template<int N> > + void f2() noexcept(noexcept(N)); > + > + template <typename _Up> > + void f3(_Up __p) noexcept(noexcept(__p)); > +}; > + > +void glob(); > +void globx() noexcept; > +struct S8 { > + void f1 () noexcept(noexcept(glob())); > + void f2 () noexcept(noexcept(globx())); > +}; > + > +S8 s8; > +SA(!noexcept(s8.f1())); > +SA(noexcept(s8.f2())); > + > +struct W { > + constexpr operator bool(); > +}; > + > +template<typename T> > +struct S9 { > + S9() noexcept(noexcept(w)) { } > + S9 &operator=(S9 &&) noexcept(T::X); > + W w; > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept42.C gcc/testsuite/g++.dg/cpp0x/noexcept42.C > new file mode 100644 > index 00000000000..b3859de9ebc > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept42.C > @@ -0,0 +1,26 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +struct S { > + void f1() noexcept(noexcept(fn())); > + void f2() noexcept(noexcept(fnx())); > + void fn(); > + void fnx() noexcept; > +}; > + > +void > +S::f1() noexcept // { dg-error "different exception specifier" } > +{ > +} > + > +void > +S::f2() // { dg-error "different exception specifier" } > +{ > +} > + > +struct S2 { > + void f1() noexcept(noexcept(nosuchfn())); // { dg-error "not declared in this scope" } > + void f2() noexcept(noexcept(nothere)); // { dg-error "not declared in this scope" } > + void f3() noexcept(noexcept(nosuchfn())) { } // { dg-error "not declared in this scope" } > + void f4() noexcept(noexcept(nothere)) { } // { dg-error "not declared in this scope" } > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept43.C gcc/testsuite/g++.dg/cpp0x/noexcept43.C > new file mode 100644 > index 00000000000..12c6d364099 > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept43.C > @@ -0,0 +1,9 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +template <typename _Alloc> class A { > + typedef _Alloc _Alloc_traits; > + A &operator=(A &&) noexcept(_Alloc_traits::_S_nothrow_move); > + A &m_fn1(A &&) noexcept(_Alloc_traits::_S_nothrow_move); > + void m_fn2(A<char>) {} > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept44.C gcc/testsuite/g++.dg/cpp0x/noexcept44.C > new file mode 100644 > index 00000000000..a81032f28e9 > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept44.C > @@ -0,0 +1,14 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +void fn1(void()); > +template <typename> class A { > + void _M_local_data(); > + A() noexcept(_M_local_data); > +}; > + > +class B { > + void _S_initialize(); > + static void _S_initialize_once(); > +}; > +void B::_S_initialize() { fn1(_S_initialize_once); } > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept45.C gcc/testsuite/g++.dg/cpp0x/noexcept45.C > new file mode 100644 > index 00000000000..39df4a6571e > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept45.C > @@ -0,0 +1,23 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +struct A > +{ > + virtual void f(); > + virtual void g() noexcept; > + virtual void h() noexcept(false); > +}; > + > +struct B : A > +{ > + void f() noexcept(true); > + void g() noexcept(true); > + void h() noexcept(true); > +}; > + > +struct D : A > +{ > + void f() noexcept(false); > + void g() noexcept(false); // { dg-error "looser exception specification" } > + void h() noexcept(false); > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept46.C gcc/testsuite/g++.dg/cpp0x/noexcept46.C > new file mode 100644 > index 00000000000..da7490d651c > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept46.C > @@ -0,0 +1,28 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +void f() noexcept(false); > +void g() noexcept(true); > +void h() noexcept; > + > +struct B { > + friend void f() noexcept(false); > + friend void g() noexcept(false); // { dg-error "different exception specifier" } > + friend void h() noexcept(false); // { dg-error "different exception specifier" } > +}; > + > +struct C { > + friend void f() noexcept(true); // { dg-error "different exception specifier" } > + friend void g() noexcept(true); // { dg-error "different exception specifier" } > + friend void h() noexcept(true); // { dg-error "different exception specifier" } > +}; > + > +void o() noexcept(false); > +void p() noexcept(true); > +void q() noexcept; > + > +struct D { > + friend void o() noexcept(true); // { dg-error "different exception specifier" } > + friend void p() noexcept(true); > + friend void q() noexcept(true); > +}; > diff --git gcc/testsuite/g++.dg/eh/shadow1.C gcc/testsuite/g++.dg/eh/shadow1.C > index 0ba6145ef0c..6bccc704d49 100644 > --- gcc/testsuite/g++.dg/eh/shadow1.C > +++ gcc/testsuite/g++.dg/eh/shadow1.C > @@ -18,7 +18,7 @@ struct D : private B > // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } > struct E : public D > { > - virtual void V () throw (D); // { dg-error "looser throw" "" { target { ! c++17 } } } > + virtual void V () throw (D); // { dg-error "looser exception" "" { target { ! c++17 } } } > }; // { dg-error "dynamic exception specification" "" { target c++17 } .-1 } > // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } > B* foo (D *); Marek
Ping. On Fri, May 17, 2019 at 10:35:29AM -0400, Marek Polacek wrote: > Ping. > > On Fri, May 10, 2019 at 03:21:33PM -0400, Marek Polacek wrote: > > Coming back to this. I didn't think this was suitable for GCC 9. > > > > On Mon, Jan 07, 2019 at 10:44:37AM -0500, Jason Merrill wrote: > > > On 12/19/18 3:27 PM, Marek Polacek wrote: > > > > Prompted by Jon's observation in 52869, I noticed that we don't treat > > > > a noexcept-specifier as a complete-class context of a class ([class.mem]/6). > > > > As with member function bodies, default arguments, and NSDMIs, names used in > > > > a noexcept-specifier of a member-function can be declared later in the class > > > > body, so we need to wait and parse them at the end of the class. > > > > For that, I've made use of DEFAULT_ARG (now best to be renamed to UNPARSED_ARG). > > > > > > Or DEFERRED_PARSE, yes. > > > > I didn't change the name but I'm happy to do it as a follow up. > > > > > > + /* We can't compare unparsed noexcept-specifiers. Save the old decl > > > > + and check this again after we've parsed the noexcept-specifiers > > > > + for real. */ > > > > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > > > > + { > > > > + vec_safe_push (DEFARG_INSTANTIATIONS (TREE_PURPOSE (new_exceptions)), > > > > + copy_decl (old_decl)); > > > > + return; > > > > + } > > > > > > Why copy_decl? > > > > This is so that we don't lose the diagnostic in noexcept46.C. If I don't use > > copy_decl then the tree is shared and subsequent changes to it make us not > > detect discrepancies like noexcept(false) vs. noexcept(true) on the same decl. > > > > > It seems wasteful to allocate a vec to hold this single decl; let's make the > > > last field of tree_default_arg a union instead. And add a new macro for the > > > single decl case. > > > > Done. But that required also adding GTY markers *and* a new BOOL_BITFIELD for > > the sake of GTY((desc)). > > > > > I notice that default_arg currently uses tree_common for some reason, and we > > > ought to be able to save two words by switching to tree_base > > > > Done. > > > > > > @@ -1245,6 +1245,7 @@ nothrow_spec_p (const_tree spec) > > > > || TREE_VALUE (spec) > > > > || spec == noexcept_false_spec > > > > || TREE_PURPOSE (spec) == error_mark_node > > > > + || TREE_CODE (TREE_PURPOSE (spec)) == DEFAULT_ARG > > > > > > Maybe use UNPARSED_NOEXCEPT_SPEC_P here? > > > > Done. > > > > > > +/* Make sure that any member-function parameters are in scope. > > > > + For instance, a function's noexcept-specifier can use the function's > > > > + parameters: > > > > + > > > > + struct S { > > > > + void fn (int p) noexcept(noexcept(p)); > > > > + }; > > > > + > > > > + so we need to make sure name lookup can find them. This is used > > > > + when we delay parsing of the noexcept-specifier. */ > > > > + > > > > +static void > > > > +maybe_begin_member_function_processing (tree decl) > > > > > > This name is pretty misleading. How about inject_parm_decls, to go with > > > inject_this_parameter? > > > > Done. > > > > > > +/* Undo the effects of maybe_begin_member_function_processing. */ > > > > + > > > > +static void > > > > +maybe_end_member_function_processing (void) > > > > > > And then perhaps pop_injected_parms. > > > > Done. > > > > > > +/* Check throw specifier of OVERRIDER is at least as strict as > > > > + the one of BASEFN. */ > > > > + > > > > +bool > > > > +maybe_check_throw_specifier (tree overrider, tree basefn) > > > > +{ > > > > + maybe_instantiate_noexcept (basefn); > > > > + maybe_instantiate_noexcept (overrider); > > > > + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > > > > + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > > > > + > > > > + if (DECL_INVALID_OVERRIDER_P (overrider)) > > > > + return true; > > > > + > > > > + /* Can't check this yet. Pretend this is fine and let > > > > + noexcept_override_late_checks check this later. */ > > > > + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) > > > > + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) > > > > + return true; > > > > + > > > > + if (!comp_except_specs (base_throw, over_throw, ce_derived)) > > > > + { > > > > + auto_diagnostic_group d; > > > > + error ("looser throw specifier for %q+#F", overrider); > > > > > > Since we're touching this diagnostic, let's correct it now to "exception > > > specification". And add "on overriding virtual function". > > > > Ok, changed to the more up-to-date term. > > > > Two further changes were required since my changes to detecting 'this' for > > static member functions: > > 1) use THIS_FORBIDDEN if needed when parsing the delayed noexcept, > > 2) careful about friend member functions -- its DECL_CONTEXT is not the > > containing class, need to use DECL_FRIEND_CONTEXT. > > > > Both of these points are tested in g++.dg/cpp0x/this1.C. > > > > Bootstrapped/regtested on x86_64-linux, ok for trunk? > > > > 2019-05-10 Marek Polacek <polacek@redhat.com> > > > > PR c++/86476 - noexcept-specifier is a complete-class context. > > PR c++/52869 > > * cp-tree.def (DEFAULT_ARG): Update commentary. > > * cp-tree.h (DEFARG_DECL, DEFARG_NOEXCEPT_P, UNPARSED_NOEXCEPT_SPEC_P): > > New macros. > > (tree_default_arg): Add a tree field, make the last two fields into a > > union. Add GTY markers. > > (check_redeclaration_exception_specification): Declare. > > (maybe_check_throw_specifier): Declare. > > * decl.c (check_redeclaration_exception_specification): No longer > > static. Handle UNPARSED_NOEXCEPT_SPEC_P. > > * except.c (nothrow_spec_p): Accept DEFAULT_ARG in the assert. > > * parser.c (cp_parser_noexcept_specification_opt, > > cp_parser_late_noexcept_specifier, noexcept_override_late_checks): > > Forward-declare. > > (unparsed_noexcepts): New macro. > > (push_unparsed_function_queues): Update initializer. > > (cp_parser_init_declarator): Maybe save the noexcept-specifier to > > post process. > > (inject_parm_decls): New. > > (pop_injected_parms): New. > > (cp_parser_class_specifier_1): Implement delayed parsing of > > noexcept-specifiers. > > (cp_parser_member_declaration): Maybe save the noexcept-specifier to > > post process. > > (cp_parser_save_noexcept): New. > > (cp_parser_late_noexcept_specifier): New. > > (noexcept_override_late_checks): New. > > (cp_parser_noexcept_specification_opt): Call cp_parser_save_noexcept > > instead of the normal processing if needed. > > (cp_parser_save_member_function_body): Maybe save the > > noexcept-specifier to post process. > > * parser.h (cp_unparsed_functions_entry): Add new field to carry > > a noexcept-specifier. > > * pt.c (dependent_type_p_r): Handle unparsed noexcept expression. > > * search.c (maybe_check_throw_specifier): New function, broken out > > of... > > (check_final_overrider): ...here. Call maybe_check_throw_specifier. > > * tree.c (canonical_eh_spec): Handle UNPARSED_NOEXCEPT_SPEC_P. > > (cp_tree_equal): Handle DEFAULT_ARG. > > * typeck2.c (merge_exception_specifiers): If an unparsed noexcept > > expression has been passed, return it instead of merging it. > > > > * g++.dg/cpp0x/noexcept41.C: New test. > > * g++.dg/cpp0x/noexcept42.C: New test. > > * g++.dg/cpp0x/noexcept43.C: New test. > > * g++.dg/cpp0x/noexcept44.C: New test. > > * g++.dg/cpp0x/noexcept45.C: New test. > > * g++.dg/cpp0x/noexcept46.C: New test. > > * g++.dg/eh/shadow1.C: Adjust dg-error. > > > > diff --git gcc/cp/cp-tree.def gcc/cp/cp-tree.def > > index 03c105b5c4c..33eb5d25efe 100644 > > --- gcc/cp/cp-tree.def > > +++ gcc/cp/cp-tree.def > > @@ -209,7 +209,9 @@ DEFTREECODE (USING_STMT, "using_stmt", tcc_statement, 1) > > > > /* An un-parsed default argument. Holds a vector of input tokens and > > a vector of places where the argument was instantiated before > > - parsing had occurred. */ > > + parsing had occurred. This is also used for delayed NSDMIs and > > + noexcept-specifier parsing. For a noexcept-specifier, we use a tree > > + holding a function declaration used for late checking. */ > > DEFTREECODE (DEFAULT_ARG, "default_arg", tcc_exceptional, 0) > > > > /* An uninstantiated/unevaluated noexcept-specification. For the > > diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h > > index f253857b02a..ef14a011293 100644 > > --- gcc/cp/cp-tree.h > > +++ gcc/cp/cp-tree.h > > @@ -1178,12 +1178,20 @@ enum cp_identifier_kind { > > #define DEFARG_TOKENS(NODE) \ > > (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->tokens) > > #define DEFARG_INSTANTIATIONS(NODE) \ > > - (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->instantiations) > > + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->u.instantiations) > > +#define DEFARG_DECL(NODE) \ > > + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->u.decl) > > +#define DEFARG_NOEXCEPT_P(NODE) \ > > + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->noexcept_p) > > > > struct GTY (()) tree_default_arg { > > - struct tree_common common; > > + struct tree_base base; > > struct cp_token_cache *tokens; > > - vec<tree, va_gc> *instantiations; > > + BOOL_BITFIELD noexcept_p : 1; > > + union { > > + vec<tree, va_gc>* GTY((tag ("0"))) instantiations; > > + tree GTY((tag ("1"))) decl; > > + } GTY((desc ("%1.noexcept_p"))) u; > > }; > > > > > > @@ -1197,6 +1205,9 @@ struct GTY (()) tree_default_arg { > > #define UNEVALUATED_NOEXCEPT_SPEC_P(NODE) \ > > (DEFERRED_NOEXCEPT_SPEC_P (NODE) \ > > && DEFERRED_NOEXCEPT_PATTERN (TREE_PURPOSE (NODE)) == NULL_TREE) > > +#define UNPARSED_NOEXCEPT_SPEC_P(NODE) \ > > + ((NODE) && (TREE_PURPOSE (NODE)) \ > > + && (TREE_CODE (TREE_PURPOSE (NODE)) == DEFAULT_ARG)) > > > > struct GTY (()) tree_deferred_noexcept { > > struct tree_base base; > > @@ -6464,6 +6475,8 @@ extern bool check_array_designated_initializer (constructor_elt *, > > unsigned HOST_WIDE_INT); > > extern bool check_for_uninitialized_const_var (tree, bool, tsubst_flags_t); > > extern tree build_explicit_specifier (tree, tsubst_flags_t); > > +extern void check_redeclaration_exception_specification > > + (tree, tree); > > > > /* in decl2.c */ > > extern void record_mangling (tree, bool); > > @@ -6929,6 +6942,7 @@ extern tree copied_binfo (tree, tree); > > extern tree original_binfo (tree, tree); > > extern int shared_member_p (tree); > > extern bool any_dependent_bases_p (tree = current_nonlambda_class_type ()); > > +extern bool maybe_check_throw_specifier (tree, tree); > > > > /* The representation of a deferred access check. */ > > > > diff --git gcc/cp/decl.c gcc/cp/decl.c > > index 36014dc628e..a2effa13623 100644 > > --- gcc/cp/decl.c > > +++ gcc/cp/decl.c > > @@ -1139,7 +1139,7 @@ warn_extern_redeclared_static (tree newdecl, tree olddecl) > > function templates. If their exception specifications do not > > match, issue a diagnostic. */ > > > > -static void > > +void > > check_redeclaration_exception_specification (tree new_decl, > > tree old_decl) > > { > > @@ -1151,6 +1151,15 @@ check_redeclaration_exception_specification (tree new_decl, > > && UNEVALUATED_NOEXCEPT_SPEC_P (old_exceptions)) > > return; > > > > + /* We can't compare unparsed noexcept-specifiers. Save the old decl > > + and check this again after we've parsed the noexcept-specifiers > > + for real. */ > > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > > + { > > + DEFARG_DECL (TREE_PURPOSE (new_exceptions)) = copy_decl (old_decl); > > + return; > > + } > > + > > if (!type_dependent_expression_p (old_decl)) > > { > > maybe_instantiate_noexcept (new_decl); > > diff --git gcc/cp/except.c gcc/cp/except.c > > index afc261073d7..208c9c1461d 100644 > > --- gcc/cp/except.c > > +++ gcc/cp/except.c > > @@ -1248,6 +1248,7 @@ nothrow_spec_p (const_tree spec) > > || TREE_VALUE (spec) > > || spec == noexcept_false_spec > > || TREE_PURPOSE (spec) == error_mark_node > > + || UNPARSED_NOEXCEPT_SPEC_P (spec) > > || processing_template_decl); > > > > return false; > > diff --git gcc/cp/parser.c gcc/cp/parser.c > > index 12beadf5096..41197ab3486 100644 > > --- gcc/cp/parser.c > > +++ gcc/cp/parser.c > > @@ -247,6 +247,12 @@ static void cp_lexer_stop_debugging > > > > static cp_token_cache *cp_token_cache_new > > (cp_token *, cp_token *); > > +static tree cp_parser_noexcept_specification_opt > > + (cp_parser *, bool, bool *, bool); > > +static tree cp_parser_late_noexcept_specifier > > + (cp_parser *, tree); > > +static void noexcept_override_late_checks > > + (tree, tree); > > > > static void cp_parser_initial_pragma > > (cp_token *); > > @@ -1974,11 +1980,14 @@ cp_parser_context_new (cp_parser_context* next) > > parser->unparsed_queues->last ().nsdmis > > #define unparsed_classes \ > > parser->unparsed_queues->last ().classes > > +#define unparsed_noexcepts \ > > + parser->unparsed_queues->last ().noexcepts > > > > static void > > push_unparsed_function_queues (cp_parser *parser) > > { > > - cp_unparsed_functions_entry e = {NULL, make_tree_vector (), NULL, NULL}; > > + cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL, > > + NULL }; > > vec_safe_push (parser->unparsed_queues, e); > > } > > > > @@ -20515,7 +20524,13 @@ cp_parser_init_declarator (cp_parser* parser, > > /*asmspec=*/NULL_TREE, > > attr_chainon (attributes, prefix_attributes)); > > if (decl && TREE_CODE (decl) == FUNCTION_DECL) > > - cp_parser_save_default_args (parser, decl); > > + { > > + cp_parser_save_default_args (parser, decl); > > + /* Remember if there is a noexcept-specifier to post process. */ > > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > > + vec_safe_push (unparsed_noexcepts, decl); > > + } > > cp_finalize_omp_declare_simd (parser, decl); > > cp_finalize_oacc_routine (parser, decl, false); > > } > > @@ -23334,6 +23349,45 @@ cp_parser_class_name (cp_parser *parser, > > return decl; > > } > > > > +/* Make sure that any member-function parameters are in scope. > > + For instance, a function's noexcept-specifier can use the function's > > + parameters: > > + > > + struct S { > > + void fn (int p) noexcept(noexcept(p)); > > + }; > > + > > + so we need to make sure name lookup can find them. This is used > > + when we delay parsing of the noexcept-specifier. */ > > + > > +static void > > +inject_parm_decls (tree decl) > > +{ > > + begin_scope (sk_function_parms, decl); > > + tree args = DECL_ARGUMENTS (decl); > > + args = nreverse (args); > > + > > + tree next; > > + for (tree parm = args; parm; parm = next) > > + { > > + next = DECL_CHAIN (parm); > > + if (TREE_CODE (parm) == PARM_DECL) > > + pushdecl (parm); > > + } > > + /* Get the decls in their original chain order and record in the > > + function. This is all and only the PARM_DECLs that were > > + pushed into scope by the loop above. */ > > + DECL_ARGUMENTS (decl) = get_local_decls (); > > +} > > + > > +/* Undo the effects of inject_parm_decls. */ > > + > > +static void > > +pop_injected_parms (void) > > +{ > > + pop_bindings_and_leave_scope (); > > +} > > + > > /* Parse a class-specifier. > > > > class-specifier: > > @@ -23644,6 +23698,66 @@ cp_parser_class_specifier_1 (cp_parser* parser) > > vec_safe_truncate (unparsed_classes, 0); > > after_nsdmi_defaulted_late_checks (type); > > > > + /* If there are noexcept-specifiers that have not yet been processed, > > + take care of them now. */ > > + class_type = NULL_TREE; > > + pushed_scope = NULL_TREE; > > + FOR_EACH_VEC_SAFE_ELT (unparsed_noexcepts, ix, decl) > > + { > > + tree ctx = (DECL_FRIEND_P (decl) ? DECL_FRIEND_CONTEXT (decl) > > + : DECL_CONTEXT (decl)); > > + if (class_type != ctx) > > + { > > + if (pushed_scope) > > + pop_scope (pushed_scope); > > + class_type = ctx; > > + pushed_scope = push_scope (class_type); > > + } > > + > > + /* Make sure that any template parameters are in scope. */ > > + maybe_begin_member_template_processing (decl); > > + > > + /* Make sure that any member-function parameters are in scope. */ > > + inject_parm_decls (decl); > > + > > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > > + spec = TREE_PURPOSE (spec); > > + tree old_decl = DEFARG_DECL (spec); > > + > > + /* 'this' is not allowed in static member functions. */ > > + unsigned char local_variables_forbidden_p > > + = parser->local_variables_forbidden_p; > > + if (DECL_THIS_STATIC (decl)) > > + parser->local_variables_forbidden_p |= THIS_FORBIDDEN; > > + > > + /* Now we can parse the noexcept-specifier. */ > > + spec = cp_parser_late_noexcept_specifier (parser, spec); > > + > > + /* Restore the state of local_variables_forbidden_p. */ > > + parser->local_variables_forbidden_p = local_variables_forbidden_p; > > + if (spec != error_mark_node) > > + TREE_TYPE (decl) = build_exception_variant (TREE_TYPE (decl), spec); > > + > > + /* If we've stashed an old declaration, it means we need to > > + perform late redeclaration checking. */ > > + if (old_decl) > > + check_redeclaration_exception_specification (decl, old_decl); > > + > > + /* The finish_struct call above performed various override checking, > > + but it skipped unparsed noexcept-specifier operands. Now that we > > + have resolved them, check again. */ > > + noexcept_override_late_checks (type, decl); > > + > > + /* Remove any member-function parameters from the symbol table. */ > > + pop_injected_parms (); > > + > > + /* Remove any template parameters from the symbol table. */ > > + maybe_end_member_template_processing (); > > + } > > + vec_safe_truncate (unparsed_noexcepts, 0); > > + if (pushed_scope) > > + pop_scope (pushed_scope); > > + > > /* Now parse the body of the functions. */ > > if (flag_openmp) > > { > > @@ -24817,6 +24931,12 @@ cp_parser_member_declaration (cp_parser* parser) > > else > > decl = finish_fully_implicit_template (parser, decl); > > } > > + if (decl && TREE_CODE (decl) == FUNCTION_DECL) > > + { > > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > > + vec_safe_push (unparsed_noexcepts, decl); > > + } > > } > > > > cp_finalize_omp_declare_simd (parser, decl); > > @@ -25199,6 +25319,90 @@ cp_parser_base_specifier (cp_parser* parser) > > > > /* Exception handling [gram.exception] */ > > > > +/* Save the tokens that make up the noexcept-specifier for a member-function. > > + Returns a DEFAULT_ARG. */ > > + > > +static tree > > +cp_parser_save_noexcept (cp_parser *parser) > > +{ > > + cp_token *first = parser->lexer->next_token; > > + /* We want everything up to, including, the final ')'. */ > > + cp_parser_cache_group (parser, CPP_CLOSE_PAREN, /*depth=*/0); > > + cp_token *last = parser->lexer->next_token; > > + > > + /* As with default arguments and NSDMIs, make use of DEFAULT_ARG > > + to carry the information we will need. */ > > + tree expr = make_node (DEFAULT_ARG); > > + /* Save away the noexcept-specifier; we will process it when the > > + class is complete. */ > > + DEFARG_TOKENS (expr) = cp_token_cache_new (first, last); > > + DEFARG_DECL (expr) = NULL_TREE; > > + DEFARG_NOEXCEPT_P (expr) = true; > > + expr = build_tree_list (expr, NULL_TREE); > > + return expr; > > +} > > + > > +/* Used for late processing of noexcept-specifiers of member-functions. > > + DEFAULT_ARG is the unparsed operand of a noexcept-specifier which > > + we saved for later; parse it now. */ > > + > > +static tree > > +cp_parser_late_noexcept_specifier (cp_parser *parser, tree default_arg) > > +{ > > + /* Make sure we've gotten something that hasn't been parsed yet. */ > > + gcc_assert (TREE_CODE (default_arg) == DEFAULT_ARG); > > + > > + push_unparsed_function_queues (parser); > > + > > + /* Push the saved tokens for the noexcept-specifier onto the parser's > > + lexer stack. */ > > + cp_token_cache *tokens = DEFARG_TOKENS (default_arg); > > + cp_parser_push_lexer_for_tokens (parser, tokens); > > + > > + /* Parse the cached noexcept-specifier. */ > > + tree parsed_arg > > + = cp_parser_noexcept_specification_opt (parser, > > + /*require_constexpr=*/true, > > + NULL, > > + /*return_cond=*/false); > > + > > + /* Revert to the main lexer. */ > > + cp_parser_pop_lexer (parser); > > + > > + /* Restore the queue. */ > > + pop_unparsed_function_queues (parser); > > + > > + /* And we're done. */ > > + return parsed_arg; > > +} > > + > > +/* Perform late checking of overriding function with respect to their > > + noexcept-specifiers. TYPE is the class and FNDECL is the function > > + that potentially overrides some virtual function with the same > > + signature. */ > > + > > +static void > > +noexcept_override_late_checks (tree type, tree fndecl) > > +{ > > + tree binfo = TYPE_BINFO (type); > > + tree base_binfo; > > + > > + if (DECL_STATIC_FUNCTION_P (fndecl)) > > + return; > > + > > + for (int i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i) > > + { > > + tree basetype = BINFO_TYPE (base_binfo); > > + > > + if (!TYPE_POLYMORPHIC_P (basetype)) > > + continue; > > + > > + tree fn = look_for_overrides_here (basetype, fndecl); > > + if (fn) > > + maybe_check_throw_specifier (fndecl, fn); > > + } > > +} > > + > > /* Parse an (optional) noexcept-specification. > > > > noexcept-specification: > > @@ -25227,6 +25431,18 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, > > if (cp_parser_is_keyword (token, RID_NOEXCEPT)) > > { > > tree expr; > > + > > + /* [class.mem]/6 says that a noexcept-specifer (within the > > + member-specification of the class) is a complete-class context of > > + a class. So, if the noexcept-specifier has the optional expression, > > + just save the tokens, and reparse this after we're done with the > > + class. */ > > + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_OPEN_PAREN > > + && at_class_scope_p () > > + && TYPE_BEING_DEFINED (current_class_type) > > + && !LAMBDA_TYPE_P (current_class_type)) > > + return cp_parser_save_noexcept (parser); > > + > > cp_lexer_consume_token (parser->lexer); > > > > if (cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) > > @@ -28427,7 +28643,12 @@ cp_parser_save_member_function_body (cp_parser* parser, > > return error_mark_node; > > } > > > > - /* Remember it, if there default args to post process. */ > > + /* Remember if there is a noexcept-specifier to post process. */ > > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fn)); > > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > > + vec_safe_push (unparsed_noexcepts, fn); > > + > > + /* Remember it, if there are default args to post process. */ > > cp_parser_save_default_args (parser, fn); > > > > /* Save away the tokens that make up the body of the > > diff --git gcc/cp/parser.h gcc/cp/parser.h > > index c03a9d87af5..2890788f489 100644 > > --- gcc/cp/parser.h > > +++ gcc/cp/parser.h > > @@ -166,6 +166,9 @@ struct GTY(()) cp_unparsed_functions_entry { > > /* Nested classes go in this vector, so that we can do some final > > processing after parsing any NSDMIs. */ > > vec<tree, va_gc> *classes; > > + > > + /* Functions with noexcept-specifiers that require post-processing. */ > > + vec<tree, va_gc> *noexcepts; > > }; > > > > > > diff --git gcc/cp/pt.c gcc/cp/pt.c > > index d6976e08690..c00d14fd954 100644 > > --- gcc/cp/pt.c > > +++ gcc/cp/pt.c > > @@ -25308,8 +25308,9 @@ dependent_type_p_r (tree type) > > if (tree noex = TREE_PURPOSE (spec)) > > /* Treat DEFERRED_NOEXCEPT as non-dependent, since it doesn't > > affect overload resolution and treating it as dependent breaks > > - things. */ > > + things. Same for an unparsed noexcept expression. */ > > if (TREE_CODE (noex) != DEFERRED_NOEXCEPT > > + && TREE_CODE (noex) != DEFAULT_ARG > > && value_dependent_expression_p (noex)) > > return true; > > return false; > > diff --git gcc/cp/search.c gcc/cp/search.c > > index 4c3fffda717..5a3a0cf2824 100644 > > --- gcc/cp/search.c > > +++ gcc/cp/search.c > > @@ -1863,6 +1863,39 @@ locate_field_accessor (tree basetype_path, tree field_decl, bool const_p) > > NULL, &lfd); > > } > > > > +/* Check throw specifier of OVERRIDER is at least as strict as > > + the one of BASEFN. */ > > + > > +bool > > +maybe_check_throw_specifier (tree overrider, tree basefn) > > +{ > > + maybe_instantiate_noexcept (basefn); > > + maybe_instantiate_noexcept (overrider); > > + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > > + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > > + > > + if (DECL_INVALID_OVERRIDER_P (overrider)) > > + return true; > > + > > + /* Can't check this yet. Pretend this is fine and let > > + noexcept_override_late_checks check this later. */ > > + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) > > + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) > > + return true; > > + > > + if (!comp_except_specs (base_throw, over_throw, ce_derived)) > > + { > > + auto_diagnostic_group d; > > + error ("looser exception specification on overriding virtual function " > > + "%q+#F", overrider); > > + inform (DECL_SOURCE_LOCATION (basefn), > > + "overridden function is %q#F", basefn); > > + DECL_INVALID_OVERRIDER_P (overrider) = 1; > > + return false; > > + } > > + return true; > > +} > > + > > /* Check that virtual overrider OVERRIDER is acceptable for base function > > BASEFN. Issue diagnostic, and return zero, if unacceptable. */ > > > > @@ -1873,7 +1906,6 @@ check_final_overrider (tree overrider, tree basefn) > > tree base_type = TREE_TYPE (basefn); > > tree over_return = fndecl_declared_return_type (overrider); > > tree base_return = fndecl_declared_return_type (basefn); > > - tree over_throw, base_throw; > > > > int fail = 0; > > > > @@ -1957,21 +1989,8 @@ check_final_overrider (tree overrider, tree basefn) > > return 0; > > } > > > > - /* Check throw specifier is at least as strict. */ > > - maybe_instantiate_noexcept (basefn); > > - maybe_instantiate_noexcept (overrider); > > - base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > > - over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > > - > > - if (!comp_except_specs (base_throw, over_throw, ce_derived)) > > - { > > - auto_diagnostic_group d; > > - error ("looser throw specifier for %q+#F", overrider); > > - inform (DECL_SOURCE_LOCATION (basefn), > > - "overridden function is %q#F", basefn); > > - DECL_INVALID_OVERRIDER_P (overrider) = 1; > > - return 0; > > - } > > + if (!maybe_check_throw_specifier (overrider, basefn)) > > + return 0; > > > > /* Check for conflicting type attributes. But leave transaction_safe for > > set_one_vmethod_tm_attributes. */ > > diff --git gcc/cp/tree.c gcc/cp/tree.c > > index 718eed349c6..bc0080d6720 100644 > > --- gcc/cp/tree.c > > +++ gcc/cp/tree.c > > @@ -2550,6 +2550,7 @@ canonical_eh_spec (tree raises) > > if (raises == NULL_TREE) > > return raises; > > else if (DEFERRED_NOEXCEPT_SPEC_P (raises) > > + || UNPARSED_NOEXCEPT_SPEC_P (raises) > > || uses_template_parms (raises) > > || uses_template_parms (TREE_PURPOSE (raises))) > > /* Keep a dependent or deferred exception specification. */ > > @@ -3662,6 +3663,7 @@ cp_tree_equal (tree t1, tree t2) > > case IDENTIFIER_NODE: > > case SSA_NAME: > > case USING_DECL: > > + case DEFAULT_ARG: > > return false; > > > > case BASELINK: > > diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c > > index df002a1664c..8cbc48fb44f 100644 > > --- gcc/cp/typeck2.c > > +++ gcc/cp/typeck2.c > > @@ -2393,6 +2393,12 @@ merge_exception_specifiers (tree list, tree add) > > if (list == error_mark_node || add == error_mark_node) > > return error_mark_node; > > > > + /* We don't want to lose the unparsed operand lest we miss diagnostics. */ > > + if (UNPARSED_NOEXCEPT_SPEC_P (list)) > > + return list; > > + else if (UNPARSED_NOEXCEPT_SPEC_P (add)) > > + return add; > > + > > /* No exception-specifier or noexcept(false) are less strict than > > anything else. Prefer the newer variant (LIST). */ > > if (!list || list == noexcept_false_spec) > > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept41.C gcc/testsuite/g++.dg/cpp0x/noexcept41.C > > new file mode 100644 > > index 00000000000..43b38c2446f > > --- /dev/null > > +++ gcc/testsuite/g++.dg/cpp0x/noexcept41.C > > @@ -0,0 +1,147 @@ > > +// PR c++/86476 - noexcept-specifier is a complete-class context > > +// { dg-do compile { target c++11 } } > > + > > +#define SA(X) static_assert(X, #X) > > + > > +struct S { > > + void f1() noexcept(noexcept(i)) { } > > + void f2() noexcept(noexcept(fn())) { } > > + void f3() noexcept(noexcept(fnx())) { } > > + void f4() noexcept(noexcept(i)); > > + void f5() noexcept(noexcept(fn())); > > + void f6() noexcept(noexcept(fnx())); > > + > > + void f7() noexcept(1); > > + void f8() noexcept(0); > > + void f9() noexcept(b); > > + void f10() noexcept(!b); > > + > > + int i; > > + static constexpr auto b = true; > > + void fny() noexcept(noexcept(fn())); > > + void fn(); > > + void fnx() noexcept; > > +}; > > + > > +S s; > > +SA(noexcept(s.f1())); > > +SA(!noexcept(s.f2())); > > +SA(noexcept(s.f3())); > > +SA(noexcept(s.f4())); > > +SA(!noexcept(s.f5())); > > +SA(noexcept(s.f6())); > > +SA(noexcept(s.f7())); > > +SA(!noexcept(s.f8())); > > +SA(noexcept(s.f9())); > > +SA(!noexcept(s.f10())); > > + > > +struct S2 { > > + struct V { > > + void f1() noexcept(noexcept(fn())); > > + void f2() noexcept(noexcept(fnx())); > > + void f3() noexcept(noexcept(fn())) { } > > + void f4() noexcept(noexcept(fnx())) { } > > + void fn(); > > + void fnx() noexcept; > > + } v; > > + void fn(); > > + void fnx(); > > +}; > > + > > +S2 s2; > > +SA(!noexcept(s2.v.f1())); > > +SA(noexcept(s2.v.f2())); > > +SA(!noexcept(s2.v.f3())); > > +SA(noexcept(s2.v.f4())); > > + > > +struct S3 { > > + void f1() noexcept(noexcept(fn())); > > + void f2() noexcept(noexcept(fnx())); > > + void fn(); > > + void fnx() noexcept; > > +}; > > + > > +void > > +S3::f1() noexcept(noexcept(fn())) > > +{ > > +} > > + > > +void > > +S3::f2() noexcept(noexcept(fnx())) > > +{ > > +} > > + > > +struct S4 { > > + int f1 (int p) noexcept(noexcept(p)) { return p; } > > + int f2 (int p) noexcept(noexcept(p)); > > + int f3 (int p = 10) noexcept(noexcept(p)); > > + int f4 () noexcept(noexcept(S4{})); > > +}; > > + > > +S4 s4; > > +SA(noexcept(s4.f1(1))); > > +SA(noexcept(s4.f2(1))); > > +SA(noexcept(s4.f3())); > > +SA(noexcept(s4.f4())); > > + > > +template<typename T> > > +struct S5 { > > + void f1() noexcept(noexcept(i)) { } > > + void f2() noexcept(noexcept(fn())) { } > > + void f3() noexcept(noexcept(fnx())) { } > > + void f4() noexcept(noexcept(i)); > > + void f5() noexcept(noexcept(fn())); > > + void f6() noexcept(noexcept(fnx())); > > + > > + int i; > > + void fny() noexcept(noexcept(fn())); > > + void fn(); > > + void fnx() noexcept; > > +}; > > + > > +S5<int> s5; > > +SA(noexcept(s5.f1())); > > +SA(!noexcept(s5.f2())); > > +SA(noexcept(s5.f3())); > > +SA(noexcept(s5.f4())); > > +SA(!noexcept(s5.f5())); > > +SA(noexcept(s5.f6())); > > + > > +template<typename T> > > +struct S6 { > > + void f1() noexcept(noexcept(x)); > > + T x; > > +}; > > + > > +struct S7 { > > + template<typename U> > > + void f1 () noexcept(noexcept(U(1))) { } > > + > > + template<int N> > > + void f2() noexcept(noexcept(N)); > > + > > + template <typename _Up> > > + void f3(_Up __p) noexcept(noexcept(__p)); > > +}; > > + > > +void glob(); > > +void globx() noexcept; > > +struct S8 { > > + void f1 () noexcept(noexcept(glob())); > > + void f2 () noexcept(noexcept(globx())); > > +}; > > + > > +S8 s8; > > +SA(!noexcept(s8.f1())); > > +SA(noexcept(s8.f2())); > > + > > +struct W { > > + constexpr operator bool(); > > +}; > > + > > +template<typename T> > > +struct S9 { > > + S9() noexcept(noexcept(w)) { } > > + S9 &operator=(S9 &&) noexcept(T::X); > > + W w; > > +}; > > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept42.C gcc/testsuite/g++.dg/cpp0x/noexcept42.C > > new file mode 100644 > > index 00000000000..b3859de9ebc > > --- /dev/null > > +++ gcc/testsuite/g++.dg/cpp0x/noexcept42.C > > @@ -0,0 +1,26 @@ > > +// PR c++/86476 - noexcept-specifier is a complete-class context > > +// { dg-do compile { target c++11 } } > > + > > +struct S { > > + void f1() noexcept(noexcept(fn())); > > + void f2() noexcept(noexcept(fnx())); > > + void fn(); > > + void fnx() noexcept; > > +}; > > + > > +void > > +S::f1() noexcept // { dg-error "different exception specifier" } > > +{ > > +} > > + > > +void > > +S::f2() // { dg-error "different exception specifier" } > > +{ > > +} > > + > > +struct S2 { > > + void f1() noexcept(noexcept(nosuchfn())); // { dg-error "not declared in this scope" } > > + void f2() noexcept(noexcept(nothere)); // { dg-error "not declared in this scope" } > > + void f3() noexcept(noexcept(nosuchfn())) { } // { dg-error "not declared in this scope" } > > + void f4() noexcept(noexcept(nothere)) { } // { dg-error "not declared in this scope" } > > +}; > > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept43.C gcc/testsuite/g++.dg/cpp0x/noexcept43.C > > new file mode 100644 > > index 00000000000..12c6d364099 > > --- /dev/null > > +++ gcc/testsuite/g++.dg/cpp0x/noexcept43.C > > @@ -0,0 +1,9 @@ > > +// PR c++/86476 - noexcept-specifier is a complete-class context > > +// { dg-do compile { target c++11 } } > > + > > +template <typename _Alloc> class A { > > + typedef _Alloc _Alloc_traits; > > + A &operator=(A &&) noexcept(_Alloc_traits::_S_nothrow_move); > > + A &m_fn1(A &&) noexcept(_Alloc_traits::_S_nothrow_move); > > + void m_fn2(A<char>) {} > > +}; > > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept44.C gcc/testsuite/g++.dg/cpp0x/noexcept44.C > > new file mode 100644 > > index 00000000000..a81032f28e9 > > --- /dev/null > > +++ gcc/testsuite/g++.dg/cpp0x/noexcept44.C > > @@ -0,0 +1,14 @@ > > +// PR c++/86476 - noexcept-specifier is a complete-class context > > +// { dg-do compile { target c++11 } } > > + > > +void fn1(void()); > > +template <typename> class A { > > + void _M_local_data(); > > + A() noexcept(_M_local_data); > > +}; > > + > > +class B { > > + void _S_initialize(); > > + static void _S_initialize_once(); > > +}; > > +void B::_S_initialize() { fn1(_S_initialize_once); } > > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept45.C gcc/testsuite/g++.dg/cpp0x/noexcept45.C > > new file mode 100644 > > index 00000000000..39df4a6571e > > --- /dev/null > > +++ gcc/testsuite/g++.dg/cpp0x/noexcept45.C > > @@ -0,0 +1,23 @@ > > +// PR c++/86476 - noexcept-specifier is a complete-class context > > +// { dg-do compile { target c++11 } } > > + > > +struct A > > +{ > > + virtual void f(); > > + virtual void g() noexcept; > > + virtual void h() noexcept(false); > > +}; > > + > > +struct B : A > > +{ > > + void f() noexcept(true); > > + void g() noexcept(true); > > + void h() noexcept(true); > > +}; > > + > > +struct D : A > > +{ > > + void f() noexcept(false); > > + void g() noexcept(false); // { dg-error "looser exception specification" } > > + void h() noexcept(false); > > +}; > > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept46.C gcc/testsuite/g++.dg/cpp0x/noexcept46.C > > new file mode 100644 > > index 00000000000..da7490d651c > > --- /dev/null > > +++ gcc/testsuite/g++.dg/cpp0x/noexcept46.C > > @@ -0,0 +1,28 @@ > > +// PR c++/86476 - noexcept-specifier is a complete-class context > > +// { dg-do compile { target c++11 } } > > + > > +void f() noexcept(false); > > +void g() noexcept(true); > > +void h() noexcept; > > + > > +struct B { > > + friend void f() noexcept(false); > > + friend void g() noexcept(false); // { dg-error "different exception specifier" } > > + friend void h() noexcept(false); // { dg-error "different exception specifier" } > > +}; > > + > > +struct C { > > + friend void f() noexcept(true); // { dg-error "different exception specifier" } > > + friend void g() noexcept(true); // { dg-error "different exception specifier" } > > + friend void h() noexcept(true); // { dg-error "different exception specifier" } > > +}; > > + > > +void o() noexcept(false); > > +void p() noexcept(true); > > +void q() noexcept; > > + > > +struct D { > > + friend void o() noexcept(true); // { dg-error "different exception specifier" } > > + friend void p() noexcept(true); > > + friend void q() noexcept(true); > > +}; > > diff --git gcc/testsuite/g++.dg/eh/shadow1.C gcc/testsuite/g++.dg/eh/shadow1.C > > index 0ba6145ef0c..6bccc704d49 100644 > > --- gcc/testsuite/g++.dg/eh/shadow1.C > > +++ gcc/testsuite/g++.dg/eh/shadow1.C > > @@ -18,7 +18,7 @@ struct D : private B > > // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } > > struct E : public D > > { > > - virtual void V () throw (D); // { dg-error "looser throw" "" { target { ! c++17 } } } > > + virtual void V () throw (D); // { dg-error "looser exception" "" { target { ! c++17 } } } > > }; // { dg-error "dynamic exception specification" "" { target c++17 } .-1 } > > // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } > > B* foo (D *); > > Marek Marek
On 5/10/19 3:21 PM, Marek Polacek wrote: > Coming back to this. I didn't think this was suitable for GCC 9. > > On Mon, Jan 07, 2019 at 10:44:37AM -0500, Jason Merrill wrote: >> On 12/19/18 3:27 PM, Marek Polacek wrote: >>> Prompted by Jon's observation in 52869, I noticed that we don't treat >>> a noexcept-specifier as a complete-class context of a class ([class.mem]/6). >>> As with member function bodies, default arguments, and NSDMIs, names used in >>> a noexcept-specifier of a member-function can be declared later in the class >>> body, so we need to wait and parse them at the end of the class. >>> For that, I've made use of DEFAULT_ARG (now best to be renamed to UNPARSED_ARG). >> >> Or DEFERRED_PARSE, yes. > > I didn't change the name but I'm happy to do it as a follow up. > >>> + /* We can't compare unparsed noexcept-specifiers. Save the old decl >>> + and check this again after we've parsed the noexcept-specifiers >>> + for real. */ >>> + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) >>> + { >>> + vec_safe_push (DEFARG_INSTANTIATIONS (TREE_PURPOSE (new_exceptions)), >>> + copy_decl (old_decl)); >>> + return; >>> + } >> >> Why copy_decl? > > This is so that we don't lose the diagnostic in noexcept46.C. If I don't use > copy_decl then the tree is shared and subsequent changes to it make us not > detect discrepancies like noexcept(false) vs. noexcept(true) on the same decl. OK, fair enough. > @@ -20515,7 +20524,13 @@ cp_parser_init_declarator (cp_parser* parser, > /*asmspec=*/NULL_TREE, > attr_chainon (attributes, prefix_attributes)); > if (decl && TREE_CODE (decl) == FUNCTION_DECL) > - cp_parser_save_default_args (parser, decl); > + { > + cp_parser_save_default_args (parser, decl); > + /* Remember if there is a noexcept-specifier to post process. */ > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > + vec_safe_push (unparsed_noexcepts, decl); Can we handle this in cp_parser_save_default_args rather than all its callers? > +/* Make sure that any member-function parameters are in scope. > + For instance, a function's noexcept-specifier can use the function's > + parameters: > + > + struct S { > + void fn (int p) noexcept(noexcept(p)); > + }; > + > + so we need to make sure name lookup can find them. This is used > + when we delay parsing of the noexcept-specifier. */ > + > +static void > +inject_parm_decls (tree decl) > +{ > + begin_scope (sk_function_parms, decl); > + tree args = DECL_ARGUMENTS (decl); > + args = nreverse (args); > + > + tree next; > + for (tree parm = args; parm; parm = next) > + { > + next = DECL_CHAIN (parm); > + if (TREE_CODE (parm) == PARM_DECL) > + pushdecl (parm); > + } > + /* Get the decls in their original chain order and record in the > + function. This is all and only the PARM_DECLs that were > + pushed into scope by the loop above. */ > + DECL_ARGUMENTS (decl) = get_local_decls (); > +} Can we share this code with store_parm_decls instead of having two copies? > @@ -25227,6 +25431,18 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, > + /* [class.mem]/6 says that a noexcept-specifer (within the > + member-specification of the class) is a complete-class context of > + a class. So, if the noexcept-specifier has the optional expression, > + just save the tokens, and reparse this after we're done with the > + class. */ > + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_OPEN_PAREN > + && at_class_scope_p () > + && TYPE_BEING_DEFINED (current_class_type) > + && !LAMBDA_TYPE_P (current_class_type)) > + return cp_parser_save_noexcept (parser); We might optimize the noexcept(<literal>) case, which should be pretty common. > +maybe_check_throw_specifier (tree overrider, tree basefn) maybe_check_overriding_exception_spec > diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c > index df002a1664c..8cbc48fb44f 100644 > --- gcc/cp/typeck2.c > +++ gcc/cp/typeck2.c > @@ -2393,6 +2393,12 @@ merge_exception_specifiers (tree list, tree add) > if (list == error_mark_node || add == error_mark_node) > return error_mark_node; > > + /* We don't want to lose the unparsed operand lest we miss diagnostics. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (list)) > + return list; > + else if (UNPARSED_NOEXCEPT_SPEC_P (add)) > + return add; Here you're throwing away the other side, which seems wrong. Jason
On Tue, May 28, 2019 at 11:46:53AM -0400, Jason Merrill wrote: > > @@ -20515,7 +20524,13 @@ cp_parser_init_declarator (cp_parser* parser, > > /*asmspec=*/NULL_TREE, > > attr_chainon (attributes, prefix_attributes)); > > if (decl && TREE_CODE (decl) == FUNCTION_DECL) > > - cp_parser_save_default_args (parser, decl); > > + { > > + cp_parser_save_default_args (parser, decl); > > + /* Remember if there is a noexcept-specifier to post process. */ > > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > > + vec_safe_push (unparsed_noexcepts, decl); > > Can we handle this in cp_parser_save_default_args rather than all its > callers? Yes, done. > > +/* Make sure that any member-function parameters are in scope. > > + For instance, a function's noexcept-specifier can use the function's > > + parameters: > > + > > + struct S { > > + void fn (int p) noexcept(noexcept(p)); > > + }; > > + > > + so we need to make sure name lookup can find them. This is used > > + when we delay parsing of the noexcept-specifier. */ > > + > > +static void > > +inject_parm_decls (tree decl) > > +{ > > + begin_scope (sk_function_parms, decl); > > + tree args = DECL_ARGUMENTS (decl); > > + args = nreverse (args); > > + > > + tree next; > > + for (tree parm = args; parm; parm = next) > > + { > > + next = DECL_CHAIN (parm); > > + if (TREE_CODE (parm) == PARM_DECL) > > + pushdecl (parm); > > + } > > + /* Get the decls in their original chain order and record in the > > + function. This is all and only the PARM_DECLs that were > > + pushed into scope by the loop above. */ > > + DECL_ARGUMENTS (decl) = get_local_decls (); > > +} > > Can we share this code with store_parm_decls instead of having two copies? Makes sense, the nreverse logic is tricky enough not to duplicate it. I named the new function do_push_parm_decls. > > @@ -25227,6 +25431,18 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, > > + /* [class.mem]/6 says that a noexcept-specifer (within the > > + member-specification of the class) is a complete-class context of > > + a class. So, if the noexcept-specifier has the optional expression, > > + just save the tokens, and reparse this after we're done with the > > + class. */ > > + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_OPEN_PAREN > > + && at_class_scope_p () > > + && TYPE_BEING_DEFINED (current_class_type) > > + && !LAMBDA_TYPE_P (current_class_type)) > > + return cp_parser_save_noexcept (parser); > > We might optimize the noexcept(<literal>) case, which should be pretty > common. Yeah, it's worth it. Added for numbers/false/true. > > +maybe_check_throw_specifier (tree overrider, tree basefn) > > maybe_check_overriding_exception_spec Changed. > > diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c > > index df002a1664c..8cbc48fb44f 100644 > > --- gcc/cp/typeck2.c > > +++ gcc/cp/typeck2.c > > @@ -2393,6 +2393,12 @@ merge_exception_specifiers (tree list, tree add) > > if (list == error_mark_node || add == error_mark_node) > > return error_mark_node; > > + /* We don't want to lose the unparsed operand lest we miss diagnostics. */ > > + if (UNPARSED_NOEXCEPT_SPEC_P (list)) > > + return list; > > + else if (UNPARSED_NOEXCEPT_SPEC_P (add)) > > + return add; > > Here you're throwing away the other side, which seems wrong. I sort of ended up going down a rathole, but then I realized we don't need to delay parsing of noexcept-specifiers of member friend function declarations, because they aren't members of the class. This was a huge relief because member friend function declarations can be redeclared, so we'd have to make sure to check if their noexcept-specifiers match. But member function decls can't be redeclared. I updated the comment to better reflect why what I'm doing there is correct, along with an assert. noexcept47.C tests various cases with friend declarations. Thanks, Bootstrapped/regtested on x86_64-linux, ok for trunk? 2019-06-03 Marek Polacek <polacek@redhat.com> PR c++/86476 - noexcept-specifier is a complete-class context. PR c++/52869 * cp-tree.def (DEFAULT_ARG): Update commentary. * cp-tree.h (DEFARG_DECL, DEFARG_NOEXCEPT_P, UNPARSED_NOEXCEPT_SPEC_P): New macros. (tree_default_arg): Add a tree field, make the last two fields into a union. Add GTY markers. (check_redeclaration_exception_specification, do_push_parm_decls, maybe_check_overriding_exception_spec): Declare. * decl.c (check_redeclaration_exception_specification): No longer static. Handle UNPARSED_NOEXCEPT_SPEC_P. (do_push_parm_decls): New function, broken out of... (store_parm_decls): ...here. * except.c (nothrow_spec_p): Accept DEFAULT_ARG in the assert. * parser.c (cp_parser_noexcept_specification_opt, cp_parser_late_noexcept_specifier, noexcept_override_late_checks): Forward-declare. (unparsed_noexcepts): New macro. (push_unparsed_function_queues): Update initializer. (cp_parser_direct_declarator): Pass FRIEND_P to cp_parser_exception_specification_opt. (inject_parm_decls): New. (pop_injected_parms): New. (cp_parser_class_specifier_1): Implement delayed parsing of noexcept-specifiers. (cp_parser_save_noexcept): New. (cp_parser_late_noexcept_specifier): New. (noexcept_override_late_checks): New. (cp_parser_noexcept_specification_opt): Add FRIEND_P parameter. Call cp_parser_save_noexcept instead of the normal processing if needed. (cp_parser_exception_specification_opt): Add FRIEND_P parameter and pass it to cp_parser_noexcept_specification_opt. (cp_parser_save_member_function_body): Fix comment. (cp_parser_save_default_args): Maybe save the noexcept-specifier to post process. (cp_parser_transaction): Update call to cp_parser_noexcept_specification_opt. (cp_parser_transaction_expression): Likewise. * parser.h (cp_unparsed_functions_entry): Add new field to carry a noexcept-specifier. * pt.c (dependent_type_p_r): Handle unparsed noexcept expression. * search.c (maybe_check_overriding_exception_spec): New function, broken out of... (check_final_overrider): ...here. Call maybe_check_overriding_exception_spec. * tree.c (canonical_eh_spec): Handle UNPARSED_NOEXCEPT_SPEC_P. (cp_tree_equal): Handle DEFAULT_ARG. * typeck2.c (merge_exception_specifiers): If an unparsed noexcept expression has been passed, return it instead of merging it. * g++.dg/cpp0x/noexcept41.C: New test. * g++.dg/cpp0x/noexcept42.C: New test. * g++.dg/cpp0x/noexcept43.C: New test. * g++.dg/cpp0x/noexcept44.C: New test. * g++.dg/cpp0x/noexcept45.C: New test. * g++.dg/cpp0x/noexcept46.C: New test. * g++.dg/cpp0x/noexcept47.C: New test. * g++.dg/cpp0x/noexcept48.C: New test. * g++.dg/cpp0x/noexcept49.C: New test. * g++.dg/eh/shadow1.C: Adjust dg-error. diff --git gcc/cp/cp-tree.def gcc/cp/cp-tree.def index 03c105b5c4c..33eb5d25efe 100644 --- gcc/cp/cp-tree.def +++ gcc/cp/cp-tree.def @@ -209,7 +209,9 @@ DEFTREECODE (USING_STMT, "using_stmt", tcc_statement, 1) /* An un-parsed default argument. Holds a vector of input tokens and a vector of places where the argument was instantiated before - parsing had occurred. */ + parsing had occurred. This is also used for delayed NSDMIs and + noexcept-specifier parsing. For a noexcept-specifier, we use a tree + holding a function declaration used for late checking. */ DEFTREECODE (DEFAULT_ARG, "default_arg", tcc_exceptional, 0) /* An uninstantiated/unevaluated noexcept-specification. For the diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h index 4d79c43c5af..3f1d8a853db 100644 --- gcc/cp/cp-tree.h +++ gcc/cp/cp-tree.h @@ -1187,12 +1187,20 @@ enum cp_identifier_kind { #define DEFARG_TOKENS(NODE) \ (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->tokens) #define DEFARG_INSTANTIATIONS(NODE) \ - (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->instantiations) + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->u.instantiations) +#define DEFARG_DECL(NODE) \ + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->u.decl) +#define DEFARG_NOEXCEPT_P(NODE) \ + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->noexcept_p) struct GTY (()) tree_default_arg { - struct tree_common common; + struct tree_base base; struct cp_token_cache *tokens; - vec<tree, va_gc> *instantiations; + BOOL_BITFIELD noexcept_p : 1; + union { + vec<tree, va_gc>* GTY((tag ("0"))) instantiations; + tree GTY((tag ("1"))) decl; + } GTY((desc ("%1.noexcept_p"))) u; }; @@ -1206,6 +1214,9 @@ struct GTY (()) tree_default_arg { #define UNEVALUATED_NOEXCEPT_SPEC_P(NODE) \ (DEFERRED_NOEXCEPT_SPEC_P (NODE) \ && DEFERRED_NOEXCEPT_PATTERN (TREE_PURPOSE (NODE)) == NULL_TREE) +#define UNPARSED_NOEXCEPT_SPEC_P(NODE) \ + ((NODE) && (TREE_PURPOSE (NODE)) \ + && (TREE_CODE (TREE_PURPOSE (NODE)) == DEFAULT_ARG)) struct GTY (()) tree_deferred_noexcept { struct tree_base base; @@ -6467,6 +6478,9 @@ extern bool check_array_designated_initializer (constructor_elt *, unsigned HOST_WIDE_INT); extern bool check_for_uninitialized_const_var (tree, bool, tsubst_flags_t); extern tree build_explicit_specifier (tree, tsubst_flags_t); +extern void check_redeclaration_exception_specification + (tree, tree); +extern void do_push_parm_decls (tree, tree, tree *); /* in decl2.c */ extern void record_mangling (tree, bool); @@ -6932,6 +6947,7 @@ extern tree copied_binfo (tree, tree); extern tree original_binfo (tree, tree); extern int shared_member_p (tree); extern bool any_dependent_bases_p (tree = current_nonlambda_class_type ()); +extern bool maybe_check_overriding_exception_spec (tree, tree); /* The representation of a deferred access check. */ diff --git gcc/cp/decl.c gcc/cp/decl.c index bdf397e5ecb..e8fdc08bd4a 100644 --- gcc/cp/decl.c +++ gcc/cp/decl.c @@ -1139,7 +1139,7 @@ warn_extern_redeclared_static (tree newdecl, tree olddecl) function templates. If their exception specifications do not match, issue a diagnostic. */ -static void +void check_redeclaration_exception_specification (tree new_decl, tree old_decl) { @@ -1151,6 +1151,18 @@ check_redeclaration_exception_specification (tree new_decl, && UNEVALUATED_NOEXCEPT_SPEC_P (old_exceptions)) return; + /* We can't compare unparsed noexcept-specifiers. Save the decl + and check this again after we've parsed the noexcept-specifiers + for real. */ + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) + { + DEFARG_DECL (TREE_PURPOSE (new_exceptions)) = copy_decl (old_decl); + return; + } + else + /* Member functions can't be redeclared. */ + gcc_assert (!UNPARSED_NOEXCEPT_SPEC_P (old_exceptions)); + if (!type_dependent_expression_p (old_decl)) { maybe_instantiate_noexcept (new_decl); @@ -15706,6 +15718,39 @@ use_eh_spec_block (tree fn) && !DECL_DEFAULTED_FN (fn)); } +/* Helper function to push ARGS into the current lexical scope. DECL + is the function declaration. NONPARMS is used to handle enum + constants. */ + +void +do_push_parm_decls (tree decl, tree args, tree *nonparms) +{ + /* If we're doing semantic analysis, then we'll call pushdecl + for each of these. We must do them in reverse order so that + they end in the correct forward order. */ + args = nreverse (args); + + tree next; + for (tree parm = args; parm; parm = next) + { + next = DECL_CHAIN (parm); + if (TREE_CODE (parm) == PARM_DECL) + pushdecl (parm); + else if (nonparms) + { + /* If we find an enum constant or a type tag, put it aside for + the moment. */ + TREE_CHAIN (parm) = NULL_TREE; + *nonparms = chainon (*nonparms, parm); + } + } + + /* Get the decls in their original chain order and record in the + function. This is all and only the PARM_DECLs that were + pushed into scope by the loop above. */ + DECL_ARGUMENTS (decl) = get_local_decls (); +} + /* Store the parameter declarations into the current function declaration. This is called after parsing the parameter declarations, before digesting the body of the function. @@ -15716,7 +15761,6 @@ static void store_parm_decls (tree current_function_parms) { tree fndecl = current_function_decl; - tree parm; /* This is a chain of any other decls that came in among the parm declarations. If a parm is declared with enum {foo, bar} x; @@ -15731,35 +15775,12 @@ store_parm_decls (tree current_function_parms) and complain if any redundant old-style parm decls were written. */ tree specparms = current_function_parms; - tree next; /* Must clear this because it might contain TYPE_DECLs declared at class level. */ current_binding_level->names = NULL; - /* If we're doing semantic analysis, then we'll call pushdecl - for each of these. We must do them in reverse order so that - they end in the correct forward order. */ - specparms = nreverse (specparms); - - for (parm = specparms; parm; parm = next) - { - next = DECL_CHAIN (parm); - if (TREE_CODE (parm) == PARM_DECL) - pushdecl (parm); - else - { - /* If we find an enum constant or a type tag, - put it aside for the moment. */ - TREE_CHAIN (parm) = NULL_TREE; - nonparms = chainon (nonparms, parm); - } - } - - /* Get the decls in their original chain order and record in the - function. This is all and only the PARM_DECLs that were - pushed into scope by the loop above. */ - DECL_ARGUMENTS (fndecl) = get_local_decls (); + do_push_parm_decls (fndecl, specparms, &nonparms); } else DECL_ARGUMENTS (fndecl) = NULL_TREE; diff --git gcc/cp/except.c gcc/cp/except.c index 892d5201da9..8d7b1e9bac7 100644 --- gcc/cp/except.c +++ gcc/cp/except.c @@ -1245,6 +1245,7 @@ nothrow_spec_p (const_tree spec) || TREE_VALUE (spec) || spec == noexcept_false_spec || TREE_PURPOSE (spec) == error_mark_node + || UNPARSED_NOEXCEPT_SPEC_P (spec) || processing_template_decl); return false; diff --git gcc/cp/parser.c gcc/cp/parser.c index 1de35da83ec..ea4adbcdb14 100644 --- gcc/cp/parser.c +++ gcc/cp/parser.c @@ -247,6 +247,12 @@ static void cp_lexer_stop_debugging static cp_token_cache *cp_token_cache_new (cp_token *, cp_token *); +static tree cp_parser_noexcept_specification_opt + (cp_parser *, bool, bool *, bool, bool); +static tree cp_parser_late_noexcept_specifier + (cp_parser *, tree); +static void noexcept_override_late_checks + (tree, tree); static void cp_parser_initial_pragma (cp_token *); @@ -1974,11 +1980,14 @@ cp_parser_context_new (cp_parser_context* next) parser->unparsed_queues->last ().nsdmis #define unparsed_classes \ parser->unparsed_queues->last ().classes +#define unparsed_noexcepts \ + parser->unparsed_queues->last ().noexcepts static void push_unparsed_function_queues (cp_parser *parser) { - cp_unparsed_functions_entry e = {NULL, make_tree_vector (), NULL, NULL}; + cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL, + NULL }; vec_safe_push (parser->unparsed_queues, e); } @@ -2361,7 +2370,7 @@ static tree cp_parser_exception_declaration static tree cp_parser_throw_expression (cp_parser *); static tree cp_parser_exception_specification_opt - (cp_parser *); + (cp_parser *, bool = false); static tree cp_parser_type_id_list (cp_parser *); @@ -20804,7 +20813,7 @@ cp_parser_direct_declarator (cp_parser* parser, tree tx_qual = cp_parser_tx_qualifier_opt (parser); /* And the exception-specification. */ exception_specification - = cp_parser_exception_specification_opt (parser); + = cp_parser_exception_specification_opt (parser, friend_p); attrs = cp_parser_std_attribute_spec_seq (parser); @@ -23298,6 +23307,34 @@ cp_parser_class_name (cp_parser *parser, return decl; } +/* Make sure that any member-function parameters are in scope. + For instance, a function's noexcept-specifier can use the function's + parameters: + + struct S { + void fn (int p) noexcept(noexcept(p)); + }; + + so we need to make sure name lookup can find them. This is used + when we delay parsing of the noexcept-specifier. */ + +static void +inject_parm_decls (tree decl) +{ + begin_scope (sk_function_parms, decl); + tree args = DECL_ARGUMENTS (decl); + + do_push_parm_decls (decl, args, /*nonparms=*/NULL); +} + +/* Undo the effects of inject_parm_decls. */ + +static void +pop_injected_parms (void) +{ + pop_bindings_and_leave_scope (); +} + /* Parse a class-specifier. class-specifier: @@ -23608,6 +23645,67 @@ cp_parser_class_specifier_1 (cp_parser* parser) vec_safe_truncate (unparsed_classes, 0); after_nsdmi_defaulted_late_checks (type); + /* If there are noexcept-specifiers that have not yet been processed, + take care of them now. */ + class_type = NULL_TREE; + pushed_scope = NULL_TREE; + FOR_EACH_VEC_SAFE_ELT (unparsed_noexcepts, ix, decl) + { + tree ctx = DECL_CONTEXT (decl); + if (class_type != ctx) + { + if (pushed_scope) + pop_scope (pushed_scope); + class_type = ctx; + pushed_scope = push_scope (class_type); + } + + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + spec = TREE_PURPOSE (spec); + + /* Make sure that any template parameters are in scope. */ + maybe_begin_member_template_processing (decl); + + /* Make sure that any member-function parameters are in scope. */ + inject_parm_decls (decl); + + tree old_decl = DEFARG_DECL (spec); + + /* 'this' is not allowed in static member functions. */ + unsigned char local_variables_forbidden_p + = parser->local_variables_forbidden_p; + if (DECL_THIS_STATIC (decl)) + parser->local_variables_forbidden_p |= THIS_FORBIDDEN; + + /* Now we can parse the noexcept-specifier. */ + spec = cp_parser_late_noexcept_specifier (parser, spec); + + if (spec != error_mark_node) + TREE_TYPE (decl) = build_exception_variant (TREE_TYPE (decl), spec); + + /* If we've stashed an old declaration, it means we need to + perform late redeclaration checking. */ + if (old_decl) + check_redeclaration_exception_specification (decl, old_decl); + + /* Restore the state of local_variables_forbidden_p. */ + parser->local_variables_forbidden_p = local_variables_forbidden_p; + + /* The finish_struct call above performed various override checking, + but it skipped unparsed noexcept-specifier operands. Now that we + have resolved them, check again. */ + noexcept_override_late_checks (type, decl); + + /* Remove any member-function parameters from the symbol table. */ + pop_injected_parms (); + + /* Remove any template parameters from the symbol table. */ + maybe_end_member_template_processing (); + } + vec_safe_truncate (unparsed_noexcepts, 0); + if (pushed_scope) + pop_scope (pushed_scope); + /* Now parse the body of the functions. */ if (flag_openmp) { @@ -25163,6 +25261,91 @@ cp_parser_base_specifier (cp_parser* parser) /* Exception handling [gram.exception] */ +/* Save the tokens that make up the noexcept-specifier for a member-function. + Returns a DEFAULT_ARG. */ + +static tree +cp_parser_save_noexcept (cp_parser *parser) +{ + cp_token *first = parser->lexer->next_token; + /* We want everything up to, including, the final ')'. */ + cp_parser_cache_group (parser, CPP_CLOSE_PAREN, /*depth=*/0); + cp_token *last = parser->lexer->next_token; + + /* As with default arguments and NSDMIs, make use of DEFAULT_ARG + to carry the information we will need. */ + tree expr = make_node (DEFAULT_ARG); + /* Save away the noexcept-specifier; we will process it when the + class is complete. */ + DEFARG_TOKENS (expr) = cp_token_cache_new (first, last); + DEFARG_DECL (expr) = NULL_TREE; + DEFARG_NOEXCEPT_P (expr) = true; + expr = build_tree_list (expr, NULL_TREE); + return expr; +} + +/* Used for late processing of noexcept-specifiers of member-functions. + DEFAULT_ARG is the unparsed operand of a noexcept-specifier which + we saved for later; parse it now. */ + +static tree +cp_parser_late_noexcept_specifier (cp_parser *parser, tree default_arg) +{ + /* Make sure we've gotten something that hasn't been parsed yet. */ + gcc_assert (TREE_CODE (default_arg) == DEFAULT_ARG); + + push_unparsed_function_queues (parser); + + /* Push the saved tokens for the noexcept-specifier onto the parser's + lexer stack. */ + cp_token_cache *tokens = DEFARG_TOKENS (default_arg); + cp_parser_push_lexer_for_tokens (parser, tokens); + + /* Parse the cached noexcept-specifier. */ + tree parsed_arg + = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + /*consumed_expr=*/NULL, + /*return_cond=*/false, + /*friend_p=*/false); + + /* Revert to the main lexer. */ + cp_parser_pop_lexer (parser); + + /* Restore the queue. */ + pop_unparsed_function_queues (parser); + + /* And we're done. */ + return parsed_arg; +} + +/* Perform late checking of overriding function with respect to their + noexcept-specifiers. TYPE is the class and FNDECL is the function + that potentially overrides some virtual function with the same + signature. */ + +static void +noexcept_override_late_checks (tree type, tree fndecl) +{ + tree binfo = TYPE_BINFO (type); + tree base_binfo; + + if (DECL_STATIC_FUNCTION_P (fndecl)) + return; + + for (int i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i) + { + tree basetype = BINFO_TYPE (base_binfo); + + if (!TYPE_POLYMORPHIC_P (basetype)) + continue; + + tree fn = look_for_overrides_here (basetype, fndecl); + if (fn) + maybe_check_overriding_exception_spec (fndecl, fn); + } +} + /* Parse an (optional) noexcept-specification. noexcept-specification: @@ -25173,13 +25356,15 @@ cp_parser_base_specifier (cp_parser* parser) expression if parentheses follow noexcept, or return BOOLEAN_TRUE_NODE if there are no parentheses. CONSUMED_EXPR will be set accordingly. Otherwise, returns a noexcept specification unless RETURN_COND is true, - in which case a boolean condition is returned instead. */ + in which case a boolean condition is returned instead. If FRIEND_P is true, + the function with this noexcept-specification had the `friend' specifier. */ static tree cp_parser_noexcept_specification_opt (cp_parser* parser, bool require_constexpr, bool* consumed_expr, - bool return_cond) + bool return_cond, + bool friend_p) { cp_token *token; const char *saved_message; @@ -25191,6 +25376,26 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, if (cp_parser_is_keyword (token, RID_NOEXCEPT)) { tree expr; + + /* [class.mem]/6 says that a noexcept-specifer (within the + member-specification of the class) is a complete-class context of + a class. So, if the noexcept-specifier has the optional expression, + just save the tokens, and reparse this after we're done with the + class. */ + if (cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_PAREN) + /* No need to delay parsing for a number literal or true/false. */ + && !cp_lexer_nth_token_is (parser->lexer, 3, CPP_NUMBER) + && !(cp_lexer_nth_token_is (parser->lexer, 3, CPP_KEYWORD) + && (cp_lexer_nth_token_is_keyword (parser->lexer, 3, RID_FALSE) + || cp_lexer_nth_token_is_keyword (parser->lexer, 3, + RID_TRUE))) + && at_class_scope_p () + /* Don't delay parsing for friend member functions. */ + && !friend_p + && TYPE_BEING_DEFINED (current_class_type) + && !LAMBDA_TYPE_P (current_class_type)) + return cp_parser_save_noexcept (parser); + cp_lexer_consume_token (parser->lexer); if (cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) @@ -25261,10 +25466,11 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, throw ( type-id-list [opt] ) Returns a TREE_LIST representing the exception-specification. The - TREE_VALUE of each node is a type. */ + TREE_VALUE of each node is a type. If FRIEND_P is true, the function + with this noexcept-specification had the `friend' specifier. */ static tree -cp_parser_exception_specification_opt (cp_parser* parser) +cp_parser_exception_specification_opt (cp_parser* parser, bool friend_p) { cp_token *token; tree type_id_list; @@ -25274,8 +25480,12 @@ cp_parser_exception_specification_opt (cp_parser* parser) token = cp_lexer_peek_token (parser->lexer); /* Is it a noexcept-specification? */ - type_id_list = cp_parser_noexcept_specification_opt (parser, true, NULL, - false); + type_id_list + = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + /*consumed_expr=*/NULL, + /*return_cond=*/false, + friend_p); if (type_id_list != NULL_TREE) return type_id_list; @@ -28403,7 +28613,7 @@ cp_parser_save_member_function_body (cp_parser* parser, return error_mark_node; } - /* Remember it, if there default args to post process. */ + /* Remember it, if there are default args to post process. */ cp_parser_save_default_args (parser, fn); /* Save away the tokens that make up the body of the @@ -28696,6 +28906,11 @@ cp_parser_save_default_args (cp_parser* parser, tree decl) vec_safe_push (unparsed_funs_with_default_args, entry); break; } + + /* Remember if there is a noexcept-specifier to post process. */ + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) + vec_safe_push (unparsed_noexcepts, decl); } /* DEFAULT_ARG contains the saved tokens for the initializer of DECL, @@ -40506,7 +40721,11 @@ cp_parser_transaction (cp_parser *parser, cp_token *token) noex = NULL_TREE; } else - noex = cp_parser_noexcept_specification_opt (parser, true, NULL, true); + noex = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + /*consumed_expr=*/NULL, + /*return_cond=*/true, + /*friend_p=*/false); /* Keep track if we're in the lexical scope of an outer transaction. */ new_in = this_in | (old_in & TM_STMT_ATTR_OUTER); @@ -40566,8 +40785,11 @@ cp_parser_transaction_expression (cp_parser *parser, enum rid keyword) parser->in_transaction = this_in; /* Parse a noexcept specification. */ - noex = cp_parser_noexcept_specification_opt (parser, false, &noex_expr, - true); + noex = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/false, + &noex_expr, + /*return_cond=*/true, + /*friend_p=*/false); if (!noex || !noex_expr || cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) diff --git gcc/cp/parser.h gcc/cp/parser.h index c03a9d87af5..2890788f489 100644 --- gcc/cp/parser.h +++ gcc/cp/parser.h @@ -166,6 +166,9 @@ struct GTY(()) cp_unparsed_functions_entry { /* Nested classes go in this vector, so that we can do some final processing after parsing any NSDMIs. */ vec<tree, va_gc> *classes; + + /* Functions with noexcept-specifiers that require post-processing. */ + vec<tree, va_gc> *noexcepts; }; diff --git gcc/cp/pt.c gcc/cp/pt.c index cfbd9fd4c88..0324bd404e9 100644 --- gcc/cp/pt.c +++ gcc/cp/pt.c @@ -25302,8 +25302,9 @@ dependent_type_p_r (tree type) if (tree noex = TREE_PURPOSE (spec)) /* Treat DEFERRED_NOEXCEPT as non-dependent, since it doesn't affect overload resolution and treating it as dependent breaks - things. */ + things. Same for an unparsed noexcept expression. */ if (TREE_CODE (noex) != DEFERRED_NOEXCEPT + && TREE_CODE (noex) != DEFAULT_ARG && value_dependent_expression_p (noex)) return true; return false; diff --git gcc/cp/search.c gcc/cp/search.c index dac08d44d76..372c4424747 100644 --- gcc/cp/search.c +++ gcc/cp/search.c @@ -1860,6 +1860,39 @@ locate_field_accessor (tree basetype_path, tree field_decl, bool const_p) NULL, &lfd); } +/* Check throw specifier of OVERRIDER is at least as strict as + the one of BASEFN. */ + +bool +maybe_check_overriding_exception_spec (tree overrider, tree basefn) +{ + maybe_instantiate_noexcept (basefn); + maybe_instantiate_noexcept (overrider); + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); + + if (DECL_INVALID_OVERRIDER_P (overrider)) + return true; + + /* Can't check this yet. Pretend this is fine and let + noexcept_override_late_checks check this later. */ + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) + return true; + + if (!comp_except_specs (base_throw, over_throw, ce_derived)) + { + auto_diagnostic_group d; + error ("looser exception specification on overriding virtual function " + "%q+#F", overrider); + inform (DECL_SOURCE_LOCATION (basefn), + "overridden function is %q#F", basefn); + DECL_INVALID_OVERRIDER_P (overrider) = 1; + return false; + } + return true; +} + /* Check that virtual overrider OVERRIDER is acceptable for base function BASEFN. Issue diagnostic, and return zero, if unacceptable. */ @@ -1870,7 +1903,6 @@ check_final_overrider (tree overrider, tree basefn) tree base_type = TREE_TYPE (basefn); tree over_return = fndecl_declared_return_type (overrider); tree base_return = fndecl_declared_return_type (basefn); - tree over_throw, base_throw; int fail = 0; @@ -1954,21 +1986,8 @@ check_final_overrider (tree overrider, tree basefn) return 0; } - /* Check throw specifier is at least as strict. */ - maybe_instantiate_noexcept (basefn); - maybe_instantiate_noexcept (overrider); - base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); - over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); - - if (!comp_except_specs (base_throw, over_throw, ce_derived)) - { - auto_diagnostic_group d; - error ("looser throw specifier for %q+#F", overrider); - inform (DECL_SOURCE_LOCATION (basefn), - "overridden function is %q#F", basefn); - DECL_INVALID_OVERRIDER_P (overrider) = 1; - return 0; - } + if (!maybe_check_overriding_exception_spec (overrider, basefn)) + return 0; /* Check for conflicting type attributes. But leave transaction_safe for set_one_vmethod_tm_attributes. */ diff --git gcc/cp/tree.c gcc/cp/tree.c index cd021b7f594..81c53b23ebf 100644 --- gcc/cp/tree.c +++ gcc/cp/tree.c @@ -2546,6 +2546,7 @@ canonical_eh_spec (tree raises) if (raises == NULL_TREE) return raises; else if (DEFERRED_NOEXCEPT_SPEC_P (raises) + || UNPARSED_NOEXCEPT_SPEC_P (raises) || uses_template_parms (raises) || uses_template_parms (TREE_PURPOSE (raises))) /* Keep a dependent or deferred exception specification. */ @@ -3656,6 +3657,7 @@ cp_tree_equal (tree t1, tree t2) case IDENTIFIER_NODE: case SSA_NAME: case USING_DECL: + case DEFAULT_ARG: return false; case BASELINK: diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c index e9f759d4213..eec5550bf2d 100644 --- gcc/cp/typeck2.c +++ gcc/cp/typeck2.c @@ -2391,6 +2391,15 @@ merge_exception_specifiers (tree list, tree add) if (list == error_mark_node || add == error_mark_node) return error_mark_node; + /* We don't want to lose the unparsed operand lest we miss diagnostics. + We can use the newer variant, because the old one will be saved in + DEFARG_DECL's noexcept-specifier. */ + if (UNPARSED_NOEXCEPT_SPEC_P (list)) + return list; + else + /* Member functions can't be redeclared. */ + gcc_assert (!UNPARSED_NOEXCEPT_SPEC_P (add)); + /* No exception-specifier or noexcept(false) are less strict than anything else. Prefer the newer variant (LIST). */ if (!list || list == noexcept_false_spec) diff --git gcc/testsuite/g++.dg/cpp0x/noexcept41.C gcc/testsuite/g++.dg/cpp0x/noexcept41.C new file mode 100644 index 00000000000..43b38c2446f --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept41.C @@ -0,0 +1,147 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert(X, #X) + +struct S { + void f1() noexcept(noexcept(i)) { } + void f2() noexcept(noexcept(fn())) { } + void f3() noexcept(noexcept(fnx())) { } + void f4() noexcept(noexcept(i)); + void f5() noexcept(noexcept(fn())); + void f6() noexcept(noexcept(fnx())); + + void f7() noexcept(1); + void f8() noexcept(0); + void f9() noexcept(b); + void f10() noexcept(!b); + + int i; + static constexpr auto b = true; + void fny() noexcept(noexcept(fn())); + void fn(); + void fnx() noexcept; +}; + +S s; +SA(noexcept(s.f1())); +SA(!noexcept(s.f2())); +SA(noexcept(s.f3())); +SA(noexcept(s.f4())); +SA(!noexcept(s.f5())); +SA(noexcept(s.f6())); +SA(noexcept(s.f7())); +SA(!noexcept(s.f8())); +SA(noexcept(s.f9())); +SA(!noexcept(s.f10())); + +struct S2 { + struct V { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void f3() noexcept(noexcept(fn())) { } + void f4() noexcept(noexcept(fnx())) { } + void fn(); + void fnx() noexcept; + } v; + void fn(); + void fnx(); +}; + +S2 s2; +SA(!noexcept(s2.v.f1())); +SA(noexcept(s2.v.f2())); +SA(!noexcept(s2.v.f3())); +SA(noexcept(s2.v.f4())); + +struct S3 { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void fn(); + void fnx() noexcept; +}; + +void +S3::f1() noexcept(noexcept(fn())) +{ +} + +void +S3::f2() noexcept(noexcept(fnx())) +{ +} + +struct S4 { + int f1 (int p) noexcept(noexcept(p)) { return p; } + int f2 (int p) noexcept(noexcept(p)); + int f3 (int p = 10) noexcept(noexcept(p)); + int f4 () noexcept(noexcept(S4{})); +}; + +S4 s4; +SA(noexcept(s4.f1(1))); +SA(noexcept(s4.f2(1))); +SA(noexcept(s4.f3())); +SA(noexcept(s4.f4())); + +template<typename T> +struct S5 { + void f1() noexcept(noexcept(i)) { } + void f2() noexcept(noexcept(fn())) { } + void f3() noexcept(noexcept(fnx())) { } + void f4() noexcept(noexcept(i)); + void f5() noexcept(noexcept(fn())); + void f6() noexcept(noexcept(fnx())); + + int i; + void fny() noexcept(noexcept(fn())); + void fn(); + void fnx() noexcept; +}; + +S5<int> s5; +SA(noexcept(s5.f1())); +SA(!noexcept(s5.f2())); +SA(noexcept(s5.f3())); +SA(noexcept(s5.f4())); +SA(!noexcept(s5.f5())); +SA(noexcept(s5.f6())); + +template<typename T> +struct S6 { + void f1() noexcept(noexcept(x)); + T x; +}; + +struct S7 { + template<typename U> + void f1 () noexcept(noexcept(U(1))) { } + + template<int N> + void f2() noexcept(noexcept(N)); + + template <typename _Up> + void f3(_Up __p) noexcept(noexcept(__p)); +}; + +void glob(); +void globx() noexcept; +struct S8 { + void f1 () noexcept(noexcept(glob())); + void f2 () noexcept(noexcept(globx())); +}; + +S8 s8; +SA(!noexcept(s8.f1())); +SA(noexcept(s8.f2())); + +struct W { + constexpr operator bool(); +}; + +template<typename T> +struct S9 { + S9() noexcept(noexcept(w)) { } + S9 &operator=(S9 &&) noexcept(T::X); + W w; +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept42.C gcc/testsuite/g++.dg/cpp0x/noexcept42.C new file mode 100644 index 00000000000..b3859de9ebc --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept42.C @@ -0,0 +1,26 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +struct S { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void fn(); + void fnx() noexcept; +}; + +void +S::f1() noexcept // { dg-error "different exception specifier" } +{ +} + +void +S::f2() // { dg-error "different exception specifier" } +{ +} + +struct S2 { + void f1() noexcept(noexcept(nosuchfn())); // { dg-error "not declared in this scope" } + void f2() noexcept(noexcept(nothere)); // { dg-error "not declared in this scope" } + void f3() noexcept(noexcept(nosuchfn())) { } // { dg-error "not declared in this scope" } + void f4() noexcept(noexcept(nothere)) { } // { dg-error "not declared in this scope" } +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept43.C gcc/testsuite/g++.dg/cpp0x/noexcept43.C new file mode 100644 index 00000000000..12c6d364099 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept43.C @@ -0,0 +1,9 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +template <typename _Alloc> class A { + typedef _Alloc _Alloc_traits; + A &operator=(A &&) noexcept(_Alloc_traits::_S_nothrow_move); + A &m_fn1(A &&) noexcept(_Alloc_traits::_S_nothrow_move); + void m_fn2(A<char>) {} +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept44.C gcc/testsuite/g++.dg/cpp0x/noexcept44.C new file mode 100644 index 00000000000..a81032f28e9 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept44.C @@ -0,0 +1,14 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +void fn1(void()); +template <typename> class A { + void _M_local_data(); + A() noexcept(_M_local_data); +}; + +class B { + void _S_initialize(); + static void _S_initialize_once(); +}; +void B::_S_initialize() { fn1(_S_initialize_once); } diff --git gcc/testsuite/g++.dg/cpp0x/noexcept45.C gcc/testsuite/g++.dg/cpp0x/noexcept45.C new file mode 100644 index 00000000000..39df4a6571e --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept45.C @@ -0,0 +1,23 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +struct A +{ + virtual void f(); + virtual void g() noexcept; + virtual void h() noexcept(false); +}; + +struct B : A +{ + void f() noexcept(true); + void g() noexcept(true); + void h() noexcept(true); +}; + +struct D : A +{ + void f() noexcept(false); + void g() noexcept(false); // { dg-error "looser exception specification" } + void h() noexcept(false); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept46.C gcc/testsuite/g++.dg/cpp0x/noexcept46.C new file mode 100644 index 00000000000..da7490d651c --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept46.C @@ -0,0 +1,28 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +void f() noexcept(false); +void g() noexcept(true); +void h() noexcept; + +struct B { + friend void f() noexcept(false); + friend void g() noexcept(false); // { dg-error "different exception specifier" } + friend void h() noexcept(false); // { dg-error "different exception specifier" } +}; + +struct C { + friend void f() noexcept(true); // { dg-error "different exception specifier" } + friend void g() noexcept(true); // { dg-error "different exception specifier" } + friend void h() noexcept(true); // { dg-error "different exception specifier" } +}; + +void o() noexcept(false); +void p() noexcept(true); +void q() noexcept; + +struct D { + friend void o() noexcept(true); // { dg-error "different exception specifier" } + friend void p() noexcept(true); + friend void q() noexcept(true); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept47.C gcc/testsuite/g++.dg/cpp0x/noexcept47.C new file mode 100644 index 00000000000..0848e68f9b1 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept47.C @@ -0,0 +1,83 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +int fn1 (); +int fn2 () noexcept; +int fn3 () noexcept; + +void g() noexcept(noexcept (fn2())); + +struct S1 { + friend void g1() noexcept(noexcept(fn2())); + friend void g1() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } +}; + +struct S2 { + friend void g2() noexcept(noexcept(fn1())); + friend void g2() noexcept(noexcept(fn1())); + friend void g2() noexcept(noexcept(fn1())); +}; + +struct S3 { + friend void g3() noexcept(noexcept(fn1())); + friend void g3() noexcept(noexcept(fn3())); // { dg-error "different exception specifier" } +}; + +struct S4 { + friend void g4() noexcept(noexcept(fn2())); + friend void g4() noexcept(noexcept(fn3())); +}; + +struct S5 { + friend void g() noexcept(noexcept(fn3())); +}; + +struct S6 { + friend void g() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } +}; + +struct S7 { + friend void gg() noexcept(noexcept(fn3())); +}; + +void gg() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } + +struct S8 { + friend void g8(); + friend void g8() noexcept(noexcept(fn2())); // { dg-error "different exception specifier" } +}; + +struct S9 { + friend void g9(); + friend void g9() noexcept(noexcept(fn1())); +}; + +struct S10 { + friend void g10() noexcept(noexcept(fn1())); + friend void g10(); +}; + +struct S11 { + friend void g11() noexcept(noexcept(fn2())); + friend void g11(); // { dg-error "different exception specifier" } +}; + +struct S12 { + friend void g12() noexcept(false); + friend void g12() noexcept(noexcept(fn2())); // { dg-error "different exception specifier" } +}; + +struct S13 { + friend void g13() noexcept(false); + friend void g13() noexcept(noexcept(fn1())); +}; + +struct S14 { + friend void g14() noexcept(noexcept(fn1())); + friend void g14() noexcept(false); +}; + +struct S15 { + friend void g15() noexcept(noexcept(fn2())); + friend void g15() noexcept(false); // { dg-error "different exception specifier" } +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept48.C gcc/testsuite/g++.dg/cpp0x/noexcept48.C new file mode 100644 index 00000000000..134212c3613 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept48.C @@ -0,0 +1,11 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +int g; + +struct S { + int b; + friend void fn1(int n) noexcept(noexcept(n)); + friend void fn2() noexcept(noexcept(g)); + friend void fn3() noexcept(noexcept(b)); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept49.C gcc/testsuite/g++.dg/cpp0x/noexcept49.C new file mode 100644 index 00000000000..6da7ff3361f --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept49.C @@ -0,0 +1,12 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert(X, #X) + +struct S { + static void f1() noexcept(b); + static constexpr auto b = true; +}; + +S s; +SA(noexcept(s.f1())); diff --git gcc/testsuite/g++.dg/eh/shadow1.C gcc/testsuite/g++.dg/eh/shadow1.C index 0ba6145ef0c..6bccc704d49 100644 --- gcc/testsuite/g++.dg/eh/shadow1.C +++ gcc/testsuite/g++.dg/eh/shadow1.C @@ -18,7 +18,7 @@ struct D : private B // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } struct E : public D { - virtual void V () throw (D); // { dg-error "looser throw" "" { target { ! c++17 } } } + virtual void V () throw (D); // { dg-error "looser exception" "" { target { ! c++17 } } } }; // { dg-error "dynamic exception specification" "" { target c++17 } .-1 } // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } B* foo (D *);
Ping. On Mon, Jun 03, 2019 at 09:01:37PM -0400, Marek Polacek wrote: > On Tue, May 28, 2019 at 11:46:53AM -0400, Jason Merrill wrote: > > > @@ -20515,7 +20524,13 @@ cp_parser_init_declarator (cp_parser* parser, > > > /*asmspec=*/NULL_TREE, > > > attr_chainon (attributes, prefix_attributes)); > > > if (decl && TREE_CODE (decl) == FUNCTION_DECL) > > > - cp_parser_save_default_args (parser, decl); > > > + { > > > + cp_parser_save_default_args (parser, decl); > > > + /* Remember if there is a noexcept-specifier to post process. */ > > > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > > > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > > > + vec_safe_push (unparsed_noexcepts, decl); > > > > Can we handle this in cp_parser_save_default_args rather than all its > > callers? > > Yes, done. > > > > +/* Make sure that any member-function parameters are in scope. > > > + For instance, a function's noexcept-specifier can use the function's > > > + parameters: > > > + > > > + struct S { > > > + void fn (int p) noexcept(noexcept(p)); > > > + }; > > > + > > > + so we need to make sure name lookup can find them. This is used > > > + when we delay parsing of the noexcept-specifier. */ > > > + > > > +static void > > > +inject_parm_decls (tree decl) > > > +{ > > > + begin_scope (sk_function_parms, decl); > > > + tree args = DECL_ARGUMENTS (decl); > > > + args = nreverse (args); > > > + > > > + tree next; > > > + for (tree parm = args; parm; parm = next) > > > + { > > > + next = DECL_CHAIN (parm); > > > + if (TREE_CODE (parm) == PARM_DECL) > > > + pushdecl (parm); > > > + } > > > + /* Get the decls in their original chain order and record in the > > > + function. This is all and only the PARM_DECLs that were > > > + pushed into scope by the loop above. */ > > > + DECL_ARGUMENTS (decl) = get_local_decls (); > > > +} > > > > Can we share this code with store_parm_decls instead of having two copies? > > Makes sense, the nreverse logic is tricky enough not to duplicate it. I named > the new function do_push_parm_decls. > > > > @@ -25227,6 +25431,18 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, > > > + /* [class.mem]/6 says that a noexcept-specifer (within the > > > + member-specification of the class) is a complete-class context of > > > + a class. So, if the noexcept-specifier has the optional expression, > > > + just save the tokens, and reparse this after we're done with the > > > + class. */ > > > + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_OPEN_PAREN > > > + && at_class_scope_p () > > > + && TYPE_BEING_DEFINED (current_class_type) > > > + && !LAMBDA_TYPE_P (current_class_type)) > > > + return cp_parser_save_noexcept (parser); > > > > We might optimize the noexcept(<literal>) case, which should be pretty > > common. > > Yeah, it's worth it. Added for numbers/false/true. > > > > +maybe_check_throw_specifier (tree overrider, tree basefn) > > > > maybe_check_overriding_exception_spec > > Changed. > > > > diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c > > > index df002a1664c..8cbc48fb44f 100644 > > > --- gcc/cp/typeck2.c > > > +++ gcc/cp/typeck2.c > > > @@ -2393,6 +2393,12 @@ merge_exception_specifiers (tree list, tree add) > > > if (list == error_mark_node || add == error_mark_node) > > > return error_mark_node; > > > + /* We don't want to lose the unparsed operand lest we miss diagnostics. */ > > > + if (UNPARSED_NOEXCEPT_SPEC_P (list)) > > > + return list; > > > + else if (UNPARSED_NOEXCEPT_SPEC_P (add)) > > > + return add; > > > > Here you're throwing away the other side, which seems wrong. > > I sort of ended up going down a rathole, but then I realized we don't need to > delay parsing of noexcept-specifiers of member friend function declarations, > because they aren't members of the class. This was a huge relief because > member friend function declarations can be redeclared, so we'd have to make > sure to check if their noexcept-specifiers match. But member function decls > can't be redeclared. I updated the comment to better reflect why what I'm > doing there is correct, along with an assert. > > noexcept47.C tests various cases with friend declarations. > > Thanks, > > Bootstrapped/regtested on x86_64-linux, ok for trunk? > > 2019-06-03 Marek Polacek <polacek@redhat.com> > > PR c++/86476 - noexcept-specifier is a complete-class context. > PR c++/52869 > * cp-tree.def (DEFAULT_ARG): Update commentary. > * cp-tree.h (DEFARG_DECL, DEFARG_NOEXCEPT_P, UNPARSED_NOEXCEPT_SPEC_P): > New macros. > (tree_default_arg): Add a tree field, make the last two fields into a > union. Add GTY markers. > (check_redeclaration_exception_specification, do_push_parm_decls, > maybe_check_overriding_exception_spec): Declare. > * decl.c (check_redeclaration_exception_specification): No longer > static. Handle UNPARSED_NOEXCEPT_SPEC_P. > (do_push_parm_decls): New function, broken out of... > (store_parm_decls): ...here. > * except.c (nothrow_spec_p): Accept DEFAULT_ARG in the assert. > * parser.c (cp_parser_noexcept_specification_opt, > cp_parser_late_noexcept_specifier, noexcept_override_late_checks): > Forward-declare. > (unparsed_noexcepts): New macro. > (push_unparsed_function_queues): Update initializer. > (cp_parser_direct_declarator): Pass FRIEND_P to > cp_parser_exception_specification_opt. > (inject_parm_decls): New. > (pop_injected_parms): New. > (cp_parser_class_specifier_1): Implement delayed parsing of > noexcept-specifiers. > (cp_parser_save_noexcept): New. > (cp_parser_late_noexcept_specifier): New. > (noexcept_override_late_checks): New. > (cp_parser_noexcept_specification_opt): Add FRIEND_P parameter. Call > cp_parser_save_noexcept instead of the normal processing if needed. > (cp_parser_exception_specification_opt): Add FRIEND_P parameter and > pass it to cp_parser_noexcept_specification_opt. > (cp_parser_save_member_function_body): Fix comment. > (cp_parser_save_default_args): Maybe save the noexcept-specifier to > post process. > (cp_parser_transaction): Update call to > cp_parser_noexcept_specification_opt. > (cp_parser_transaction_expression): Likewise. > * parser.h (cp_unparsed_functions_entry): Add new field to carry > a noexcept-specifier. > * pt.c (dependent_type_p_r): Handle unparsed noexcept expression. > * search.c (maybe_check_overriding_exception_spec): New function, broken > out of... > (check_final_overrider): ...here. Call > maybe_check_overriding_exception_spec. > * tree.c (canonical_eh_spec): Handle UNPARSED_NOEXCEPT_SPEC_P. > (cp_tree_equal): Handle DEFAULT_ARG. > * typeck2.c (merge_exception_specifiers): If an unparsed noexcept > expression has been passed, return it instead of merging it. > > * g++.dg/cpp0x/noexcept41.C: New test. > * g++.dg/cpp0x/noexcept42.C: New test. > * g++.dg/cpp0x/noexcept43.C: New test. > * g++.dg/cpp0x/noexcept44.C: New test. > * g++.dg/cpp0x/noexcept45.C: New test. > * g++.dg/cpp0x/noexcept46.C: New test. > * g++.dg/cpp0x/noexcept47.C: New test. > * g++.dg/cpp0x/noexcept48.C: New test. > * g++.dg/cpp0x/noexcept49.C: New test. > * g++.dg/eh/shadow1.C: Adjust dg-error. > > diff --git gcc/cp/cp-tree.def gcc/cp/cp-tree.def > index 03c105b5c4c..33eb5d25efe 100644 > --- gcc/cp/cp-tree.def > +++ gcc/cp/cp-tree.def > @@ -209,7 +209,9 @@ DEFTREECODE (USING_STMT, "using_stmt", tcc_statement, 1) > > /* An un-parsed default argument. Holds a vector of input tokens and > a vector of places where the argument was instantiated before > - parsing had occurred. */ > + parsing had occurred. This is also used for delayed NSDMIs and > + noexcept-specifier parsing. For a noexcept-specifier, we use a tree > + holding a function declaration used for late checking. */ > DEFTREECODE (DEFAULT_ARG, "default_arg", tcc_exceptional, 0) > > /* An uninstantiated/unevaluated noexcept-specification. For the > diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h > index 4d79c43c5af..3f1d8a853db 100644 > --- gcc/cp/cp-tree.h > +++ gcc/cp/cp-tree.h > @@ -1187,12 +1187,20 @@ enum cp_identifier_kind { > #define DEFARG_TOKENS(NODE) \ > (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->tokens) > #define DEFARG_INSTANTIATIONS(NODE) \ > - (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->instantiations) > + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->u.instantiations) > +#define DEFARG_DECL(NODE) \ > + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->u.decl) > +#define DEFARG_NOEXCEPT_P(NODE) \ > + (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->noexcept_p) > > struct GTY (()) tree_default_arg { > - struct tree_common common; > + struct tree_base base; > struct cp_token_cache *tokens; > - vec<tree, va_gc> *instantiations; > + BOOL_BITFIELD noexcept_p : 1; > + union { > + vec<tree, va_gc>* GTY((tag ("0"))) instantiations; > + tree GTY((tag ("1"))) decl; > + } GTY((desc ("%1.noexcept_p"))) u; > }; > > > @@ -1206,6 +1214,9 @@ struct GTY (()) tree_default_arg { > #define UNEVALUATED_NOEXCEPT_SPEC_P(NODE) \ > (DEFERRED_NOEXCEPT_SPEC_P (NODE) \ > && DEFERRED_NOEXCEPT_PATTERN (TREE_PURPOSE (NODE)) == NULL_TREE) > +#define UNPARSED_NOEXCEPT_SPEC_P(NODE) \ > + ((NODE) && (TREE_PURPOSE (NODE)) \ > + && (TREE_CODE (TREE_PURPOSE (NODE)) == DEFAULT_ARG)) > > struct GTY (()) tree_deferred_noexcept { > struct tree_base base; > @@ -6467,6 +6478,9 @@ extern bool check_array_designated_initializer (constructor_elt *, > unsigned HOST_WIDE_INT); > extern bool check_for_uninitialized_const_var (tree, bool, tsubst_flags_t); > extern tree build_explicit_specifier (tree, tsubst_flags_t); > +extern void check_redeclaration_exception_specification > + (tree, tree); > +extern void do_push_parm_decls (tree, tree, tree *); > > /* in decl2.c */ > extern void record_mangling (tree, bool); > @@ -6932,6 +6947,7 @@ extern tree copied_binfo (tree, tree); > extern tree original_binfo (tree, tree); > extern int shared_member_p (tree); > extern bool any_dependent_bases_p (tree = current_nonlambda_class_type ()); > +extern bool maybe_check_overriding_exception_spec (tree, tree); > > /* The representation of a deferred access check. */ > > diff --git gcc/cp/decl.c gcc/cp/decl.c > index bdf397e5ecb..e8fdc08bd4a 100644 > --- gcc/cp/decl.c > +++ gcc/cp/decl.c > @@ -1139,7 +1139,7 @@ warn_extern_redeclared_static (tree newdecl, tree olddecl) > function templates. If their exception specifications do not > match, issue a diagnostic. */ > > -static void > +void > check_redeclaration_exception_specification (tree new_decl, > tree old_decl) > { > @@ -1151,6 +1151,18 @@ check_redeclaration_exception_specification (tree new_decl, > && UNEVALUATED_NOEXCEPT_SPEC_P (old_exceptions)) > return; > > + /* We can't compare unparsed noexcept-specifiers. Save the decl > + and check this again after we've parsed the noexcept-specifiers > + for real. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > + { > + DEFARG_DECL (TREE_PURPOSE (new_exceptions)) = copy_decl (old_decl); > + return; > + } > + else > + /* Member functions can't be redeclared. */ > + gcc_assert (!UNPARSED_NOEXCEPT_SPEC_P (old_exceptions)); > + > if (!type_dependent_expression_p (old_decl)) > { > maybe_instantiate_noexcept (new_decl); > @@ -15706,6 +15718,39 @@ use_eh_spec_block (tree fn) > && !DECL_DEFAULTED_FN (fn)); > } > > +/* Helper function to push ARGS into the current lexical scope. DECL > + is the function declaration. NONPARMS is used to handle enum > + constants. */ > + > +void > +do_push_parm_decls (tree decl, tree args, tree *nonparms) > +{ > + /* If we're doing semantic analysis, then we'll call pushdecl > + for each of these. We must do them in reverse order so that > + they end in the correct forward order. */ > + args = nreverse (args); > + > + tree next; > + for (tree parm = args; parm; parm = next) > + { > + next = DECL_CHAIN (parm); > + if (TREE_CODE (parm) == PARM_DECL) > + pushdecl (parm); > + else if (nonparms) > + { > + /* If we find an enum constant or a type tag, put it aside for > + the moment. */ > + TREE_CHAIN (parm) = NULL_TREE; > + *nonparms = chainon (*nonparms, parm); > + } > + } > + > + /* Get the decls in their original chain order and record in the > + function. This is all and only the PARM_DECLs that were > + pushed into scope by the loop above. */ > + DECL_ARGUMENTS (decl) = get_local_decls (); > +} > + > /* Store the parameter declarations into the current function declaration. > This is called after parsing the parameter declarations, before > digesting the body of the function. > @@ -15716,7 +15761,6 @@ static void > store_parm_decls (tree current_function_parms) > { > tree fndecl = current_function_decl; > - tree parm; > > /* This is a chain of any other decls that came in among the parm > declarations. If a parm is declared with enum {foo, bar} x; > @@ -15731,35 +15775,12 @@ store_parm_decls (tree current_function_parms) > and complain if any redundant old-style parm decls were written. */ > > tree specparms = current_function_parms; > - tree next; > > /* Must clear this because it might contain TYPE_DECLs declared > at class level. */ > current_binding_level->names = NULL; > > - /* If we're doing semantic analysis, then we'll call pushdecl > - for each of these. We must do them in reverse order so that > - they end in the correct forward order. */ > - specparms = nreverse (specparms); > - > - for (parm = specparms; parm; parm = next) > - { > - next = DECL_CHAIN (parm); > - if (TREE_CODE (parm) == PARM_DECL) > - pushdecl (parm); > - else > - { > - /* If we find an enum constant or a type tag, > - put it aside for the moment. */ > - TREE_CHAIN (parm) = NULL_TREE; > - nonparms = chainon (nonparms, parm); > - } > - } > - > - /* Get the decls in their original chain order and record in the > - function. This is all and only the PARM_DECLs that were > - pushed into scope by the loop above. */ > - DECL_ARGUMENTS (fndecl) = get_local_decls (); > + do_push_parm_decls (fndecl, specparms, &nonparms); > } > else > DECL_ARGUMENTS (fndecl) = NULL_TREE; > diff --git gcc/cp/except.c gcc/cp/except.c > index 892d5201da9..8d7b1e9bac7 100644 > --- gcc/cp/except.c > +++ gcc/cp/except.c > @@ -1245,6 +1245,7 @@ nothrow_spec_p (const_tree spec) > || TREE_VALUE (spec) > || spec == noexcept_false_spec > || TREE_PURPOSE (spec) == error_mark_node > + || UNPARSED_NOEXCEPT_SPEC_P (spec) > || processing_template_decl); > > return false; > diff --git gcc/cp/parser.c gcc/cp/parser.c > index 1de35da83ec..ea4adbcdb14 100644 > --- gcc/cp/parser.c > +++ gcc/cp/parser.c > @@ -247,6 +247,12 @@ static void cp_lexer_stop_debugging > > static cp_token_cache *cp_token_cache_new > (cp_token *, cp_token *); > +static tree cp_parser_noexcept_specification_opt > + (cp_parser *, bool, bool *, bool, bool); > +static tree cp_parser_late_noexcept_specifier > + (cp_parser *, tree); > +static void noexcept_override_late_checks > + (tree, tree); > > static void cp_parser_initial_pragma > (cp_token *); > @@ -1974,11 +1980,14 @@ cp_parser_context_new (cp_parser_context* next) > parser->unparsed_queues->last ().nsdmis > #define unparsed_classes \ > parser->unparsed_queues->last ().classes > +#define unparsed_noexcepts \ > + parser->unparsed_queues->last ().noexcepts > > static void > push_unparsed_function_queues (cp_parser *parser) > { > - cp_unparsed_functions_entry e = {NULL, make_tree_vector (), NULL, NULL}; > + cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL, > + NULL }; > vec_safe_push (parser->unparsed_queues, e); > } > > @@ -2361,7 +2370,7 @@ static tree cp_parser_exception_declaration > static tree cp_parser_throw_expression > (cp_parser *); > static tree cp_parser_exception_specification_opt > - (cp_parser *); > + (cp_parser *, bool = false); > static tree cp_parser_type_id_list > (cp_parser *); > > @@ -20804,7 +20813,7 @@ cp_parser_direct_declarator (cp_parser* parser, > tree tx_qual = cp_parser_tx_qualifier_opt (parser); > /* And the exception-specification. */ > exception_specification > - = cp_parser_exception_specification_opt (parser); > + = cp_parser_exception_specification_opt (parser, friend_p); > > attrs = cp_parser_std_attribute_spec_seq (parser); > > @@ -23298,6 +23307,34 @@ cp_parser_class_name (cp_parser *parser, > return decl; > } > > +/* Make sure that any member-function parameters are in scope. > + For instance, a function's noexcept-specifier can use the function's > + parameters: > + > + struct S { > + void fn (int p) noexcept(noexcept(p)); > + }; > + > + so we need to make sure name lookup can find them. This is used > + when we delay parsing of the noexcept-specifier. */ > + > +static void > +inject_parm_decls (tree decl) > +{ > + begin_scope (sk_function_parms, decl); > + tree args = DECL_ARGUMENTS (decl); > + > + do_push_parm_decls (decl, args, /*nonparms=*/NULL); > +} > + > +/* Undo the effects of inject_parm_decls. */ > + > +static void > +pop_injected_parms (void) > +{ > + pop_bindings_and_leave_scope (); > +} > + > /* Parse a class-specifier. > > class-specifier: > @@ -23608,6 +23645,67 @@ cp_parser_class_specifier_1 (cp_parser* parser) > vec_safe_truncate (unparsed_classes, 0); > after_nsdmi_defaulted_late_checks (type); > > + /* If there are noexcept-specifiers that have not yet been processed, > + take care of them now. */ > + class_type = NULL_TREE; > + pushed_scope = NULL_TREE; > + FOR_EACH_VEC_SAFE_ELT (unparsed_noexcepts, ix, decl) > + { > + tree ctx = DECL_CONTEXT (decl); > + if (class_type != ctx) > + { > + if (pushed_scope) > + pop_scope (pushed_scope); > + class_type = ctx; > + pushed_scope = push_scope (class_type); > + } > + > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > + spec = TREE_PURPOSE (spec); > + > + /* Make sure that any template parameters are in scope. */ > + maybe_begin_member_template_processing (decl); > + > + /* Make sure that any member-function parameters are in scope. */ > + inject_parm_decls (decl); > + > + tree old_decl = DEFARG_DECL (spec); > + > + /* 'this' is not allowed in static member functions. */ > + unsigned char local_variables_forbidden_p > + = parser->local_variables_forbidden_p; > + if (DECL_THIS_STATIC (decl)) > + parser->local_variables_forbidden_p |= THIS_FORBIDDEN; > + > + /* Now we can parse the noexcept-specifier. */ > + spec = cp_parser_late_noexcept_specifier (parser, spec); > + > + if (spec != error_mark_node) > + TREE_TYPE (decl) = build_exception_variant (TREE_TYPE (decl), spec); > + > + /* If we've stashed an old declaration, it means we need to > + perform late redeclaration checking. */ > + if (old_decl) > + check_redeclaration_exception_specification (decl, old_decl); > + > + /* Restore the state of local_variables_forbidden_p. */ > + parser->local_variables_forbidden_p = local_variables_forbidden_p; > + > + /* The finish_struct call above performed various override checking, > + but it skipped unparsed noexcept-specifier operands. Now that we > + have resolved them, check again. */ > + noexcept_override_late_checks (type, decl); > + > + /* Remove any member-function parameters from the symbol table. */ > + pop_injected_parms (); > + > + /* Remove any template parameters from the symbol table. */ > + maybe_end_member_template_processing (); > + } > + vec_safe_truncate (unparsed_noexcepts, 0); > + if (pushed_scope) > + pop_scope (pushed_scope); > + > /* Now parse the body of the functions. */ > if (flag_openmp) > { > @@ -25163,6 +25261,91 @@ cp_parser_base_specifier (cp_parser* parser) > > /* Exception handling [gram.exception] */ > > +/* Save the tokens that make up the noexcept-specifier for a member-function. > + Returns a DEFAULT_ARG. */ > + > +static tree > +cp_parser_save_noexcept (cp_parser *parser) > +{ > + cp_token *first = parser->lexer->next_token; > + /* We want everything up to, including, the final ')'. */ > + cp_parser_cache_group (parser, CPP_CLOSE_PAREN, /*depth=*/0); > + cp_token *last = parser->lexer->next_token; > + > + /* As with default arguments and NSDMIs, make use of DEFAULT_ARG > + to carry the information we will need. */ > + tree expr = make_node (DEFAULT_ARG); > + /* Save away the noexcept-specifier; we will process it when the > + class is complete. */ > + DEFARG_TOKENS (expr) = cp_token_cache_new (first, last); > + DEFARG_DECL (expr) = NULL_TREE; > + DEFARG_NOEXCEPT_P (expr) = true; > + expr = build_tree_list (expr, NULL_TREE); > + return expr; > +} > + > +/* Used for late processing of noexcept-specifiers of member-functions. > + DEFAULT_ARG is the unparsed operand of a noexcept-specifier which > + we saved for later; parse it now. */ > + > +static tree > +cp_parser_late_noexcept_specifier (cp_parser *parser, tree default_arg) > +{ > + /* Make sure we've gotten something that hasn't been parsed yet. */ > + gcc_assert (TREE_CODE (default_arg) == DEFAULT_ARG); > + > + push_unparsed_function_queues (parser); > + > + /* Push the saved tokens for the noexcept-specifier onto the parser's > + lexer stack. */ > + cp_token_cache *tokens = DEFARG_TOKENS (default_arg); > + cp_parser_push_lexer_for_tokens (parser, tokens); > + > + /* Parse the cached noexcept-specifier. */ > + tree parsed_arg > + = cp_parser_noexcept_specification_opt (parser, > + /*require_constexpr=*/true, > + /*consumed_expr=*/NULL, > + /*return_cond=*/false, > + /*friend_p=*/false); > + > + /* Revert to the main lexer. */ > + cp_parser_pop_lexer (parser); > + > + /* Restore the queue. */ > + pop_unparsed_function_queues (parser); > + > + /* And we're done. */ > + return parsed_arg; > +} > + > +/* Perform late checking of overriding function with respect to their > + noexcept-specifiers. TYPE is the class and FNDECL is the function > + that potentially overrides some virtual function with the same > + signature. */ > + > +static void > +noexcept_override_late_checks (tree type, tree fndecl) > +{ > + tree binfo = TYPE_BINFO (type); > + tree base_binfo; > + > + if (DECL_STATIC_FUNCTION_P (fndecl)) > + return; > + > + for (int i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i) > + { > + tree basetype = BINFO_TYPE (base_binfo); > + > + if (!TYPE_POLYMORPHIC_P (basetype)) > + continue; > + > + tree fn = look_for_overrides_here (basetype, fndecl); > + if (fn) > + maybe_check_overriding_exception_spec (fndecl, fn); > + } > +} > + > /* Parse an (optional) noexcept-specification. > > noexcept-specification: > @@ -25173,13 +25356,15 @@ cp_parser_base_specifier (cp_parser* parser) > expression if parentheses follow noexcept, or return BOOLEAN_TRUE_NODE if > there are no parentheses. CONSUMED_EXPR will be set accordingly. > Otherwise, returns a noexcept specification unless RETURN_COND is true, > - in which case a boolean condition is returned instead. */ > + in which case a boolean condition is returned instead. If FRIEND_P is true, > + the function with this noexcept-specification had the `friend' specifier. */ > > static tree > cp_parser_noexcept_specification_opt (cp_parser* parser, > bool require_constexpr, > bool* consumed_expr, > - bool return_cond) > + bool return_cond, > + bool friend_p) > { > cp_token *token; > const char *saved_message; > @@ -25191,6 +25376,26 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, > if (cp_parser_is_keyword (token, RID_NOEXCEPT)) > { > tree expr; > + > + /* [class.mem]/6 says that a noexcept-specifer (within the > + member-specification of the class) is a complete-class context of > + a class. So, if the noexcept-specifier has the optional expression, > + just save the tokens, and reparse this after we're done with the > + class. */ > + if (cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_PAREN) > + /* No need to delay parsing for a number literal or true/false. */ > + && !cp_lexer_nth_token_is (parser->lexer, 3, CPP_NUMBER) > + && !(cp_lexer_nth_token_is (parser->lexer, 3, CPP_KEYWORD) > + && (cp_lexer_nth_token_is_keyword (parser->lexer, 3, RID_FALSE) > + || cp_lexer_nth_token_is_keyword (parser->lexer, 3, > + RID_TRUE))) > + && at_class_scope_p () > + /* Don't delay parsing for friend member functions. */ > + && !friend_p > + && TYPE_BEING_DEFINED (current_class_type) > + && !LAMBDA_TYPE_P (current_class_type)) > + return cp_parser_save_noexcept (parser); > + > cp_lexer_consume_token (parser->lexer); > > if (cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) > @@ -25261,10 +25466,11 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, > throw ( type-id-list [opt] ) > > Returns a TREE_LIST representing the exception-specification. The > - TREE_VALUE of each node is a type. */ > + TREE_VALUE of each node is a type. If FRIEND_P is true, the function > + with this noexcept-specification had the `friend' specifier. */ > > static tree > -cp_parser_exception_specification_opt (cp_parser* parser) > +cp_parser_exception_specification_opt (cp_parser* parser, bool friend_p) > { > cp_token *token; > tree type_id_list; > @@ -25274,8 +25480,12 @@ cp_parser_exception_specification_opt (cp_parser* parser) > token = cp_lexer_peek_token (parser->lexer); > > /* Is it a noexcept-specification? */ > - type_id_list = cp_parser_noexcept_specification_opt (parser, true, NULL, > - false); > + type_id_list > + = cp_parser_noexcept_specification_opt (parser, > + /*require_constexpr=*/true, > + /*consumed_expr=*/NULL, > + /*return_cond=*/false, > + friend_p); > if (type_id_list != NULL_TREE) > return type_id_list; > > @@ -28403,7 +28613,7 @@ cp_parser_save_member_function_body (cp_parser* parser, > return error_mark_node; > } > > - /* Remember it, if there default args to post process. */ > + /* Remember it, if there are default args to post process. */ > cp_parser_save_default_args (parser, fn); > > /* Save away the tokens that make up the body of the > @@ -28696,6 +28906,11 @@ cp_parser_save_default_args (cp_parser* parser, tree decl) > vec_safe_push (unparsed_funs_with_default_args, entry); > break; > } > + > + /* Remember if there is a noexcept-specifier to post process. */ > + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); > + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) > + vec_safe_push (unparsed_noexcepts, decl); > } > > /* DEFAULT_ARG contains the saved tokens for the initializer of DECL, > @@ -40506,7 +40721,11 @@ cp_parser_transaction (cp_parser *parser, cp_token *token) > noex = NULL_TREE; > } > else > - noex = cp_parser_noexcept_specification_opt (parser, true, NULL, true); > + noex = cp_parser_noexcept_specification_opt (parser, > + /*require_constexpr=*/true, > + /*consumed_expr=*/NULL, > + /*return_cond=*/true, > + /*friend_p=*/false); > > /* Keep track if we're in the lexical scope of an outer transaction. */ > new_in = this_in | (old_in & TM_STMT_ATTR_OUTER); > @@ -40566,8 +40785,11 @@ cp_parser_transaction_expression (cp_parser *parser, enum rid keyword) > parser->in_transaction = this_in; > > /* Parse a noexcept specification. */ > - noex = cp_parser_noexcept_specification_opt (parser, false, &noex_expr, > - true); > + noex = cp_parser_noexcept_specification_opt (parser, > + /*require_constexpr=*/false, > + &noex_expr, > + /*return_cond=*/true, > + /*friend_p=*/false); > > if (!noex || !noex_expr > || cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) > diff --git gcc/cp/parser.h gcc/cp/parser.h > index c03a9d87af5..2890788f489 100644 > --- gcc/cp/parser.h > +++ gcc/cp/parser.h > @@ -166,6 +166,9 @@ struct GTY(()) cp_unparsed_functions_entry { > /* Nested classes go in this vector, so that we can do some final > processing after parsing any NSDMIs. */ > vec<tree, va_gc> *classes; > + > + /* Functions with noexcept-specifiers that require post-processing. */ > + vec<tree, va_gc> *noexcepts; > }; > > > diff --git gcc/cp/pt.c gcc/cp/pt.c > index cfbd9fd4c88..0324bd404e9 100644 > --- gcc/cp/pt.c > +++ gcc/cp/pt.c > @@ -25302,8 +25302,9 @@ dependent_type_p_r (tree type) > if (tree noex = TREE_PURPOSE (spec)) > /* Treat DEFERRED_NOEXCEPT as non-dependent, since it doesn't > affect overload resolution and treating it as dependent breaks > - things. */ > + things. Same for an unparsed noexcept expression. */ > if (TREE_CODE (noex) != DEFERRED_NOEXCEPT > + && TREE_CODE (noex) != DEFAULT_ARG > && value_dependent_expression_p (noex)) > return true; > return false; > diff --git gcc/cp/search.c gcc/cp/search.c > index dac08d44d76..372c4424747 100644 > --- gcc/cp/search.c > +++ gcc/cp/search.c > @@ -1860,6 +1860,39 @@ locate_field_accessor (tree basetype_path, tree field_decl, bool const_p) > NULL, &lfd); > } > > +/* Check throw specifier of OVERRIDER is at least as strict as > + the one of BASEFN. */ > + > +bool > +maybe_check_overriding_exception_spec (tree overrider, tree basefn) > +{ > + maybe_instantiate_noexcept (basefn); > + maybe_instantiate_noexcept (overrider); > + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > + > + if (DECL_INVALID_OVERRIDER_P (overrider)) > + return true; > + > + /* Can't check this yet. Pretend this is fine and let > + noexcept_override_late_checks check this later. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) > + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) > + return true; > + > + if (!comp_except_specs (base_throw, over_throw, ce_derived)) > + { > + auto_diagnostic_group d; > + error ("looser exception specification on overriding virtual function " > + "%q+#F", overrider); > + inform (DECL_SOURCE_LOCATION (basefn), > + "overridden function is %q#F", basefn); > + DECL_INVALID_OVERRIDER_P (overrider) = 1; > + return false; > + } > + return true; > +} > + > /* Check that virtual overrider OVERRIDER is acceptable for base function > BASEFN. Issue diagnostic, and return zero, if unacceptable. */ > > @@ -1870,7 +1903,6 @@ check_final_overrider (tree overrider, tree basefn) > tree base_type = TREE_TYPE (basefn); > tree over_return = fndecl_declared_return_type (overrider); > tree base_return = fndecl_declared_return_type (basefn); > - tree over_throw, base_throw; > > int fail = 0; > > @@ -1954,21 +1986,8 @@ check_final_overrider (tree overrider, tree basefn) > return 0; > } > > - /* Check throw specifier is at least as strict. */ > - maybe_instantiate_noexcept (basefn); > - maybe_instantiate_noexcept (overrider); > - base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); > - over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); > - > - if (!comp_except_specs (base_throw, over_throw, ce_derived)) > - { > - auto_diagnostic_group d; > - error ("looser throw specifier for %q+#F", overrider); > - inform (DECL_SOURCE_LOCATION (basefn), > - "overridden function is %q#F", basefn); > - DECL_INVALID_OVERRIDER_P (overrider) = 1; > - return 0; > - } > + if (!maybe_check_overriding_exception_spec (overrider, basefn)) > + return 0; > > /* Check for conflicting type attributes. But leave transaction_safe for > set_one_vmethod_tm_attributes. */ > diff --git gcc/cp/tree.c gcc/cp/tree.c > index cd021b7f594..81c53b23ebf 100644 > --- gcc/cp/tree.c > +++ gcc/cp/tree.c > @@ -2546,6 +2546,7 @@ canonical_eh_spec (tree raises) > if (raises == NULL_TREE) > return raises; > else if (DEFERRED_NOEXCEPT_SPEC_P (raises) > + || UNPARSED_NOEXCEPT_SPEC_P (raises) > || uses_template_parms (raises) > || uses_template_parms (TREE_PURPOSE (raises))) > /* Keep a dependent or deferred exception specification. */ > @@ -3656,6 +3657,7 @@ cp_tree_equal (tree t1, tree t2) > case IDENTIFIER_NODE: > case SSA_NAME: > case USING_DECL: > + case DEFAULT_ARG: > return false; > > case BASELINK: > diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c > index e9f759d4213..eec5550bf2d 100644 > --- gcc/cp/typeck2.c > +++ gcc/cp/typeck2.c > @@ -2391,6 +2391,15 @@ merge_exception_specifiers (tree list, tree add) > if (list == error_mark_node || add == error_mark_node) > return error_mark_node; > > + /* We don't want to lose the unparsed operand lest we miss diagnostics. > + We can use the newer variant, because the old one will be saved in > + DEFARG_DECL's noexcept-specifier. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (list)) > + return list; > + else > + /* Member functions can't be redeclared. */ > + gcc_assert (!UNPARSED_NOEXCEPT_SPEC_P (add)); > + > /* No exception-specifier or noexcept(false) are less strict than > anything else. Prefer the newer variant (LIST). */ > if (!list || list == noexcept_false_spec) > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept41.C gcc/testsuite/g++.dg/cpp0x/noexcept41.C > new file mode 100644 > index 00000000000..43b38c2446f > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept41.C > @@ -0,0 +1,147 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +#define SA(X) static_assert(X, #X) > + > +struct S { > + void f1() noexcept(noexcept(i)) { } > + void f2() noexcept(noexcept(fn())) { } > + void f3() noexcept(noexcept(fnx())) { } > + void f4() noexcept(noexcept(i)); > + void f5() noexcept(noexcept(fn())); > + void f6() noexcept(noexcept(fnx())); > + > + void f7() noexcept(1); > + void f8() noexcept(0); > + void f9() noexcept(b); > + void f10() noexcept(!b); > + > + int i; > + static constexpr auto b = true; > + void fny() noexcept(noexcept(fn())); > + void fn(); > + void fnx() noexcept; > +}; > + > +S s; > +SA(noexcept(s.f1())); > +SA(!noexcept(s.f2())); > +SA(noexcept(s.f3())); > +SA(noexcept(s.f4())); > +SA(!noexcept(s.f5())); > +SA(noexcept(s.f6())); > +SA(noexcept(s.f7())); > +SA(!noexcept(s.f8())); > +SA(noexcept(s.f9())); > +SA(!noexcept(s.f10())); > + > +struct S2 { > + struct V { > + void f1() noexcept(noexcept(fn())); > + void f2() noexcept(noexcept(fnx())); > + void f3() noexcept(noexcept(fn())) { } > + void f4() noexcept(noexcept(fnx())) { } > + void fn(); > + void fnx() noexcept; > + } v; > + void fn(); > + void fnx(); > +}; > + > +S2 s2; > +SA(!noexcept(s2.v.f1())); > +SA(noexcept(s2.v.f2())); > +SA(!noexcept(s2.v.f3())); > +SA(noexcept(s2.v.f4())); > + > +struct S3 { > + void f1() noexcept(noexcept(fn())); > + void f2() noexcept(noexcept(fnx())); > + void fn(); > + void fnx() noexcept; > +}; > + > +void > +S3::f1() noexcept(noexcept(fn())) > +{ > +} > + > +void > +S3::f2() noexcept(noexcept(fnx())) > +{ > +} > + > +struct S4 { > + int f1 (int p) noexcept(noexcept(p)) { return p; } > + int f2 (int p) noexcept(noexcept(p)); > + int f3 (int p = 10) noexcept(noexcept(p)); > + int f4 () noexcept(noexcept(S4{})); > +}; > + > +S4 s4; > +SA(noexcept(s4.f1(1))); > +SA(noexcept(s4.f2(1))); > +SA(noexcept(s4.f3())); > +SA(noexcept(s4.f4())); > + > +template<typename T> > +struct S5 { > + void f1() noexcept(noexcept(i)) { } > + void f2() noexcept(noexcept(fn())) { } > + void f3() noexcept(noexcept(fnx())) { } > + void f4() noexcept(noexcept(i)); > + void f5() noexcept(noexcept(fn())); > + void f6() noexcept(noexcept(fnx())); > + > + int i; > + void fny() noexcept(noexcept(fn())); > + void fn(); > + void fnx() noexcept; > +}; > + > +S5<int> s5; > +SA(noexcept(s5.f1())); > +SA(!noexcept(s5.f2())); > +SA(noexcept(s5.f3())); > +SA(noexcept(s5.f4())); > +SA(!noexcept(s5.f5())); > +SA(noexcept(s5.f6())); > + > +template<typename T> > +struct S6 { > + void f1() noexcept(noexcept(x)); > + T x; > +}; > + > +struct S7 { > + template<typename U> > + void f1 () noexcept(noexcept(U(1))) { } > + > + template<int N> > + void f2() noexcept(noexcept(N)); > + > + template <typename _Up> > + void f3(_Up __p) noexcept(noexcept(__p)); > +}; > + > +void glob(); > +void globx() noexcept; > +struct S8 { > + void f1 () noexcept(noexcept(glob())); > + void f2 () noexcept(noexcept(globx())); > +}; > + > +S8 s8; > +SA(!noexcept(s8.f1())); > +SA(noexcept(s8.f2())); > + > +struct W { > + constexpr operator bool(); > +}; > + > +template<typename T> > +struct S9 { > + S9() noexcept(noexcept(w)) { } > + S9 &operator=(S9 &&) noexcept(T::X); > + W w; > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept42.C gcc/testsuite/g++.dg/cpp0x/noexcept42.C > new file mode 100644 > index 00000000000..b3859de9ebc > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept42.C > @@ -0,0 +1,26 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +struct S { > + void f1() noexcept(noexcept(fn())); > + void f2() noexcept(noexcept(fnx())); > + void fn(); > + void fnx() noexcept; > +}; > + > +void > +S::f1() noexcept // { dg-error "different exception specifier" } > +{ > +} > + > +void > +S::f2() // { dg-error "different exception specifier" } > +{ > +} > + > +struct S2 { > + void f1() noexcept(noexcept(nosuchfn())); // { dg-error "not declared in this scope" } > + void f2() noexcept(noexcept(nothere)); // { dg-error "not declared in this scope" } > + void f3() noexcept(noexcept(nosuchfn())) { } // { dg-error "not declared in this scope" } > + void f4() noexcept(noexcept(nothere)) { } // { dg-error "not declared in this scope" } > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept43.C gcc/testsuite/g++.dg/cpp0x/noexcept43.C > new file mode 100644 > index 00000000000..12c6d364099 > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept43.C > @@ -0,0 +1,9 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +template <typename _Alloc> class A { > + typedef _Alloc _Alloc_traits; > + A &operator=(A &&) noexcept(_Alloc_traits::_S_nothrow_move); > + A &m_fn1(A &&) noexcept(_Alloc_traits::_S_nothrow_move); > + void m_fn2(A<char>) {} > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept44.C gcc/testsuite/g++.dg/cpp0x/noexcept44.C > new file mode 100644 > index 00000000000..a81032f28e9 > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept44.C > @@ -0,0 +1,14 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +void fn1(void()); > +template <typename> class A { > + void _M_local_data(); > + A() noexcept(_M_local_data); > +}; > + > +class B { > + void _S_initialize(); > + static void _S_initialize_once(); > +}; > +void B::_S_initialize() { fn1(_S_initialize_once); } > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept45.C gcc/testsuite/g++.dg/cpp0x/noexcept45.C > new file mode 100644 > index 00000000000..39df4a6571e > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept45.C > @@ -0,0 +1,23 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +struct A > +{ > + virtual void f(); > + virtual void g() noexcept; > + virtual void h() noexcept(false); > +}; > + > +struct B : A > +{ > + void f() noexcept(true); > + void g() noexcept(true); > + void h() noexcept(true); > +}; > + > +struct D : A > +{ > + void f() noexcept(false); > + void g() noexcept(false); // { dg-error "looser exception specification" } > + void h() noexcept(false); > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept46.C gcc/testsuite/g++.dg/cpp0x/noexcept46.C > new file mode 100644 > index 00000000000..da7490d651c > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept46.C > @@ -0,0 +1,28 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +void f() noexcept(false); > +void g() noexcept(true); > +void h() noexcept; > + > +struct B { > + friend void f() noexcept(false); > + friend void g() noexcept(false); // { dg-error "different exception specifier" } > + friend void h() noexcept(false); // { dg-error "different exception specifier" } > +}; > + > +struct C { > + friend void f() noexcept(true); // { dg-error "different exception specifier" } > + friend void g() noexcept(true); // { dg-error "different exception specifier" } > + friend void h() noexcept(true); // { dg-error "different exception specifier" } > +}; > + > +void o() noexcept(false); > +void p() noexcept(true); > +void q() noexcept; > + > +struct D { > + friend void o() noexcept(true); // { dg-error "different exception specifier" } > + friend void p() noexcept(true); > + friend void q() noexcept(true); > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept47.C gcc/testsuite/g++.dg/cpp0x/noexcept47.C > new file mode 100644 > index 00000000000..0848e68f9b1 > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept47.C > @@ -0,0 +1,83 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +int fn1 (); > +int fn2 () noexcept; > +int fn3 () noexcept; > + > +void g() noexcept(noexcept (fn2())); > + > +struct S1 { > + friend void g1() noexcept(noexcept(fn2())); > + friend void g1() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } > +}; > + > +struct S2 { > + friend void g2() noexcept(noexcept(fn1())); > + friend void g2() noexcept(noexcept(fn1())); > + friend void g2() noexcept(noexcept(fn1())); > +}; > + > +struct S3 { > + friend void g3() noexcept(noexcept(fn1())); > + friend void g3() noexcept(noexcept(fn3())); // { dg-error "different exception specifier" } > +}; > + > +struct S4 { > + friend void g4() noexcept(noexcept(fn2())); > + friend void g4() noexcept(noexcept(fn3())); > +}; > + > +struct S5 { > + friend void g() noexcept(noexcept(fn3())); > +}; > + > +struct S6 { > + friend void g() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } > +}; > + > +struct S7 { > + friend void gg() noexcept(noexcept(fn3())); > +}; > + > +void gg() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } > + > +struct S8 { > + friend void g8(); > + friend void g8() noexcept(noexcept(fn2())); // { dg-error "different exception specifier" } > +}; > + > +struct S9 { > + friend void g9(); > + friend void g9() noexcept(noexcept(fn1())); > +}; > + > +struct S10 { > + friend void g10() noexcept(noexcept(fn1())); > + friend void g10(); > +}; > + > +struct S11 { > + friend void g11() noexcept(noexcept(fn2())); > + friend void g11(); // { dg-error "different exception specifier" } > +}; > + > +struct S12 { > + friend void g12() noexcept(false); > + friend void g12() noexcept(noexcept(fn2())); // { dg-error "different exception specifier" } > +}; > + > +struct S13 { > + friend void g13() noexcept(false); > + friend void g13() noexcept(noexcept(fn1())); > +}; > + > +struct S14 { > + friend void g14() noexcept(noexcept(fn1())); > + friend void g14() noexcept(false); > +}; > + > +struct S15 { > + friend void g15() noexcept(noexcept(fn2())); > + friend void g15() noexcept(false); // { dg-error "different exception specifier" } > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept48.C gcc/testsuite/g++.dg/cpp0x/noexcept48.C > new file mode 100644 > index 00000000000..134212c3613 > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept48.C > @@ -0,0 +1,11 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +int g; > + > +struct S { > + int b; > + friend void fn1(int n) noexcept(noexcept(n)); > + friend void fn2() noexcept(noexcept(g)); > + friend void fn3() noexcept(noexcept(b)); > +}; > diff --git gcc/testsuite/g++.dg/cpp0x/noexcept49.C gcc/testsuite/g++.dg/cpp0x/noexcept49.C > new file mode 100644 > index 00000000000..6da7ff3361f > --- /dev/null > +++ gcc/testsuite/g++.dg/cpp0x/noexcept49.C > @@ -0,0 +1,12 @@ > +// PR c++/86476 - noexcept-specifier is a complete-class context > +// { dg-do compile { target c++11 } } > + > +#define SA(X) static_assert(X, #X) > + > +struct S { > + static void f1() noexcept(b); > + static constexpr auto b = true; > +}; > + > +S s; > +SA(noexcept(s.f1())); > diff --git gcc/testsuite/g++.dg/eh/shadow1.C gcc/testsuite/g++.dg/eh/shadow1.C > index 0ba6145ef0c..6bccc704d49 100644 > --- gcc/testsuite/g++.dg/eh/shadow1.C > +++ gcc/testsuite/g++.dg/eh/shadow1.C > @@ -18,7 +18,7 @@ struct D : private B > // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } > struct E : public D > { > - virtual void V () throw (D); // { dg-error "looser throw" "" { target { ! c++17 } } } > + virtual void V () throw (D); // { dg-error "looser exception" "" { target { ! c++17 } } } > }; // { dg-error "dynamic exception specification" "" { target c++17 } .-1 } > // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } > B* foo (D *); Marek
On 6/3/19 9:01 PM, Marek Polacek wrote: > I sort of ended up going down a rathole, but then I realized we don't need to > delay parsing of noexcept-specifiers of member friend function declarations, > because they aren't members of the class. Where are you getting this from? I'm definitely sympathetic to the idea that noexcept-specifiers of friend functions shouldn't need to be complete-class contexts, but 10.3 doesn't make that distinction that I can see. > This was a huge relief because > member friend function declarations can be redeclared, so we'd have to make > sure to check if their noexcept-specifiers match. But member function decls > can't be redeclared. I updated the comment to better reflect why what I'm > doing there is correct, along with an assert. But then why do you still need this: > + /* We can't compare unparsed noexcept-specifiers. Save the decl > + and check this again after we've parsed the noexcept-specifiers > + for real. */ > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > + { > + DEFARG_DECL (TREE_PURPOSE (new_exceptions)) = copy_decl (old_decl); > + return; > + } ? Jason
On Tue, Jun 11, 2019 at 11:46:05PM -0400, Jason Merrill wrote: > On 6/3/19 9:01 PM, Marek Polacek wrote: > > > I sort of ended up going down a rathole, but then I realized we don't need to > > delay parsing of noexcept-specifiers of member friend function declarations, > > because they aren't members of the class. > > Where are you getting this from? I'm definitely sympathetic to the idea > that noexcept-specifiers of friend functions shouldn't need to be > complete-class contexts, but 10.3 doesn't make that distinction that I can > see. When I tested my patch I noticed that none of the 3 compilers I tried handled this scenario, so I thought I was missing something. But if the standard really doesn't say that noexcept-specifiers of friend functions don't have to be complete-class contexts, then perhaps it needs to say so. Should I raise this on the reflector? > > This was a huge relief because > > member friend function declarations can be redeclared, so we'd have to make > > sure to check if their noexcept-specifiers match. But member function decls > > can't be redeclared. I updated the comment to better reflect why what I'm > > doing there is correct, along with an assert. > > But then why do you still need this: > > > + /* We can't compare unparsed noexcept-specifiers. Save the decl > > + and check this again after we've parsed the noexcept-specifiers > > + for real. */ > > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > > + { > > + DEFARG_DECL (TREE_PURPOSE (new_exceptions)) = copy_decl (old_decl); > > + return; > > + } > > ? Eh... I don't. The following version is with the DEFARG_DECL junk removed. Bootstrapped/regtested on x86_64-linux, ok for trunk? 2019-06-14 Marek Polacek <polacek@redhat.com> PR c++/86476 - noexcept-specifier is a complete-class context. PR c++/52869 * cp-tree.def (DEFAULT_ARG): Update commentary. * cp-tree.h (UNPARSED_NOEXCEPT_SPEC_P): New macro. (tree_default_arg): Use tree_base instead of tree_common. (do_push_parm_decls, maybe_check_overriding_exception_spec): Declare. * decl.c (do_push_parm_decls): New function, broken out of... (store_parm_decls): ...here. Call it. * except.c (nothrow_spec_p): Accept DEFAULT_ARG in the assert. * parser.c (cp_parser_noexcept_specification_opt, cp_parser_late_noexcept_specifier, noexcept_override_late_checks): Forward-declare. (unparsed_noexcepts): New macro. (push_unparsed_function_queues): Update initializer. (cp_parser_direct_declarator): Pass FRIEND_P to cp_parser_exception_specification_opt. (inject_parm_decls): New. (pop_injected_parms): New. (cp_parser_class_specifier_1): Implement delayed parsing of noexcept-specifiers. (cp_parser_save_noexcept): New. (cp_parser_late_noexcept_specifier): New. (noexcept_override_late_checks): New. (cp_parser_noexcept_specification_opt): Add FRIEND_P parameter. Call cp_parser_save_noexcept instead of the normal processing if needed. (cp_parser_exception_specification_opt): Add FRIEND_P parameter and pass it to cp_parser_noexcept_specification_opt. (cp_parser_save_member_function_body): Fix comment. (cp_parser_save_default_args): Maybe save the noexcept-specifier to post process. (cp_parser_transaction): Update call to cp_parser_noexcept_specification_opt. (cp_parser_transaction_expression): Likewise. * parser.h (cp_unparsed_functions_entry): Add new field to carry a noexcept-specifier. * pt.c (dependent_type_p_r): Handle unparsed noexcept expression. * search.c (maybe_check_overriding_exception_spec): New function, broken out of... (check_final_overrider): ...here. Call maybe_check_overriding_exception_spec. * tree.c (canonical_eh_spec): Handle UNPARSED_NOEXCEPT_SPEC_P. (cp_tree_equal): Handle DEFAULT_ARG. * g++.dg/cpp0x/noexcept42.C: New test. * g++.dg/cpp0x/noexcept43.C: New test. * g++.dg/cpp0x/noexcept44.C: New test. * g++.dg/cpp0x/noexcept45.C: New test. * g++.dg/cpp0x/noexcept46.C: New test. * g++.dg/cpp0x/noexcept47.C: New test. * g++.dg/cpp0x/noexcept48.C: New test. * g++.dg/cpp0x/noexcept49.C: New test. * g++.dg/cpp0x/noexcept50.C: New test. * g++.dg/eh/shadow1.C: Adjust dg-error. diff --git gcc/cp/cp-tree.def gcc/cp/cp-tree.def index 03c105b5c4c..475c584fd4c 100644 --- gcc/cp/cp-tree.def +++ gcc/cp/cp-tree.def @@ -209,7 +209,8 @@ DEFTREECODE (USING_STMT, "using_stmt", tcc_statement, 1) /* An un-parsed default argument. Holds a vector of input tokens and a vector of places where the argument was instantiated before - parsing had occurred. */ + parsing had occurred. This is also used for delayed NSDMIs and + noexcept-specifier parsing. */ DEFTREECODE (DEFAULT_ARG, "default_arg", tcc_exceptional, 0) /* An uninstantiated/unevaluated noexcept-specification. For the diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h index 1f4e1e15554..107a322cc82 100644 --- gcc/cp/cp-tree.h +++ gcc/cp/cp-tree.h @@ -1190,7 +1190,7 @@ enum cp_identifier_kind { (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->instantiations) struct GTY (()) tree_default_arg { - struct tree_common common; + struct tree_base base; struct cp_token_cache *tokens; vec<tree, va_gc> *instantiations; }; @@ -1206,6 +1206,9 @@ struct GTY (()) tree_default_arg { #define UNEVALUATED_NOEXCEPT_SPEC_P(NODE) \ (DEFERRED_NOEXCEPT_SPEC_P (NODE) \ && DEFERRED_NOEXCEPT_PATTERN (TREE_PURPOSE (NODE)) == NULL_TREE) +#define UNPARSED_NOEXCEPT_SPEC_P(NODE) \ + ((NODE) && (TREE_PURPOSE (NODE)) \ + && (TREE_CODE (TREE_PURPOSE (NODE)) == DEFAULT_ARG)) struct GTY (()) tree_deferred_noexcept { struct tree_base base; @@ -6467,6 +6470,7 @@ extern bool check_array_designated_initializer (constructor_elt *, unsigned HOST_WIDE_INT); extern bool check_for_uninitialized_const_var (tree, bool, tsubst_flags_t); extern tree build_explicit_specifier (tree, tsubst_flags_t); +extern void do_push_parm_decls (tree, tree, tree *); /* in decl2.c */ extern void record_mangling (tree, bool); @@ -6932,6 +6936,7 @@ extern tree copied_binfo (tree, tree); extern tree original_binfo (tree, tree); extern int shared_member_p (tree); extern bool any_dependent_bases_p (tree = current_nonlambda_class_type ()); +extern bool maybe_check_overriding_exception_spec (tree, tree); /* The representation of a deferred access check. */ diff --git gcc/cp/decl.c gcc/cp/decl.c index 0a3ef452536..43d7ba6a114 100644 --- gcc/cp/decl.c +++ gcc/cp/decl.c @@ -15720,6 +15720,39 @@ use_eh_spec_block (tree fn) && !DECL_DEFAULTED_FN (fn)); } +/* Helper function to push ARGS into the current lexical scope. DECL + is the function declaration. NONPARMS is used to handle enum + constants. */ + +void +do_push_parm_decls (tree decl, tree args, tree *nonparms) +{ + /* If we're doing semantic analysis, then we'll call pushdecl + for each of these. We must do them in reverse order so that + they end in the correct forward order. */ + args = nreverse (args); + + tree next; + for (tree parm = args; parm; parm = next) + { + next = DECL_CHAIN (parm); + if (TREE_CODE (parm) == PARM_DECL) + pushdecl (parm); + else if (nonparms) + { + /* If we find an enum constant or a type tag, put it aside for + the moment. */ + TREE_CHAIN (parm) = NULL_TREE; + *nonparms = chainon (*nonparms, parm); + } + } + + /* Get the decls in their original chain order and record in the + function. This is all and only the PARM_DECLs that were + pushed into scope by the loop above. */ + DECL_ARGUMENTS (decl) = get_local_decls (); +} + /* Store the parameter declarations into the current function declaration. This is called after parsing the parameter declarations, before digesting the body of the function. @@ -15730,7 +15763,6 @@ static void store_parm_decls (tree current_function_parms) { tree fndecl = current_function_decl; - tree parm; /* This is a chain of any other decls that came in among the parm declarations. If a parm is declared with enum {foo, bar} x; @@ -15745,35 +15777,12 @@ store_parm_decls (tree current_function_parms) and complain if any redundant old-style parm decls were written. */ tree specparms = current_function_parms; - tree next; /* Must clear this because it might contain TYPE_DECLs declared at class level. */ current_binding_level->names = NULL; - /* If we're doing semantic analysis, then we'll call pushdecl - for each of these. We must do them in reverse order so that - they end in the correct forward order. */ - specparms = nreverse (specparms); - - for (parm = specparms; parm; parm = next) - { - next = DECL_CHAIN (parm); - if (TREE_CODE (parm) == PARM_DECL) - pushdecl (parm); - else - { - /* If we find an enum constant or a type tag, - put it aside for the moment. */ - TREE_CHAIN (parm) = NULL_TREE; - nonparms = chainon (nonparms, parm); - } - } - - /* Get the decls in their original chain order and record in the - function. This is all and only the PARM_DECLs that were - pushed into scope by the loop above. */ - DECL_ARGUMENTS (fndecl) = get_local_decls (); + do_push_parm_decls (fndecl, specparms, &nonparms); } else DECL_ARGUMENTS (fndecl) = NULL_TREE; diff --git gcc/cp/except.c gcc/cp/except.c index 892d5201da9..8d7b1e9bac7 100644 --- gcc/cp/except.c +++ gcc/cp/except.c @@ -1245,6 +1245,7 @@ nothrow_spec_p (const_tree spec) || TREE_VALUE (spec) || spec == noexcept_false_spec || TREE_PURPOSE (spec) == error_mark_node + || UNPARSED_NOEXCEPT_SPEC_P (spec) || processing_template_decl); return false; diff --git gcc/cp/parser.c gcc/cp/parser.c index 8f5ae84670a..0d926cc0bd5 100644 --- gcc/cp/parser.c +++ gcc/cp/parser.c @@ -247,6 +247,12 @@ static void cp_lexer_stop_debugging static cp_token_cache *cp_token_cache_new (cp_token *, cp_token *); +static tree cp_parser_noexcept_specification_opt + (cp_parser *, bool, bool *, bool, bool); +static tree cp_parser_late_noexcept_specifier + (cp_parser *, tree); +static void noexcept_override_late_checks + (tree, tree); static void cp_parser_initial_pragma (cp_token *); @@ -1974,11 +1980,14 @@ cp_parser_context_new (cp_parser_context* next) parser->unparsed_queues->last ().nsdmis #define unparsed_classes \ parser->unparsed_queues->last ().classes +#define unparsed_noexcepts \ + parser->unparsed_queues->last ().noexcepts static void push_unparsed_function_queues (cp_parser *parser) { - cp_unparsed_functions_entry e = {NULL, make_tree_vector (), NULL, NULL}; + cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL, + NULL }; vec_safe_push (parser->unparsed_queues, e); } @@ -2361,7 +2370,7 @@ static tree cp_parser_exception_declaration static tree cp_parser_throw_expression (cp_parser *); static tree cp_parser_exception_specification_opt - (cp_parser *); + (cp_parser *, bool = false); static tree cp_parser_type_id_list (cp_parser *); @@ -20816,7 +20825,7 @@ cp_parser_direct_declarator (cp_parser* parser, tree tx_qual = cp_parser_tx_qualifier_opt (parser); /* And the exception-specification. */ exception_specification - = cp_parser_exception_specification_opt (parser); + = cp_parser_exception_specification_opt (parser, friend_p); attrs = cp_parser_std_attribute_spec_seq (parser); @@ -23310,6 +23319,34 @@ cp_parser_class_name (cp_parser *parser, return decl; } +/* Make sure that any member-function parameters are in scope. + For instance, a function's noexcept-specifier can use the function's + parameters: + + struct S { + void fn (int p) noexcept(noexcept(p)); + }; + + so we need to make sure name lookup can find them. This is used + when we delay parsing of the noexcept-specifier. */ + +static void +inject_parm_decls (tree decl) +{ + begin_scope (sk_function_parms, decl); + tree args = DECL_ARGUMENTS (decl); + + do_push_parm_decls (decl, args, /*nonparms=*/NULL); +} + +/* Undo the effects of inject_parm_decls. */ + +static void +pop_injected_parms (void) +{ + pop_bindings_and_leave_scope (); +} + /* Parse a class-specifier. class-specifier: @@ -23620,6 +23657,60 @@ cp_parser_class_specifier_1 (cp_parser* parser) vec_safe_truncate (unparsed_classes, 0); after_nsdmi_defaulted_late_checks (type); + /* If there are noexcept-specifiers that have not yet been processed, + take care of them now. */ + class_type = NULL_TREE; + pushed_scope = NULL_TREE; + FOR_EACH_VEC_SAFE_ELT (unparsed_noexcepts, ix, decl) + { + tree ctx = DECL_CONTEXT (decl); + if (class_type != ctx) + { + if (pushed_scope) + pop_scope (pushed_scope); + class_type = ctx; + pushed_scope = push_scope (class_type); + } + + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + spec = TREE_PURPOSE (spec); + + /* Make sure that any template parameters are in scope. */ + maybe_begin_member_template_processing (decl); + + /* Make sure that any member-function parameters are in scope. */ + inject_parm_decls (decl); + + /* 'this' is not allowed in static member functions. */ + unsigned char local_variables_forbidden_p + = parser->local_variables_forbidden_p; + if (DECL_THIS_STATIC (decl)) + parser->local_variables_forbidden_p |= THIS_FORBIDDEN; + + /* Now we can parse the noexcept-specifier. */ + spec = cp_parser_late_noexcept_specifier (parser, spec); + + if (spec != error_mark_node) + TREE_TYPE (decl) = build_exception_variant (TREE_TYPE (decl), spec); + + /* Restore the state of local_variables_forbidden_p. */ + parser->local_variables_forbidden_p = local_variables_forbidden_p; + + /* The finish_struct call above performed various override checking, + but it skipped unparsed noexcept-specifier operands. Now that we + have resolved them, check again. */ + noexcept_override_late_checks (type, decl); + + /* Remove any member-function parameters from the symbol table. */ + pop_injected_parms (); + + /* Remove any template parameters from the symbol table. */ + maybe_end_member_template_processing (); + } + vec_safe_truncate (unparsed_noexcepts, 0); + if (pushed_scope) + pop_scope (pushed_scope); + /* Now parse the body of the functions. */ if (flag_openmp) { @@ -25175,6 +25266,89 @@ cp_parser_base_specifier (cp_parser* parser) /* Exception handling [gram.exception] */ +/* Save the tokens that make up the noexcept-specifier for a member-function. + Returns a DEFAULT_ARG. */ + +static tree +cp_parser_save_noexcept (cp_parser *parser) +{ + cp_token *first = parser->lexer->next_token; + /* We want everything up to, including, the final ')'. */ + cp_parser_cache_group (parser, CPP_CLOSE_PAREN, /*depth=*/0); + cp_token *last = parser->lexer->next_token; + + /* As with default arguments and NSDMIs, make use of DEFAULT_ARG + to carry the information we will need. */ + tree expr = make_node (DEFAULT_ARG); + /* Save away the noexcept-specifier; we will process it when the + class is complete. */ + DEFARG_TOKENS (expr) = cp_token_cache_new (first, last); + expr = build_tree_list (expr, NULL_TREE); + return expr; +} + +/* Used for late processing of noexcept-specifiers of member-functions. + DEFAULT_ARG is the unparsed operand of a noexcept-specifier which + we saved for later; parse it now. */ + +static tree +cp_parser_late_noexcept_specifier (cp_parser *parser, tree default_arg) +{ + /* Make sure we've gotten something that hasn't been parsed yet. */ + gcc_assert (TREE_CODE (default_arg) == DEFAULT_ARG); + + push_unparsed_function_queues (parser); + + /* Push the saved tokens for the noexcept-specifier onto the parser's + lexer stack. */ + cp_token_cache *tokens = DEFARG_TOKENS (default_arg); + cp_parser_push_lexer_for_tokens (parser, tokens); + + /* Parse the cached noexcept-specifier. */ + tree parsed_arg + = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + /*consumed_expr=*/NULL, + /*return_cond=*/false, + /*friend_p=*/false); + + /* Revert to the main lexer. */ + cp_parser_pop_lexer (parser); + + /* Restore the queue. */ + pop_unparsed_function_queues (parser); + + /* And we're done. */ + return parsed_arg; +} + +/* Perform late checking of overriding function with respect to their + noexcept-specifiers. TYPE is the class and FNDECL is the function + that potentially overrides some virtual function with the same + signature. */ + +static void +noexcept_override_late_checks (tree type, tree fndecl) +{ + tree binfo = TYPE_BINFO (type); + tree base_binfo; + + if (DECL_STATIC_FUNCTION_P (fndecl)) + return; + + for (int i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i) + { + tree basetype = BINFO_TYPE (base_binfo); + + if (!TYPE_POLYMORPHIC_P (basetype)) + continue; + + tree fn = look_for_overrides_here (basetype, fndecl); + if (fn) + maybe_check_overriding_exception_spec (fndecl, fn); + } +} + /* Parse an (optional) noexcept-specification. noexcept-specification: @@ -25185,13 +25359,15 @@ cp_parser_base_specifier (cp_parser* parser) expression if parentheses follow noexcept, or return BOOLEAN_TRUE_NODE if there are no parentheses. CONSUMED_EXPR will be set accordingly. Otherwise, returns a noexcept specification unless RETURN_COND is true, - in which case a boolean condition is returned instead. */ + in which case a boolean condition is returned instead. If FRIEND_P is true, + the function with this noexcept-specification had the `friend' specifier. */ static tree cp_parser_noexcept_specification_opt (cp_parser* parser, bool require_constexpr, bool* consumed_expr, - bool return_cond) + bool return_cond, + bool friend_p) { cp_token *token; const char *saved_message; @@ -25203,6 +25379,26 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, if (cp_parser_is_keyword (token, RID_NOEXCEPT)) { tree expr; + + /* [class.mem]/6 says that a noexcept-specifer (within the + member-specification of the class) is a complete-class context of + a class. So, if the noexcept-specifier has the optional expression, + just save the tokens, and reparse this after we're done with the + class. */ + if (cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_PAREN) + /* No need to delay parsing for a number literal or true/false. */ + && !cp_lexer_nth_token_is (parser->lexer, 3, CPP_NUMBER) + && !(cp_lexer_nth_token_is (parser->lexer, 3, CPP_KEYWORD) + && (cp_lexer_nth_token_is_keyword (parser->lexer, 3, RID_FALSE) + || cp_lexer_nth_token_is_keyword (parser->lexer, 3, + RID_TRUE))) + && at_class_scope_p () + /* Don't delay parsing for friend member functions. */ + && !friend_p + && TYPE_BEING_DEFINED (current_class_type) + && !LAMBDA_TYPE_P (current_class_type)) + return cp_parser_save_noexcept (parser); + cp_lexer_consume_token (parser->lexer); if (cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) @@ -25273,10 +25469,11 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, throw ( type-id-list [opt] ) Returns a TREE_LIST representing the exception-specification. The - TREE_VALUE of each node is a type. */ + TREE_VALUE of each node is a type. If FRIEND_P is true, the function + with this noexcept-specification had the `friend' specifier. */ static tree -cp_parser_exception_specification_opt (cp_parser* parser) +cp_parser_exception_specification_opt (cp_parser* parser, bool friend_p) { cp_token *token; tree type_id_list; @@ -25286,8 +25483,12 @@ cp_parser_exception_specification_opt (cp_parser* parser) token = cp_lexer_peek_token (parser->lexer); /* Is it a noexcept-specification? */ - type_id_list = cp_parser_noexcept_specification_opt (parser, true, NULL, - false); + type_id_list + = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + /*consumed_expr=*/NULL, + /*return_cond=*/false, + friend_p); if (type_id_list != NULL_TREE) return type_id_list; @@ -28415,7 +28616,7 @@ cp_parser_save_member_function_body (cp_parser* parser, return error_mark_node; } - /* Remember it, if there default args to post process. */ + /* Remember it, if there are default args to post process. */ cp_parser_save_default_args (parser, fn); /* Save away the tokens that make up the body of the @@ -28708,6 +28909,11 @@ cp_parser_save_default_args (cp_parser* parser, tree decl) vec_safe_push (unparsed_funs_with_default_args, entry); break; } + + /* Remember if there is a noexcept-specifier to post process. */ + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) + vec_safe_push (unparsed_noexcepts, decl); } /* DEFAULT_ARG contains the saved tokens for the initializer of DECL, @@ -40579,7 +40785,11 @@ cp_parser_transaction (cp_parser *parser, cp_token *token) noex = NULL_TREE; } else - noex = cp_parser_noexcept_specification_opt (parser, true, NULL, true); + noex = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + /*consumed_expr=*/NULL, + /*return_cond=*/true, + /*friend_p=*/false); /* Keep track if we're in the lexical scope of an outer transaction. */ new_in = this_in | (old_in & TM_STMT_ATTR_OUTER); @@ -40639,8 +40849,11 @@ cp_parser_transaction_expression (cp_parser *parser, enum rid keyword) parser->in_transaction = this_in; /* Parse a noexcept specification. */ - noex = cp_parser_noexcept_specification_opt (parser, false, &noex_expr, - true); + noex = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/false, + &noex_expr, + /*return_cond=*/true, + /*friend_p=*/false); if (!noex || !noex_expr || cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) diff --git gcc/cp/parser.h gcc/cp/parser.h index c03a9d87af5..2890788f489 100644 --- gcc/cp/parser.h +++ gcc/cp/parser.h @@ -166,6 +166,9 @@ struct GTY(()) cp_unparsed_functions_entry { /* Nested classes go in this vector, so that we can do some final processing after parsing any NSDMIs. */ vec<tree, va_gc> *classes; + + /* Functions with noexcept-specifiers that require post-processing. */ + vec<tree, va_gc> *noexcepts; }; diff --git gcc/cp/pt.c gcc/cp/pt.c index 2a626526c6f..05bc4a3546a 100644 --- gcc/cp/pt.c +++ gcc/cp/pt.c @@ -25307,8 +25307,9 @@ dependent_type_p_r (tree type) if (tree noex = TREE_PURPOSE (spec)) /* Treat DEFERRED_NOEXCEPT as non-dependent, since it doesn't affect overload resolution and treating it as dependent breaks - things. */ + things. Same for an unparsed noexcept expression. */ if (TREE_CODE (noex) != DEFERRED_NOEXCEPT + && TREE_CODE (noex) != DEFAULT_ARG && value_dependent_expression_p (noex)) return true; return false; diff --git gcc/cp/search.c gcc/cp/search.c index dac08d44d76..372c4424747 100644 --- gcc/cp/search.c +++ gcc/cp/search.c @@ -1860,6 +1860,39 @@ locate_field_accessor (tree basetype_path, tree field_decl, bool const_p) NULL, &lfd); } +/* Check throw specifier of OVERRIDER is at least as strict as + the one of BASEFN. */ + +bool +maybe_check_overriding_exception_spec (tree overrider, tree basefn) +{ + maybe_instantiate_noexcept (basefn); + maybe_instantiate_noexcept (overrider); + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); + + if (DECL_INVALID_OVERRIDER_P (overrider)) + return true; + + /* Can't check this yet. Pretend this is fine and let + noexcept_override_late_checks check this later. */ + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) + return true; + + if (!comp_except_specs (base_throw, over_throw, ce_derived)) + { + auto_diagnostic_group d; + error ("looser exception specification on overriding virtual function " + "%q+#F", overrider); + inform (DECL_SOURCE_LOCATION (basefn), + "overridden function is %q#F", basefn); + DECL_INVALID_OVERRIDER_P (overrider) = 1; + return false; + } + return true; +} + /* Check that virtual overrider OVERRIDER is acceptable for base function BASEFN. Issue diagnostic, and return zero, if unacceptable. */ @@ -1870,7 +1903,6 @@ check_final_overrider (tree overrider, tree basefn) tree base_type = TREE_TYPE (basefn); tree over_return = fndecl_declared_return_type (overrider); tree base_return = fndecl_declared_return_type (basefn); - tree over_throw, base_throw; int fail = 0; @@ -1954,21 +1986,8 @@ check_final_overrider (tree overrider, tree basefn) return 0; } - /* Check throw specifier is at least as strict. */ - maybe_instantiate_noexcept (basefn); - maybe_instantiate_noexcept (overrider); - base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); - over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); - - if (!comp_except_specs (base_throw, over_throw, ce_derived)) - { - auto_diagnostic_group d; - error ("looser throw specifier for %q+#F", overrider); - inform (DECL_SOURCE_LOCATION (basefn), - "overridden function is %q#F", basefn); - DECL_INVALID_OVERRIDER_P (overrider) = 1; - return 0; - } + if (!maybe_check_overriding_exception_spec (overrider, basefn)) + return 0; /* Check for conflicting type attributes. But leave transaction_safe for set_one_vmethod_tm_attributes. */ diff --git gcc/cp/tree.c gcc/cp/tree.c index cd021b7f594..81c53b23ebf 100644 --- gcc/cp/tree.c +++ gcc/cp/tree.c @@ -2546,6 +2546,7 @@ canonical_eh_spec (tree raises) if (raises == NULL_TREE) return raises; else if (DEFERRED_NOEXCEPT_SPEC_P (raises) + || UNPARSED_NOEXCEPT_SPEC_P (raises) || uses_template_parms (raises) || uses_template_parms (TREE_PURPOSE (raises))) /* Keep a dependent or deferred exception specification. */ @@ -3656,6 +3657,7 @@ cp_tree_equal (tree t1, tree t2) case IDENTIFIER_NODE: case SSA_NAME: case USING_DECL: + case DEFAULT_ARG: return false; case BASELINK: diff --git gcc/testsuite/g++.dg/cpp0x/noexcept42.C gcc/testsuite/g++.dg/cpp0x/noexcept42.C new file mode 100644 index 00000000000..b3859de9ebc --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept42.C @@ -0,0 +1,26 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +struct S { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void fn(); + void fnx() noexcept; +}; + +void +S::f1() noexcept // { dg-error "different exception specifier" } +{ +} + +void +S::f2() // { dg-error "different exception specifier" } +{ +} + +struct S2 { + void f1() noexcept(noexcept(nosuchfn())); // { dg-error "not declared in this scope" } + void f2() noexcept(noexcept(nothere)); // { dg-error "not declared in this scope" } + void f3() noexcept(noexcept(nosuchfn())) { } // { dg-error "not declared in this scope" } + void f4() noexcept(noexcept(nothere)) { } // { dg-error "not declared in this scope" } +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept43.C gcc/testsuite/g++.dg/cpp0x/noexcept43.C new file mode 100644 index 00000000000..12c6d364099 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept43.C @@ -0,0 +1,9 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +template <typename _Alloc> class A { + typedef _Alloc _Alloc_traits; + A &operator=(A &&) noexcept(_Alloc_traits::_S_nothrow_move); + A &m_fn1(A &&) noexcept(_Alloc_traits::_S_nothrow_move); + void m_fn2(A<char>) {} +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept44.C gcc/testsuite/g++.dg/cpp0x/noexcept44.C new file mode 100644 index 00000000000..a81032f28e9 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept44.C @@ -0,0 +1,14 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +void fn1(void()); +template <typename> class A { + void _M_local_data(); + A() noexcept(_M_local_data); +}; + +class B { + void _S_initialize(); + static void _S_initialize_once(); +}; +void B::_S_initialize() { fn1(_S_initialize_once); } diff --git gcc/testsuite/g++.dg/cpp0x/noexcept45.C gcc/testsuite/g++.dg/cpp0x/noexcept45.C new file mode 100644 index 00000000000..39df4a6571e --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept45.C @@ -0,0 +1,23 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +struct A +{ + virtual void f(); + virtual void g() noexcept; + virtual void h() noexcept(false); +}; + +struct B : A +{ + void f() noexcept(true); + void g() noexcept(true); + void h() noexcept(true); +}; + +struct D : A +{ + void f() noexcept(false); + void g() noexcept(false); // { dg-error "looser exception specification" } + void h() noexcept(false); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept46.C gcc/testsuite/g++.dg/cpp0x/noexcept46.C new file mode 100644 index 00000000000..da7490d651c --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept46.C @@ -0,0 +1,28 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +void f() noexcept(false); +void g() noexcept(true); +void h() noexcept; + +struct B { + friend void f() noexcept(false); + friend void g() noexcept(false); // { dg-error "different exception specifier" } + friend void h() noexcept(false); // { dg-error "different exception specifier" } +}; + +struct C { + friend void f() noexcept(true); // { dg-error "different exception specifier" } + friend void g() noexcept(true); // { dg-error "different exception specifier" } + friend void h() noexcept(true); // { dg-error "different exception specifier" } +}; + +void o() noexcept(false); +void p() noexcept(true); +void q() noexcept; + +struct D { + friend void o() noexcept(true); // { dg-error "different exception specifier" } + friend void p() noexcept(true); + friend void q() noexcept(true); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept47.C gcc/testsuite/g++.dg/cpp0x/noexcept47.C new file mode 100644 index 00000000000..0848e68f9b1 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept47.C @@ -0,0 +1,83 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +int fn1 (); +int fn2 () noexcept; +int fn3 () noexcept; + +void g() noexcept(noexcept (fn2())); + +struct S1 { + friend void g1() noexcept(noexcept(fn2())); + friend void g1() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } +}; + +struct S2 { + friend void g2() noexcept(noexcept(fn1())); + friend void g2() noexcept(noexcept(fn1())); + friend void g2() noexcept(noexcept(fn1())); +}; + +struct S3 { + friend void g3() noexcept(noexcept(fn1())); + friend void g3() noexcept(noexcept(fn3())); // { dg-error "different exception specifier" } +}; + +struct S4 { + friend void g4() noexcept(noexcept(fn2())); + friend void g4() noexcept(noexcept(fn3())); +}; + +struct S5 { + friend void g() noexcept(noexcept(fn3())); +}; + +struct S6 { + friend void g() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } +}; + +struct S7 { + friend void gg() noexcept(noexcept(fn3())); +}; + +void gg() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } + +struct S8 { + friend void g8(); + friend void g8() noexcept(noexcept(fn2())); // { dg-error "different exception specifier" } +}; + +struct S9 { + friend void g9(); + friend void g9() noexcept(noexcept(fn1())); +}; + +struct S10 { + friend void g10() noexcept(noexcept(fn1())); + friend void g10(); +}; + +struct S11 { + friend void g11() noexcept(noexcept(fn2())); + friend void g11(); // { dg-error "different exception specifier" } +}; + +struct S12 { + friend void g12() noexcept(false); + friend void g12() noexcept(noexcept(fn2())); // { dg-error "different exception specifier" } +}; + +struct S13 { + friend void g13() noexcept(false); + friend void g13() noexcept(noexcept(fn1())); +}; + +struct S14 { + friend void g14() noexcept(noexcept(fn1())); + friend void g14() noexcept(false); +}; + +struct S15 { + friend void g15() noexcept(noexcept(fn2())); + friend void g15() noexcept(false); // { dg-error "different exception specifier" } +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept48.C gcc/testsuite/g++.dg/cpp0x/noexcept48.C new file mode 100644 index 00000000000..134212c3613 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept48.C @@ -0,0 +1,11 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +int g; + +struct S { + int b; + friend void fn1(int n) noexcept(noexcept(n)); + friend void fn2() noexcept(noexcept(g)); + friend void fn3() noexcept(noexcept(b)); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept49.C gcc/testsuite/g++.dg/cpp0x/noexcept49.C new file mode 100644 index 00000000000..6da7ff3361f --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept49.C @@ -0,0 +1,12 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert(X, #X) + +struct S { + static void f1() noexcept(b); + static constexpr auto b = true; +}; + +S s; +SA(noexcept(s.f1())); diff --git gcc/testsuite/g++.dg/cpp0x/noexcept50.C gcc/testsuite/g++.dg/cpp0x/noexcept50.C new file mode 100644 index 00000000000..43b38c2446f --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept50.C @@ -0,0 +1,147 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert(X, #X) + +struct S { + void f1() noexcept(noexcept(i)) { } + void f2() noexcept(noexcept(fn())) { } + void f3() noexcept(noexcept(fnx())) { } + void f4() noexcept(noexcept(i)); + void f5() noexcept(noexcept(fn())); + void f6() noexcept(noexcept(fnx())); + + void f7() noexcept(1); + void f8() noexcept(0); + void f9() noexcept(b); + void f10() noexcept(!b); + + int i; + static constexpr auto b = true; + void fny() noexcept(noexcept(fn())); + void fn(); + void fnx() noexcept; +}; + +S s; +SA(noexcept(s.f1())); +SA(!noexcept(s.f2())); +SA(noexcept(s.f3())); +SA(noexcept(s.f4())); +SA(!noexcept(s.f5())); +SA(noexcept(s.f6())); +SA(noexcept(s.f7())); +SA(!noexcept(s.f8())); +SA(noexcept(s.f9())); +SA(!noexcept(s.f10())); + +struct S2 { + struct V { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void f3() noexcept(noexcept(fn())) { } + void f4() noexcept(noexcept(fnx())) { } + void fn(); + void fnx() noexcept; + } v; + void fn(); + void fnx(); +}; + +S2 s2; +SA(!noexcept(s2.v.f1())); +SA(noexcept(s2.v.f2())); +SA(!noexcept(s2.v.f3())); +SA(noexcept(s2.v.f4())); + +struct S3 { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void fn(); + void fnx() noexcept; +}; + +void +S3::f1() noexcept(noexcept(fn())) +{ +} + +void +S3::f2() noexcept(noexcept(fnx())) +{ +} + +struct S4 { + int f1 (int p) noexcept(noexcept(p)) { return p; } + int f2 (int p) noexcept(noexcept(p)); + int f3 (int p = 10) noexcept(noexcept(p)); + int f4 () noexcept(noexcept(S4{})); +}; + +S4 s4; +SA(noexcept(s4.f1(1))); +SA(noexcept(s4.f2(1))); +SA(noexcept(s4.f3())); +SA(noexcept(s4.f4())); + +template<typename T> +struct S5 { + void f1() noexcept(noexcept(i)) { } + void f2() noexcept(noexcept(fn())) { } + void f3() noexcept(noexcept(fnx())) { } + void f4() noexcept(noexcept(i)); + void f5() noexcept(noexcept(fn())); + void f6() noexcept(noexcept(fnx())); + + int i; + void fny() noexcept(noexcept(fn())); + void fn(); + void fnx() noexcept; +}; + +S5<int> s5; +SA(noexcept(s5.f1())); +SA(!noexcept(s5.f2())); +SA(noexcept(s5.f3())); +SA(noexcept(s5.f4())); +SA(!noexcept(s5.f5())); +SA(noexcept(s5.f6())); + +template<typename T> +struct S6 { + void f1() noexcept(noexcept(x)); + T x; +}; + +struct S7 { + template<typename U> + void f1 () noexcept(noexcept(U(1))) { } + + template<int N> + void f2() noexcept(noexcept(N)); + + template <typename _Up> + void f3(_Up __p) noexcept(noexcept(__p)); +}; + +void glob(); +void globx() noexcept; +struct S8 { + void f1 () noexcept(noexcept(glob())); + void f2 () noexcept(noexcept(globx())); +}; + +S8 s8; +SA(!noexcept(s8.f1())); +SA(noexcept(s8.f2())); + +struct W { + constexpr operator bool(); +}; + +template<typename T> +struct S9 { + S9() noexcept(noexcept(w)) { } + S9 &operator=(S9 &&) noexcept(T::X); + W w; +}; diff --git gcc/testsuite/g++.dg/eh/shadow1.C gcc/testsuite/g++.dg/eh/shadow1.C index 0ba6145ef0c..6bccc704d49 100644 --- gcc/testsuite/g++.dg/eh/shadow1.C +++ gcc/testsuite/g++.dg/eh/shadow1.C @@ -18,7 +18,7 @@ struct D : private B // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } struct E : public D { - virtual void V () throw (D); // { dg-error "looser throw" "" { target { ! c++17 } } } + virtual void V () throw (D); // { dg-error "looser exception" "" { target { ! c++17 } } } }; // { dg-error "dynamic exception specification" "" { target c++17 } .-1 } // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } B* foo (D *);
On 6/14/19 4:54 PM, Marek Polacek wrote: > On Tue, Jun 11, 2019 at 11:46:05PM -0400, Jason Merrill wrote: >> On 6/3/19 9:01 PM, Marek Polacek wrote: >> >>> I sort of ended up going down a rathole, but then I realized we don't need to >>> delay parsing of noexcept-specifiers of member friend function declarations, >>> because they aren't members of the class. >> >> Where are you getting this from? I'm definitely sympathetic to the idea >> that noexcept-specifiers of friend functions shouldn't need to be >> complete-class contexts, but 10.3 doesn't make that distinction that I can >> see. > > When I tested my patch I noticed that none of the 3 compilers I tried handled > this scenario, so I thought I was missing something. But if the standard > really doesn't say that noexcept-specifiers of friend functions don't have to > be complete-class contexts, then perhaps it needs to say so. Should I raise > this on the reflector? Sounds good. >>> This was a huge relief because >>> member friend function declarations can be redeclared, so we'd have to make >>> sure to check if their noexcept-specifiers match. But member function decls >>> can't be redeclared. I updated the comment to better reflect why what I'm >>> doing there is correct, along with an assert. >> >> But then why do you still need this: >> >>> + /* We can't compare unparsed noexcept-specifiers. Save the decl >>> + and check this again after we've parsed the noexcept-specifiers >>> + for real. */ >>> + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) >>> + { >>> + DEFARG_DECL (TREE_PURPOSE (new_exceptions)) = copy_decl (old_decl); >>> + return; >>> + } >> >> ? > > Eh... I don't. The following version is with the DEFARG_DECL junk removed. > > Bootstrapped/regtested on x86_64-linux, ok for trunk? > > 2019-06-14 Marek Polacek <polacek@redhat.com> > > PR c++/86476 - noexcept-specifier is a complete-class context. > PR c++/52869 > * cp-tree.def (DEFAULT_ARG): Update commentary. I'd still like to rename this, can you do that in a follow-up? > @@ -25203,6 +25379,26 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, > if (cp_parser_is_keyword (token, RID_NOEXCEPT)) > { > tree expr; > + > + /* [class.mem]/6 says that a noexcept-specifer (within the > + member-specification of the class) is a complete-class context of > + a class. So, if the noexcept-specifier has the optional expression, > + just save the tokens, and reparse this after we're done with the > + class. */ > + if (cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_PAREN) > + /* No need to delay parsing for a number literal or true/false. */ > + && !cp_lexer_nth_token_is (parser->lexer, 3, CPP_NUMBER) > + && !(cp_lexer_nth_token_is (parser->lexer, 3, CPP_KEYWORD) > + && (cp_lexer_nth_token_is_keyword (parser->lexer, 3, RID_FALSE) > + || cp_lexer_nth_token_is_keyword (parser->lexer, 3, > + RID_TRUE))) Maybe do immediate parsing for any keyword, not just true/false? I can't think of a keyword that delayed parsing would make a difference for. I think we also need to check that token 4 is close paren, so we still get delayed parsing for noexcept (1 + foo). Jason
On Fri, Jun 21, 2019 at 04:47:46PM -0400, Jason Merrill wrote: > On 6/14/19 4:54 PM, Marek Polacek wrote: > > On Tue, Jun 11, 2019 at 11:46:05PM -0400, Jason Merrill wrote: > > > On 6/3/19 9:01 PM, Marek Polacek wrote: > > > > > > > I sort of ended up going down a rathole, but then I realized we don't need to > > > > delay parsing of noexcept-specifiers of member friend function declarations, > > > > because they aren't members of the class. > > > > > > Where are you getting this from? I'm definitely sympathetic to the idea > > > that noexcept-specifiers of friend functions shouldn't need to be > > > complete-class contexts, but 10.3 doesn't make that distinction that I can > > > see. > > > > When I tested my patch I noticed that none of the 3 compilers I tried handled > > this scenario, so I thought I was missing something. But if the standard > > really doesn't say that noexcept-specifiers of friend functions don't have to > > be complete-class contexts, then perhaps it needs to say so. Should I raise > > this on the reflector? > > Sounds good. Will do. > > > > This was a huge relief because > > > > member friend function declarations can be redeclared, so we'd have to make > > > > sure to check if their noexcept-specifiers match. But member function decls > > > > can't be redeclared. I updated the comment to better reflect why what I'm > > > > doing there is correct, along with an assert. > > > > > > But then why do you still need this: > > > > > > > + /* We can't compare unparsed noexcept-specifiers. Save the decl > > > > + and check this again after we've parsed the noexcept-specifiers > > > > + for real. */ > > > > + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) > > > > + { > > > > + DEFARG_DECL (TREE_PURPOSE (new_exceptions)) = copy_decl (old_decl); > > > > + return; > > > > + } > > > > > > ? > > > > Eh... I don't. The following version is with the DEFARG_DECL junk removed. > > > > Bootstrapped/regtested on x86_64-linux, ok for trunk? > > > > 2019-06-14 Marek Polacek <polacek@redhat.com> > > > > PR c++/86476 - noexcept-specifier is a complete-class context. > > PR c++/52869 > > * cp-tree.def (DEFAULT_ARG): Update commentary. > > I'd still like to rename this, can you do that in a follow-up? Absolutely. > > @@ -25203,6 +25379,26 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, > > if (cp_parser_is_keyword (token, RID_NOEXCEPT)) > > { > > tree expr; > > + > > + /* [class.mem]/6 says that a noexcept-specifer (within the > > + member-specification of the class) is a complete-class context of > > + a class. So, if the noexcept-specifier has the optional expression, > > + just save the tokens, and reparse this after we're done with the > > + class. */ > > + if (cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_PAREN) > > + /* No need to delay parsing for a number literal or true/false. */ > > + && !cp_lexer_nth_token_is (parser->lexer, 3, CPP_NUMBER) > > + && !(cp_lexer_nth_token_is (parser->lexer, 3, CPP_KEYWORD) > > + && (cp_lexer_nth_token_is_keyword (parser->lexer, 3, RID_FALSE) > > + || cp_lexer_nth_token_is_keyword (parser->lexer, 3, > > + RID_TRUE))) > > Maybe do immediate parsing for any keyword, not just true/false? I can't > think of a keyword that delayed parsing would make a difference for. Probably true. > I think we also need to check that token 4 is close paren, so we still get > delayed parsing for noexcept (1 + foo). Indeed, fixed, in a way that makes the whole conditional more readable. I've renamed some tests, otherwise no changes. Bootstrap/regtest running on x86_64-linux, ok for trunk if it passes? 2019-06-21 Marek Polacek <polacek@redhat.com> PR c++/86476 - noexcept-specifier is a complete-class context. PR c++/52869 * cp-tree.def (DEFAULT_ARG): Update commentary. * cp-tree.h (UNPARSED_NOEXCEPT_SPEC_P): New macro. (tree_default_arg): Use tree_base instead of tree_common. (do_push_parm_decls, maybe_check_overriding_exception_spec): Declare. * decl.c (do_push_parm_decls): New function, broken out of... (store_parm_decls): ...here. Call it. * except.c (nothrow_spec_p): Accept DEFAULT_ARG in the assert. * parser.c (cp_parser_noexcept_specification_opt, cp_parser_late_noexcept_specifier, noexcept_override_late_checks): Forward-declare. (unparsed_noexcepts): New macro. (push_unparsed_function_queues): Update initializer. (cp_parser_direct_declarator): Pass FRIEND_P to cp_parser_exception_specification_opt. (inject_parm_decls): New. (pop_injected_parms): New. (cp_parser_class_specifier_1): Implement delayed parsing of noexcept-specifiers. (cp_parser_save_noexcept): New. (cp_parser_late_noexcept_specifier): New. (noexcept_override_late_checks): New. (cp_parser_noexcept_specification_opt): Add FRIEND_P parameter. Call cp_parser_save_noexcept instead of the normal processing if needed. (cp_parser_exception_specification_opt): Add FRIEND_P parameter and pass it to cp_parser_noexcept_specification_opt. (cp_parser_save_member_function_body): Fix comment. (cp_parser_save_default_args): Maybe save the noexcept-specifier to post process. (cp_parser_transaction): Update call to cp_parser_noexcept_specification_opt. (cp_parser_transaction_expression): Likewise. * parser.h (cp_unparsed_functions_entry): Add new field to carry a noexcept-specifier. * pt.c (dependent_type_p_r): Handle unparsed noexcept expression. * search.c (maybe_check_overriding_exception_spec): New function, broken out of... (check_final_overrider): ...here. Call maybe_check_overriding_exception_spec. * tree.c (canonical_eh_spec): Handle UNPARSED_NOEXCEPT_SPEC_P. (cp_tree_equal): Handle DEFAULT_ARG. * g++.dg/cpp0x/noexcept45.C: New test. * g++.dg/cpp0x/noexcept46.C: New test. * g++.dg/cpp0x/noexcept47.C: New test. * g++.dg/cpp0x/noexcept48.C: New test. * g++.dg/cpp0x/noexcept49.C: New test. * g++.dg/cpp0x/noexcept50.C: New test. * g++.dg/cpp0x/noexcept51.C: New test. * g++.dg/cpp0x/noexcept52.C: New test. * g++.dg/cpp0x/noexcept53.C: New test. * g++.dg/eh/shadow1.C: Adjust dg-error. diff --git gcc/cp/cp-tree.def gcc/cp/cp-tree.def index 03c105b5c4c..475c584fd4c 100644 --- gcc/cp/cp-tree.def +++ gcc/cp/cp-tree.def @@ -209,7 +209,8 @@ DEFTREECODE (USING_STMT, "using_stmt", tcc_statement, 1) /* An un-parsed default argument. Holds a vector of input tokens and a vector of places where the argument was instantiated before - parsing had occurred. */ + parsing had occurred. This is also used for delayed NSDMIs and + noexcept-specifier parsing. */ DEFTREECODE (DEFAULT_ARG, "default_arg", tcc_exceptional, 0) /* An uninstantiated/unevaluated noexcept-specification. For the diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h index 98f7a0c0cd0..2c05e638915 100644 --- gcc/cp/cp-tree.h +++ gcc/cp/cp-tree.h @@ -1190,7 +1190,7 @@ enum cp_identifier_kind { (((struct tree_default_arg *)DEFAULT_ARG_CHECK (NODE))->instantiations) struct GTY (()) tree_default_arg { - struct tree_common common; + struct tree_base base; struct cp_token_cache *tokens; vec<tree, va_gc> *instantiations; }; @@ -1206,6 +1206,9 @@ struct GTY (()) tree_default_arg { #define UNEVALUATED_NOEXCEPT_SPEC_P(NODE) \ (DEFERRED_NOEXCEPT_SPEC_P (NODE) \ && DEFERRED_NOEXCEPT_PATTERN (TREE_PURPOSE (NODE)) == NULL_TREE) +#define UNPARSED_NOEXCEPT_SPEC_P(NODE) \ + ((NODE) && (TREE_PURPOSE (NODE)) \ + && (TREE_CODE (TREE_PURPOSE (NODE)) == DEFAULT_ARG)) struct GTY (()) tree_deferred_noexcept { struct tree_base base; @@ -6467,6 +6470,7 @@ extern bool check_array_designated_initializer (constructor_elt *, unsigned HOST_WIDE_INT); extern bool check_for_uninitialized_const_var (tree, bool, tsubst_flags_t); extern tree build_explicit_specifier (tree, tsubst_flags_t); +extern void do_push_parm_decls (tree, tree, tree *); /* in decl2.c */ extern void record_mangling (tree, bool); @@ -6932,6 +6936,7 @@ extern tree copied_binfo (tree, tree); extern tree original_binfo (tree, tree); extern int shared_member_p (tree); extern bool any_dependent_bases_p (tree = current_nonlambda_class_type ()); +extern bool maybe_check_overriding_exception_spec (tree, tree); /* The representation of a deferred access check. */ diff --git gcc/cp/decl.c gcc/cp/decl.c index 98b54d542a0..b7ad6dead6b 100644 --- gcc/cp/decl.c +++ gcc/cp/decl.c @@ -15755,6 +15755,39 @@ use_eh_spec_block (tree fn) && !DECL_DEFAULTED_FN (fn)); } +/* Helper function to push ARGS into the current lexical scope. DECL + is the function declaration. NONPARMS is used to handle enum + constants. */ + +void +do_push_parm_decls (tree decl, tree args, tree *nonparms) +{ + /* If we're doing semantic analysis, then we'll call pushdecl + for each of these. We must do them in reverse order so that + they end in the correct forward order. */ + args = nreverse (args); + + tree next; + for (tree parm = args; parm; parm = next) + { + next = DECL_CHAIN (parm); + if (TREE_CODE (parm) == PARM_DECL) + pushdecl (parm); + else if (nonparms) + { + /* If we find an enum constant or a type tag, put it aside for + the moment. */ + TREE_CHAIN (parm) = NULL_TREE; + *nonparms = chainon (*nonparms, parm); + } + } + + /* Get the decls in their original chain order and record in the + function. This is all and only the PARM_DECLs that were + pushed into scope by the loop above. */ + DECL_ARGUMENTS (decl) = get_local_decls (); +} + /* Store the parameter declarations into the current function declaration. This is called after parsing the parameter declarations, before digesting the body of the function. @@ -15765,7 +15798,6 @@ static void store_parm_decls (tree current_function_parms) { tree fndecl = current_function_decl; - tree parm; /* This is a chain of any other decls that came in among the parm declarations. If a parm is declared with enum {foo, bar} x; @@ -15780,35 +15812,12 @@ store_parm_decls (tree current_function_parms) and complain if any redundant old-style parm decls were written. */ tree specparms = current_function_parms; - tree next; /* Must clear this because it might contain TYPE_DECLs declared at class level. */ current_binding_level->names = NULL; - /* If we're doing semantic analysis, then we'll call pushdecl - for each of these. We must do them in reverse order so that - they end in the correct forward order. */ - specparms = nreverse (specparms); - - for (parm = specparms; parm; parm = next) - { - next = DECL_CHAIN (parm); - if (TREE_CODE (parm) == PARM_DECL) - pushdecl (parm); - else - { - /* If we find an enum constant or a type tag, - put it aside for the moment. */ - TREE_CHAIN (parm) = NULL_TREE; - nonparms = chainon (nonparms, parm); - } - } - - /* Get the decls in their original chain order and record in the - function. This is all and only the PARM_DECLs that were - pushed into scope by the loop above. */ - DECL_ARGUMENTS (fndecl) = get_local_decls (); + do_push_parm_decls (fndecl, specparms, &nonparms); } else DECL_ARGUMENTS (fndecl) = NULL_TREE; diff --git gcc/cp/except.c gcc/cp/except.c index 71f5d609f10..1f87c5ab695 100644 --- gcc/cp/except.c +++ gcc/cp/except.c @@ -1245,6 +1245,7 @@ nothrow_spec_p (const_tree spec) || TREE_VALUE (spec) || spec == noexcept_false_spec || TREE_PURPOSE (spec) == error_mark_node + || UNPARSED_NOEXCEPT_SPEC_P (spec) || processing_template_decl); return false; diff --git gcc/cp/parser.c gcc/cp/parser.c index 5cbc4551d1a..4d4d32973d9 100644 --- gcc/cp/parser.c +++ gcc/cp/parser.c @@ -247,6 +247,12 @@ static void cp_lexer_stop_debugging static cp_token_cache *cp_token_cache_new (cp_token *, cp_token *); +static tree cp_parser_noexcept_specification_opt + (cp_parser *, bool, bool *, bool, bool); +static tree cp_parser_late_noexcept_specifier + (cp_parser *, tree); +static void noexcept_override_late_checks + (tree, tree); static void cp_parser_initial_pragma (cp_token *); @@ -1974,11 +1980,14 @@ cp_parser_context_new (cp_parser_context* next) parser->unparsed_queues->last ().nsdmis #define unparsed_classes \ parser->unparsed_queues->last ().classes +#define unparsed_noexcepts \ + parser->unparsed_queues->last ().noexcepts static void push_unparsed_function_queues (cp_parser *parser) { - cp_unparsed_functions_entry e = {NULL, make_tree_vector (), NULL, NULL}; + cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL, + NULL }; vec_safe_push (parser->unparsed_queues, e); } @@ -2361,7 +2370,7 @@ static tree cp_parser_exception_declaration static tree cp_parser_throw_expression (cp_parser *); static tree cp_parser_exception_specification_opt - (cp_parser *); + (cp_parser *, bool = false); static tree cp_parser_type_id_list (cp_parser *); @@ -20816,7 +20825,7 @@ cp_parser_direct_declarator (cp_parser* parser, tree tx_qual = cp_parser_tx_qualifier_opt (parser); /* And the exception-specification. */ exception_specification - = cp_parser_exception_specification_opt (parser); + = cp_parser_exception_specification_opt (parser, friend_p); attrs = cp_parser_std_attribute_spec_seq (parser); @@ -23310,6 +23319,34 @@ cp_parser_class_name (cp_parser *parser, return decl; } +/* Make sure that any member-function parameters are in scope. + For instance, a function's noexcept-specifier can use the function's + parameters: + + struct S { + void fn (int p) noexcept(noexcept(p)); + }; + + so we need to make sure name lookup can find them. This is used + when we delay parsing of the noexcept-specifier. */ + +static void +inject_parm_decls (tree decl) +{ + begin_scope (sk_function_parms, decl); + tree args = DECL_ARGUMENTS (decl); + + do_push_parm_decls (decl, args, /*nonparms=*/NULL); +} + +/* Undo the effects of inject_parm_decls. */ + +static void +pop_injected_parms (void) +{ + pop_bindings_and_leave_scope (); +} + /* Parse a class-specifier. class-specifier: @@ -23620,6 +23657,60 @@ cp_parser_class_specifier_1 (cp_parser* parser) vec_safe_truncate (unparsed_classes, 0); after_nsdmi_defaulted_late_checks (type); + /* If there are noexcept-specifiers that have not yet been processed, + take care of them now. */ + class_type = NULL_TREE; + pushed_scope = NULL_TREE; + FOR_EACH_VEC_SAFE_ELT (unparsed_noexcepts, ix, decl) + { + tree ctx = DECL_CONTEXT (decl); + if (class_type != ctx) + { + if (pushed_scope) + pop_scope (pushed_scope); + class_type = ctx; + pushed_scope = push_scope (class_type); + } + + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + spec = TREE_PURPOSE (spec); + + /* Make sure that any template parameters are in scope. */ + maybe_begin_member_template_processing (decl); + + /* Make sure that any member-function parameters are in scope. */ + inject_parm_decls (decl); + + /* 'this' is not allowed in static member functions. */ + unsigned char local_variables_forbidden_p + = parser->local_variables_forbidden_p; + if (DECL_THIS_STATIC (decl)) + parser->local_variables_forbidden_p |= THIS_FORBIDDEN; + + /* Now we can parse the noexcept-specifier. */ + spec = cp_parser_late_noexcept_specifier (parser, spec); + + if (spec != error_mark_node) + TREE_TYPE (decl) = build_exception_variant (TREE_TYPE (decl), spec); + + /* Restore the state of local_variables_forbidden_p. */ + parser->local_variables_forbidden_p = local_variables_forbidden_p; + + /* The finish_struct call above performed various override checking, + but it skipped unparsed noexcept-specifier operands. Now that we + have resolved them, check again. */ + noexcept_override_late_checks (type, decl); + + /* Remove any member-function parameters from the symbol table. */ + pop_injected_parms (); + + /* Remove any template parameters from the symbol table. */ + maybe_end_member_template_processing (); + } + vec_safe_truncate (unparsed_noexcepts, 0); + if (pushed_scope) + pop_scope (pushed_scope); + /* Now parse the body of the functions. */ if (flag_openmp) { @@ -25175,6 +25266,89 @@ cp_parser_base_specifier (cp_parser* parser) /* Exception handling [gram.exception] */ +/* Save the tokens that make up the noexcept-specifier for a member-function. + Returns a DEFAULT_ARG. */ + +static tree +cp_parser_save_noexcept (cp_parser *parser) +{ + cp_token *first = parser->lexer->next_token; + /* We want everything up to, including, the final ')'. */ + cp_parser_cache_group (parser, CPP_CLOSE_PAREN, /*depth=*/0); + cp_token *last = parser->lexer->next_token; + + /* As with default arguments and NSDMIs, make use of DEFAULT_ARG + to carry the information we will need. */ + tree expr = make_node (DEFAULT_ARG); + /* Save away the noexcept-specifier; we will process it when the + class is complete. */ + DEFARG_TOKENS (expr) = cp_token_cache_new (first, last); + expr = build_tree_list (expr, NULL_TREE); + return expr; +} + +/* Used for late processing of noexcept-specifiers of member-functions. + DEFAULT_ARG is the unparsed operand of a noexcept-specifier which + we saved for later; parse it now. */ + +static tree +cp_parser_late_noexcept_specifier (cp_parser *parser, tree default_arg) +{ + /* Make sure we've gotten something that hasn't been parsed yet. */ + gcc_assert (TREE_CODE (default_arg) == DEFAULT_ARG); + + push_unparsed_function_queues (parser); + + /* Push the saved tokens for the noexcept-specifier onto the parser's + lexer stack. */ + cp_token_cache *tokens = DEFARG_TOKENS (default_arg); + cp_parser_push_lexer_for_tokens (parser, tokens); + + /* Parse the cached noexcept-specifier. */ + tree parsed_arg + = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + /*consumed_expr=*/NULL, + /*return_cond=*/false, + /*friend_p=*/false); + + /* Revert to the main lexer. */ + cp_parser_pop_lexer (parser); + + /* Restore the queue. */ + pop_unparsed_function_queues (parser); + + /* And we're done. */ + return parsed_arg; +} + +/* Perform late checking of overriding function with respect to their + noexcept-specifiers. TYPE is the class and FNDECL is the function + that potentially overrides some virtual function with the same + signature. */ + +static void +noexcept_override_late_checks (tree type, tree fndecl) +{ + tree binfo = TYPE_BINFO (type); + tree base_binfo; + + if (DECL_STATIC_FUNCTION_P (fndecl)) + return; + + for (int i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i) + { + tree basetype = BINFO_TYPE (base_binfo); + + if (!TYPE_POLYMORPHIC_P (basetype)) + continue; + + tree fn = look_for_overrides_here (basetype, fndecl); + if (fn) + maybe_check_overriding_exception_spec (fndecl, fn); + } +} + /* Parse an (optional) noexcept-specification. noexcept-specification: @@ -25185,13 +25359,15 @@ cp_parser_base_specifier (cp_parser* parser) expression if parentheses follow noexcept, or return BOOLEAN_TRUE_NODE if there are no parentheses. CONSUMED_EXPR will be set accordingly. Otherwise, returns a noexcept specification unless RETURN_COND is true, - in which case a boolean condition is returned instead. */ + in which case a boolean condition is returned instead. If FRIEND_P is true, + the function with this noexcept-specification had the `friend' specifier. */ static tree cp_parser_noexcept_specification_opt (cp_parser* parser, bool require_constexpr, bool* consumed_expr, - bool return_cond) + bool return_cond, + bool friend_p) { cp_token *token; const char *saved_message; @@ -25203,6 +25379,27 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, if (cp_parser_is_keyword (token, RID_NOEXCEPT)) { tree expr; + + /* [class.mem]/6 says that a noexcept-specifer (within the + member-specification of the class) is a complete-class context of + a class. So, if the noexcept-specifier has the optional expression, + just save the tokens, and reparse this after we're done with the + class. */ + const bool literal_p + = ((cp_lexer_nth_token_is (parser->lexer, 3, CPP_NUMBER) + || cp_lexer_nth_token_is (parser->lexer, 3, CPP_KEYWORD)) + && cp_lexer_nth_token_is (parser->lexer, 4, CPP_CLOSE_PAREN)); + + if (cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_PAREN) + /* No need to delay parsing for a number literal or true/false. */ + && !literal_p + && at_class_scope_p () + /* Don't delay parsing for friend member functions. */ + && !friend_p + && TYPE_BEING_DEFINED (current_class_type) + && !LAMBDA_TYPE_P (current_class_type)) + return cp_parser_save_noexcept (parser); + cp_lexer_consume_token (parser->lexer); if (cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) @@ -25273,10 +25470,11 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, throw ( type-id-list [opt] ) Returns a TREE_LIST representing the exception-specification. The - TREE_VALUE of each node is a type. */ + TREE_VALUE of each node is a type. If FRIEND_P is true, the function + with this noexcept-specification had the `friend' specifier. */ static tree -cp_parser_exception_specification_opt (cp_parser* parser) +cp_parser_exception_specification_opt (cp_parser* parser, bool friend_p) { cp_token *token; tree type_id_list; @@ -25286,8 +25484,12 @@ cp_parser_exception_specification_opt (cp_parser* parser) token = cp_lexer_peek_token (parser->lexer); /* Is it a noexcept-specification? */ - type_id_list = cp_parser_noexcept_specification_opt (parser, true, NULL, - false); + type_id_list + = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + /*consumed_expr=*/NULL, + /*return_cond=*/false, + friend_p); if (type_id_list != NULL_TREE) return type_id_list; @@ -28435,7 +28637,7 @@ cp_parser_save_member_function_body (cp_parser* parser, return error_mark_node; } - /* Remember it, if there default args to post process. */ + /* Remember it, if there are default args to post process. */ cp_parser_save_default_args (parser, fn); /* Save away the tokens that make up the body of the @@ -28728,6 +28930,11 @@ cp_parser_save_default_args (cp_parser* parser, tree decl) vec_safe_push (unparsed_funs_with_default_args, entry); break; } + + /* Remember if there is a noexcept-specifier to post process. */ + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) + vec_safe_push (unparsed_noexcepts, decl); } /* DEFAULT_ARG contains the saved tokens for the initializer of DECL, @@ -40599,7 +40806,11 @@ cp_parser_transaction (cp_parser *parser, cp_token *token) noex = NULL_TREE; } else - noex = cp_parser_noexcept_specification_opt (parser, true, NULL, true); + noex = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + /*consumed_expr=*/NULL, + /*return_cond=*/true, + /*friend_p=*/false); /* Keep track if we're in the lexical scope of an outer transaction. */ new_in = this_in | (old_in & TM_STMT_ATTR_OUTER); @@ -40659,8 +40870,11 @@ cp_parser_transaction_expression (cp_parser *parser, enum rid keyword) parser->in_transaction = this_in; /* Parse a noexcept specification. */ - noex = cp_parser_noexcept_specification_opt (parser, false, &noex_expr, - true); + noex = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/false, + &noex_expr, + /*return_cond=*/true, + /*friend_p=*/false); if (!noex || !noex_expr || cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) diff --git gcc/cp/parser.h gcc/cp/parser.h index c03a9d87af5..2890788f489 100644 --- gcc/cp/parser.h +++ gcc/cp/parser.h @@ -166,6 +166,9 @@ struct GTY(()) cp_unparsed_functions_entry { /* Nested classes go in this vector, so that we can do some final processing after parsing any NSDMIs. */ vec<tree, va_gc> *classes; + + /* Functions with noexcept-specifiers that require post-processing. */ + vec<tree, va_gc> *noexcepts; }; diff --git gcc/cp/pt.c gcc/cp/pt.c index 69de55369dd..fb89b933524 100644 --- gcc/cp/pt.c +++ gcc/cp/pt.c @@ -25313,8 +25313,9 @@ dependent_type_p_r (tree type) if (tree noex = TREE_PURPOSE (spec)) /* Treat DEFERRED_NOEXCEPT as non-dependent, since it doesn't affect overload resolution and treating it as dependent breaks - things. */ + things. Same for an unparsed noexcept expression. */ if (TREE_CODE (noex) != DEFERRED_NOEXCEPT + && TREE_CODE (noex) != DEFAULT_ARG && value_dependent_expression_p (noex)) return true; return false; diff --git gcc/cp/search.c gcc/cp/search.c index dac08d44d76..372c4424747 100644 --- gcc/cp/search.c +++ gcc/cp/search.c @@ -1860,6 +1860,39 @@ locate_field_accessor (tree basetype_path, tree field_decl, bool const_p) NULL, &lfd); } +/* Check throw specifier of OVERRIDER is at least as strict as + the one of BASEFN. */ + +bool +maybe_check_overriding_exception_spec (tree overrider, tree basefn) +{ + maybe_instantiate_noexcept (basefn); + maybe_instantiate_noexcept (overrider); + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); + + if (DECL_INVALID_OVERRIDER_P (overrider)) + return true; + + /* Can't check this yet. Pretend this is fine and let + noexcept_override_late_checks check this later. */ + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) + return true; + + if (!comp_except_specs (base_throw, over_throw, ce_derived)) + { + auto_diagnostic_group d; + error ("looser exception specification on overriding virtual function " + "%q+#F", overrider); + inform (DECL_SOURCE_LOCATION (basefn), + "overridden function is %q#F", basefn); + DECL_INVALID_OVERRIDER_P (overrider) = 1; + return false; + } + return true; +} + /* Check that virtual overrider OVERRIDER is acceptable for base function BASEFN. Issue diagnostic, and return zero, if unacceptable. */ @@ -1870,7 +1903,6 @@ check_final_overrider (tree overrider, tree basefn) tree base_type = TREE_TYPE (basefn); tree over_return = fndecl_declared_return_type (overrider); tree base_return = fndecl_declared_return_type (basefn); - tree over_throw, base_throw; int fail = 0; @@ -1954,21 +1986,8 @@ check_final_overrider (tree overrider, tree basefn) return 0; } - /* Check throw specifier is at least as strict. */ - maybe_instantiate_noexcept (basefn); - maybe_instantiate_noexcept (overrider); - base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); - over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); - - if (!comp_except_specs (base_throw, over_throw, ce_derived)) - { - auto_diagnostic_group d; - error ("looser throw specifier for %q+#F", overrider); - inform (DECL_SOURCE_LOCATION (basefn), - "overridden function is %q#F", basefn); - DECL_INVALID_OVERRIDER_P (overrider) = 1; - return 0; - } + if (!maybe_check_overriding_exception_spec (overrider, basefn)) + return 0; /* Check for conflicting type attributes. But leave transaction_safe for set_one_vmethod_tm_attributes. */ diff --git gcc/cp/tree.c gcc/cp/tree.c index 978aea56193..ebfe362595f 100644 --- gcc/cp/tree.c +++ gcc/cp/tree.c @@ -2546,6 +2546,7 @@ canonical_eh_spec (tree raises) if (raises == NULL_TREE) return raises; else if (DEFERRED_NOEXCEPT_SPEC_P (raises) + || UNPARSED_NOEXCEPT_SPEC_P (raises) || uses_template_parms (raises) || uses_template_parms (TREE_PURPOSE (raises))) /* Keep a dependent or deferred exception specification. */ @@ -3656,6 +3657,7 @@ cp_tree_equal (tree t1, tree t2) case IDENTIFIER_NODE: case SSA_NAME: case USING_DECL: + case DEFAULT_ARG: return false; case BASELINK: diff --git gcc/testsuite/g++.dg/cpp0x/noexcept45.C gcc/testsuite/g++.dg/cpp0x/noexcept45.C new file mode 100644 index 00000000000..39df4a6571e --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept45.C @@ -0,0 +1,23 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +struct A +{ + virtual void f(); + virtual void g() noexcept; + virtual void h() noexcept(false); +}; + +struct B : A +{ + void f() noexcept(true); + void g() noexcept(true); + void h() noexcept(true); +}; + +struct D : A +{ + void f() noexcept(false); + void g() noexcept(false); // { dg-error "looser exception specification" } + void h() noexcept(false); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept46.C gcc/testsuite/g++.dg/cpp0x/noexcept46.C new file mode 100644 index 00000000000..da7490d651c --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept46.C @@ -0,0 +1,28 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +void f() noexcept(false); +void g() noexcept(true); +void h() noexcept; + +struct B { + friend void f() noexcept(false); + friend void g() noexcept(false); // { dg-error "different exception specifier" } + friend void h() noexcept(false); // { dg-error "different exception specifier" } +}; + +struct C { + friend void f() noexcept(true); // { dg-error "different exception specifier" } + friend void g() noexcept(true); // { dg-error "different exception specifier" } + friend void h() noexcept(true); // { dg-error "different exception specifier" } +}; + +void o() noexcept(false); +void p() noexcept(true); +void q() noexcept; + +struct D { + friend void o() noexcept(true); // { dg-error "different exception specifier" } + friend void p() noexcept(true); + friend void q() noexcept(true); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept47.C gcc/testsuite/g++.dg/cpp0x/noexcept47.C new file mode 100644 index 00000000000..0848e68f9b1 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept47.C @@ -0,0 +1,83 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +int fn1 (); +int fn2 () noexcept; +int fn3 () noexcept; + +void g() noexcept(noexcept (fn2())); + +struct S1 { + friend void g1() noexcept(noexcept(fn2())); + friend void g1() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } +}; + +struct S2 { + friend void g2() noexcept(noexcept(fn1())); + friend void g2() noexcept(noexcept(fn1())); + friend void g2() noexcept(noexcept(fn1())); +}; + +struct S3 { + friend void g3() noexcept(noexcept(fn1())); + friend void g3() noexcept(noexcept(fn3())); // { dg-error "different exception specifier" } +}; + +struct S4 { + friend void g4() noexcept(noexcept(fn2())); + friend void g4() noexcept(noexcept(fn3())); +}; + +struct S5 { + friend void g() noexcept(noexcept(fn3())); +}; + +struct S6 { + friend void g() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } +}; + +struct S7 { + friend void gg() noexcept(noexcept(fn3())); +}; + +void gg() noexcept(noexcept(fn1())); // { dg-error "different exception specifier" } + +struct S8 { + friend void g8(); + friend void g8() noexcept(noexcept(fn2())); // { dg-error "different exception specifier" } +}; + +struct S9 { + friend void g9(); + friend void g9() noexcept(noexcept(fn1())); +}; + +struct S10 { + friend void g10() noexcept(noexcept(fn1())); + friend void g10(); +}; + +struct S11 { + friend void g11() noexcept(noexcept(fn2())); + friend void g11(); // { dg-error "different exception specifier" } +}; + +struct S12 { + friend void g12() noexcept(false); + friend void g12() noexcept(noexcept(fn2())); // { dg-error "different exception specifier" } +}; + +struct S13 { + friend void g13() noexcept(false); + friend void g13() noexcept(noexcept(fn1())); +}; + +struct S14 { + friend void g14() noexcept(noexcept(fn1())); + friend void g14() noexcept(false); +}; + +struct S15 { + friend void g15() noexcept(noexcept(fn2())); + friend void g15() noexcept(false); // { dg-error "different exception specifier" } +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept48.C gcc/testsuite/g++.dg/cpp0x/noexcept48.C new file mode 100644 index 00000000000..134212c3613 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept48.C @@ -0,0 +1,11 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +int g; + +struct S { + int b; + friend void fn1(int n) noexcept(noexcept(n)); + friend void fn2() noexcept(noexcept(g)); + friend void fn3() noexcept(noexcept(b)); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept49.C gcc/testsuite/g++.dg/cpp0x/noexcept49.C new file mode 100644 index 00000000000..6da7ff3361f --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept49.C @@ -0,0 +1,12 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert(X, #X) + +struct S { + static void f1() noexcept(b); + static constexpr auto b = true; +}; + +S s; +SA(noexcept(s.f1())); diff --git gcc/testsuite/g++.dg/cpp0x/noexcept50.C gcc/testsuite/g++.dg/cpp0x/noexcept50.C new file mode 100644 index 00000000000..43b38c2446f --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept50.C @@ -0,0 +1,147 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert(X, #X) + +struct S { + void f1() noexcept(noexcept(i)) { } + void f2() noexcept(noexcept(fn())) { } + void f3() noexcept(noexcept(fnx())) { } + void f4() noexcept(noexcept(i)); + void f5() noexcept(noexcept(fn())); + void f6() noexcept(noexcept(fnx())); + + void f7() noexcept(1); + void f8() noexcept(0); + void f9() noexcept(b); + void f10() noexcept(!b); + + int i; + static constexpr auto b = true; + void fny() noexcept(noexcept(fn())); + void fn(); + void fnx() noexcept; +}; + +S s; +SA(noexcept(s.f1())); +SA(!noexcept(s.f2())); +SA(noexcept(s.f3())); +SA(noexcept(s.f4())); +SA(!noexcept(s.f5())); +SA(noexcept(s.f6())); +SA(noexcept(s.f7())); +SA(!noexcept(s.f8())); +SA(noexcept(s.f9())); +SA(!noexcept(s.f10())); + +struct S2 { + struct V { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void f3() noexcept(noexcept(fn())) { } + void f4() noexcept(noexcept(fnx())) { } + void fn(); + void fnx() noexcept; + } v; + void fn(); + void fnx(); +}; + +S2 s2; +SA(!noexcept(s2.v.f1())); +SA(noexcept(s2.v.f2())); +SA(!noexcept(s2.v.f3())); +SA(noexcept(s2.v.f4())); + +struct S3 { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void fn(); + void fnx() noexcept; +}; + +void +S3::f1() noexcept(noexcept(fn())) +{ +} + +void +S3::f2() noexcept(noexcept(fnx())) +{ +} + +struct S4 { + int f1 (int p) noexcept(noexcept(p)) { return p; } + int f2 (int p) noexcept(noexcept(p)); + int f3 (int p = 10) noexcept(noexcept(p)); + int f4 () noexcept(noexcept(S4{})); +}; + +S4 s4; +SA(noexcept(s4.f1(1))); +SA(noexcept(s4.f2(1))); +SA(noexcept(s4.f3())); +SA(noexcept(s4.f4())); + +template<typename T> +struct S5 { + void f1() noexcept(noexcept(i)) { } + void f2() noexcept(noexcept(fn())) { } + void f3() noexcept(noexcept(fnx())) { } + void f4() noexcept(noexcept(i)); + void f5() noexcept(noexcept(fn())); + void f6() noexcept(noexcept(fnx())); + + int i; + void fny() noexcept(noexcept(fn())); + void fn(); + void fnx() noexcept; +}; + +S5<int> s5; +SA(noexcept(s5.f1())); +SA(!noexcept(s5.f2())); +SA(noexcept(s5.f3())); +SA(noexcept(s5.f4())); +SA(!noexcept(s5.f5())); +SA(noexcept(s5.f6())); + +template<typename T> +struct S6 { + void f1() noexcept(noexcept(x)); + T x; +}; + +struct S7 { + template<typename U> + void f1 () noexcept(noexcept(U(1))) { } + + template<int N> + void f2() noexcept(noexcept(N)); + + template <typename _Up> + void f3(_Up __p) noexcept(noexcept(__p)); +}; + +void glob(); +void globx() noexcept; +struct S8 { + void f1 () noexcept(noexcept(glob())); + void f2 () noexcept(noexcept(globx())); +}; + +S8 s8; +SA(!noexcept(s8.f1())); +SA(noexcept(s8.f2())); + +struct W { + constexpr operator bool(); +}; + +template<typename T> +struct S9 { + S9() noexcept(noexcept(w)) { } + S9 &operator=(S9 &&) noexcept(T::X); + W w; +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept51.C gcc/testsuite/g++.dg/cpp0x/noexcept51.C new file mode 100644 index 00000000000..a81032f28e9 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept51.C @@ -0,0 +1,14 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +void fn1(void()); +template <typename> class A { + void _M_local_data(); + A() noexcept(_M_local_data); +}; + +class B { + void _S_initialize(); + static void _S_initialize_once(); +}; +void B::_S_initialize() { fn1(_S_initialize_once); } diff --git gcc/testsuite/g++.dg/cpp0x/noexcept52.C gcc/testsuite/g++.dg/cpp0x/noexcept52.C new file mode 100644 index 00000000000..12c6d364099 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept52.C @@ -0,0 +1,9 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +template <typename _Alloc> class A { + typedef _Alloc _Alloc_traits; + A &operator=(A &&) noexcept(_Alloc_traits::_S_nothrow_move); + A &m_fn1(A &&) noexcept(_Alloc_traits::_S_nothrow_move); + void m_fn2(A<char>) {} +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept53.C gcc/testsuite/g++.dg/cpp0x/noexcept53.C new file mode 100644 index 00000000000..b3859de9ebc --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept53.C @@ -0,0 +1,26 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +struct S { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void fn(); + void fnx() noexcept; +}; + +void +S::f1() noexcept // { dg-error "different exception specifier" } +{ +} + +void +S::f2() // { dg-error "different exception specifier" } +{ +} + +struct S2 { + void f1() noexcept(noexcept(nosuchfn())); // { dg-error "not declared in this scope" } + void f2() noexcept(noexcept(nothere)); // { dg-error "not declared in this scope" } + void f3() noexcept(noexcept(nosuchfn())) { } // { dg-error "not declared in this scope" } + void f4() noexcept(noexcept(nothere)) { } // { dg-error "not declared in this scope" } +}; diff --git gcc/testsuite/g++.dg/eh/shadow1.C gcc/testsuite/g++.dg/eh/shadow1.C index 0ba6145ef0c..6bccc704d49 100644 --- gcc/testsuite/g++.dg/eh/shadow1.C +++ gcc/testsuite/g++.dg/eh/shadow1.C @@ -18,7 +18,7 @@ struct D : private B // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } struct E : public D { - virtual void V () throw (D); // { dg-error "looser throw" "" { target { ! c++17 } } } + virtual void V () throw (D); // { dg-error "looser exception" "" { target { ! c++17 } } } }; // { dg-error "dynamic exception specification" "" { target c++17 } .-1 } // { dg-warning "deprecated" "" { target { c++11 && { ! c++17 } } } .-2 } B* foo (D *);
On 6/21/19 5:29 PM, Marek Polacek wrote: > On Fri, Jun 21, 2019 at 04:47:46PM -0400, Jason Merrill wrote: >> On 6/14/19 4:54 PM, Marek Polacek wrote: >>> On Tue, Jun 11, 2019 at 11:46:05PM -0400, Jason Merrill wrote: >>>> On 6/3/19 9:01 PM, Marek Polacek wrote: >>>> >>>>> I sort of ended up going down a rathole, but then I realized we don't need to >>>>> delay parsing of noexcept-specifiers of member friend function declarations, >>>>> because they aren't members of the class. >>>> >>>> Where are you getting this from? I'm definitely sympathetic to the idea >>>> that noexcept-specifiers of friend functions shouldn't need to be >>>> complete-class contexts, but 10.3 doesn't make that distinction that I can >>>> see. >>> >>> When I tested my patch I noticed that none of the 3 compilers I tried handled >>> this scenario, so I thought I was missing something. But if the standard >>> really doesn't say that noexcept-specifiers of friend functions don't have to >>> be complete-class contexts, then perhaps it needs to say so. Should I raise >>> this on the reflector? >> >> Sounds good. > > Will do. > >>>>> This was a huge relief because >>>>> member friend function declarations can be redeclared, so we'd have to make >>>>> sure to check if their noexcept-specifiers match. But member function decls >>>>> can't be redeclared. I updated the comment to better reflect why what I'm >>>>> doing there is correct, along with an assert. >>>> >>>> But then why do you still need this: >>>> >>>>> + /* We can't compare unparsed noexcept-specifiers. Save the decl >>>>> + and check this again after we've parsed the noexcept-specifiers >>>>> + for real. */ >>>>> + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) >>>>> + { >>>>> + DEFARG_DECL (TREE_PURPOSE (new_exceptions)) = copy_decl (old_decl); >>>>> + return; >>>>> + } >>>> >>>> ? >>> >>> Eh... I don't. The following version is with the DEFARG_DECL junk removed. >>> >>> Bootstrapped/regtested on x86_64-linux, ok for trunk? >>> >>> 2019-06-14 Marek Polacek <polacek@redhat.com> >>> >>> PR c++/86476 - noexcept-specifier is a complete-class context. >>> PR c++/52869 >>> * cp-tree.def (DEFAULT_ARG): Update commentary. >> >> I'd still like to rename this, can you do that in a follow-up? > > Absolutely. > >>> @@ -25203,6 +25379,26 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, >>> if (cp_parser_is_keyword (token, RID_NOEXCEPT)) >>> { >>> tree expr; >>> + >>> + /* [class.mem]/6 says that a noexcept-specifer (within the >>> + member-specification of the class) is a complete-class context of >>> + a class. So, if the noexcept-specifier has the optional expression, >>> + just save the tokens, and reparse this after we're done with the >>> + class. */ >>> + if (cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_PAREN) >>> + /* No need to delay parsing for a number literal or true/false. */ >>> + && !cp_lexer_nth_token_is (parser->lexer, 3, CPP_NUMBER) >>> + && !(cp_lexer_nth_token_is (parser->lexer, 3, CPP_KEYWORD) >>> + && (cp_lexer_nth_token_is_keyword (parser->lexer, 3, RID_FALSE) >>> + || cp_lexer_nth_token_is_keyword (parser->lexer, 3, >>> + RID_TRUE))) >> >> Maybe do immediate parsing for any keyword, not just true/false? I can't >> think of a keyword that delayed parsing would make a difference for. > > Probably true. > >> I think we also need to check that token 4 is close paren, so we still get >> delayed parsing for noexcept (1 + foo). > > Indeed, fixed, in a way that makes the whole conditional more readable. > > I've renamed some tests, otherwise no changes. > > Bootstrap/regtest running on x86_64-linux, ok for trunk if it passes? Yes, thanks. Jason
diff --git gcc/cp/cp-tree.def gcc/cp/cp-tree.def index 43d90eb1efb..aa8b752d8f4 100644 --- gcc/cp/cp-tree.def +++ gcc/cp/cp-tree.def @@ -209,7 +209,9 @@ DEFTREECODE (USING_STMT, "using_stmt", tcc_statement, 1) /* An un-parsed default argument. Holds a vector of input tokens and a vector of places where the argument was instantiated before - parsing had occurred. */ + parsing had occurred. This is also used for delayed NSDMIs and + noexcept-specifier parsing. For a noexcept-specifier, the vector + holds a function declaration used for late checking. */ DEFTREECODE (DEFAULT_ARG, "default_arg", tcc_exceptional, 0) /* An uninstantiated/unevaluated noexcept-specification. For the diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h index 1d806b782bd..bd3cd200fcb 100644 --- gcc/cp/cp-tree.h +++ gcc/cp/cp-tree.h @@ -1193,6 +1193,9 @@ struct GTY (()) tree_default_arg { #define UNEVALUATED_NOEXCEPT_SPEC_P(NODE) \ (DEFERRED_NOEXCEPT_SPEC_P (NODE) \ && DEFERRED_NOEXCEPT_PATTERN (TREE_PURPOSE (NODE)) == NULL_TREE) +#define UNPARSED_NOEXCEPT_SPEC_P(NODE) \ + ((NODE) && (TREE_PURPOSE (NODE)) \ + && (TREE_CODE (TREE_PURPOSE (NODE)) == DEFAULT_ARG)) struct GTY (()) tree_deferred_noexcept { struct tree_base base; @@ -6430,6 +6433,8 @@ extern bool check_array_designated_initializer (constructor_elt *, unsigned HOST_WIDE_INT); extern bool check_for_uninitialized_const_var (tree, bool, tsubst_flags_t); extern tree build_explicit_specifier (tree, tsubst_flags_t); +extern void check_redeclaration_exception_specification + (tree, tree); /* in decl2.c */ extern void record_mangling (tree, bool); @@ -6894,6 +6899,7 @@ extern tree copied_binfo (tree, tree); extern tree original_binfo (tree, tree); extern int shared_member_p (tree); extern bool any_dependent_bases_p (tree = current_nonlambda_class_type ()); +extern bool maybe_check_throw_specifier (tree, tree); /* The representation of a deferred access check. */ diff --git gcc/cp/decl.c gcc/cp/decl.c index d6028e3608c..74444db8623 100644 --- gcc/cp/decl.c +++ gcc/cp/decl.c @@ -1140,7 +1140,7 @@ warn_extern_redeclared_static (tree newdecl, tree olddecl) function templates. If their exception specifications do not match, issue a diagnostic. */ -static void +void check_redeclaration_exception_specification (tree new_decl, tree old_decl) { @@ -1152,6 +1152,16 @@ check_redeclaration_exception_specification (tree new_decl, && UNEVALUATED_NOEXCEPT_SPEC_P (old_exceptions)) return; + /* We can't compare unparsed noexcept-specifiers. Save the old decl + and check this again after we've parsed the noexcept-specifiers + for real. */ + if (UNPARSED_NOEXCEPT_SPEC_P (new_exceptions)) + { + vec_safe_push (DEFARG_INSTANTIATIONS (TREE_PURPOSE (new_exceptions)), + copy_decl (old_decl)); + return; + } + if (!type_dependent_expression_p (old_decl)) { maybe_instantiate_noexcept (new_decl); diff --git gcc/cp/except.c gcc/cp/except.c index b04eb0c5332..92fc39d3968 100644 --- gcc/cp/except.c +++ gcc/cp/except.c @@ -1245,6 +1245,7 @@ nothrow_spec_p (const_tree spec) || TREE_VALUE (spec) || spec == noexcept_false_spec || TREE_PURPOSE (spec) == error_mark_node + || TREE_CODE (TREE_PURPOSE (spec)) == DEFAULT_ARG || processing_template_decl); return false; diff --git gcc/cp/parser.c gcc/cp/parser.c index b57e35d04c5..267b3518156 100644 --- gcc/cp/parser.c +++ gcc/cp/parser.c @@ -247,6 +247,12 @@ static void cp_lexer_stop_debugging static cp_token_cache *cp_token_cache_new (cp_token *, cp_token *); +static tree cp_parser_noexcept_specification_opt + (cp_parser *, bool, bool *, bool); +static tree cp_parser_late_noexcept_specifier + (cp_parser *, tree); +static void noexcept_override_late_checks + (tree, tree); static void cp_parser_initial_pragma (cp_token *); @@ -1968,11 +1974,14 @@ cp_parser_context_new (cp_parser_context* next) parser->unparsed_queues->last ().nsdmis #define unparsed_classes \ parser->unparsed_queues->last ().classes +#define unparsed_noexcepts \ + parser->unparsed_queues->last ().noexcepts static void push_unparsed_function_queues (cp_parser *parser) { - cp_unparsed_functions_entry e = {NULL, make_tree_vector (), NULL, NULL}; + cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL, + NULL }; vec_safe_push (parser->unparsed_queues, e); } @@ -20315,7 +20324,13 @@ cp_parser_init_declarator (cp_parser* parser, /*asmspec=*/NULL_TREE, attr_chainon (attributes, prefix_attributes)); if (decl && TREE_CODE (decl) == FUNCTION_DECL) - cp_parser_save_default_args (parser, decl); + { + cp_parser_save_default_args (parser, decl); + /* Remember if there is a noexcept-specifier to post process. */ + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) + vec_safe_push (unparsed_noexcepts, decl); + } cp_finalize_omp_declare_simd (parser, decl); cp_finalize_oacc_routine (parser, decl, false); } @@ -23058,6 +23073,45 @@ cp_parser_class_name (cp_parser *parser, return decl; } +/* Make sure that any member-function parameters are in scope. + For instance, a function's noexcept-specifier can use the function's + parameters: + + struct S { + void fn (int p) noexcept(noexcept(p)); + }; + + so we need to make sure name lookup can find them. This is used + when we delay parsing of the noexcept-specifier. */ + +static void +maybe_begin_member_function_processing (tree decl) +{ + begin_scope (sk_function_parms, decl); + tree args = DECL_ARGUMENTS (decl); + args = nreverse (args); + + tree next; + for (tree parm = args; parm; parm = next) + { + next = DECL_CHAIN (parm); + if (TREE_CODE (parm) == PARM_DECL) + pushdecl (parm); + } + /* Get the decls in their original chain order and record in the + function. This is all and only the PARM_DECLs that were + pushed into scope by the loop above. */ + DECL_ARGUMENTS (decl) = get_local_decls (); +} + +/* Undo the effects of maybe_begin_member_function_processing. */ + +static void +maybe_end_member_function_processing (void) +{ + pop_bindings_and_leave_scope (); +} + /* Parse a class-specifier. class-specifier: @@ -23368,6 +23422,56 @@ cp_parser_class_specifier_1 (cp_parser* parser) vec_safe_truncate (unparsed_classes, 0); after_nsdmi_defaulted_late_checks (type); + /* If there are noexcept-specifiers that have not yet been processed, + take care of them now. */ + class_type = NULL_TREE; + pushed_scope = NULL_TREE; + FOR_EACH_VEC_SAFE_ELT (unparsed_noexcepts, ix, decl) + { + if (class_type != DECL_CONTEXT (decl)) + { + if (pushed_scope) + pop_scope (pushed_scope); + class_type = DECL_CONTEXT (decl); + pushed_scope = push_scope (class_type); + } + + /* Make sure that any template parameters are in scope. */ + maybe_begin_member_template_processing (decl); + + /* Make sure that any member-function parameters are in scope. */ + maybe_begin_member_function_processing (decl); + + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + spec = TREE_PURPOSE (spec); + vec<tree, va_gc> *p = DEFARG_INSTANTIATIONS (spec); + tree old_decl = (p ? (*p)[0] : NULL_TREE); + + /* Now we can parse the noexcept-specifier. */ + spec = cp_parser_late_noexcept_specifier (parser, spec); + if (spec != error_mark_node) + TREE_TYPE (decl) = build_exception_variant (TREE_TYPE (decl), spec); + + /* If we've stashed an old declaration, it means we need to + perform late redeclaration checking. */ + if (old_decl) + check_redeclaration_exception_specification (decl, old_decl); + + /* The finish_struct call above performed various override checking, + but it skipped unparsed noexcept-specifier operands. Now that we + have resolved them, check again. */ + noexcept_override_late_checks (type, decl); + + /* Remove any member-function parameters from the symbol table. */ + maybe_end_member_function_processing (); + + /* Remove any template parameters from the symbol table. */ + maybe_end_member_template_processing (); + } + vec_safe_truncate (unparsed_noexcepts, 0); + if (pushed_scope) + pop_scope (pushed_scope); + /* Now parse the body of the functions. */ if (flag_openmp) { @@ -24537,6 +24641,12 @@ cp_parser_member_declaration (cp_parser* parser) else decl = finish_fully_implicit_template (parser, decl); } + if (decl && TREE_CODE (decl) == FUNCTION_DECL) + { + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) + vec_safe_push (unparsed_noexcepts, decl); + } } cp_finalize_omp_declare_simd (parser, decl); @@ -24919,6 +25029,89 @@ cp_parser_base_specifier (cp_parser* parser) /* Exception handling [gram.exception] */ +/* Save the tokens that make up the noexcept-specifier for a member-function. + Returns a DEFAULT_ARG. */ + +static tree +cp_parser_save_noexcept (cp_parser *parser) +{ + cp_token *first = parser->lexer->next_token; + /* We want everything up to, including, the final ')'. */ + cp_parser_cache_group (parser, CPP_CLOSE_PAREN, /*depth=*/0); + cp_token *last = parser->lexer->next_token; + + /* As with default arguments and NSDMIs, make us of DEFAULT_ARG + to carry the information we will need. */ + tree expr = make_node (DEFAULT_ARG); + /* Save away the noexcept-specifier; we will process it when the + class is complete. */ + DEFARG_TOKENS (expr) = cp_token_cache_new (first, last); + DEFARG_INSTANTIATIONS (expr) = NULL; + expr = build_tree_list (expr, NULL_TREE); + return expr; +} + +/* Used for late processing of noexcept-specifiers of member-functions. + DEFAULT_ARG is the unparsed operand of a noexcept-specifier which + we saved for later; parse it now. */ + +static tree +cp_parser_late_noexcept_specifier (cp_parser *parser, tree default_arg) +{ + /* Make sure we've gotten something that hasn't been parsed yet. */ + gcc_assert (TREE_CODE (default_arg) == DEFAULT_ARG); + + push_unparsed_function_queues (parser); + + /* Push the saved tokens for the noexcept-specifier onto the parser's + lexer stack. */ + cp_token_cache *tokens = DEFARG_TOKENS (default_arg); + cp_parser_push_lexer_for_tokens (parser, tokens); + + /* Parse the cached noexcept-specifier. */ + tree parsed_arg + = cp_parser_noexcept_specification_opt (parser, + /*require_constexpr=*/true, + NULL, + /*return_cond=*/false); + + /* Revert to the main lexer. */ + cp_parser_pop_lexer (parser); + + /* Restore the queue. */ + pop_unparsed_function_queues (parser); + + /* And we're done. */ + return parsed_arg; +} + +/* Perform late checking of overriding function with respect to their + noexcept-specifiers. TYPE is the class and FNDECL is the function + that potentially overrides some virtual function with the same + signature. */ + +static void +noexcept_override_late_checks (tree type, tree fndecl) +{ + tree binfo = TYPE_BINFO (type); + tree base_binfo; + + if (DECL_STATIC_FUNCTION_P (fndecl)) + return; + + for (int i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i) + { + tree basetype = BINFO_TYPE (base_binfo); + + if (!TYPE_POLYMORPHIC_P (basetype)) + continue; + + tree fn = look_for_overrides_here (basetype, fndecl); + if (fn) + maybe_check_throw_specifier (fndecl, fn); + } +} + /* Parse an (optional) noexcept-specification. noexcept-specification: @@ -24947,6 +25140,18 @@ cp_parser_noexcept_specification_opt (cp_parser* parser, if (cp_parser_is_keyword (token, RID_NOEXCEPT)) { tree expr; + + /* [class.mem]/6 says that a noexcept-specifer (within the + member-specification of the class) is a complete-class context of + a class. So, if the noexcept-specifier has the optional expression, + just save the tokens, and reparse this after we're done with the + class. */ + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_OPEN_PAREN + && at_class_scope_p () + && TYPE_BEING_DEFINED (current_class_type) + && !LAMBDA_TYPE_P (current_class_type)) + return cp_parser_save_noexcept (parser); + cp_lexer_consume_token (parser->lexer); if (cp_lexer_peek_token (parser->lexer)->type == CPP_OPEN_PAREN) @@ -28120,7 +28325,12 @@ cp_parser_save_member_function_body (cp_parser* parser, return error_mark_node; } - /* Remember it, if there default args to post process. */ + /* Remember if there is a noexcept-specifier to post process. */ + tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fn)); + if (UNPARSED_NOEXCEPT_SPEC_P (spec)) + vec_safe_push (unparsed_noexcepts, fn); + + /* Remember it, if there are default args to post process. */ cp_parser_save_default_args (parser, fn); /* Save away the tokens that make up the body of the diff --git gcc/cp/parser.h gcc/cp/parser.h index 8bfa3f3b9c4..df0c6c1960e 100644 --- gcc/cp/parser.h +++ gcc/cp/parser.h @@ -166,6 +166,9 @@ struct GTY(()) cp_unparsed_functions_entry { /* Nested classes go in this vector, so that we can do some final processing after parsing any NSDMIs. */ vec<tree, va_gc> *classes; + + /* Functions with noexcept-specifiers that require post-processing. */ + vec<tree, va_gc> *noexcepts; }; diff --git gcc/cp/pt.c gcc/cp/pt.c index e99de71ea9e..382a14f7d6a 100644 --- gcc/cp/pt.c +++ gcc/cp/pt.c @@ -24988,8 +24988,9 @@ dependent_type_p_r (tree type) if (tree noex = TREE_PURPOSE (spec)) /* Treat DEFERRED_NOEXCEPT as non-dependent, since it doesn't affect overload resolution and treating it as dependent breaks - things. */ + things. Same for an unparsed noexcept expression. */ if (TREE_CODE (noex) != DEFERRED_NOEXCEPT + && TREE_CODE (noex) != DEFAULT_ARG && value_dependent_expression_p (noex)) return true; return false; diff --git gcc/cp/search.c gcc/cp/search.c index d700fe328f4..3e6494dd98f 100644 --- gcc/cp/search.c +++ gcc/cp/search.c @@ -1850,6 +1850,38 @@ locate_field_accessor (tree basetype_path, tree field_decl, bool const_p) NULL, &lfd); } +/* Check throw specifier of OVERRIDER is at least as strict as + the one of BASEFN. */ + +bool +maybe_check_throw_specifier (tree overrider, tree basefn) +{ + maybe_instantiate_noexcept (basefn); + maybe_instantiate_noexcept (overrider); + tree base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); + tree over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); + + if (DECL_INVALID_OVERRIDER_P (overrider)) + return true; + + /* Can't check this yet. Pretend this is fine and let + noexcept_override_late_checks check this later. */ + if (UNPARSED_NOEXCEPT_SPEC_P (base_throw) + || UNPARSED_NOEXCEPT_SPEC_P (over_throw)) + return true; + + if (!comp_except_specs (base_throw, over_throw, ce_derived)) + { + auto_diagnostic_group d; + error ("looser throw specifier for %q+#F", overrider); + inform (DECL_SOURCE_LOCATION (basefn), + "overridden function is %q#F", basefn); + DECL_INVALID_OVERRIDER_P (overrider) = 1; + return false; + } + return true; +} + /* Check that virtual overrider OVERRIDER is acceptable for base function BASEFN. Issue diagnostic, and return zero, if unacceptable. */ @@ -1860,7 +1892,6 @@ check_final_overrider (tree overrider, tree basefn) tree base_type = TREE_TYPE (basefn); tree over_return = fndecl_declared_return_type (overrider); tree base_return = fndecl_declared_return_type (basefn); - tree over_throw, base_throw; int fail = 0; @@ -1944,21 +1975,8 @@ check_final_overrider (tree overrider, tree basefn) return 0; } - /* Check throw specifier is at least as strict. */ - maybe_instantiate_noexcept (basefn); - maybe_instantiate_noexcept (overrider); - base_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (basefn)); - over_throw = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (overrider)); - - if (!comp_except_specs (base_throw, over_throw, ce_derived)) - { - auto_diagnostic_group d; - error ("looser throw specifier for %q+#F", overrider); - inform (DECL_SOURCE_LOCATION (basefn), - "overridden function is %q#F", basefn); - DECL_INVALID_OVERRIDER_P (overrider) = 1; - return 0; - } + if (!maybe_check_throw_specifier (overrider, basefn)) + return 0; /* Check for conflicting type attributes. But leave transaction_safe for set_one_vmethod_tm_attributes. */ diff --git gcc/cp/tree.c gcc/cp/tree.c index 97074dfab56..a7d1e58c73a 100644 --- gcc/cp/tree.c +++ gcc/cp/tree.c @@ -2542,6 +2542,7 @@ canonical_eh_spec (tree raises) if (raises == NULL_TREE) return raises; else if (DEFERRED_NOEXCEPT_SPEC_P (raises) + || UNPARSED_NOEXCEPT_SPEC_P (raises) || uses_template_parms (raises) || uses_template_parms (TREE_PURPOSE (raises))) /* Keep a dependent or deferred exception specification. */ @@ -3653,6 +3654,7 @@ cp_tree_equal (tree t1, tree t2) case TEMPLATE_DECL: case IDENTIFIER_NODE: case SSA_NAME: + case DEFAULT_ARG: return false; case BASELINK: diff --git gcc/cp/typeck2.c gcc/cp/typeck2.c index 64e36efd17e..6fec77d8269 100644 --- gcc/cp/typeck2.c +++ gcc/cp/typeck2.c @@ -2302,6 +2302,12 @@ merge_exception_specifiers (tree list, tree add) { tree noex, orig_list; + /* We don't want to lose the unparsed operand lest we miss diagnostics. */ + if (UNPARSED_NOEXCEPT_SPEC_P (list)) + return list; + else if (UNPARSED_NOEXCEPT_SPEC_P (add)) + return add; + /* No exception-specifier or noexcept(false) are less strict than anything else. Prefer the newer variant (LIST). */ if (!list || list == noexcept_false_spec) diff --git gcc/testsuite/g++.dg/cpp0x/noexcept34.C gcc/testsuite/g++.dg/cpp0x/noexcept34.C new file mode 100644 index 00000000000..43b38c2446f --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept34.C @@ -0,0 +1,147 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert(X, #X) + +struct S { + void f1() noexcept(noexcept(i)) { } + void f2() noexcept(noexcept(fn())) { } + void f3() noexcept(noexcept(fnx())) { } + void f4() noexcept(noexcept(i)); + void f5() noexcept(noexcept(fn())); + void f6() noexcept(noexcept(fnx())); + + void f7() noexcept(1); + void f8() noexcept(0); + void f9() noexcept(b); + void f10() noexcept(!b); + + int i; + static constexpr auto b = true; + void fny() noexcept(noexcept(fn())); + void fn(); + void fnx() noexcept; +}; + +S s; +SA(noexcept(s.f1())); +SA(!noexcept(s.f2())); +SA(noexcept(s.f3())); +SA(noexcept(s.f4())); +SA(!noexcept(s.f5())); +SA(noexcept(s.f6())); +SA(noexcept(s.f7())); +SA(!noexcept(s.f8())); +SA(noexcept(s.f9())); +SA(!noexcept(s.f10())); + +struct S2 { + struct V { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void f3() noexcept(noexcept(fn())) { } + void f4() noexcept(noexcept(fnx())) { } + void fn(); + void fnx() noexcept; + } v; + void fn(); + void fnx(); +}; + +S2 s2; +SA(!noexcept(s2.v.f1())); +SA(noexcept(s2.v.f2())); +SA(!noexcept(s2.v.f3())); +SA(noexcept(s2.v.f4())); + +struct S3 { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void fn(); + void fnx() noexcept; +}; + +void +S3::f1() noexcept(noexcept(fn())) +{ +} + +void +S3::f2() noexcept(noexcept(fnx())) +{ +} + +struct S4 { + int f1 (int p) noexcept(noexcept(p)) { return p; } + int f2 (int p) noexcept(noexcept(p)); + int f3 (int p = 10) noexcept(noexcept(p)); + int f4 () noexcept(noexcept(S4{})); +}; + +S4 s4; +SA(noexcept(s4.f1(1))); +SA(noexcept(s4.f2(1))); +SA(noexcept(s4.f3())); +SA(noexcept(s4.f4())); + +template<typename T> +struct S5 { + void f1() noexcept(noexcept(i)) { } + void f2() noexcept(noexcept(fn())) { } + void f3() noexcept(noexcept(fnx())) { } + void f4() noexcept(noexcept(i)); + void f5() noexcept(noexcept(fn())); + void f6() noexcept(noexcept(fnx())); + + int i; + void fny() noexcept(noexcept(fn())); + void fn(); + void fnx() noexcept; +}; + +S5<int> s5; +SA(noexcept(s5.f1())); +SA(!noexcept(s5.f2())); +SA(noexcept(s5.f3())); +SA(noexcept(s5.f4())); +SA(!noexcept(s5.f5())); +SA(noexcept(s5.f6())); + +template<typename T> +struct S6 { + void f1() noexcept(noexcept(x)); + T x; +}; + +struct S7 { + template<typename U> + void f1 () noexcept(noexcept(U(1))) { } + + template<int N> + void f2() noexcept(noexcept(N)); + + template <typename _Up> + void f3(_Up __p) noexcept(noexcept(__p)); +}; + +void glob(); +void globx() noexcept; +struct S8 { + void f1 () noexcept(noexcept(glob())); + void f2 () noexcept(noexcept(globx())); +}; + +S8 s8; +SA(!noexcept(s8.f1())); +SA(noexcept(s8.f2())); + +struct W { + constexpr operator bool(); +}; + +template<typename T> +struct S9 { + S9() noexcept(noexcept(w)) { } + S9 &operator=(S9 &&) noexcept(T::X); + W w; +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept35.C gcc/testsuite/g++.dg/cpp0x/noexcept35.C new file mode 100644 index 00000000000..b3859de9ebc --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept35.C @@ -0,0 +1,26 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +struct S { + void f1() noexcept(noexcept(fn())); + void f2() noexcept(noexcept(fnx())); + void fn(); + void fnx() noexcept; +}; + +void +S::f1() noexcept // { dg-error "different exception specifier" } +{ +} + +void +S::f2() // { dg-error "different exception specifier" } +{ +} + +struct S2 { + void f1() noexcept(noexcept(nosuchfn())); // { dg-error "not declared in this scope" } + void f2() noexcept(noexcept(nothere)); // { dg-error "not declared in this scope" } + void f3() noexcept(noexcept(nosuchfn())) { } // { dg-error "not declared in this scope" } + void f4() noexcept(noexcept(nothere)) { } // { dg-error "not declared in this scope" } +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept36.C gcc/testsuite/g++.dg/cpp0x/noexcept36.C new file mode 100644 index 00000000000..12c6d364099 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept36.C @@ -0,0 +1,9 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +template <typename _Alloc> class A { + typedef _Alloc _Alloc_traits; + A &operator=(A &&) noexcept(_Alloc_traits::_S_nothrow_move); + A &m_fn1(A &&) noexcept(_Alloc_traits::_S_nothrow_move); + void m_fn2(A<char>) {} +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept37.C gcc/testsuite/g++.dg/cpp0x/noexcept37.C new file mode 100644 index 00000000000..a81032f28e9 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept37.C @@ -0,0 +1,14 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +void fn1(void()); +template <typename> class A { + void _M_local_data(); + A() noexcept(_M_local_data); +}; + +class B { + void _S_initialize(); + static void _S_initialize_once(); +}; +void B::_S_initialize() { fn1(_S_initialize_once); } diff --git gcc/testsuite/g++.dg/cpp0x/noexcept38.C gcc/testsuite/g++.dg/cpp0x/noexcept38.C new file mode 100644 index 00000000000..9e5545bc022 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept38.C @@ -0,0 +1,23 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +struct A +{ + virtual void f(); + virtual void g() noexcept; + virtual void h() noexcept(false); +}; + +struct B : A +{ + void f() noexcept(true); + void g() noexcept(true); + void h() noexcept(true); +}; + +struct D : A +{ + void f() noexcept(false); + void g() noexcept(false); // { dg-error "looser throw specifier" } + void h() noexcept(false); +}; diff --git gcc/testsuite/g++.dg/cpp0x/noexcept39.C gcc/testsuite/g++.dg/cpp0x/noexcept39.C new file mode 100644 index 00000000000..da7490d651c --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/noexcept39.C @@ -0,0 +1,28 @@ +// PR c++/86476 - noexcept-specifier is a complete-class context +// { dg-do compile { target c++11 } } + +void f() noexcept(false); +void g() noexcept(true); +void h() noexcept; + +struct B { + friend void f() noexcept(false); + friend void g() noexcept(false); // { dg-error "different exception specifier" } + friend void h() noexcept(false); // { dg-error "different exception specifier" } +}; + +struct C { + friend void f() noexcept(true); // { dg-error "different exception specifier" } + friend void g() noexcept(true); // { dg-error "different exception specifier" } + friend void h() noexcept(true); // { dg-error "different exception specifier" } +}; + +void o() noexcept(false); +void p() noexcept(true); +void q() noexcept; + +struct D { + friend void o() noexcept(true); // { dg-error "different exception specifier" } + friend void p() noexcept(true); + friend void q() noexcept(true); +};