Repository: llvm/llvm-project
Pull request #131166: [C23] Add __builtin_c23_va_start
Author: login: AaronBallman
Labels: name: clang
description: Clang issues not falling into any other category
name: backend:X86
name: c23
name: clang:frontend
description: Language frontend issues, e.g. anything involving "Sema"
name: clang:headers
description: Headers provided by Clang, e.g. for intrinsics
name: clang:codegen
description: IR generation bugs: mangling, exceptions, etc.
Created: 2025-03-13T16:32:53Z | Merged: 2025-03-15T15:01:54Z

This builtin is supported by GCC and is a way to improve diagnostic behavior for va_start in C23 mode. C23 no longer requires a second argument to the va_start macro in support of variadic functions with no leading parameters. However, we still want to diagnose passing more than two arguments, or diagnose when passing something other than the last parameter in the variadic function.

This also updates the freestanding <stdarg.h> header to use the new builtin, same as how GCC works.

Fixes #124031

Linked issue #124031: [clang] Support __builtin_c23_va_start
GCC added `__builtin_c23_va_start()` to diagnose improper arguments to `va_start`. Some projects may want to take advantage of this.

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107980
Linked issue comments: user: login: llvmbot
created_at: 2025-01-23T00:06:38Z
body: 
@llvm/issue-subscribers-clang-frontend

Author: Nathan Chancellor (nathanchance)

<details>
GCC added `__builtin_c23_va_start()` to diagnose improper arguments to `va_start`. Some projects may want to take advantage of this.

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107980
</details>

author_association: MEMBER
user: login: shafik
created_at: 2025-01-23T01:27:44Z
body: CC @AaronBallman 
author_association: COLLABORATOR

Diff:
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 387251804fa76..cc12ff5bad353 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -4283,6 +4283,17 @@ the ellipsis. This function initializes the given ``__builtin_va_list`` object.
 It is undefined behavior to call this function on an already initialized
 ``__builtin_va_list`` object.
 
+* ``void __builtin_c23_va_start(__builtin_va_list list, ...)``
+
+A builtin function for the target-specific ``va_start`` function-like macro,
+available only in C23 and later. The builtin accepts zero or one argument for
+the ellipsis (``...``). If such an argument is provided, it should be the name
+of the parameter preceeding the ellipsis, which is used for compatibility with
+C versions before C23. It is an error to provide two or more variadic arguments.
+This function initializes the given ``__builtin_va_list`` object. It is
+undefined behavior to call this function on an already initialized
+``__builtin_va_list`` object.
+
 * ``void __builtin_va_end(__builtin_va_list list)``
 
 A builtin function for the target-specific ``va_end`` function-like macro. This
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 6456acfcc2ada..b5966f9fa1c63 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -123,6 +123,10 @@ C2y Feature Support
 
 C23 Feature Support
 ^^^^^^^^^^^^^^^^^^^
+- Added ``__builtin_c23_va_start()`` for compatibility with GCC and to enable
+  better diagnostic behavior for the ``va_start()`` macro in C23 and later.
+  This also updates the definition of ``va_start()`` in ``<stdarg.h>`` to use
+  the new builtin. Fixes #GH124031.
 
 Non-comprehensive list of changes in this release
 -------------------------------------------------
diff --git a/clang/include/clang/Basic/Builtins.h b/clang/include/clang/Basic/Builtins.h
index 12c250afb4c61..3a5e31de2bc50 100644
--- a/clang/include/clang/Basic/Builtins.h
+++ b/clang/include/clang/Basic/Builtins.h
@@ -44,6 +44,7 @@ enum LanguageID : uint16_t {
   OCL_DSE = 0x400,           // builtin requires OpenCL device side enqueue.
   ALL_OCL_LANGUAGES = 0x800, // builtin for OCL languages.
   HLSL_LANG = 0x1000,        // builtin requires HLSL.
+  C23_LANG = 0x2000,         // builtin requires C23 or later.
   ALL_LANGUAGES = C_LANG | CXX_LANG | OBJC_LANG, // builtin for all languages.
   ALL_GNU_LANGUAGES = ALL_LANGUAGES | GNU_LANG,  // builtin requires GNU mode.
   ALL_MS_LANGUAGES = ALL_LANGUAGES | MS_LANG     // builtin requires MS mode.
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 2fbdfaea57ccd..72a5e495c4059 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
   let Prototype = "void(__builtin_va_list_ref, ...)";
 }
 
+def BuiltinC23VaStart : LangBuiltin<"C23_LANG"> {
+  let Spellings = ["__builtin_c23_va_start"];
+  let Attributes = [NoThrow, CustomTypeChecking];
+  let Prototype = "void(__builtin_va_list_ref, ...)";
+}
+
 def BuiltinStdargStart : Builtin {
   let Spellings = ["__builtin_stdarg_start"];
   let Attributes = [NoThrow, CustomTypeChecking];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 135d3bd161356..86c9c955c1c78 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10619,6 +10619,9 @@ def warn_second_arg_of_va_start_not_last_non_variadic_param : Warning<
 def warn_c17_compat_ellipsis_only_parameter : Warning<
   "'...' as the only parameter of a function is incompatible with C standards "
   "before C23">, DefaultIgnore, InGroup<CPre23Compat>;
+def warn_c17_compat_va_start_one_arg : Warning<
+  "passing only one argument to 'va_start' is incompatible with C standards "
+  "before C23">, DefaultIgnore, InGroup<CPre23Compat>;
 def warn_va_start_type_is_undefined : Warning<
   "passing %select{an object that undergoes default argument promotion|"
   "an object of reference type|a parameter declared with the 'register' "
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b6bf1e468ef2c..414f3c6eff038 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2499,9 +2499,9 @@ class Sema final : public SemaBase {
 
   void checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, CallExpr *TheCall);
 
-  /// Check the arguments to '__builtin_va_start' or '__builtin_ms_va_start'
-  /// for validity.  Emit an error and return true on failure; return false
-  /// on success.
+  /// Check the arguments to '__builtin_va_start', '__builtin_ms_va_start',
+  /// or '__builtin_c23_va_start' for validity. Emit an error and return true
+  /// on failure; return false on success.
   bool BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall);
   bool BuiltinVAStartARMMicrosoft(CallExpr *Call);
 
diff --git a/clang/lib/Basic/Builtins.cpp b/clang/lib/Basic/Builtins.cpp
index e7829a461bbc5..885abdc152e3a 100644
--- a/clang/lib/Basic/Builtins.cpp
+++ b/clang/lib/Basic/Builtins.cpp
@@ -191,6 +191,9 @@ static bool builtinIsSupported(const llvm::StringTable &Strings,
   /* consteval Unsupported */
   if (!LangOpts.CPlusPlus20 && strchr(AttributesStr.data(), 'G') != nullptr)
     return false;
+  /* C23 unsupported */
+  if (!LangOpts.C23 && BuiltinInfo.Langs == C23_LANG)
+    return false;
   return true;
 }
 
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index a5ed2595bad4d..c126f88b9e3a5 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -3551,6 +3551,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
   case Builtin::BI__builtin_stdarg_start:
   case Builtin::BI__builtin_va_start:
   case Builtin::BI__va_start:
