diff --git a/NEWS b/NEWS index 4b1f259fde..6ffc7df286 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,10 @@ Major new features: * The ISO C23 memalignment function has been added. +* As specified in ISO C23, the assert macro is defined to take variable + arguments to support expressions with a comma inside a compound + literal initializer not surrounded by parentheses. + Deprecated and removed features, and other changes affecting compatibility: * Support for dumped heaps has been removed - malloc_set_state() now always diff --git a/assert/Makefile b/assert/Makefile index 8d106d8752..d6fffdde95 100644 --- a/assert/Makefile +++ b/assert/Makefile @@ -36,12 +36,18 @@ routines := \ tests := \ test-assert \ test-assert-2 \ + test-assert-c99 \ + test-assert-gnu99 \ test-assert-perr \ + test-assert-variadic \ tst-assert-c++ \ tst-assert-g++ \ tst-assert-sa-2025-0001 \ # tests +CFLAGS-test-assert-c99.c += -std=c99 +CFLAGS-test-assert-gnu99.c += -std=gnu99 + ifeq ($(have-cxx-thread_local),yes) CFLAGS-tst-assert-c++.o = -std=c++11 LDLIBS-tst-assert-c++ = -lstdc++ diff --git a/assert/assert.h b/assert/assert.h index 31892aebcb..839406ddf7 100644 --- a/assert/assert.h +++ b/assert/assert.h @@ -40,6 +40,24 @@ # define __ASSERT_VOID_CAST (void) #endif +/* C23 makes assert a variadic macro so that expressions with a comma + not between parentheses, but that would still be valid as a single + function argument, such as those involving compound literals with a + comma in the initializer list, can be passed to assert. This + depends on support for variadic macros (added in C99 and GCC 2.95), + and on support for _Bool (added in C99 and GCC 3.0) in order to + validate that only a single expression is passed as an argument, + and is currently implemented only for C. */ +#if (__GLIBC_USE (ISOC23) \ + && (defined __GNUC__ \ + ? __GNUC_PREREQ (3, 0) \ + : defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) \ + && !defined __cplusplus) +# define __ASSERT_VARIADIC 1 +#else +# define __ASSERT_VARIADIC 0 +#endif + /* void assert (int expression); If NDEBUG is defined, do nothing. @@ -47,7 +65,11 @@ #ifdef NDEBUG -# define assert(expr) (__ASSERT_VOID_CAST (0)) +# if __ASSERT_VARIADIC +# define assert(...) (__ASSERT_VOID_CAST (0)) +# else +# define assert(expr) (__ASSERT_VOID_CAST (0)) +# endif /* void assert_perror (int errnum); @@ -80,6 +102,13 @@ extern void __assert (const char *__assertion, const char *__file, int __line) __THROW __attribute__ ((__noreturn__)) __COLD; +# if __ASSERT_VARIADIC +/* This function is not defined and is not called outside of an + unevaluated sizeof, but serves to verify that the argument to + assert is a single expression. */ +extern _Bool __assert_single_arg (_Bool); +# endif + __END_DECLS /* When possible, define assert so that it does not add extra @@ -102,23 +131,40 @@ __END_DECLS : __assert_fail (#expr, __ASSERT_FILE, __ASSERT_LINE, \ __ASSERT_FUNCTION)) # elif !defined __GNUC__ || defined __STRICT_ANSI__ -# define assert(expr) \ +# if __ASSERT_VARIADIC +# define assert(...) \ + (((void) sizeof (__assert_single_arg (__VA_ARGS__)), __VA_ARGS__) \ + ? __ASSERT_VOID_CAST (0) \ + : __assert_fail (#__VA_ARGS__, __FILE__, __LINE__, __ASSERT_FUNCTION)) +# else +# define assert(expr) \ ((expr) \ ? __ASSERT_VOID_CAST (0) \ : __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION)) +# endif # else +# if __ASSERT_VARIADIC +# define assert(...) \ + ((void) sizeof (__assert_single_arg (__VA_ARGS__)), __extension__ ({ \ + if (__VA_ARGS__) \ + ; /* empty */ \ + else \ + __assert_fail (#__VA_ARGS__, __FILE__, __LINE__, __ASSERT_FUNCTION); \ + })) +# else /* The first occurrence of EXPR is not evaluated due to the sizeof, but will trigger any pedantic warnings masked by the __extension__ for the second occurrence. The ternary operator is required to support function pointers and bit fields in this context, and to suppress the evaluation of variable length arrays. */ -# define assert(expr) \ +# define assert(expr) \ ((void) sizeof ((expr) ? 1 : 0), __extension__ ({ \ if (expr) \ ; /* empty */ \ else \ __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); \ })) +# endif # endif # ifdef __USE_GNU diff --git a/assert/test-assert-c99.c b/assert/test-assert-c99.c new file mode 100644 index 0000000000..7e68b22751 --- /dev/null +++ b/assert/test-assert-c99.c @@ -0,0 +1,3 @@ +#undef _GNU_SOURCE + +#include diff --git a/assert/test-assert-gnu99.c b/assert/test-assert-gnu99.c new file mode 100644 index 0000000000..7e68b22751 --- /dev/null +++ b/assert/test-assert-gnu99.c @@ -0,0 +1,3 @@ +#undef _GNU_SOURCE + +#include diff --git a/assert/test-assert-variadic.c b/assert/test-assert-variadic.c new file mode 100644 index 0000000000..5188488410 --- /dev/null +++ b/assert/test-assert-variadic.c @@ -0,0 +1,50 @@ +/* Test assert as a variadic macro. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#undef NDEBUG +#include + +static void +test1 (void) +{ + /* This is two macro arguments, but one function argument, so must + be accepted for C23. */ + assert ((int [2]) { 0, 1 }[1]); +} + +#define NDEBUG +#include + +static void +test2 (void) +{ + /* With NDEBUG, arbitrary sequences of tokens are valid as assert + arguments; they do not need to form a single expression. */ + assert (1, 2, 3, *); + assert (); +} + +static int +do_test (void) +{ + test1 (); + test2 (); + return 0; +} + +#include