On 10/6/25 4:57 PM, Jakub Jelinek wrote:
On Sun, Oct 05, 2025 at 04:00:16PM +0100, Jason Merrill wrote:
Oh, I forgot to enable
    #define __STDC_VERSION_STDARG_H__ 202311L
also for C++26, shall I do this in this patch or incrementally?

In this patch seems desirable.

Here is a new version of the patch, which defines this + adds test for that.

Or don't have any unspecified argument functions, not even builtins
and require them to be (...) but then live with
those 3 tests not working.
One is trying to declare
extern "C" int signbit (double);
which is kind of wrong because signbit is a builtin type-generic
macro and no signbit function exists, another is doing the same
for isnan:
extern "C" int isnan (double);
(I think we could live with these 2)

Pedantically, these only seem like errors at link time if in fact no such
function is provided by the library, and glibc provides a definition of
isnan(double).  And the isnan test isn't even linked.  A permerror seems
wrong, it should only be a warning.

But also I don't see why we can't handle these redeclarations just as before
after adding the flag; we just need to adjust the prototype_p check in
decls_match.

I just went with only setting TYPE_NO_NAMED_ARGS_STDARG_P for cxx_dialect >=
cxx26 in order not to affect older standards at all, and made
structural_comptypes punt on TYPE_NO_NAMED_ARGS_STDARG_P differences (to
avoid the ICEs).
I think all those signbit and isnan tests are just weird, C says those are
macros, not functions, and glibc actually doesn't define those as functions.
So it is just the test that try something weird.  With this patch everything
passes.
As for the permerror, I don't think it is wrong, the ... functions have
sometimes a different ABI (e.g. on x86_64 caller needs to initialize %rax
to number of float ... arguments), so implicitly casting from int ()
function type which doesn't have any arguments to int (...) feels
importantly incompatible.  With explicit cast as now done in this version
of the patch it works fine.

Ah, the permerror is on the conversion, I agree that's correct. I thought you were talking about a permerror on the declaration.

but weak1.C is
#pragma weak bar1 = foo1
extern "C" void foo1 (void) {}
where #pragma weak kind of introduces a (...) type.

I would think the name introduced by #pragma weak should have the type of
the target; by the time we declare the alias in
maybe_apply_pending_pragma_weaks we have a declaration for the target or
it's an error.

I agree maybe_apply_pending_pragma_weaks is just weird (but with this
version of the patch it doesn't fail).  It is only for the
#pragma foo = bar
case where foo has not been declared in the whole function and you just want
to make foo be exported as an alias to bar (everything else is handled
elsewhere).
It seems to be totally wrong e.g. for VAR_DECLs or I think for C23 as well
given that it doesn't have implicit function definitions.
       decl = build_decl (UNKNOWN_LOCATION,
                          target ? TREE_CODE (target->decl) : FUNCTION_DECL,
                          alias_id, default_function_type);
I think perhaps we could move the if (!target) check with error + continue
before this build_decl and drop the target ? and : FUNCTION_DECL parts and
use TREE_TYPE (target->decl) instead of default_function_type?

That said, because it doesn't fail with this version of the patch, perhaps
it can be done independently.

Agreed.

2025-10-06  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/
        * 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-objcp-common.cc (names_builtin_p): Likewise.
        * decl.cc (grokfndecl, grokdeclarator, check_function_type,
        static_fn_type): Pass true as last build_function_type argument if
        in some cases second argument is NULL for C++26 or if original
        FUNCTION_TYPE has TYPE_NO_NAMED_ARGS_STDARG_P flag set.
        * typeck.cc (merge_types): Likewise.
        (structural_comptypes): Return false for TYPE_NO_NAMED_ARGS_STDARG_P
        differences.
        * lambda.cc (maybe_add_lambda_conv_op): Likewise.
        * tree.cc (strip_typedefs): Likewise.
        * name-lookup.cc (push_local_extern_decl_alias): Likewise.
        * module.cc (trees_in::tree_node): Likewise.
        * pt.cc (copy_default_args_to_explicit_spec,
        rebuild_function_or_method_type, build_deduction_guide,
        alias_ctad_tweaks): Likewise.
        * 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.

--- gcc/ginclude/stdarg.h.jj    2025-10-04 09:42:23.775001859 +0200
+++ gcc/ginclude/stdarg.h       2025-10-06 16:05:22.066861182 +0200
@@ -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 + 0 >= 202400L

Why __cplusplus + 0?  I don't see that pattern anywhere else.

  #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 + 0 >= 202400L
  #define __STDC_VERSION_STDARG_H__     202311L
  #endif
--- gcc/cp/parser.cc.jj 2025-10-04 09:50:00.063564536 +0200
+++ gcc/cp/parser.cc    2025-10-06 17:05:27.170484820 +0200
@@ -6407,6 +6407,118 @@ cp_parser_primary_expression (cp_parser
            return build_x_va_arg (combined_loc, expression, type);
          }
+ case RID_C23_VA_START:

Please factor this out into a separate function, with a grammar summary in the comment.

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

Why not use cp_parser_skip_to_closing_parenthesis instead of this loop? Needs a comment if there's a reason.

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

require_close returns the token, so it seems unnecessary to peek it first.

+           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 ret;
+         }
+
        case RID_OFFSETOF:
          return cp_parser_builtin_offsetof (parser);
@@ -15324,7 +15326,9 @@ grokdeclarator (const cp_declarator *dec
                is_xobj_member_function = false;
              }
- type = build_function_type (type, arg_types);
+           type = build_function_type (type, arg_types,
+                                       cxx_dialect >= cxx26
+                                       && arg_types == NULL_TREE);

How about adding cp_build_function_type to encapsulate this pattern?

Jason

Reply via email to