https://gcc.gnu.org/g:4e44fe4280bf2d9ddc135dec18ec109805c539b9
commit r16-4338-g4e44fe4280bf2d9ddc135dec18ec109805c539b9 Author: Jakub Jelinek <[email protected]> Date: Thu Oct 9 22:41:30 2025 +0200 c++: C++26 va_start - part of P3348R4 - C++26 should refer to C23 not C17 The C++26 https://wg21.link/P3348R4 C++26 should refer to C23 not C17 paper among other things changes va_start macro in the similar way how C23 has changed it. Now, unlike C17 and older, C++ has since forever allowed int (...) but just one wasn't able to use va_start/va_arg/va_end in such functions. With the current C++26 draft wording, we'd have to #define va_start(V, ...) __builtin_va_start (V, 0) like we've used for C23 before the PR107980 change. But Jonathan has kindly filed https://cplusplus.github.io/LWG/issue4388 which similarly to C23 will if accepted allow to define it as #define va_start(...) __builtin_c23_va_start(__VA_ARGS__) and let the compiler diagnose undesirable cases (see stdarg6.C testcase in the patch for what it can diagnose, basically anything that isn't either va_start (ap) or va_start (ap, i) where i is the last argument's identifier). This patch implements what assumes LWG4388 will pass. It also defines #define __STDC_VERSION_STDARG_H__ 202311L also for C++26. The hardest part is actually something different. C23 had to differentiate between C99 void foo (); i.e. unspecified arguments (but not stdarg) and the new C23 void bar (...); which is stdarg, but in both cases TYPE_ARG_TYPES (fntype) is NULL. This has been implemented through the new TYPE_NO_NAMED_ARGS_STDARG_P flag, fntypes with that flag set are considered stdarg_p and allow va_start in those, while fntypes with NULL TYPE_ARG_TYPES but the flag cleared are not stdarg_p, can accept any number of arguments but can't use va_start. So, I had to change various places in the C++ FE to pass true as the third argument to build_function_type for calls which are meant to be (...) so that one can actually use va_start in those. Done only for C++26 in order not to disturb older versions too much. And there is a problem with some of the builtins and #pragma weak which are using (...) declarations more in the sense of C17 unspecified arguments rather than this call has variable arguments. So, structural_comptypes now considers the non-C++26 (...) used for selected builtins and #pragma weak incompatible with C++26 (...) to avoid ICEs. 2025-10-09 Jakub Jelinek <[email protected]> gcc/ * ginclude/stdarg.h (va_start): Use __builtin_c23_va_start also for C++26. (__STDC_VERSION_STDARG_H__): Also define for C++26. gcc/c-family/ * c-common.h (D_CXX26): Define. * c-common.cc (c_common_resword): Add D_CXX26 to __builtin_c23_va_start flags, mention D_CXX26 in comment. gcc/cp/ * cp-tree.h (cp_build_function_type): Declare. * lex.cc: Implement va_start changes from P3348R4 - C++26 should refer to C23 not C17 paper. (init_reswords): Set D_CXX26 in mask for C++23 and older. * parser.cc (cp_parser_primary_expression): Handle RID_C23_VA_START. (cp_parser_builtin_c23_va_start): New function. * cp-objcp-common.cc (names_builtin_p): Likewise. * decl.cc (grokfndecl, check_function_type): Pass TYPE_NO_NAMED_ARGS_STDARG_P as last arg to build_function_type. (grokdeclarator, static_fn_type): Use cp_build_function_type instead of build_function_type. * typeck.cc (merge_types): Likewise. (structural_comptypes): Return false for TYPE_NO_NAMED_ARGS_STDARG_P differences. * lambda.cc (maybe_add_lambda_conv_op): Use cp_build_function_type instead of build_function_type. * tree.cc (cp_build_function_type): New function. (strip_typedefs): Pass TYPE_NO_NAMED_ARGS_STDARG_P as last arg to build_function_type. * name-lookup.cc (push_local_extern_decl_alias): Likewise. * module.cc (trees_in::tree_node): Use cp_build_function_type instead of build_function_type. * pt.cc (copy_default_args_to_explicit_spec, rebuild_function_or_method_type, build_deduction_guide): Likewise. (alias_ctad_tweaks): Pass TYPE_NO_NAMED_ARGS_STDARG_P as last arg to build_function_type. * decl2.cc (change_return_type, cp_reconstruct_complex_type): Likewise. gcc/testsuite/ * c-c++-common/cpp/has-builtin-4.c: Expect __has_builtin (__builtin_c23_va_start) == 1 also for C++26. * c-c++-common/Wvarargs.c (foo3): Don't expect undefined behavior warning for C++26. * g++.dg/cpp26/stdarg1.C: New test. * g++.dg/cpp26/stdarg2.C: New test. * g++.dg/cpp26/stdarg3.C: New test. * g++.dg/cpp26/stdarg4.C: New test. * g++.dg/cpp26/stdarg5.C: New test. * g++.dg/cpp26/stdarg6.C: New test. * g++.dg/cpp26/stdarg7.C: New test. * g++.dg/cpp26/stdarg8.C: New test. * g++.dg/cpp26/stdarg9.C: New test. * g++.dg/opt/pr60849.C (foo): Add explicit cast. Diff: --- gcc/c-family/c-common.cc | 9 +- gcc/c-family/c-common.h | 1 + gcc/cp/cp-objcp-common.cc | 1 + gcc/cp/cp-tree.h | 1 + gcc/cp/decl.cc | 13 +- gcc/cp/decl2.cc | 6 +- gcc/cp/lambda.cc | 7 +- gcc/cp/lex.cc | 2 + gcc/cp/module.cc | 2 +- gcc/cp/name-lookup.cc | 6 +- gcc/cp/parser.cc | 131 +++++++++++++++ gcc/cp/pt.cc | 10 +- gcc/cp/tree.cc | 16 +- gcc/cp/typeck.cc | 15 +- gcc/ginclude/stdarg.h | 6 +- gcc/testsuite/c-c++-common/Wvarargs.c | 2 +- gcc/testsuite/c-c++-common/cpp/has-builtin-4.c | 2 +- gcc/testsuite/g++.dg/cpp26/stdarg1.C | 158 ++++++++++++++++++ gcc/testsuite/g++.dg/cpp26/stdarg2.C | 212 +++++++++++++++++++++++++ gcc/testsuite/g++.dg/cpp26/stdarg3.C | 7 + gcc/testsuite/g++.dg/cpp26/stdarg4.C | 7 + gcc/testsuite/g++.dg/cpp26/stdarg5.C | 5 + gcc/testsuite/g++.dg/cpp26/stdarg6.C | 112 +++++++++++++ gcc/testsuite/g++.dg/cpp26/stdarg7.C | 11 ++ gcc/testsuite/g++.dg/cpp26/stdarg8.C | 26 +++ gcc/testsuite/g++.dg/cpp26/stdarg9.C | 16 ++ gcc/testsuite/g++.dg/opt/pr60849.C | 2 +- 27 files changed, 753 insertions(+), 33 deletions(-) diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc index e7dd4602ac11..54e16f708134 100644 --- a/gcc/c-family/c-common.cc +++ b/gcc/c-family/c-common.cc @@ -376,9 +376,10 @@ static bool nonnull_check_p (tree, unsigned HOST_WIDE_INT); C --std=c17: D_C23 | D_CXXONLY | D_OBJC C --std=c23: D_CXXONLY | D_OBJC ObjC is like C except that D_OBJC and D_CXX_OBJC are not set - C++ --std=c++98: D_CONLY | D_CXX11 | D_CXX20 | D_OBJC - C++ --std=c++11: D_CONLY | D_CXX20 | D_OBJC - C++ --std=c++20: D_CONLY | D_OBJC + C++ --std=c++98: D_CONLY | D_CXX11 | D_CXX20 | D_CXX26 | D_OBJC + C++ --std=c++11: D_CONLY | D_CXX20 | D_CXX26 | D_OBJC + C++ --std=c++20: D_CONLY | D_CXX26 | D_OBJC + C++ --std=c++26: D_CONLY | D_OBJC ObjC++ is like C++ except that D_OBJC is not set If -fno-asm is used, D_ASM is added to the mask. If @@ -462,7 +463,7 @@ const struct c_common_resword c_common_reswords[] = { "__builtin_tgmath", RID_BUILTIN_TGMATH, D_CONLY }, { "__builtin_offsetof", RID_OFFSETOF, 0 }, { "__builtin_types_compatible_p", RID_TYPES_COMPATIBLE_P, D_CONLY }, - { "__builtin_c23_va_start", RID_C23_VA_START, D_C23 }, + { "__builtin_c23_va_start", RID_C23_VA_START, D_C23 | D_CXX26 }, { "__builtin_va_arg", RID_VA_ARG, 0 }, { "__complex", RID_COMPLEX, 0 }, { "__complex__", RID_COMPLEX, 0 }, diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h index b6021d241731..bedbd4a94b0e 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -448,6 +448,7 @@ extern machine_mode c_default_pointer_mode; #define D_CXX20 0x8000 /* In C++, C++20 only. */ #define D_CXX_COROUTINES 0x10000 /* In C++, only with coroutines. */ #define D_CXX_MODULES 0x20000 /* In C++, only with modules. */ +#define D_CXX26 0x40000 /* In C++, C++26 only. */ #define D_CXX_CONCEPTS_FLAGS D_CXXONLY | D_CXX_CONCEPTS #define D_CXX_CHAR8_T_FLAGS D_CXXONLY | D_CXX_CHAR8_T diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc index ee1c0ba3de3e..c7e88cb7bfea 100644 --- a/gcc/cp/cp-objcp-common.cc +++ b/gcc/cp/cp-objcp-common.cc @@ -588,6 +588,7 @@ names_builtin_p (const char *name) case RID_BUILTIN_BIT_CAST: case RID_OFFSETOF: case RID_VA_ARG: + case RID_C23_VA_START: return 1; case RID_BUILTIN_OPERATOR_NEW: case RID_BUILTIN_OPERATOR_DELETE: diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 7298d3b81bd7..fcba9f5c0b02 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -8491,6 +8491,7 @@ extern tree cp_build_reference_type (tree, bool); extern tree move (tree); extern tree cp_build_qualified_type (tree, int, tsubst_flags_t = tf_warning_or_error); +extern tree cp_build_function_type (tree, tree); extern bool cv_qualified_p (const_tree); extern tree cv_unqualified (tree); extern special_function_kind special_function_p (const_tree); diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 885be3b2cecf..31f3c4b77928 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -12413,11 +12413,13 @@ grokfndecl (tree ctype, if (!same_type_p (TREE_TYPE (TREE_TYPE (decl)), integer_type_node)) { - tree oldtypeargs = TYPE_ARG_TYPES (TREE_TYPE (decl)); + tree dtype = TREE_TYPE (decl); + tree oldtypeargs = TYPE_ARG_TYPES (dtype); tree newtype; error_at (declspecs->locations[ds_type_spec], "%<::main%> must return %<int%>"); - newtype = build_function_type (integer_type_node, oldtypeargs); + newtype = build_function_type (integer_type_node, oldtypeargs, + TYPE_NO_NAMED_ARGS_STDARG_P (dtype)); TREE_TYPE (decl) = newtype; } if (warn_main) @@ -15324,7 +15326,7 @@ grokdeclarator (const cp_declarator *declarator, is_xobj_member_function = false; } - type = build_function_type (type, arg_types); + type = cp_build_function_type (type, arg_types); tree attrs = declarator->std_attributes; if (tx_qual) @@ -19312,7 +19314,8 @@ check_function_type (tree decl, tree current_function_parms) void_type_node, TREE_CHAIN (args)); else - fntype = build_function_type (void_type_node, args); + fntype = build_function_type (void_type_node, args, + TYPE_NO_NAMED_ARGS_STDARG_P (fntype)); fntype = (cp_build_type_attribute_variant (fntype, TYPE_ATTRIBUTES (TREE_TYPE (decl)))); fntype = cxx_copy_lang_qualifiers (fntype, TREE_TYPE (decl)); @@ -20809,7 +20812,7 @@ static_fn_type (tree memfntype) return memfntype; gcc_assert (TREE_CODE (memfntype) == METHOD_TYPE); args = TYPE_ARG_TYPES (memfntype); - fntype = build_function_type (TREE_TYPE (memfntype), TREE_CHAIN (args)); + fntype = cp_build_function_type (TREE_TYPE (memfntype), TREE_CHAIN (args)); fntype = apply_memfn_quals (fntype, type_memfn_quals (memfntype)); fntype = (cp_build_type_attribute_variant (fntype, TYPE_ATTRIBUTES (memfntype))); diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc index dfaef30dbfc0..0073f83a10ca 100644 --- a/gcc/cp/decl2.cc +++ b/gcc/cp/decl2.cc @@ -233,7 +233,8 @@ change_return_type (tree new_ret, tree fntype) if (TREE_CODE (fntype) == FUNCTION_TYPE) { - newtype = build_function_type (new_ret, args); + newtype = build_function_type (new_ret, args, + TYPE_NO_NAMED_ARGS_STDARG_P (fntype)); newtype = apply_memfn_quals (newtype, type_memfn_quals (fntype)); } @@ -1698,7 +1699,8 @@ cp_reconstruct_complex_type (tree type, tree bottom) else if (TREE_CODE (type) == FUNCTION_TYPE) { inner = cp_reconstruct_complex_type (TREE_TYPE (type), bottom); - outer = build_function_type (inner, TYPE_ARG_TYPES (type)); + outer = build_function_type (inner, TYPE_ARG_TYPES (type), + TYPE_NO_NAMED_ARGS_STDARG_P (type)); outer = apply_memfn_quals (outer, type_memfn_quals (type)); } else if (TREE_CODE (type) == METHOD_TYPE) diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc index 575daaa9d711..c2655a9949a7 100644 --- a/gcc/cp/lambda.cc +++ b/gcc/cp/lambda.cc @@ -1329,9 +1329,10 @@ maybe_add_lambda_conv_op (tree type) } tree stattype - = build_function_type (fn_result, FUNCTION_FIRST_USER_PARMTYPE (callop)); - stattype = (cp_build_type_attribute_variant - (stattype, TYPE_ATTRIBUTES (optype))); + = cp_build_function_type (fn_result, + FUNCTION_FIRST_USER_PARMTYPE (callop)); + stattype = cp_build_type_attribute_variant (stattype, + TYPE_ATTRIBUTES (optype)); if (flag_noexcept_type && TYPE_NOTHROW_P (TREE_TYPE (callop))) stattype = build_exception_variant (stattype, noexcept_true_spec); diff --git a/gcc/cp/lex.cc b/gcc/cp/lex.cc index da86989a41d2..3bc48659a8c7 100644 --- a/gcc/cp/lex.cc +++ b/gcc/cp/lex.cc @@ -243,6 +243,8 @@ init_reswords (void) mask |= D_CXX11; if (cxx_dialect < cxx20) mask |= D_CXX20; + if (cxx_dialect < cxx26) + mask |= D_CXX26; if (!flag_concepts) mask |= D_CXX_CONCEPTS; if (!flag_coroutines) diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index 103d7506da2d..bdc7e6af8748 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -10414,7 +10414,7 @@ trees_in::tree_node (bool is_use) if (klass) res = build_method_type_directly (klass, res, args); else - res = build_function_type (res, args); + res = cp_build_function_type (res, args); } } break; diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc index 09d16db2ead8..b7530616ef06 100644 --- a/gcc/cp/name-lookup.cc +++ b/gcc/cp/name-lookup.cc @@ -3726,7 +3726,11 @@ push_local_extern_decl_alias (tree decl) chain = &TREE_CHAIN (*chain); } - tree fn_type = build_function_type (TREE_TYPE (type), nargs); + bool no_named_args_stdarg + = TYPE_NO_NAMED_ARGS_STDARG_P (type); + tree fn_type + = build_function_type (TREE_TYPE (type), nargs, + no_named_args_stdarg); fn_type = apply_memfn_quals (fn_type, type_memfn_quals (type)); diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 362cddbaf692..b4000527bf30 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -2575,6 +2575,7 @@ static cp_expr cp_parser_expression (cp_parser *, cp_id_kind * = NULL, bool = false, bool = false, bool = false); static cp_expr cp_parser_constant_expression (cp_parser *, int = 0, bool * = NULL, bool = false); +static cp_expr cp_parser_builtin_c23_va_start (cp_parser *); static cp_expr cp_parser_builtin_offsetof (cp_parser *); static cp_expr cp_parser_lambda_expression @@ -6407,6 +6408,9 @@ cp_parser_primary_expression (cp_parser *parser, return build_x_va_arg (combined_loc, expression, type); } + case RID_C23_VA_START: + return cp_parser_builtin_c23_va_start (parser); + case RID_OFFSETOF: return cp_parser_builtin_offsetof (parser); @@ -11497,6 +11501,133 @@ cp_parser_constant_expression (cp_parser* parser, return expression; } +/* Parse __builtin_c23_va_start. + + c23-va-start-expression: + __builtin_c23_va_start ( assignment-expression ) + __builtin_c23_va_start ( assignment-expression , identifier ) + __builtin_c23_va_start ( assignment-expression , tokens[opt] ) + + The first form is the expected new C++26 form, the second if + identifier is the name of the last parameter before ... is meant + for backwards compatibility with C++23 and older. + The third form where LWG4388 requires all the preprocessing tokens + to be convertible to tokens and it can't contain unbalanced + parentheses is parsed with a warning and the tokens are just skipped. + This is because C++26 like C23 defines va_start macro as + va_start (ap, ...) and says second and later arguments to the macro + are discarded, yet we want to diagnose when people use something + which wasn't valid before C++26 and is not the single argument + va_start either. */ + +static cp_expr +cp_parser_builtin_c23_va_start (cp_parser *parser) +{ + location_t start_loc = cp_lexer_peek_token (parser->lexer)->location; + cp_lexer_consume_token (parser->lexer); + /* Look for the opening `('. */ + matching_parens parens; + parens.require_open (parser); + location_t arg_loc = cp_lexer_peek_token (parser->lexer)->location; + /* Now, parse the assignment-expression. */ + tree expression = cp_parser_assignment_expression (parser); + if (!cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_PAREN)) + { + location_t cloc = cp_lexer_peek_token (parser->lexer)->location; + if (!cp_parser_require (parser, CPP_COMMA, RT_COMMA)) + { + cp_parser_skip_to_closing_parenthesis (parser, false, false, + /*consume_paren=*/ true); + return error_mark_node; + } + if (cp_lexer_next_token_is (parser->lexer, CPP_NAME) + && cp_lexer_nth_token_is (parser->lexer, 2, CPP_CLOSE_PAREN)) + { + tree name = cp_lexer_peek_token (parser->lexer)->u.value; + location_t nloc = cp_lexer_peek_token (parser->lexer)->location; + tree decl = lookup_name (name); + tree last_parm = tree_last (DECL_ARGUMENTS (current_function_decl)); + if (!last_parm || decl != last_parm) + warning_at (nloc, OPT_Wvarargs, "optional second parameter of " + "%<va_start%> not last named argument"); + else + { + /* __builtin_va_start parsing does mark the argument as used and + read, for -Wunused* purposes mark it the same. */ + TREE_USED (last_parm) = 1; + mark_exp_read (last_parm); + } + cp_lexer_consume_token (parser->lexer); + } + else + { + unsigned nesting_depth = 0; + location_t sloc = cp_lexer_peek_token (parser->lexer)->location; + location_t eloc = sloc; + + /* For va_start (ap,) the ) comes from stdarg.h. + Use location of , in that case, otherwise without -Wsystem-headers + nothing is reported. After all, the problematic token is the + comma in that case. */ + if (cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_PAREN)) + sloc = eloc = cloc; + /* Not using cp_parser_skip_to_closing_parenthesis here, because + the tokens in second and further arguments don't have to be + fully balanced, only can't contain unbalanced parentheses. + So, va_start (ap, [[[[[[[[[{{{{{{{{{}]); + is valid C++ for which we want to warn, + #define X id); something ( + va_start (ap, X); + is IFNDR (not detectable unless the preprocessor special cases + va_start macro). */ + while (true) + { + cp_token *token = cp_lexer_peek_token (parser->lexer); + if (token->type == CPP_CLOSE_PAREN && !nesting_depth) + break; + + if (token->type == CPP_EOF) + break; + if (token->type == CPP_OPEN_PAREN) + ++nesting_depth; + else if (token->type == CPP_CLOSE_PAREN) + --nesting_depth; + else if (token->type == CPP_PRAGMA) + { + cp_parser_skip_to_pragma_eol (parser, token); + continue; + } + eloc = token->location; + cp_lexer_consume_token (parser->lexer); + } + if (sloc != eloc) + sloc = make_location (sloc, sloc, eloc); + warning_at (sloc, OPT_Wvarargs, + "%<va_start%> macro used with additional " + "arguments other than identifier of the " + "last named argument"); + } + } + /* Look for the closing `)'. */ + location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location; + /* Construct a location of the form: + __builtin_c23_va_start (ap, arg) + ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ + with the caret at the first argument, ranging from the start + of the "__builtin_c23_va_start" token to the close paren. */ + location_t combined_loc = make_location (arg_loc, start_loc, finish_loc); + parens.require_close (parser); + tree fndecl = builtin_decl_explicit (BUILT_IN_VA_START); + releasing_vec args; + vec_safe_push (args, expression); + vec_safe_push (args, integer_zero_node); + tree ret = finish_call_expr (fndecl, &args, false, true, + tf_warning_or_error); + if (TREE_CODE (ret) == CALL_EXPR) + SET_EXPR_LOCATION (ret, combined_loc); + return cp_expr (ret, combined_loc); +} + /* Parse __builtin_offsetof. offsetof-expression: diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 9b32049e71be..f61223f0baba 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -2654,8 +2654,7 @@ copy_default_args_to_explicit_spec (tree decl) new_spec_types); } else - new_type = build_function_type (TREE_TYPE (old_type), - new_spec_types); + new_type = cp_build_function_type (TREE_TYPE (old_type), new_spec_types); new_type = cp_build_type_attribute_variant (new_type, TYPE_ATTRIBUTES (old_type)); new_type = cxx_copy_lang_qualifiers (new_type, old_type); @@ -14779,7 +14778,7 @@ rebuild_function_or_method_type (tree t, tree args, tree return_type, tree new_type; if (TREE_CODE (t) == FUNCTION_TYPE) { - new_type = build_function_type (return_type, arg_types); + new_type = cp_build_function_type (return_type, arg_types); new_type = apply_memfn_quals (new_type, type_memfn_quals (t)); } else @@ -31100,7 +31099,7 @@ build_deduction_guide (tree type, tree ctor, tree outer_args, tsubst_flags_t com = copy_node (INNERMOST_TEMPLATE_PARMS (tparms)); } - tree fntype = build_function_type (type, fparms); + tree fntype = cp_build_function_type (type, fparms); tree ded_fn = build_lang_decl_loc (loc, FUNCTION_DECL, dguide_name (type), fntype); @@ -31532,7 +31531,8 @@ alias_ctad_tweaks (tree tmpl, tree uguides) tree fntype = TREE_TYPE (fprime); ret = lookup_template_class (TPARMS_PRIMARY_TEMPLATE (atparms), targs, in_decl, NULL_TREE, complain); - fntype = build_function_type (ret, TYPE_ARG_TYPES (fntype)); + fntype = build_function_type (ret, TYPE_ARG_TYPES (fntype), + TYPE_NO_NAMED_ARGS_STDARG_P (fntype)); TREE_TYPE (fprime) = fntype; if (TREE_CODE (fprime) == TEMPLATE_DECL) TREE_TYPE (DECL_TEMPLATE_RESULT (fprime)) = fntype; diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index c6ce7e988cee..814465c65292 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -1537,6 +1537,19 @@ cp_build_qualified_type (tree type, int type_quals, return result; } +/* Return a FUNCTION_TYPE for a function returning VALUE_TYPE + with ARG_TYPES arguments. Wrapper around build_function_type + which ensures TYPE_NO_NAMED_ARGS_STDARG_P is set if ARG_TYPES + is NULL for C++26. */ + +tree +cp_build_function_type (tree value_type, tree arg_types) +{ + return build_function_type (value_type, arg_types, + cxx_dialect >= cxx26 + && arg_types == NULL_TREE); +} + /* Return TYPE with const and volatile removed. */ tree @@ -1782,7 +1795,8 @@ strip_typedefs (tree t, bool *remove_attributes /* = NULL */, } else { - result = build_function_type (type, arg_types); + result = build_function_type (type, arg_types, + TYPE_NO_NAMED_ARGS_STDARG_P (t)); result = apply_memfn_quals (result, type_memfn_quals (t)); } diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc index b876409d8474..dbadeb770858 100644 --- a/gcc/cp/typeck.cc +++ b/gcc/cp/typeck.cc @@ -1035,7 +1035,7 @@ merge_types (tree t1, tree t2) gcc_assert (quals == type_memfn_quals (t2)); gcc_assert (rqual == type_memfn_rqual (t2)); - tree rval = build_function_type (valtype, parms); + tree rval = cp_build_function_type (valtype, parms); rval = apply_memfn_quals (rval, quals); tree raises = merge_exception_specifiers (TYPE_RAISES_EXCEPTIONS (t1), TYPE_RAISES_EXCEPTIONS (t2)); @@ -1058,10 +1058,10 @@ merge_types (tree t1, tree t2) /* If this was a member function type, get back to the original type of type member function (i.e., without the class instance variable up front. */ - t1 = build_function_type (TREE_TYPE (t1), - TREE_CHAIN (TYPE_ARG_TYPES (t1))); - t2 = build_function_type (TREE_TYPE (t2), - TREE_CHAIN (TYPE_ARG_TYPES (t2))); + t1 = cp_build_function_type (TREE_TYPE (t1), + TREE_CHAIN (TYPE_ARG_TYPES (t1))); + t2 = cp_build_function_type (TREE_TYPE (t2), + TREE_CHAIN (TYPE_ARG_TYPES (t2))); t3 = merge_types (t1, t2); t3 = build_method_type_directly (basetype, TREE_TYPE (t3), TYPE_ARG_TYPES (t3)); @@ -1550,8 +1550,11 @@ structural_comptypes (tree t1, tree t2, int strict) return false; break; - case METHOD_TYPE: case FUNCTION_TYPE: + if (TYPE_NO_NAMED_ARGS_STDARG_P (t1) != TYPE_NO_NAMED_ARGS_STDARG_P (t2)) + return false; + /* FALLTHRU */ + case METHOD_TYPE: /* Exception specs and memfn_rquals were checked above. */ if (!same_type_p (TREE_TYPE (t1), TREE_TYPE (t2))) return false; diff --git a/gcc/ginclude/stdarg.h b/gcc/ginclude/stdarg.h index 3a1939716668..648428863f54 100644 --- a/gcc/ginclude/stdarg.h +++ b/gcc/ginclude/stdarg.h @@ -44,7 +44,8 @@ typedef __builtin_va_list __gnuc_va_list; if this invocation was from the user program. */ #ifdef _STDARG_H -#if defined __STDC_VERSION__ && __STDC_VERSION__ > 201710L +#if (defined __STDC_VERSION__ && __STDC_VERSION__ > 201710L) \ + || __cplusplus >= 202400L #define va_start(...) __builtin_c23_va_start(__VA_ARGS__) #else #define va_start(v,l) __builtin_va_start(v,l) @@ -125,7 +126,8 @@ typedef __gnuc_va_list va_list; #endif /* not __svr4__ */ -#if defined __STDC_VERSION__ && __STDC_VERSION__ > 201710L +#if (defined __STDC_VERSION__ && __STDC_VERSION__ > 201710L) \ + || __cplusplus >= 202400L #define __STDC_VERSION_STDARG_H__ 202311L #endif diff --git a/gcc/testsuite/c-c++-common/Wvarargs.c b/gcc/testsuite/c-c++-common/Wvarargs.c index ea86ba30dcd7..71f24f861b7f 100644 --- a/gcc/testsuite/c-c++-common/Wvarargs.c +++ b/gcc/testsuite/c-c++-common/Wvarargs.c @@ -50,6 +50,6 @@ foo3 (int a, register int b, ...) // { dg-warning "ISO C\\+\\+17 does not allow { va_list vp; /* 'b' is declared with register storage, so warn. */ - va_start (vp, b); /* { dg-warning "undefined behavior" } */ + va_start (vp, b); /* { dg-warning "undefined behavior" "" { target { c || c++23_down } } } */ va_end (vp); } diff --git a/gcc/testsuite/c-c++-common/cpp/has-builtin-4.c b/gcc/testsuite/c-c++-common/cpp/has-builtin-4.c index 65d2b188d9aa..186e2f1bbfb5 100644 --- a/gcc/testsuite/c-c++-common/cpp/has-builtin-4.c +++ b/gcc/testsuite/c-c++-common/cpp/has-builtin-4.c @@ -9,7 +9,7 @@ #if __has_builtin (__builtin_va_arg) != 1 #error "no __builtin_va_arg" #endif -#if __STDC_VERSION__ >= 202311L +#if (__STDC_VERSION__ >= 202311L || __cplusplus >= 202400L) #if __has_builtin (__builtin_c23_va_start) != 1 #error "no __builtin_c23_va_start" #endif diff --git a/gcc/testsuite/g++.dg/cpp26/stdarg1.C b/gcc/testsuite/g++.dg/cpp26/stdarg1.C new file mode 100644 index 000000000000..4ce288b7a1f7 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/stdarg1.C @@ -0,0 +1,158 @@ +// P3348R4 - C++26 should refer to C23 not C17 +// { dg-do run { target c++26 } } + +#include <stdarg.h> + +double +f (...) +{ + va_list ap; + va_start (ap); + double ret = va_arg (ap, int); + ret += va_arg (ap, double); + ret += va_arg (ap, int); + ret += va_arg (ap, double); + va_end (ap); + return ret; +} + +void +g (...) +{ + va_list ap; + va_start (ap, random ! ignored, ignored ** text); // { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" } + for (int i = 0; i < 10; i++) + if (va_arg (ap, double) != i) + __builtin_abort (); + va_end (ap); +} + +void +h1 (int x, ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); +} + +void +h2 (int x(), ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); +} + +void +h3 (int x[10], ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); +} + +void +h4 (char x, ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); +} + +void +h5 (float x, ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); +} + +void +h6 (long x, ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); +} + +struct s { char c[1000]; }; + +void +h7 (struct s x, ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); +} + +int +main () +{ + if (f (1, 2.0, 3, 4.0) != 10.0) + __builtin_abort (); + g (0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0); + g (0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f); + h1 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + h2 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + h3 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + h4 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + h5 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + h6 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + h7 (s {}, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); +} diff --git a/gcc/testsuite/g++.dg/cpp26/stdarg2.C b/gcc/testsuite/g++.dg/cpp26/stdarg2.C new file mode 100644 index 000000000000..218bcd6cf18c --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/stdarg2.C @@ -0,0 +1,212 @@ +// P3348R4 - C++26 should refer to C23 not C17 +// { dg-do run { target c++26 } } + +#include <stdarg.h> + +struct s { char c[1000]; }; + +struct s +f (...) +{ + va_list ap; + va_start (ap); + double r = va_arg (ap, int); + r += va_arg (ap, double); + r += va_arg (ap, int); + r += va_arg (ap, double); + va_end (ap); + struct s ret = {}; + ret.c[0] = r; + ret.c[999] = 42; + return ret; +} + +struct s +g (...) +{ + va_list ap; + va_start (ap, random ! ignored, ignored ** text); // { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" } + for (int i = 0; i < 10; i++) + if (va_arg (ap, double) != i) + __builtin_abort (); + va_end (ap); + struct s ret = {}; + ret.c[0] = 17; + ret.c[999] = 58; + return ret; +} + +struct s +h1 (int x, ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); + struct s ret = {}; + ret.c[0] = 32; + ret.c[999] = 95; + return ret; +} + +struct s +h2 (int x(), ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); + struct s ret = {}; + ret.c[0] = 5; + ret.c[999] = 125; + return ret; +} + +struct s +h3 (int x[10], ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); + struct s ret = {}; + ret.c[0] = 8; + ret.c[999] = 12; + return ret; +} + +struct s +h4 (char x, ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); + struct s ret = {}; + ret.c[0] = 18; + ret.c[999] = 28; + return ret; +} + +struct s +h5 (float x, ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); + struct s ret = {}; + ret.c[0] = 38; + ret.c[999] = 48; + return ret; +} + +struct s +h6 (long x, ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); + struct s ret = {}; + ret.c[0] = 58; + ret.c[999] = 68; + return ret; +} + +struct s +h7 (struct s x, ...) +{ + va_list ap; + va_start (ap); + for (int i = 0; i < 10; i++) + { + if (va_arg (ap, double) != i) + __builtin_abort (); + i++; + if (va_arg (ap, int) != i) + __builtin_abort (); + } + va_end (ap); + struct s ret = {}; + ret.c[0] = 78; + ret.c[999] = 88; + return ret; +} + +int +main () +{ + struct s x = f (1, 2.0, 3, 4.0); + if (x.c[0] != 10 || x.c[999] != 42) + __builtin_abort (); + x = g (0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0); + if (x.c[0] != 17 || x.c[999] != 58) + __builtin_abort (); + x = g (0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f); + if (x.c[0] != 17 || x.c[999] != 58) + __builtin_abort (); + x = h1 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + if (x.c[0] != 32 || x.c[999] != 95) + __builtin_abort (); + x = h2 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + if (x.c[0] != 5 || x.c[999] != 125) + __builtin_abort (); + x = h3 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + if (x.c[0] != 8 || x.c[999] != 12) + __builtin_abort (); + x = h4 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + if (x.c[0] != 18 || x.c[999] != 28) + __builtin_abort (); + x = h5 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + if (x.c[0] != 38 || x.c[999] != 48) + __builtin_abort (); + x = h6 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + if (x.c[0] != 58 || x.c[999] != 68) + __builtin_abort (); + x = h7 (s {}, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9); + if (x.c[0] != 78 || x.c[999] != 88) + __builtin_abort (); +} diff --git a/gcc/testsuite/g++.dg/cpp26/stdarg3.C b/gcc/testsuite/g++.dg/cpp26/stdarg3.C new file mode 100644 index 000000000000..7ed3d67343ab --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/stdarg3.C @@ -0,0 +1,7 @@ +// P3348R4 - C++26 should refer to C23 not C17 +// { dg-do run { target c++26 } } +// { dg-additional-options "-O2" } + +#include "stdarg1.C" + +// { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" "" { target *-*-* } 0 } diff --git a/gcc/testsuite/g++.dg/cpp26/stdarg4.C b/gcc/testsuite/g++.dg/cpp26/stdarg4.C new file mode 100644 index 000000000000..d1a3350e0aa6 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/stdarg4.C @@ -0,0 +1,7 @@ +// P3348R4 - C++26 should refer to C23 not C17 +// { dg-do run { target c++26 } } +// { dg-additional-options "-O2" } + +#include "stdarg2.C" + +// { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" "" { target *-*-* } 0 } diff --git a/gcc/testsuite/g++.dg/cpp26/stdarg5.C b/gcc/testsuite/g++.dg/cpp26/stdarg5.C new file mode 100644 index 000000000000..8c01089a49f4 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/stdarg5.C @@ -0,0 +1,5 @@ +// P3348R4 - C++26 should refer to C23 not C17 +// { dg-do run { target c++26 } } +// { dg-additional-options "-O2" } + +#include "../../gcc.dg/c23-stdarg-9.c" diff --git a/gcc/testsuite/g++.dg/cpp26/stdarg6.C b/gcc/testsuite/g++.dg/cpp26/stdarg6.C new file mode 100644 index 000000000000..b3d1a85ef17a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/stdarg6.C @@ -0,0 +1,112 @@ +// P3348R4 - C++26 should refer to C23 not C17 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-O2" } + +#include <stdarg.h> + +int i; + +void +f0 (...) +{ + va_list ap; + va_start (ap); + va_end (ap); +} + +void +f1 (...) +{ + va_list ap; + va_start (ap, i); // { dg-warning "optional second parameter of 'va_start' not last named argument" } + va_end (ap); +} + +void +f2 (...) +{ + int j = 0; + va_list ap; + va_start (ap, j); // { dg-warning "optional second parameter of 'va_start' not last named argument" } + va_end (ap); +} + +void +f3 (int k, int l, ...) +{ + va_list ap; + va_start (ap, k); // { dg-warning "optional second parameter of 'va_start' not last named argument" } + va_end (ap); +} + +void +f4 (int k, int l, ...) +{ + va_list ap; + va_start (ap, l); + va_end (ap); +} + +void +f5 (int k, int l, ...) +{ + va_list ap; + va_start (ap, (int) l); // { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" } + va_end (ap); +} + +void +f6 (int k, int l, ...) +{ + va_list ap; + va_start (ap, l + 0); // { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" } + va_end (ap); +} + +void +f7 (int k, int l, ...) +{ + va_list ap; + va_start (ap, ()()(), [][][], {}{}{}, *+-/1({[_*_]})%&&!?!?); // { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" } + va_end (ap); +} + +void +f8 (...) +{ + va_list ap; + va_start (ap,); // { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" } + va_end (ap); +} + +void +f9 (int k, int l, ...) +{ + va_list ap; + va_start (ap, k+l+****2); // { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" } + va_end (ap); +} + +void +f10 (int m, ...) +{ + va_list ap; + va_start (ap, m); + va_end (ap); +} + +void +f11 (int k, int l, ...) +{ + va_list ap; + va_start (ap, ()()()[[[}}}); // { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" } + va_end (ap); +} + +void +f12 (int k, int l, ...) +{ + va_list ap; + va_start (ap, ]]]]]]{{{{{{); // { dg-warning "'va_start' macro used with additional arguments other than identifier of the last named argument" } + va_end (ap); +} diff --git a/gcc/testsuite/g++.dg/cpp26/stdarg7.C b/gcc/testsuite/g++.dg/cpp26/stdarg7.C new file mode 100644 index 000000000000..e60e3cba9b94 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/stdarg7.C @@ -0,0 +1,11 @@ +// P3348R4 - C++26 should refer to C23 not C17 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-O2" } + +#include <stdarg.h> + +void +f (...) +{ + va_start (); // { dg-error "expected primary-expression before '\\\)' token" } +} diff --git a/gcc/testsuite/g++.dg/cpp26/stdarg8.C b/gcc/testsuite/g++.dg/cpp26/stdarg8.C new file mode 100644 index 000000000000..71006041abac --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/stdarg8.C @@ -0,0 +1,26 @@ +// P3348R4 - C++26 should refer to C23 not C17 +// { dg-do run { target c++26 } } + +#include <stdarg.h> + +int +main () +{ + int v = 0; + auto a = [&] (...) { + va_list ap; + va_start (ap); + int b = 42; + if (v) + b = va_arg (ap, int); + va_end (ap); + return b; + }; + if (a () != 42) + __builtin_abort (); + v = 1; + if (a (1, 2) != 1) + __builtin_abort (); + if (a (13, 2.0f, 2ULL) != 13) + __builtin_abort (); +} diff --git a/gcc/testsuite/g++.dg/cpp26/stdarg9.C b/gcc/testsuite/g++.dg/cpp26/stdarg9.C new file mode 100644 index 000000000000..0f0d7541dc7a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/stdarg9.C @@ -0,0 +1,16 @@ +// P3348R4 - C++26 should refer to C23 not C17 +// { dg-do compile } + +#include <stdarg.h> + +#if __cplusplus >= 202400L +#ifndef __STDC_VERSION_STDARG_H__ +#error __STDC_VERSION_STDARG_H__ not defined for C++26 +#elif __STDC_VERSION_STDARG_H__ != 202311L +#error Unexpected __STDC_VERSION_STDARG_H__ value +#endif +#else +#ifdef __STDC_VERSION_STDARG_H__ +#error __STDC_VERSION_STDARG_H__ defined for C++ < 26 +#endif +#endif diff --git a/gcc/testsuite/g++.dg/opt/pr60849.C b/gcc/testsuite/g++.dg/opt/pr60849.C index 52d8826b0c88..efce543511ff 100644 --- a/gcc/testsuite/g++.dg/opt/pr60849.C +++ b/gcc/testsuite/g++.dg/opt/pr60849.C @@ -7,7 +7,7 @@ extern "C" int isnan (); void foo(float a) { int (*xx)(...); - xx = isnan; + xx = (int (*)(...)) isnan; if (xx(a)) g++; }