+  case Builtin::BI__builtin_c23_va_start:
   case Builtin::BI__builtin_va_end:
     EmitVAStartEnd(BuiltinID == Builtin::BI__va_start
                        ? EmitScalarExpr(E->getArg(0))
diff --git a/clang/lib/Headers/__stdarg_va_arg.h b/clang/lib/Headers/__stdarg_va_arg.h
index 89bd2f65d3bea..ebdb6f9d4b1eb 100644
--- a/clang/lib/Headers/__stdarg_va_arg.h
+++ b/clang/lib/Headers/__stdarg_va_arg.h
@@ -10,8 +10,8 @@
 #ifndef va_arg
 
 #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
-/* C23 does not require the second parameter for va_start. */
-#define va_start(ap, ...) __builtin_va_start(ap, 0)
+/* C23 uses a special builtin. */
+#define va_start(...) __builtin_c23_va_start(__VA_ARGS__)
 #else
 /* Versions before C23 do require the second parameter. */
 #define va_start(ap, param) __builtin_va_start(ap, param)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index e99e30d75df94..500e7be84f9fa 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2161,6 +2161,7 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
   case Builtin::BI__builtin_ms_va_start:
   case Builtin::BI__builtin_stdarg_start:
   case Builtin::BI__builtin_va_start:
+  case Builtin::BI__builtin_c23_va_start:
     if (BuiltinVAStart(BuiltinID, TheCall))
       return ExprError();
     break;
@@ -4844,15 +4845,27 @@ static bool checkVAStartIsInVariadicFunction(Sema &S, Expr *Fn,
 
 bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
   Expr *Fn = TheCall->getCallee();
-
   if (checkVAStartABI(*this, BuiltinID, Fn))
     return true;
 
-  // In C23 mode, va_start only needs one argument. However, the builtin still
-  // requires two arguments (which matches the behavior of the GCC builtin),
-  // <stdarg.h> passes `0` as the second argument in C23 mode.
-  if (checkArgCount(TheCall, 2))
-    return true;
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start) {
+    // This builtin requires one argument (the va_list), allows two arguments,
+    // but diagnoses more than two arguments. e.g.,
+    //   __builtin_c23_va_start(); // error
+    //   __builtin_c23_va_start(list); // ok
+    //   __builtin_c23_va_start(list, param); // ok
+    //   __builtin_c23_va_start(list, anything, anything); // error
+    // This differs from the GCC behavior in that they accept the last case
+    // with a warning, but it doesn't seem like a useful behavior to allow.
+    if (checkArgCountRange(TheCall, 1, 2))
+      return true;
+  } else {
+    // In C23 mode, va_start only needs one argument. However, the builtin still
+    // requires two arguments (which matches the behavior of the GCC builtin),
+    // <stdarg.h> passes `0` as the second argument in C23 mode.
+    if (checkArgCount(TheCall, 2))
+      return true;
+  }
 
   // Type-check the first argument normally.
   if (checkBuiltinArgument(*this, TheCall, 0))
@@ -4863,23 +4876,34 @@ bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
   if (checkVAStartIsInVariadicFunction(*this, Fn, &LastParam))
     return true;
 
-  // Verify that the second argument to the builtin is the last argument of the
-  // current function or method. In C23 mode, if the second argument is an
-  // integer constant expression with value 0, then we don't bother with this
-  // check.
-  bool SecondArgIsLastNamedArgument = false;
+  // Verify that the second argument to the builtin is the last non-variadic
+  // argument of the current function or method. In C23 mode, if the call is
+  // not to __builtin_c23_va_start, and the second argument is an integer
+  // constant expression with value 0, then we don't bother with this check.
+  // For __builtin_c23_va_start, we only perform the check for the second
+  // argument being the last argument to the current function if there is a
+  // second argument present.
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start &&
+      TheCall->getNumArgs() < 2) {
+    Diag(TheCall->getExprLoc(), diag::warn_c17_compat_va_start_one_arg);
+    return false;
+  }
+
   const Expr *Arg = TheCall->getArg(1)->IgnoreParenCasts();
   if (std::optional<llvm::APSInt> Val =
           TheCall->getArg(1)->getIntegerConstantExpr(Context);
-      Val && LangOpts.C23 && *Val == 0)
+      Val && LangOpts.C23 && *Val == 0 &&
+      BuiltinID != Builtin::BI__builtin_c23_va_start) {
+    Diag(TheCall->getExprLoc(), diag::warn_c17_compat_va_start_one_arg);
     return false;
+  }
 
   // These are valid if SecondArgIsLastNamedArgument is false after the next
   // block.
   QualType Type;
   SourceLocation ParamLoc;
   bool IsCRegister = false;
