Hi! While in C++ the ellipsis argument conversions include "An argument that has type cv std::nullptr_t is converted to type void*" in C23 a nullptr_t argument is not promoted in any way, but va_arg description says: "the type of the next argument is nullptr_t and type is a pointer type that has the same representation and alignment requirements as a pointer to a character type." So, while in C++ check_function_sentinel will never see NULLPTR_TYPE, for C23 it can see that and currently we incorrectly warn about those.
The only question is whether we should warn on any argument with nullptr_t type or just about nullptr (nullptr_t argument with integer_zerop value). Through undefined behavior guess one could pass non-NULL pointer that way, say by union { void *p; nullptr_t q; } u; u.p = &whatever; and pass u.q to ..., but valid code should always pass something that will read as (char *) 0 when read using va_arg (ap, char *), so I think it is better not to warn rather than warn in those cases. Note, clang seems to pass (void *)0 rather than expression of nullptr_t type to ellipsis in C23 mode as if it did the C++ ellipsis argument conversions, in that case guess not warning about that would be even safer, but what GCC does I think follows the spec more closely, even when in a valid program one shouldn't be able to observe the difference. Ok for trunk and later 13.3 if it passes bootstrap/regtest (so far just checked on the sentinel related C/C++ tests)? 2024-04-19 Jakub Jelinek <ja...@redhat.com> PR c/114780 * c-common.cc (check_function_sentinel): Allow as sentinel any argument of NULLPTR_TYPE. * gcc.dg/format/sentinel-2.c: New test. --- gcc/c-family/c-common.cc.jj 2024-03-27 19:39:17.968676626 +0100 +++ gcc/c-family/c-common.cc 2024-04-19 12:58:01.577985800 +0200 @@ -5783,6 +5783,7 @@ check_function_sentinel (const_tree fnty sentinel = fold_for_warn (argarray[nargs - 1 - pos]); if ((!POINTER_TYPE_P (TREE_TYPE (sentinel)) || !integer_zerop (sentinel)) + && TREE_CODE (TREE_TYPE (sentinel)) != NULLPTR_TYPE /* Although __null (in C++) is only an integer we allow it nevertheless, as we are guaranteed that it's exactly as wide as a pointer, and we don't want to force --- gcc/testsuite/gcc.dg/format/sentinel-2.c.jj 2024-04-19 12:57:57.431043948 +0200 +++ gcc/testsuite/gcc.dg/format/sentinel-2.c 2024-04-19 12:58:39.020460785 +0200 @@ -0,0 +1,21 @@ +/* PR c/114780 */ +/* { dg-do compile } */ +/* { dg-options "-std=c23 -Wformat" } */ + +#include <stddef.h> + +[[gnu::sentinel]] void foo (int, ...); +[[gnu::sentinel]] void bar (...); + +void +baz (nullptr_t p) +{ + foo (1, 2, nullptr); + foo (3, 4, 5, p); + bar (nullptr); + bar (p); + foo (6, 7, 0); // { dg-warning "missing sentinel in function call" } + bar (0); // { dg-warning "missing sentinel in function call" } + foo (8, 9, NULL); + bar (NULL); +} Jakub