-
+  bool SecondArgIsLastNamedArgument = false;
   if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(Arg)) {
     if (const ParmVarDecl *PV = dyn_cast<ParmVarDecl>(DR->getDecl())) {
       SecondArgIsLastNamedArgument = PV == LastParam;
diff --git a/clang/test/C/C23/n2975.c b/clang/test/C/C23/n2975.c
index 338f987f6a65d..6e7c936855e51 100644
--- a/clang/test/C/C23/n2975.c
+++ b/clang/test/C/C23/n2975.c
@@ -6,28 +6,21 @@
 
 #include <stdarg.h>
 
-#define DERP this is an error
-
 void func(...) { // expected-warning {{'...' as the only parameter of a function is incompatible with C standards before C23}}
   // Show that va_start doesn't require the second argument in C23 mode.
   va_list list;
-  va_start(list); // expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}} expected-note@* {{macro 'va_start' defined here}}
-  va_end(list);
-
-  // Show that va_start doesn't expand or evaluate the second argument.
-  va_start(list, DERP);
+  va_start(list); // expected-warning {{passing only one argument to 'va_start' is incompatible with C standards before C23}}
   va_end(list);
 
-  // FIXME: it would be kinder to diagnose this instead of silently accepting it.
-  va_start(list, 1, 2);
+  va_start(list, 1, 2); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
   va_end(list);
 
   // We didn't change the behavior of __builtin_va_start (and neither did GCC).
   __builtin_va_start(list); // expected-error {{too few arguments to function call, expected 2, have 1}}
 
   // Verify that the return type of a call to va_start is 'void'.
-  _Static_assert(__builtin_types_compatible_p(__typeof__(va_start(list)), void), ""); // expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}} expected-note@* {{macro 'va_start' defined here}}
-  _Static_assert(__builtin_types_compatible_p(__typeof__(__builtin_va_start(list, 0)), void), "");
+  _Static_assert(__builtin_types_compatible_p(__typeof__(va_start(list)), void), ""); // expected-warning {{passing only one argument to 'va_start' is incompatible with C standards before C23}}
+  _Static_assert(__builtin_types_compatible_p(__typeof__(__builtin_va_start(list, 0)), void), ""); // expected-warning {{passing only one argument to 'va_start' is incompatible with C standards before C23}}
 }
 
 // Show that function pointer types also don't need an argument before the
@@ -37,13 +30,7 @@ typedef void (*fp)(...); // expected-warning {{'...' as the only parameter of a
 // Passing something other than the argument before the ... is still not valid.
 void diag(int a, int b, ...) {
   va_list list;
-  // FIXME: the call to va_start should also diagnose the same way as the call
-  // to __builtin_va_start. However, because va_start is not allowed to expand
-  // or evaluate the second argument, we can't pass it along to
-  // __builtin_va_start to get that diagnostic. So in C17 and earlier, we will
-  // diagnose this use through the macro, but in C23 and later we've lost the
-  // diagnostic entirely. GCC has the same issue currently.
-  va_start(list, a);
+  va_start(list, a); // expected-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
   // However, the builtin itself is under no such constraints regarding
   // expanding or evaluating the second argument, so it can still diagnose.
   __builtin_va_start(list, a); // expected-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
diff --git a/clang/test/CodeGen/varargs.c b/clang/test/CodeGen/varargs.c
index 625399b87f7ad..71ca88c0d635d 100644
--- a/clang/test/CodeGen/varargs.c
+++ b/clang/test/CodeGen/varargs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple i386-unknown-unknown -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple i386-unknown-unknown -std=c2y -emit-llvm -o - %s | FileCheck %s
 
 // PR6433 - Don't crash on va_arg(typedef).
 typedef double gdouble;
@@ -21,3 +21,14 @@ void vla(int n, ...)
   void *p;
   p = __builtin_va_arg(ap, typeof (int (*)[++n])); // CHECK: add nsw i32 {{.*}}, 1
 }
+
+// Ensure that __builtin_va_start(list, 0) and __builtin_c23_va_start(list)
+// have the same codegen.
+void noargs(...) {
+  __builtin_va_list list;
+  // CHECK:   %list = alloca ptr
+  __builtin_va_start(list, 0);
+  // CHECK-NEXT: call void @llvm.va_start.p0(ptr %list)  
+  __builtin_c23_va_start(list);
+  // CHECK-NEXT: call void @llvm.va_start.p0(ptr %list)
+}
diff --git a/clang/test/Sema/c23-varargs.c b/clang/test/Sema/c23-varargs.c
new file mode 100644
index 0000000000000..1700d5a429160
--- /dev/null
+++ b/clang/test/Sema/c23-varargs.c
@@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple i386-pc-unknown
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple x86_64-apple-darwin9
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -fms-compatibility -verify=expected,both %s -triple x86_64-pc-win32
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -ffreestanding -verify=both,pre-c23 %s
+
+void foo(int x, int y, ...) {
+  __builtin_va_list list;
+  __builtin_c23_va_start();           // pre-c23-error {{use of unknown builtin '__builtin_c23_va_start'}} \
+                                         expected-error{{too few arguments to function call, expected 1, have 0}}
+  // Note, the unknown builtin diagnostic is only issued once per function,
+  // which is why the rest of the lines do not get the same diagonstic.
+  __builtin_c23_va_start(list);       // ok
+  __builtin_c23_va_start(list, 0);    // expected-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
+  __builtin_c23_va_start(list, x);    // expected-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
+  __builtin_c23_va_start(list, y);    // ok
+  __builtin_c23_va_start(list, 0, 1); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
+  __builtin_c23_va_start(list, y, y); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
+}
+
+// Test the same thing as above, only with the macro from stdarg.h. This will
+// not have the unknown builtin diagnostics, but will have different
+// diagnostics between C23 and earlier modes.
+#include <stdarg.h>
+void bar(int x, int y, ...) {
+  // FIXME: the "use of undeclared identifier 'va_start'" diagnostics is an odd
+  // follow-on diagnostic that should be silenced.
+  va_list list;
+  va_start();           // pre-c23-error {{too few arguments provided to function-like macro invocation}} \
+                           pre-c23-error {{use of undeclared identifier 'va_start'}} \
+                           expected-error{{too few arguments to function call, expected 1, have 0}}
+  va_start(list);       // pre-c23-error {{too few arguments provided to function-like macro invocation}} \
+                           pre-c23-error {{use of undeclared identifier 'va_start'}}
+  va_start(list, 0);    // both-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
+  va_start(list, x);    // both-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
+  va_start(list, y);    // ok
+  va_start(list, 0, 1); // pre-c23-error {{too many arguments provided to function-like macro invocation}} \
+                           pre-c23-error {{use of undeclared identifier 'va_start'}} \
+                           expected-error {{too many arguments to function call, expected at most 2, have 3}}
+  va_start(list, y, y); // pre-c23-error {{too many arguments provided to function-like macro invocation}} \
+                           pre-c23-error {{use of undeclared identifier 'va_start'}} \
+                           expected-error {{too many arguments to function call, expected at most 2, have 3}}	
+  // pre-c23-note@__stdarg_va_arg.h:* 4 {{macro 'va_start' defined here}}
+}


Reviews:
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T16:35:33Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T16:49:20Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T16:51:40Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T16:55:52Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T16:57:48Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T16:58:34Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T17:01:41Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T17:03:49Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T17:10:00Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T17:12:26Z
user: login: erichkeane
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-13T17:12:52Z
user: login: Sirraide
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T18:32:05Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T18:36:24Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T18:38:48Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T18:40:24Z
user: login: Sirraide
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T18:49:22Z
user: login: Sirraide
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-13T18:49:42Z
user: login: jyknight
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T20:21:28Z
user: login: Sirraide
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T20:23:41Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T12:11:31Z
user: login: cor3ntin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T12:24:24Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T12:26:56Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T12:29:10Z
user: login: cor3ntin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T12:43:02Z
user: login: erichkeane
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-14T14:33:21Z
user: login: cor3ntin
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T16:16:50Z

Review comments:
diff_hunk: @@ -6,27 +6,20 @@
 
 #include <stdarg.h>
 
-#define DERP this is an error
-
 void func(...) { // expected-warning {{'...' as the only parameter of a function is incompatible with C standards before C23}}
   // Show that va_start doesn't require the second argument in C23 mode.
   va_list list;
-  va_start(list); // expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}} expected-note@* {{macro 'va_start' defined here}}
-  va_end(list);
-
-  // Show that va_start doesn't expand or evaluate the second argument.
-  va_start(list, DERP);
+  va_start(list);
path: clang/test/C/C23/n2975.c
user: login: AaronBallman
body: > `// expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}}`

This no longer fires because the definition of `va_start` in C23 mode now passes `__VA_ARGS__` so `list` is being passed as the variadic argument.

> `// Show that va_start doesn't expand or evaluate the second argument.`

This was changed during NB comment resolution. Now arguments are evaluated and expanded.
created_at: 2025-03-13T16:35:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple i386-pc-unknown
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple x86_64-apple-darwin9
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -fms-compatibility -verify=expected,both %s -triple x86_64-pc-win32
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -ffreestanding -verify=both,pre-c23 %s
+
+void foo(int x, int y, ...) {
+  __builtin_va_list list;
+  __builtin_c23_va_start();           // pre-c23-error {{use of unknown builtin '__builtin_c23_va_start'}} \
+                                         expected-error{{too few arguments to function call, expected 1, have 0}}
+  // Note, the unknown builtin diagnostic is only issued once per function,
+  // which is why the rest of the lines do not get the same diagonstic.
+  __builtin_c23_va_start(list);       // ok
+  __builtin_c23_va_start(list, 0);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
+  __builtin_c23_va_start(list, x);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
+  __builtin_c23_va_start(list, y);    // ok
+  __builtin_c23_va_start(list, 0, 1); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
+  __builtin_c23_va_start(list, y, y); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
+}
+
+// Test the same thing as above, only with the macro from stdarg.h. This will
+// not have the unknown builtin diagnostics, but will have different
+// diagnostics between C23 and earlier modes.
+#include <stdarg.h>
path: clang/test/Sema/c23-varargs.c
user: login: erichkeane
body: Where is this coming from?!  We shouldn't really have sema tests using system includes, this'll break on systems that get va_start 'right'.
created_at: 2025-03-13T16:42:26Z
author_association: COLLABORATOR
diff_hunk: @@ -4864,14 +4877,22 @@ bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
     return true;
 
   // Verify that the second argument to the builtin is the last argument of the
path: clang/lib/Sema/SemaChecking.cpp
user: login: erichkeane
body: ```suggestion
  // Verify that the second argument to the builtin is the last non-variadic argument of the
```

right?  Alternatively 'named', though that isn't really accurate.
created_at: 2025-03-13T16:44:05Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple i386-pc-unknown
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple x86_64-apple-darwin9
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -fms-compatibility -verify=expected,both %s -triple x86_64-pc-win32
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -ffreestanding -verify=both,pre-c23 %s
+
+void foo(int x, int y, ...) {
+  __builtin_va_list list;
+  __builtin_c23_va_start();           // pre-c23-error {{use of unknown builtin '__builtin_c23_va_start'}} \
+                                         expected-error{{too few arguments to function call, expected 1, have 0}}
+  // Note, the unknown builtin diagnostic is only issued once per function,
+  // which is why the rest of the lines do not get the same diagonstic.
+  __builtin_c23_va_start(list);       // ok
+  __builtin_c23_va_start(list, 0);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
path: clang/test/Sema/c23-varargs.c
user: login: erichkeane
body: This error message isn't really accurate.  Consider:

```
void foo(int x, int y, int, ...) {
__builtin_va_list list;
__builtin_c23_va_start(list, y); // error: Not last named parameter.  Erich: ORLY?!
}
created_at: 2025-03-13T16:45:26Z
author_association: COLLABORATOR
diff_hunk: @@ -4864,14 +4877,22 @@ bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
     return true;
 
   // Verify that the second argument to the builtin is the last argument of the
-  // current function or method. In C23 mode, if the second argument is an
-  // integer constant expression with value 0, then we don't bother with this
-  // check.
+  // current function or method. In C23 mode and the call is not to
+  // __builtin_c23_va_start, if the second argument is an integer constant
+  // expression with value 0, then we don't bother with this check. For
+  // __builtin_c23_va_start, we only perform the check for the second argument
+  // being the last argument to the current function if there is a second
+  // argument present.
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start &&
+      TheCall->getNumArgs() < 2)
+    return false;
+
   bool SecondArgIsLastNamedArgument = false;
path: clang/lib/Sema/SemaChecking.cpp
user: login: erichkeane
body: Why is this so 'high'?  Shouldn't this be down near 4903 (new numbers).
created_at: 2025-03-13T16:47:36Z
author_association: COLLABORATOR
diff_hunk: @@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
   let Prototype = "void(__builtin_va_list_ref, ...)";
 }
 
+def BuiltinC32VaStart : LangBuiltin<"C23_LANG"> {
path: clang/include/clang/Basic/Builtins.td
user: login: erichkeane
body: Actually KIND OF sad that this is C23 only.  a `va_start` macro having to change its implementation based on language version is sad.  I would think making this builtin available as an extension in all language modes would be defensible.
created_at: 2025-03-13T16:51:40Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple i386-pc-unknown
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple x86_64-apple-darwin9
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -fms-compatibility -verify=expected,both %s -triple x86_64-pc-win32
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -ffreestanding -verify=both,pre-c23 %s
+
+void foo(int x, int y, ...) {
+  __builtin_va_list list;
+  __builtin_c23_va_start();           // pre-c23-error {{use of unknown builtin '__builtin_c23_va_start'}} \
+                                         expected-error{{too few arguments to function call, expected 1, have 0}}
+  // Note, the unknown builtin diagnostic is only issued once per function,
+  // which is why the rest of the lines do not get the same diagonstic.
+  __builtin_c23_va_start(list);       // ok
+  __builtin_c23_va_start(list, 0);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
+  __builtin_c23_va_start(list, x);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
+  __builtin_c23_va_start(list, y);    // ok
+  __builtin_c23_va_start(list, 0, 1); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
+  __builtin_c23_va_start(list, y, y); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
+}
+
+// Test the same thing as above, only with the macro from stdarg.h. This will
+// not have the unknown builtin diagnostics, but will have different
+// diagnostics between C23 and earlier modes.
+#include <stdarg.h>
path: clang/test/Sema/c23-varargs.c
user: login: AaronBallman
body: Same as always, it's a freestanding header. Hence passing `-ffreestanding`. This is one we provide, so there's no issue here.
created_at: 2025-03-13T16:55:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple i386-pc-unknown
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple x86_64-apple-darwin9
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -fms-compatibility -verify=expected,both %s -triple x86_64-pc-win32
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -ffreestanding -verify=both,pre-c23 %s
+
+void foo(int x, int y, ...) {
+  __builtin_va_list list;
+  __builtin_c23_va_start();           // pre-c23-error {{use of unknown builtin '__builtin_c23_va_start'}} \
+                                         expected-error{{too few arguments to function call, expected 1, have 0}}
+  // Note, the unknown builtin diagnostic is only issued once per function,
+  // which is why the rest of the lines do not get the same diagonstic.
+  __builtin_c23_va_start(list);       // ok
+  __builtin_c23_va_start(list, 0);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
path: clang/test/Sema/c23-varargs.c
user: login: AaronBallman
body: Agreed, but that's existing diagnostic wording. :-)
created_at: 2025-03-13T16:57:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple i386-pc-unknown
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple x86_64-apple-darwin9
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -fms-compatibility -verify=expected,both %s -triple x86_64-pc-win32
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -ffreestanding -verify=both,pre-c23 %s
+
+void foo(int x, int y, ...) {
+  __builtin_va_list list;
+  __builtin_c23_va_start();           // pre-c23-error {{use of unknown builtin '__builtin_c23_va_start'}} \
+                                         expected-error{{too few arguments to function call, expected 1, have 0}}
+  // Note, the unknown builtin diagnostic is only issued once per function,
+  // which is why the rest of the lines do not get the same diagonstic.
+  __builtin_c23_va_start(list);       // ok
+  __builtin_c23_va_start(list, 0);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
+  __builtin_c23_va_start(list, x);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
+  __builtin_c23_va_start(list, y);    // ok
+  __builtin_c23_va_start(list, 0, 1); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
+  __builtin_c23_va_start(list, y, y); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
+}
+
+// Test the same thing as above, only with the macro from stdarg.h. This will
+// not have the unknown builtin diagnostics, but will have different
+// diagnostics between C23 and earlier modes.
+#include <stdarg.h>
path: clang/test/Sema/c23-varargs.c
user: login: erichkeane
body: Ah!  Didn't realize it was the one we provide and not a system one.  SGTM.
created_at: 2025-03-13T16:58:34Z
author_association: COLLABORATOR
diff_hunk: @@ -4864,14 +4877,22 @@ bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
     return true;
 
   // Verify that the second argument to the builtin is the last argument of the
-  // current function or method. In C23 mode, if the second argument is an
-  // integer constant expression with value 0, then we don't bother with this
-  // check.
+  // current function or method. In C23 mode and the call is not to
+  // __builtin_c23_va_start, if the second argument is an integer constant
+  // expression with value 0, then we don't bother with this check. For
+  // __builtin_c23_va_start, we only perform the check for the second argument
+  // being the last argument to the current function if there is a second
+  // argument present.
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start &&
+      TheCall->getNumArgs() < 2)
+    return false;
+
   bool SecondArgIsLastNamedArgument = false;
path: clang/lib/Sema/SemaChecking.cpp
user: login: AaronBallman
body: Sure, I can move it.
created_at: 2025-03-13T16:59:54Z
author_association: COLLABORATOR
diff_hunk: @@ -4864,14 +4877,22 @@ bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
     return true;
 
   // Verify that the second argument to the builtin is the last argument of the
path: clang/lib/Sema/SemaChecking.cpp
user: login: AaronBallman
body: Yup, I'll fix.
created_at: 2025-03-13T17:00:16Z
author_association: COLLABORATOR
diff_hunk: @@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
   let Prototype = "void(__builtin_va_list_ref, ...)";
 }
 
+def BuiltinC32VaStart : LangBuiltin<"C23_LANG"> {
path: clang/include/clang/Basic/Builtins.td
user: login: AaronBallman
body: This matches GCC's behavior: https://godbolt.org/z/K8Tdqa9v4
created_at: 2025-03-13T17:01:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple i386-pc-unknown
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple x86_64-apple-darwin9
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -fms-compatibility -verify=expected,both %s -triple x86_64-pc-win32
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -ffreestanding -verify=both,pre-c23 %s
+
+void foo(int x, int y, ...) {
+  __builtin_va_list list;
+  __builtin_c23_va_start();           // pre-c23-error {{use of unknown builtin '__builtin_c23_va_start'}} \
+                                         expected-error{{too few arguments to function call, expected 1, have 0}}
+  // Note, the unknown builtin diagnostic is only issued once per function,
+  // which is why the rest of the lines do not get the same diagonstic.
+  __builtin_c23_va_start(list);       // ok
+  __builtin_c23_va_start(list, 0);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
path: clang/test/Sema/c23-varargs.c
user: login: AaronBallman
body: No issue from me with rewording it; that would be a Good First Issue for someone if you wanted to file an issue for it.
created_at: 2025-03-13T17:03:49Z
author_association: COLLABORATOR
diff_hunk: @@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
   let Prototype = "void(__builtin_va_list_ref, ...)";
 }
 
+def BuiltinC32VaStart : LangBuiltin<"C23_LANG"> {
path: clang/include/clang/Basic/Builtins.td
user: login: erichkeane
body: still KIND OF sad. 
created_at: 2025-03-13T17:10:00Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple i386-pc-unknown
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple x86_64-apple-darwin9
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -fms-compatibility -verify=expected,both %s -triple x86_64-pc-win32
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -ffreestanding -verify=both,pre-c23 %s
+
+void foo(int x, int y, ...) {
+  __builtin_va_list list;
+  __builtin_c23_va_start();           // pre-c23-error {{use of unknown builtin '__builtin_c23_va_start'}} \
+                                         expected-error{{too few arguments to function call, expected 1, have 0}}
+  // Note, the unknown builtin diagnostic is only issued once per function,
+  // which is why the rest of the lines do not get the same diagonstic.
+  __builtin_c23_va_start(list);       // ok
+  __builtin_c23_va_start(list, 0);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
path: clang/test/Sema/c23-varargs.c
user: login: erichkeane
body: Created here: https://github.com/llvm/llvm-project/issues/131171
created_at: 2025-03-13T17:12:26Z
author_association: COLLABORATOR
diff_hunk: @@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
   let Prototype = "void(__builtin_va_list_ref, ...)";
 }
 
+def BuiltinC32VaStart : LangBuiltin<"C23_LANG"> {
path: clang/include/clang/Basic/Builtins.td
user: login: Sirraide
body: ```suggestion
def BuiltinC23VaStart : LangBuiltin<"C23_LANG"> {
```
👀
created_at: 2025-03-13T18:26:34Z
author_association: MEMBER
diff_hunk: @@ -4844,15 +4845,27 @@ static bool checkVAStartIsInVariadicFunction(Sema &S, Expr *Fn,
 
 bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
   Expr *Fn = TheCall->getCallee();
-
   if (checkVAStartABI(*this, BuiltinID, Fn))
     return true;
 
-  // In C23 mode, va_start only needs one argument. However, the builtin still
-  // requires two arguments (which matches the behavior of the GCC builtin),
-  // <stdarg.h> passes `0` as the second argument in C23 mode.
-  if (checkArgCount(TheCall, 2))
-    return true;
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start) {
+    // This builtin requires one argument (the va_list), allows two arguments,
+    // but diagnoses more than two arguments. e.g.,
+    //   __builtin_c23_va_start(); // error
+    //   __builtin_c23_va_start(list); // ok
+    //   __builtin_c23_va_start(list, param); // ok
+    //   __builtin_c23_va_start(list, anything, anything); // error
+    // This differs from the GCC behavior in that they accept the last case
+    // with a warning, but it doesn't seem like a useful behavior to allow.
+    if (checkArgCountRange(TheCall, 1, 2))
+      return true;
+  } else {
+    // In C23 mode, va_start only needs one argument. However, the builtin still
+    // requires two arguments (which matches the behavior of the GCC builtin),
+    // <stdarg.h> passes `0` as the second argument in C23 mode.
path: clang/lib/Sema/SemaChecking.cpp
user: login: Sirraide
body: > // <stdarg.h> passes `0` as the second argument in C23 mode.

Does this still happen now that we have the new builtin?

created_at: 2025-03-13T18:28:36Z
author_association: MEMBER
diff_hunk: @@ -4863,23 +4876,30 @@ bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
   if (checkVAStartIsInVariadicFunction(*this, Fn, &LastParam))
     return true;
 
-  // Verify that the second argument to the builtin is the last argument of the
-  // current function or method. In C23 mode, if the second argument is an
-  // integer constant expression with value 0, then we don't bother with this
-  // check.
-  bool SecondArgIsLastNamedArgument = false;
+  // Verify that the second argument to the builtin is the last non-variadic
+  // argument of the current function or method. In C23 mode and the call is
+  // not to __builtin_c23_va_start, if the second argument is an integer
path: clang/lib/Sema/SemaChecking.cpp
user: login: Sirraide
body: ```suggestion
  // argument of the current function or method. In C23 mode, if the call is
  // not to __builtin_c23_va_start, and the second argument is an integer
```
This sentence has ended up a bit scrambled.
created_at: 2025-03-13T18:30:15Z
author_association: MEMBER
diff_hunk: @@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
   let Prototype = "void(__builtin_va_list_ref, ...)";
 }
 
+def BuiltinC32VaStart : LangBuiltin<"C23_LANG"> {
path: clang/include/clang/Basic/Builtins.td
user: login: erichkeane
body: holy eye test batman!
created_at: 2025-03-13T18:36:24Z
author_association: COLLABORATOR
diff_hunk: @@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
   let Prototype = "void(__builtin_va_list_ref, ...)";
 }
 
+def BuiltinC32VaStart : LangBuiltin<"C23_LANG"> {
path: clang/include/clang/Basic/Builtins.td
user: login: AaronBallman
body: ROFL, wow. :-D
created_at: 2025-03-13T18:38:48Z
author_association: COLLABORATOR
diff_hunk: @@ -4844,15 +4845,27 @@ static bool checkVAStartIsInVariadicFunction(Sema &S, Expr *Fn,
 
 bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
   Expr *Fn = TheCall->getCallee();
-
   if (checkVAStartABI(*this, BuiltinID, Fn))
     return true;
 
-  // In C23 mode, va_start only needs one argument. However, the builtin still
-  // requires two arguments (which matches the behavior of the GCC builtin),
-  // <stdarg.h> passes `0` as the second argument in C23 mode.
-  if (checkArgCount(TheCall, 2))
-    return true;
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start) {
+    // This builtin requires one argument (the va_list), allows two arguments,
+    // but diagnoses more than two arguments. e.g.,
+    //   __builtin_c23_va_start(); // error
+    //   __builtin_c23_va_start(list); // ok
+    //   __builtin_c23_va_start(list, param); // ok
+    //   __builtin_c23_va_start(list, anything, anything); // error
+    // This differs from the GCC behavior in that they accept the last case
+    // with a warning, but it doesn't seem like a useful behavior to allow.
+    if (checkArgCountRange(TheCall, 1, 2))
+      return true;
+  } else {
+    // In C23 mode, va_start only needs one argument. However, the builtin still
+    // requires two arguments (which matches the behavior of the GCC builtin),
+    // <stdarg.h> passes `0` as the second argument in C23 mode.
path: clang/lib/Sema/SemaChecking.cpp
user: login: AaronBallman
body: Yes, for compatibility with folks calling `__builtin_va_start` directly (which could be in other standard library code, for example).
created_at: 2025-03-13T18:40:24Z
author_association: COLLABORATOR
diff_hunk: @@ -4844,15 +4845,27 @@ static bool checkVAStartIsInVariadicFunction(Sema &S, Expr *Fn,
 
 bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
   Expr *Fn = TheCall->getCallee();
-
   if (checkVAStartABI(*this, BuiltinID, Fn))
     return true;
 
-  // In C23 mode, va_start only needs one argument. However, the builtin still
-  // requires two arguments (which matches the behavior of the GCC builtin),
-  // <stdarg.h> passes `0` as the second argument in C23 mode.
-  if (checkArgCount(TheCall, 2))
-    return true;
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start) {
+    // This builtin requires one argument (the va_list), allows two arguments,
+    // but diagnoses more than two arguments. e.g.,
+    //   __builtin_c23_va_start(); // error
+    //   __builtin_c23_va_start(list); // ok
+    //   __builtin_c23_va_start(list, param); // ok
+    //   __builtin_c23_va_start(list, anything, anything); // error
+    // This differs from the GCC behavior in that they accept the last case
+    // with a warning, but it doesn't seem like a useful behavior to allow.
+    if (checkArgCountRange(TheCall, 1, 2))
+      return true;
+  } else {
+    // In C23 mode, va_start only needs one argument. However, the builtin still
+    // requires two arguments (which matches the behavior of the GCC builtin),
+    // <stdarg.h> passes `0` as the second argument in C23 mode.
path: clang/lib/Sema/SemaChecking.cpp
user: login: Sirraide
body: Makes sense
created_at: 2025-03-13T18:49:21Z
author_association: MEMBER
diff_hunk: @@ -6,27 +6,20 @@
 
 #include <stdarg.h>
 
-#define DERP this is an error
-
 void func(...) { // expected-warning {{'...' as the only parameter of a function is incompatible with C standards before C23}}
   // Show that va_start doesn't require the second argument in C23 mode.
   va_list list;
-  va_start(list); // expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}} expected-note@* {{macro 'va_start' defined here}}
-  va_end(list);
-
-  // Show that va_start doesn't expand or evaluate the second argument.
-  va_start(list, DERP);
+  va_start(list);
path: clang/test/C/C23/n2975.c
user: login: jyknight
body: I haven't found the `*-compat` warnings ever actually helpful (if you want to check compat with previous versions, just build with the previous version!), but, given that they do exist, should a new diagnostic be emitted under `-Wpre-c23-compat` when __builtin_c23_va_start is called with 1-arg?
created_at: 2025-03-13T20:19:29Z
author_association: MEMBER
diff_hunk: @@ -6,27 +6,20 @@
 
 #include <stdarg.h>
 
-#define DERP this is an error
-
 void func(...) { // expected-warning {{'...' as the only parameter of a function is incompatible with C standards before C23}}
   // Show that va_start doesn't require the second argument in C23 mode.
   va_list list;
-  va_start(list); // expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}} expected-note@* {{macro 'va_start' defined here}}
-  va_end(list);
-
-  // Show that va_start doesn't expand or evaluate the second argument.
-  va_start(list, DERP);
+  va_start(list);
path: clang/test/C/C23/n2975.c
user: login: Sirraide
body: From what I recall the builtin is only available in C23 mode or later, so I’m not sure we need to worry about compat warnings—or am I misunderstanding the scenario here?
created_at: 2025-03-13T20:23:41Z
author_association: MEMBER
diff_hunk: @@ -6,27 +6,20 @@
 
 #include <stdarg.h>
 
-#define DERP this is an error
-
 void func(...) { // expected-warning {{'...' as the only parameter of a function is incompatible with C standards before C23}}
   // Show that va_start doesn't require the second argument in C23 mode.
   va_list list;
-  va_start(list); // expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}} expected-note@* {{macro 'va_start' defined here}}
-  va_end(list);
-
-  // Show that va_start doesn't expand or evaluate the second argument.
-  va_start(list, DERP);
+  va_start(list);
path: clang/test/C/C23/n2975.c
user: login: AaronBallman
body: > I haven't found the `*-compat` warnings ever actually helpful (if you want to check compat with previous versions, just build with the previous version!), but, given that they do exist, should a new diagnostic be emitted under `-Wpre-c23-compat` when __builtin_c23_va_start is called with 1-arg?

Yeah, it probably should given that `va_start(list)` won't compile under previous language modes.
created_at: 2025-03-14T12:11:31Z
author_association: COLLABORATOR
diff_hunk: @@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
   let Prototype = "void(__builtin_va_list_ref, ...)";
 }
 
+def BuiltinC23VaStart : LangBuiltin<"C23_LANG"> {
path: clang/include/clang/Basic/Builtins.td
user: login: cor3ntin
body: Is there any reason to restrict to C23?
The only thing that should be conditional is what `va_start` uses
In particular, C++ will align on C23 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3348r2.pdf (page 9)
created_at: 2025-03-14T12:21:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4844,15 +4845,27 @@ static bool checkVAStartIsInVariadicFunction(Sema &S, Expr *Fn,
 
 bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
   Expr *Fn = TheCall->getCallee();
-
   if (checkVAStartABI(*this, BuiltinID, Fn))
     return true;
 
-  // In C23 mode, va_start only needs one argument. However, the builtin still
-  // requires two arguments (which matches the behavior of the GCC builtin),
-  // <stdarg.h> passes `0` as the second argument in C23 mode.
-  if (checkArgCount(TheCall, 2))
-    return true;
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start) {
+    // This builtin requires one argument (the va_list), allows two arguments,
+    // but diagnoses more than two arguments. e.g.,
+    //   __builtin_c23_va_start(); // error
+    //   __builtin_c23_va_start(list); // ok
+    //   __builtin_c23_va_start(list, param); // ok
+    //   __builtin_c23_va_start(list, anything, anything); // error
+    // This differs from the GCC behavior in that they accept the last case
+    // with a warning, but it doesn't seem like a useful behavior to allow.
+    if (checkArgCountRange(TheCall, 1, 2))
+      return true;
+  } else {
+    // In C23 mode, va_start only needs one argument. However, the builtin still
+    // requires two arguments (which matches the behavior of the GCC builtin),
+    // <stdarg.h> passes `0` as the second argument in C23 mode.
path: clang/lib/Sema/SemaChecking.cpp
user: login: cor3ntin
body: I'm not actually sure it does.
I think it's cleaner if each builtin has a specific behavior, and people who use the builtin directly get the semantic of the builtin, not of `va_start`
created_at: 2025-03-14T12:24:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
   let Prototype = "void(__builtin_va_list_ref, ...)";
 }
 
+def BuiltinC23VaStart : LangBuiltin<"C23_LANG"> {
path: clang/include/clang/Basic/Builtins.td
user: login: AaronBallman
body: This matches GCC's behavior and I don't think we want to extend support to earlier C modes given that the only use case is when the function signature is `(...)` which is invalid in earlier C modes.
created_at: 2025-03-14T12:26:56Z
author_association: COLLABORATOR
diff_hunk: @@ -4844,15 +4845,27 @@ static bool checkVAStartIsInVariadicFunction(Sema &S, Expr *Fn,
 
 bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
   Expr *Fn = TheCall->getCallee();
-
   if (checkVAStartABI(*this, BuiltinID, Fn))
     return true;
 
-  // In C23 mode, va_start only needs one argument. However, the builtin still
-  // requires two arguments (which matches the behavior of the GCC builtin),
-  // <stdarg.h> passes `0` as the second argument in C23 mode.
-  if (checkArgCount(TheCall, 2))
-    return true;
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start) {
+    // This builtin requires one argument (the va_list), allows two arguments,
+    // but diagnoses more than two arguments. e.g.,
+    //   __builtin_c23_va_start(); // error
+    //   __builtin_c23_va_start(list); // ok
+    //   __builtin_c23_va_start(list, param); // ok
+    //   __builtin_c23_va_start(list, anything, anything); // error
+    // This differs from the GCC behavior in that they accept the last case
+    // with a warning, but it doesn't seem like a useful behavior to allow.
+    if (checkArgCountRange(TheCall, 1, 2))
+      return true;
+  } else {
+    // In C23 mode, va_start only needs one argument. However, the builtin still
+    // requires two arguments (which matches the behavior of the GCC builtin),
+    // <stdarg.h> passes `0` as the second argument in C23 mode.
path: clang/lib/Sema/SemaChecking.cpp
user: login: AaronBallman
body: This is existing behavior, matches GCC's behavior, and changing it risks breaking code: https://godbolt.org/z/r6P97ex99
created_at: 2025-03-14T12:29:10Z
author_association: COLLABORATOR
diff_hunk: @@ -4844,15 +4845,27 @@ static bool checkVAStartIsInVariadicFunction(Sema &S, Expr *Fn,
 
 bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
   Expr *Fn = TheCall->getCallee();
-
   if (checkVAStartABI(*this, BuiltinID, Fn))
     return true;
 
-  // In C23 mode, va_start only needs one argument. However, the builtin still
-  // requires two arguments (which matches the behavior of the GCC builtin),
-  // <stdarg.h> passes `0` as the second argument in C23 mode.
-  if (checkArgCount(TheCall, 2))
-    return true;
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start) {
+    // This builtin requires one argument (the va_list), allows two arguments,
+    // but diagnoses more than two arguments. e.g.,
+    //   __builtin_c23_va_start(); // error
+    //   __builtin_c23_va_start(list); // ok
+    //   __builtin_c23_va_start(list, param); // ok
+    //   __builtin_c23_va_start(list, anything, anything); // error
+    // This differs from the GCC behavior in that they accept the last case
+    // with a warning, but it doesn't seem like a useful behavior to allow.
+    if (checkArgCountRange(TheCall, 1, 2))
+      return true;
+  } else {
+    // In C23 mode, va_start only needs one argument. However, the builtin still
+    // requires two arguments (which matches the behavior of the GCC builtin),
+    // <stdarg.h> passes `0` as the second argument in C23 mode.
path: clang/lib/Sema/SemaChecking.cpp
user: login: cor3ntin
body: Fair enough, let's be consistent with gcc (even if it's a bit weird in a vacuum)
created_at: 2025-03-14T12:43:02Z
author_association: CONTRIBUTOR

PR comments:
user: login: llvmbot
created_at: 2025-03-13T16:33:29Z
body: 
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-codegen
@llvm/pr-subscribers-clang

@llvm/pr-subscribers-backend-x86

Author: Aaron Ballman (AaronBallman)

<details>
<summary>Changes</summary>

This builtin is supported by GCC and is a way to improve diagnostic behavior for va_start in C23 mode. C23 no longer requires a second argument to the va_start macro in support of variadic functions with no leading parameters. However, we still want to diagnose passing more than two arguments, or diagnose when passing something other than the last parameter in the variadic function.

This also updates the freestanding &lt;stdarg.h&gt; header to use the new builtin, same as how GCC works.

---
Full diff: https://github.com/llvm/llvm-project/pull/131166.diff


9 Files Affected:

- (modified) clang/docs/ReleaseNotes.rst (+4) 
- (modified) clang/include/clang/Basic/Builtins.h (+1) 
- (modified) clang/include/clang/Basic/Builtins.td (+6) 
- (modified) clang/include/clang/Sema/Sema.h (+3-3) 
- (modified) clang/lib/Basic/Builtins.cpp (+3) 
- (modified) clang/lib/Headers/__stdarg_va_arg.h (+2-2) 
- (modified) clang/lib/Sema/SemaChecking.cpp (+31-10) 
- (modified) clang/test/C/C23/n2975.c (+4-17) 
- (added) clang/test/Sema/c23-varargs.c (+43) 


``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index e16cfc69590fa..abb87c16c4ddb 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -123,6 +123,10 @@ C2y Feature Support
 
 C23 Feature Support
 ^^^^^^^^^^^^^^^^^^^
+- Added ``__builtin_c23_va_start()`` for compatibility with GCC and to enable
+  better diagnostic behavior for the ``va_start()`` macro in C23 and later.
+  This also updates the definition of ``va_start()`` in ``<stdarg.h>`` to use
+  the new builtin. Fixes #GH124031.
 
 Non-comprehensive list of changes in this release
 -------------------------------------------------
diff --git a/clang/include/clang/Basic/Builtins.h b/clang/include/clang/Basic/Builtins.h
index 12c250afb4c61..3a5e31de2bc50 100644
--- a/clang/include/clang/Basic/Builtins.h
+++ b/clang/include/clang/Basic/Builtins.h
@@ -44,6 +44,7 @@ enum LanguageID : uint16_t {
   OCL_DSE = 0x400,           // builtin requires OpenCL device side enqueue.
   ALL_OCL_LANGUAGES = 0x800, // builtin for OCL languages.
   HLSL_LANG = 0x1000,        // builtin requires HLSL.
+  C23_LANG = 0x2000,         // builtin requires C23 or later.
   ALL_LANGUAGES = C_LANG | CXX_LANG | OBJC_LANG, // builtin for all languages.
   ALL_GNU_LANGUAGES = ALL_LANGUAGES | GNU_LANG,  // builtin requires GNU mode.
   ALL_MS_LANGUAGES = ALL_LANGUAGES | MS_LANG     // builtin requires MS mode.
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 2fbdfaea57ccd..526f7040aebea 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
   let Prototype = "void(__builtin_va_list_ref, ...)";
 }
 
+def BuiltinC32VaStart : LangBuiltin<"C23_LANG"> {
+  let Spellings = ["__builtin_c23_va_start"];
+  let Attributes = [NoThrow, CustomTypeChecking];
+  let Prototype = "void(__builtin_va_list_ref, ...)";
+}
+
 def BuiltinStdargStart : Builtin {
   let Spellings = ["__builtin_stdarg_start"];
   let Attributes = [NoThrow, CustomTypeChecking];
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 9ac26d8728446..026eb2b0e93ef 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2499,9 +2499,9 @@ class Sema final : public SemaBase {
 
   void checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, CallExpr *TheCall);
 
-  /// Check the arguments to '__builtin_va_start' or '__builtin_ms_va_start'
-  /// for validity.  Emit an error and return true on failure; return false
-  /// on success.
+  /// Check the arguments to '__builtin_va_start', '__builtin_ms_va_start',
+  /// or '__builtin_c23_va_start' for validity. Emit an error and return true
+  /// on failure; return false on success.
   bool BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall);
   bool BuiltinVAStartARMMicrosoft(CallExpr *Call);
 
diff --git a/clang/lib/Basic/Builtins.cpp b/clang/lib/Basic/Builtins.cpp
index e7829a461bbc5..885abdc152e3a 100644
--- a/clang/lib/Basic/Builtins.cpp
+++ b/clang/lib/Basic/Builtins.cpp
@@ -191,6 +191,9 @@ static bool builtinIsSupported(const llvm::StringTable &Strings,
   /* consteval Unsupported */
   if (!LangOpts.CPlusPlus20 && strchr(AttributesStr.data(), 'G') != nullptr)
     return false;
+  /* C23 unsupported */
+  if (!LangOpts.C23 && BuiltinInfo.Langs == C23_LANG)
+    return false;
   return true;
 }
 
diff --git a/clang/lib/Headers/__stdarg_va_arg.h b/clang/lib/Headers/__stdarg_va_arg.h
index 89bd2f65d3bea..ebdb6f9d4b1eb 100644
--- a/clang/lib/Headers/__stdarg_va_arg.h
+++ b/clang/lib/Headers/__stdarg_va_arg.h
@@ -10,8 +10,8 @@
 #ifndef va_arg
 
 #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
-/* C23 does not require the second parameter for va_start. */
-#define va_start(ap, ...) __builtin_va_start(ap, 0)
+/* C23 uses a special builtin. */
+#define va_start(...) __builtin_c23_va_start(__VA_ARGS__)
 #else
 /* Versions before C23 do require the second parameter. */
 #define va_start(ap, param) __builtin_va_start(ap, param)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 9bcb014ab9bfc..91cdcdf6bb5ac 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2161,6 +2161,7 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
   case Builtin::BI__builtin_ms_va_start:
   case Builtin::BI__builtin_stdarg_start:
   case Builtin::BI__builtin_va_start:
+  case Builtin::BI__builtin_c23_va_start:
     if (BuiltinVAStart(BuiltinID, TheCall))
       return ExprError();
     break;
@@ -4844,15 +4845,27 @@ static bool checkVAStartIsInVariadicFunction(Sema &S, Expr *Fn,
 
 bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
   Expr *Fn = TheCall->getCallee();
-
   if (checkVAStartABI(*this, BuiltinID, Fn))
     return true;
 
-  // In C23 mode, va_start only needs one argument. However, the builtin still
-  // requires two arguments (which matches the behavior of the GCC builtin),
-  // <stdarg.h> passes `0` as the second argument in C23 mode.
-  if (checkArgCount(TheCall, 2))
-    return true;
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start) {
+    // This builtin requires one argument (the va_list), allows two arguments,
+    // but diagnoses more than two arguments. e.g.,
+    //   __builtin_c23_va_start(); // error
+    //   __builtin_c23_va_start(list); // ok
+    //   __builtin_c23_va_start(list, param); // ok
+    //   __builtin_c23_va_start(list, anything, anything); // error
+    // This differs from the GCC behavior in that they accept the last case
+    // with a warning, but it doesn't seem like a useful behavior to allow.
+    if (checkArgCountRange(TheCall, 1, 2))
+      return true;
+  } else {
+    // In C23 mode, va_start only needs one argument. However, the builtin still
+    // requires two arguments (which matches the behavior of the GCC builtin),
+    // <stdarg.h> passes `0` as the second argument in C23 mode.
+    if (checkArgCount(TheCall, 2))
+      return true;
+  }
 
   // Type-check the first argument normally.
   if (checkBuiltinArgument(*this, TheCall, 0))
@@ -4864,14 +4877,22 @@ bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
     return true;
 
   // Verify that the second argument to the builtin is the last argument of the
-  // current function or method. In C23 mode, if the second argument is an
-  // integer constant expression with value 0, then we don't bother with this
-  // check.
+  // current function or method. In C23 mode and the call is not to
+  // __builtin_c23_va_start, if the second argument is an integer constant
+  // expression with value 0, then we don't bother with this check. For
+  // __builtin_c23_va_start, we only perform the check for the second argument
+  // being the last argument to the current function if there is a second
+  // argument present.
+  if (BuiltinID == Builtin::BI__builtin_c23_va_start &&
+      TheCall->getNumArgs() < 2)
+    return false;
+
   bool SecondArgIsLastNamedArgument = false;
   const Expr *Arg = TheCall->getArg(1)->IgnoreParenCasts();
   if (std::optional<llvm::APSInt> Val =
           TheCall->getArg(1)->getIntegerConstantExpr(Context);
-      Val && LangOpts.C23 && *Val == 0)
+      Val && LangOpts.C23 && *Val == 0 &&
+      BuiltinID != Builtin::BI__builtin_c23_va_start)
     return false;
 
   // These are valid if SecondArgIsLastNamedArgument is false after the next
diff --git a/clang/test/C/C23/n2975.c b/clang/test/C/C23/n2975.c
index 2269400fe47c3..5c3225e9fd876 100644
--- a/clang/test/C/C23/n2975.c
+++ b/clang/test/C/C23/n2975.c
@@ -6,27 +6,20 @@
 
 #include <stdarg.h>
 
-#define DERP this is an error
-
 void func(...) { // expected-warning {{'...' as the only parameter of a function is incompatible with C standards before C23}}
   // Show that va_start doesn't require the second argument in C23 mode.
   va_list list;
-  va_start(list); // expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}} expected-note@* {{macro 'va_start' defined here}}
-  va_end(list);
-
-  // Show that va_start doesn't expand or evaluate the second argument.
-  va_start(list, DERP);
+  va_start(list);
   va_end(list);
 
-  // FIXME: it would be kinder to diagnose this instead of silently accepting it.
-  va_start(list, 1, 2);
+  va_start(list, 1, 2); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
   va_end(list);
 
   // We didn't change the behavior of __builtin_va_start (and neither did GCC).
   __builtin_va_start(list); // expected-error {{too few arguments to function call, expected 2, have 1}}
 
   // Verify that the return type of a call to va_start is 'void'.
-  _Static_assert(__builtin_types_compatible_p(__typeof__(va_start(list)), void), ""); // expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}} expected-note@* {{macro 'va_start' defined here}}
+  _Static_assert(__builtin_types_compatible_p(__typeof__(va_start(list)), void), "");
   _Static_assert(__builtin_types_compatible_p(__typeof__(__builtin_va_start(list, 0)), void), "");
 }
 
@@ -37,13 +30,7 @@ typedef void (*fp)(...); // expected-warning {{'...' as the only parameter of a
 // Passing something other than the argument before the ... is still not valid.
 void diag(int a, int b, ...) {
   va_list list;
-  // FIXME: the call to va_start should also diagnose the same way as the call
-  // to __builtin_va_start. However, because va_start is not allowed to expand
-  // or evaluate the second argument, we can't pass it along to
-  // __builtin_va_start to get that diagnostic. So in C17 and earlier, we will
-  // diagnose this use through the macro, but in C23 and later we've lost the
-  // diagnostic entirely. GCC has the same issue currently.
-  va_start(list, a);
+  va_start(list, a); // expected-warning {{second argument to 'va_start' is not the last named parameter}}
   // However, the builtin itself is under no such constraints regarding
   // expanding or evaluating the second argument, so it can still diagnose.
   __builtin_va_start(list, a); // expected-warning {{second argument to 'va_start' is not the last named parameter}}
diff --git a/clang/test/Sema/c23-varargs.c b/clang/test/Sema/c23-varargs.c
new file mode 100644
index 0000000000000..fa59357843f13
--- /dev/null
+++ b/clang/test/Sema/c23-varargs.c
@@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple i386-pc-unknown
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple x86_64-apple-darwin9
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -fms-compatibility -verify=expected,both %s -triple x86_64-pc-win32
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -ffreestanding -verify=both,pre-c23 %s
+
+void foo(int x, int y, ...) {
+  __builtin_va_list list;
+  __builtin_c23_va_start();           // pre-c23-error {{use of unknown builtin '__builtin_c23_va_start'}} \
+                                         expected-error{{too few arguments to function call, expected 1, have 0}}
+  // Note, the unknown builtin diagnostic is only issued once per function,
+  // which is why the rest of the lines do not get the same diagonstic.
+  __builtin_c23_va_start(list);       // ok
+  __builtin_c23_va_start(list, 0);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
+  __builtin_c23_va_start(list, x);    // expected-warning {{second argument to 'va_start' is not the last named parameter}}
+  __builtin_c23_va_start(list, y);    // ok
+  __builtin_c23_va_start(list, 0, 1); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
+  __builtin_c23_va_start(list, y, y); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
+}
+
+// Test the same thing as above, only with the macro from stdarg.h. This will
+// not have the unknown builtin diagnostics, but will have different
+// diagnostics between C23 and earlier modes.
+#include <stdarg.h>
+void bar(int x, int y, ...) {
+  // FIXME: the "use of undeclared identifier 'va_start'" diagnostics is an odd
+  // follow-on diagnostic that should be silenced.
+  va_list list;
+  va_start();           // pre-c23-error {{too few arguments provided to function-like macro invocation}} \
+                           pre-c23-error {{use of undeclared identifier 'va_start'}} \
+                           expected-error{{too few arguments to function call, expected 1, have 0}}
+  va_start(list);       // pre-c23-error {{too few arguments provided to function-like macro invocation}} \
+                           pre-c23-error {{use of undeclared identifier 'va_start'}}
+  va_start(list, 0);    // both-warning {{second argument to 'va_start' is not the last named parameter}}
+  va_start(list, x);    // both-warning {{second argument to 'va_start' is not the last named parameter}}
+  va_start(list, y);    // ok
+  va_start(list, 0, 1); // pre-c23-error {{too many arguments provided to function-like macro invocation}} \
+                           pre-c23-error {{use of undeclared identifier 'va_start'}} \
+                           expected-error {{too many arguments to function call, expected at most 2, have 3}}
+  va_start(list, y, y); // pre-c23-error {{too many arguments provided to function-like macro invocation}} \
+                           pre-c23-error {{use of undeclared identifier 'va_start'}} \
+                           expected-error {{too many arguments to function call, expected at most 2, have 3}}	
+  // pre-c23-note@__stdarg_va_arg.h:* 4 {{macro 'va_start' defined here}}
+}

``````````

</details>

author_association: MEMBER
user: login: llvm-ci
created_at: 2025-03-15T16:56:27Z
body: LLVM Buildbot has detected a new failure on builder `sanitizer-x86_64-linux-bootstrap-asan` running on `sanitizer-buildbot2` while building `clang` at step 2 "annotate".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/52/builds/6817

<details>
<summary>Here is the relevant piece of the build log for the reference</summary>

```
Step 2 (annotate) failure: 'python ../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py' (failure)
...
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using lld-link: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/lld-link
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using ld64.lld: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using wasm-ld: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using ld.lld: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/ld.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using lld-link: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/lld-link
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using ld64.lld: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using wasm-ld: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/main.py:72: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 900 seconds was requested on the command line. Forcing timeout to be 900 seconds.
-- Testing: 89668 tests, 88 workers --
Testing:  0.. 10
FAIL: Clang :: Interpreter/inline-virtual.cpp (12816 of 89668)
******************** TEST 'Clang :: Interpreter/inline-virtual.cpp' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
RUN: at line 6: cat /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp | /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/clang-repl -Xcc -fno-rtti -Xcc -fno-sized-deallocation      | /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/FileCheck /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp
+ cat /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp
+ /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/clang-repl -Xcc -fno-rtti -Xcc -fno-sized-deallocation
+ /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/FileCheck /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp
JIT session error: In graph incr_module_18-jitted-objectbuffer, section .text._ZN1AC2Ei: relocation target "_ZTV1A" at address 0x6e91c6d1e000 is out of range of Delta32 fixup at 0x7291c762f082 (_ZN1AC2Ei, 0x7291c762f070 + 0x12)
JIT session error: Failed to materialize symbols: { (main, { DW.ref.__gxx_personality_v0 }) }
error: Failed to materialize symbols: { (main, { _ZN1AC2Ei, a1, $.incr_module_18.__inits.0, __orc_init_func.incr_module_18, DW.ref.__gxx_personality_v0 }) }
JIT session error: Failed to materialize symbols: { (main, { a1 }) }
error: Failed to materialize symbols: { (main, { $.incr_module_19.__inits.0, __orc_init_func.incr_module_19 }) }
error: Failed to materialize symbols: { (main, { __orc_init_func.incr_module_18 }) }
JIT session error: Failed to materialize symbols: { (main, { _ZN1AC2Ei }) }
error: Failed to materialize symbols: { (main, { __orc_init_func.incr_module_23, a2, $.incr_module_23.__inits.0 }) }
error: Failed to materialize symbols: { (main, { __orc_init_func.incr_module_23 }) }
/home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp:22:11: error: CHECK: expected string not found in input
// CHECK: ~A(1)
          ^
<stdin>:1:1: note: scanning from here
clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl... clang-repl> clang-repl... clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> 
^

Input file: <stdin>
Check file: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp

-dump-input=help explains the following input dump.

Input was:
<<<<<<
          1: clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl... clang-repl> clang-repl... clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl>  
check:22     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
>>>>>>

--

Step 11 (stage2/asan check) failure: stage2/asan check (failure)
...
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using lld-link: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/lld-link
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using ld64.lld: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using wasm-ld: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using ld.lld: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/ld.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using lld-link: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/lld-link
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using ld64.lld: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:512: note: using wasm-ld: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/llvm/utils/lit/lit/main.py:72: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 900 seconds was requested on the command line. Forcing timeout to be 900 seconds.
-- Testing: 89668 tests, 88 workers --
Testing:  0.. 10
FAIL: Clang :: Interpreter/inline-virtual.cpp (12816 of 89668)
******************** TEST 'Clang :: Interpreter/inline-virtual.cpp' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
RUN: at line 6: cat /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp | /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/clang-repl -Xcc -fno-rtti -Xcc -fno-sized-deallocation      | /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/FileCheck /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp
+ cat /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp
+ /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/clang-repl -Xcc -fno-rtti -Xcc -fno-sized-deallocation
+ /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm_build_asan/bin/FileCheck /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp
JIT session error: In graph incr_module_18-jitted-objectbuffer, section .text._ZN1AC2Ei: relocation target "_ZTV1A" at address 0x6e91c6d1e000 is out of range of Delta32 fixup at 0x7291c762f082 (_ZN1AC2Ei, 0x7291c762f070 + 0x12)
JIT session error: Failed to materialize symbols: { (main, { DW.ref.__gxx_personality_v0 }) }
error: Failed to materialize symbols: { (main, { _ZN1AC2Ei, a1, $.incr_module_18.__inits.0, __orc_init_func.incr_module_18, DW.ref.__gxx_personality_v0 }) }
JIT session error: Failed to materialize symbols: { (main, { a1 }) }
error: Failed to materialize symbols: { (main, { $.incr_module_19.__inits.0, __orc_init_func.incr_module_19 }) }
error: Failed to materialize symbols: { (main, { __orc_init_func.incr_module_18 }) }
JIT session error: Failed to materialize symbols: { (main, { _ZN1AC2Ei }) }
error: Failed to materialize symbols: { (main, { __orc_init_func.incr_module_23, a2, $.incr_module_23.__inits.0 }) }
error: Failed to materialize symbols: { (main, { __orc_init_func.incr_module_23 }) }
/home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp:22:11: error: CHECK: expected string not found in input
// CHECK: ~A(1)
          ^
<stdin>:1:1: note: scanning from here
clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl... clang-repl> clang-repl... clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> 
^

Input file: <stdin>
Check file: /home/b/sanitizer-x86_64-linux-bootstrap-asan/build/llvm-project/clang/test/Interpreter/inline-virtual.cpp

-dump-input=help explains the following input dump.

Input was:
<<<<<<
          1: clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl... clang-repl> clang-repl... clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl> clang-repl>  
check:22     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
>>>>>>

--


```

</details>
author_association: NONE