Re: N3322 functionality tests
Lasse Collin <[email protected]> Mon, 3 Aug 2026 19:23:40 +0300
| Newsgroups | gmane.comp.lib.gnulib.bugs |
|---|---|
| Message-ID | <[email protected]> |
--MP_/0GE0PZyaCoutYn+kfVmh4mo
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
On 2026-07-31 Paul Eggert wrote:
> On 2026-07-31 14:34, Bruno Haible wrote:
>
> > Lasse has already done a large part of the testing work.
>
> I guess I don't have quite the confidence that the testing work is
> close to done.
I don't either. I only tested a few easy targets, and a passing test
doesn't guarantee that it truly works safely. Still, it feels possible
that the nonnull attributes in GCC < 15 builtins and glibc headers
could be the only major problems.
This is tangential to the main topic, but in my opinion nonnull
shouldn't be applied to functions that aren't guaranteed to crash on
NULL.
- explicit_bzero(NULL, 0) is OK on BSDs, but it's UB with glibc.
- open(NULL, O_RDONLY) fails with EFAULT, which matches the
documentation in open(2). It's UB only due to the nonnull
attribute. (It being UB in POSIX doesn't force every implementation
to be UB.)
- Already fixed: fstatat(fd, NULL, stat_ptr, AT_EMPTY_PATH) is valid
in Linux >= 6.11, but the nonnull in glibc used to make it UB.
I don't believe that the added UB from nonnull provides significant
optimization benefits with many functions. A call with NULL might
happen due a bug, and the extra UB from nonnull might make the bug more
severe. The diagnostics part of nonnull (-Wnonnull and sanitizer) is
great, but that should be achieved without creating more possibilities
for UB.
Other C libraries might have a function or two that aren't N3322
compatible. Gnulib could replace them. I doubt many implementations
have deliberately made the N3322 behavior UB.
> And part of this is it sounds like my goals weren't the same. Mine
> was to guarantee a small extra element of software safety by ensuring
> N3322 semantics, whereas the current Gnulib goal, I guess, is more
> modest, namely, to scout for implementations that obviously don't
> conform to N3322.
My goals changed during this discussion, but I didn't communicate that,
sorry. I was hoping for N3322 support at first, but seems it's not
practical yet. One would need to replace many functions, and a wrapper
for memcpy might add overhead in hot code.
-fno-delete-null-pointer-checks would be simple, but it too might add
overhead and it doesn't silence sanitizers.
The draft patch was indeed to scout other implementations. I attached
an updated version that checks for glibc <= 2.99, although I'm not sure
anymore if such a patch makes sense in Gnulib. This topic is a bigger
mess than I had hoped.
It might be that Clang+glibc is fine apart from bsearch because nonnull
doesn't affect optimizations unless a definition of that function is
visible. With a quick search I didn't spot other inline functions in
glibc headers that could be problematic.
With LTO, functions from different translation units become visible,
and then Clang will optimize based on nonnull in situations where it
wouldn't without LTO. This didn't affect the patched Gnulib tests; only
bsearch failed still.
I didn't allow Clang + glibc in the patch because nonnull would still
be noisy with sanitizers.
> * In gnulib-readme.texi say that portable code should not assume
> N3322, other than assuming that NULL + 0 works.
This is the way for now (likely many years).
NULL + 0 easily results in NULL < NULL and possibly also NULL - NULL. I
hope it's safe to assume that all three work.
For example, lib/mbsnlen.c and lib/memmove.c can execute NULL < NULL
with NULL arguments. (Actually, memmove(NULL, ptr, 0) executes ptr <
NULL, which is still undefined. It's an obsolete module though.)
> I suspect that ten years from now it'll still be dicey in general,
> and there still won't be a good way for users to see whether their
> guess is right if they guess the riskier approach of assuming N3322
> in their apps.
I'm afraid you are right. One could hope it will be enough to refuse to
compile if the compiler isn't in C2y mode, but compiler might be
upgraded before libc.
* * *
N3322 function arguments won't assumed for now, but three commits
already added Autoconf checks:
e3915945e6dc memset_explicit: Guarantee N3322 functionality.
101b9d7f82f7 wcsncat: Guarantee N3322 functionality.
bf799ba1fb48 strndup: Guarantee N3322 functionality.
Should these Autoconf checks be disabled for now? These checks aren't
complete, because they don't detect that the nonnull attribute in
glibc's headers makes calls with NULL arguments undefined behavior.
If those Autoconf checks are disabled, then the matching N3322 tests
need to be disabled too.
* * *
The comment in lib/string.in.h is somewhat misleading:
/* Declarations for ISO C N3322. */
#if defined __GNUC__ && __GNUC__ >= 15 && !defined __clang__
# ifndef memcpy
_GL_EXTERN_C void *memcpy (void *__dest, const void *__src, size_t __n)
# if __GLIBC__ + (__GLIBC_MINOR__ >= 2) > 2
_GL_ATTRIBUTE_NOTHROW
# endif
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (1, 3)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 3);
glibc's <string.h> has the nonnull attribute in memcpy. Adding the
above nonnull_if_nonzero declaration doesn't cancel the nonnull
attribute from glibc's header, so the above is effectively a no-op.
Once glibc is fixed, its <string.h> should have the same attributes as
above, so the above becomes redundant.
With libcs that declare these functions without any attributes, the
above might improve diagnostics. However, in GCC >= 15 the builtin
memcpy already has these attributes, so for memcpy and other builtins
the above does nothing. Only non-builtins might benefit.
If the possible benefits with non-builtins with non-glibc makes these
declarations worthwhile, then these declarations are fine no matter if
Gnulib claims N3322 support or not.
--
Lasse Collin
--MP_/0GE0PZyaCoutYn+kfVmh4mo
Content-Type: text/x-patch
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename=0001-tests-Verify-N3322-compatibility-of-the-function-pro.patch
From e2744ffbe67414a2d37b67175f97f6f47bfbff3f Mon Sep 17 00:00:00 2001
From: Lasse Collin <[email protected]>
Date: Mon, 3 Aug 2026 18:02:33 +0300
Subject: [PATCH] tests: Verify N3322 compatibility of the function prototypes
Test that the compiler doesn't optimize a NULL check away when it sees
the prototypes in the headers <string.h>, <wchar.h>, and <stdlib.h>.
The existing tests explicitly avoided the effects of the attributes
in the function prototypes.
Exclude GCC < 15 and glibc which are known to be problematic.
Use 2.99 as a placeholder for a fixed glibc version. It should
be updated once the glibc headers have been changed.
* tests/test-bsearch.c: Add the test null_bsearch.
* tests/test-memccpy.c: Add the test null_memccpy.
* tests/test-memchr.c: Add the test null_memchr.
* tests/test-memcmp.c: Add the test null_memcmp.
* tests/test-memcpy.c: Add the test null_memcpy.
* tests/test-memmove.c: Add the test null_memmove.
* tests/test-memset.c: Add the test null_memset.
* tests/test-memset_explicit.c: Add the test null_memset_explicit.
* tests/test-qsort.c: Add the test null_qsort.
* tests/test-strncat.c: Add the test null_strncat.
* tests/test-strncmp.c: Add the test null_strncmp.
* tests/test-strncpy.c: Add the test null_strncpy.
* tests/test-strndup.c: Add the test null_strndup.
* tests/test-wcsncat.c: Add the test null_wcsncat.
* tests/test-wcsncmp.c: Add the test null_wcsncmp.
* tests/test-wcsncpy.c: Add the test null_wcsncpy.
* tests/test-wmemchr.c: Add the test null_wmemchr.
* tests/test-wmemcmp.c: Add the test null_wmemcmp.
* tests/test-wmemcpy.c: Add the test null_wmemcpy.
* tests/test-wmemmove.c: Add the test null_wmemmove.
* tests/test-wmemset.c: Add the test null_wmemset.
---
ChangeLog | 31 +++++++++++++++++++++++++++++++
tests/test-bsearch.c | 17 +++++++++++++++++
tests/test-memccpy.c | 15 +++++++++++++++
tests/test-memchr.c | 17 +++++++++++++++++
tests/test-memcmp.c | 17 +++++++++++++++++
tests/test-memcpy.c | 19 +++++++++++++++++++
tests/test-memmove.c | 17 +++++++++++++++++
tests/test-memset.c | 17 +++++++++++++++++
tests/test-memset_explicit.c | 15 +++++++++++++++
tests/test-qsort.c | 17 +++++++++++++++++
tests/test-strncat.c | 17 +++++++++++++++++
tests/test-strncmp.c | 17 +++++++++++++++++
tests/test-strncpy.c | 17 +++++++++++++++++
tests/test-strndup.c | 17 +++++++++++++++++
tests/test-wcsncat.c | 15 +++++++++++++++
tests/test-wcsncmp.c | 15 +++++++++++++++
tests/test-wcsncpy.c | 15 +++++++++++++++
tests/test-wmemchr.c | 12 ++++++++++++
tests/test-wmemcmp.c | 12 ++++++++++++
tests/test-wmemcpy.c | 12 ++++++++++++
tests/test-wmemmove.c | 12 ++++++++++++
tests/test-wmemset.c | 12 ++++++++++++
22 files changed, 355 insertions(+)
diff --git a/ChangeLog b/ChangeLog
index f3fb990b6a..29b301b0e3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,34 @@
+2026-08-03 Lasse Collin <[email protected]>
+
+ tests: Verify N3322 compatibility of the function prototypes.
+ Test that the compiler doesn't optimize a NULL check away when it sees
+ the prototypes in the headers <string.h>, <wchar.h>, and <stdlib.h>.
+ The existing tests explicitly avoided the effects of the attributes
+ in the function prototypes.
+ Exclude GCC < 15 and glibc which are known to be problematic.
+ Use 2.99 as a placeholder for a fixed glibc version. It should
+ be updated once the glibc headers have been changed.
+ * tests/test-bsearch.c: Add the test null_bsearch.
+ * tests/test-memccpy.c: Add the test null_memccpy.
+ * tests/test-memchr.c: Add the test null_memchr.
+ * tests/test-memcmp.c: Add the test null_memcmp.
+ * tests/test-memcpy.c: Add the test null_memcpy.
+ * tests/test-memmove.c: Add the test null_memmove.
+ * tests/test-memset.c: Add the test null_memset.
+ * tests/test-memset_explicit.c: Add the test null_memset_explicit.
+ * tests/test-qsort.c: Add the test null_qsort.
+ * tests/test-strncat.c: Add the test null_strncat.
+ * tests/test-strncmp.c: Add the test null_strncmp.
+ * tests/test-strncpy.c: Add the test null_strncpy.
+ * tests/test-strndup.c: Add the test null_strndup.
+ * tests/test-wcsncat.c: Add the test null_wcsncat.
+ * tests/test-wcsncmp.c: Add the test null_wcsncmp.
+ * tests/test-wcsncpy.c: Add the test null_wcsncpy.
+ * tests/test-wmemchr.c: Add the test null_wmemchr.
+ * tests/test-wmemcmp.c: Add the test null_wmemcmp.
+ * tests/test-wmemcpy.c: Add the test null_wmemcpy.
+ * tests/test-wmemmove.c: Add the test null_wmemmove.
+ * tests/test-wmemset.c: Add the test null_wmemset.
2026-08-01 Bruno Haible <[email protected]>
free-posix tests: Avoid failure on Ubuntu 24.04 (regression 2026-07-28).
diff --git a/tests/test-bsearch.c b/tests/test-bsearch.c
index 6f318256cb..96df08b1ed 100644
--- a/tests/test-bsearch.c
+++ b/tests/test-bsearch.c
@@ -21,6 +21,20 @@
#include "macros.h"
+/* Test the prototype in <stdlib.h> + compiler.
+ In glibc >= 2.18, <bits/stdlib-bsearch.h> has an inline version.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static void *
+null_bsearch (void const *key, void const *base, size_t nel, size_t width,
+ int (*compar) (void const *, void const *))
+{
+ void const *p = bsearch (key, base, nel, width, compar);
+#if ! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__)
+ ASSERT (base == NULL);
+#endif
+ return (void *) p;
+}
+
/* Test the library, not the compiler+library. */
static void *
lib_bsearch (void const *key, void const *base, size_t nel, size_t width,
@@ -48,5 +62,8 @@ main (void)
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3322.pdf>. */
ASSERT (bsearch ("x", NULL, 0, 1, cmp) == NULL);
+ volatile_bsearch = null_bsearch;
+ ASSERT (bsearch ("x", NULL, 0, 1, cmp) == NULL);
+
return test_exit_status;
}
diff --git a/tests/test-memccpy.c b/tests/test-memccpy.c
index 4232e36fb1..62a26b73bf 100644
--- a/tests/test-memccpy.c
+++ b/tests/test-memccpy.c
@@ -23,6 +23,18 @@
#include "macros.h"
+/* Test the prototype in <string.h> + compiler.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static void *
+null_memccpy (void *dest, const void *src, int c, size_t n)
+{
+ void *p = memccpy (dest, src, c, n);
+#if ! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__)
+ ASSERT (dest == NULL);
+#endif
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static void *
lib_memccpy (void *dest, void const *src, int c, size_t n)
@@ -47,5 +59,8 @@ main (void)
ASSERT (memccpy (y, NULL, '?', 0) == NULL);
}
+ volatile_memccpy = null_memccpy;
+ ASSERT (memccpy (NULL, "x", '?', 0) == NULL);
+
return test_exit_status;
}
diff --git a/tests/test-memchr.c b/tests/test-memchr.c
index 7d72dbac5b..1edde65eee 100644
--- a/tests/test-memchr.c
+++ b/tests/test-memchr.c
@@ -27,6 +27,20 @@ SIGNATURE_CHECK (memchr, void *, (void const *, int, size_t));
#include "zerosize-ptr.h"
#include "macros.h"
+/* Test the prototype in <string.h> + compiler.
+ In GCC < 15 this is a builtin that has the nonnull attribute.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static void *
+null_memchr (void const *s, int c, size_t n)
+{
+ const void *p = memchr (s, c, n);
+#if (! defined __GNUC__ || __GNUC__ >= 15 || defined __clang__) \
+ && (! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__))
+ ASSERT (s == NULL);
+#endif
+ return (void *) p;
+}
+
/* Test the library, not the compiler+library. */
static void *
lib_memchr (void const *s, int c, size_t n)
@@ -138,5 +152,8 @@ main (void)
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3322.pdf>. */
ASSERT (memchr (NULL, '?', 0) == NULL);
+ volatile_memchr = null_memchr;
+ ASSERT (memchr (NULL, '?', 0) == NULL);
+
return test_exit_status;
}
diff --git a/tests/test-memcmp.c b/tests/test-memcmp.c
index d773652214..30b99d2735 100644
--- a/tests/test-memcmp.c
+++ b/tests/test-memcmp.c
@@ -25,6 +25,20 @@ SIGNATURE_CHECK (memcmp, int, (void const *, void const *, size_t));
#include "zerosize-ptr.h"
#include "macros.h"
+/* Test the prototype in <string.h> + compiler.
+ In GCC < 15 this is a builtin that has the nonnull attribute.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static int
+null_memcmp (void const *s1, void const *s2, size_t n)
+{
+ int r = memcmp (s1, s2, n);
+#if (! defined __GNUC__ || __GNUC__ >= 15 || defined __clang__) \
+ && (! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__))
+ ASSERT (s1 == NULL);
+#endif
+ return r;
+}
+
/* Test the library, not the compiler+library. */
static int
lib_memcmp (void const *s1, void const *s2, size_t n)
@@ -86,5 +100,8 @@ main (void)
ASSERT (memcmp ("x", NULL, 0) == 0);
ASSERT (memcmp (NULL, NULL, 0) == 0);
+ volatile_memcmp = null_memcmp;
+ ASSERT (memcmp (NULL, "x", 0) == 0);
+
return test_exit_status;
}
diff --git a/tests/test-memcpy.c b/tests/test-memcpy.c
index 180525fca6..cd82b80d9c 100644
--- a/tests/test-memcpy.c
+++ b/tests/test-memcpy.c
@@ -23,6 +23,20 @@
#include "macros.h"
+/* Test the prototype in <string.h> + compiler.
+ In GCC < 15 this is a builtin that has the nonnull attribute.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static void *
+null_memcpy (void *dest, const void *src, size_t n)
+{
+ void *p = memcpy (dest, src, n);
+#if (! defined __GNUC__ || __GNUC__ >= 15 || defined __clang__) \
+ && (! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__))
+ ASSERT (dest == NULL);
+#endif
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static void *
lib_memcpy (void *s1, void const *s2, size_t n)
@@ -47,5 +61,10 @@ main (void)
ASSERT (memcpy (y, NULL, 0) == y);
}
+ /* Redirect via the volatile function pointer to ensure that that the ASSERT
+ in null_memcpy won't be optimized away by GCC 6.3.0. */
+ volatile_memcpy = null_memcpy;
+ ASSERT (memcpy (NULL, "x", 0) == NULL);
+
return test_exit_status;
}
diff --git a/tests/test-memmove.c b/tests/test-memmove.c
index 438d5aaa83..ef71b7c6fa 100644
--- a/tests/test-memmove.c
+++ b/tests/test-memmove.c
@@ -23,6 +23,20 @@
#include "macros.h"
+/* Test the prototype in <string.h> + compiler.
+ In GCC < 15 this is a builtin that has the nonnull attribute.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static void *
+null_memmove (void *dest, const void *src, size_t n)
+{
+ void * p = memmove (dest, src, n);
+#if (! defined __GNUC__ || __GNUC__ >= 15 || defined __clang__) \
+ && (! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__))
+ ASSERT (dest == NULL);
+#endif
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static void *
lib_memmove (void *s1, void const *s2, size_t n)
@@ -47,5 +61,8 @@ main (void)
ASSERT (memmove (y, NULL, 0) == y);
}
+ volatile_memmove = null_memmove;
+ ASSERT (memmove (NULL, "x", 0) == NULL);
+
return test_exit_status;
}
diff --git a/tests/test-memset.c b/tests/test-memset.c
index be744d0dfd..9e33fd947c 100644
--- a/tests/test-memset.c
+++ b/tests/test-memset.c
@@ -23,6 +23,20 @@
#include "macros.h"
+/* Test the prototype in <string.h> + compiler.
+ In GCC < 15 this is a builtin that has the nonnull attribute.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static void *
+null_memset (void *s, int c, size_t n)
+{
+ void *p = memset (s, c, n);
+#if (! defined __GNUC__ || __GNUC__ >= 15 || defined __clang__) \
+ && (! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__))
+ ASSERT (s == NULL);
+#endif
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static void *
lib_memset (void *s, int c, size_t n)
@@ -41,5 +55,8 @@ main (void)
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3322.pdf>. */
ASSERT (memset (NULL, '?', 0) == NULL);
+ volatile_memset = null_memset;
+ ASSERT (memset (NULL, '?', 0) == NULL);
+
return test_exit_status;
}
diff --git a/tests/test-memset_explicit.c b/tests/test-memset_explicit.c
index d02431b4bb..b9bafa1399 100644
--- a/tests/test-memset_explicit.c
+++ b/tests/test-memset_explicit.c
@@ -48,6 +48,18 @@ static char zero[SECRET_SIZE] = { 0 };
# define memset_explicit memset
#endif
+/* Test the prototype in <string.h> + compiler.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static void *
+null_memset_explicit (void *s, int c, size_t n)
+{
+ void *p = memset_explicit (s, c, n);
+#if ! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__)
+ ASSERT (s == NULL);
+#endif
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static void *
lib_memset_explicit (void *s, int c, size_t n)
@@ -268,5 +280,8 @@ main ()
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3322.pdf>. */
ASSERT (memset_explicit (NULL, '?', 0) == NULL);
+ volatile_memset_explicit = null_memset_explicit;
+ ASSERT (memset_explicit (NULL, '?', 0) == NULL);
+
return test_exit_status;
}
diff --git a/tests/test-qsort.c b/tests/test-qsort.c
index 43c4909f03..534a189876 100644
--- a/tests/test-qsort.c
+++ b/tests/test-qsort.c
@@ -19,6 +19,20 @@
/* Specification. */
#include <stdlib.h>
+#include "macros.h"
+
+/* Test the prototype in <stdlib.h> + compiler.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static void
+null_qsort (void *base, size_t nel, size_t width,
+ int (*compar) (void const *, void const *))
+{
+ qsort (base, nel, width, compar);
+#if ! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__)
+ ASSERT (base == NULL);
+#endif
+}
+
/* Test the library, not the compiler+library. */
static void
lib_qsort (void *base, size_t nel, size_t width,
@@ -46,5 +60,8 @@ main (void)
qsort (NULL, 0, 1, cmp);
+ volatile_qsort = null_qsort;
+ qsort (NULL, 0, 1, cmp);
+
return 0;
}
diff --git a/tests/test-strncat.c b/tests/test-strncat.c
index 1817bb9d10..7f542a3b37 100644
--- a/tests/test-strncat.c
+++ b/tests/test-strncat.c
@@ -29,6 +29,20 @@ SIGNATURE_CHECK (strncat, char *, (char *, const char *, size_t));
#include "zerosize-ptr.h"
#include "macros.h"
+/* Test the prototype in <string.h> + compiler.
+ In GCC < 15 this is a builtin that has the nonnull attribute.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static char *
+null_strncat (char *s1, char const *s2, size_t n)
+{
+ char *p = strncat (s1, s2, n);
+#if (! defined __GNUC__ || __GNUC__ >= 15 || defined __clang__) \
+ && (! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__))
+ ASSERT (s2 == NULL);
+#endif
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static char *
lib_strncat (char *s1, char const *s2, size_t n)
@@ -80,6 +94,9 @@ main ()
{
char y[2] = { 'x', '\0' };
ASSERT (strncat (y, NULL, 0) == y);
+
+ volatile_strncat = null_strncat;
+ ASSERT (strncat (y, NULL, 0) == y);
}
return test_exit_status;
diff --git a/tests/test-strncmp.c b/tests/test-strncmp.c
index 6d49af9056..0108e6eb55 100644
--- a/tests/test-strncmp.c
+++ b/tests/test-strncmp.c
@@ -23,6 +23,20 @@
#include "macros.h"
+/* Test the prototype in <string.h> + compiler.
+ In GCC < 15 this is a builtin that has the nonnull attribute.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static int
+null_strncmp (char const *s1, char const *s2, size_t n)
+{
+ int r = strncmp (s1, s2, n);
+#if (! defined __GNUC__ || __GNUC__ >= 15 || defined __clang__) \
+ && (! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__))
+ ASSERT (s1 == NULL);
+#endif
+ return r;
+}
+
/* Test the library, not the compiler+library. */
static int
lib_strncmp (char const *s1, char const *s2, size_t n)
@@ -43,5 +57,8 @@ main (void)
ASSERT (strncmp ("x", NULL, 0) == 0);
ASSERT (strncmp (NULL, NULL, 0) == 0);
+ volatile_strncmp = null_strncmp;
+ ASSERT (strncmp (NULL, "x", 0) == 0);
+
return test_exit_status;
}
diff --git a/tests/test-strncpy.c b/tests/test-strncpy.c
index 83535a4bee..0c116bd9aa 100644
--- a/tests/test-strncpy.c
+++ b/tests/test-strncpy.c
@@ -25,6 +25,20 @@
#include "zerosize-ptr.h"
#include "macros.h"
+/* Test the prototype in <string.h> + compiler.
+ In GCC < 15 this is a builtin that has the nonnull attribute.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static char *
+null_strncpy (char *s1, char const *s2, size_t n)
+{
+ char *p = strncpy (s1, s2, n);
+#if (! defined __GNUC__ || __GNUC__ >= 15 || defined __clang__) \
+ && (! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__))
+ ASSERT (s2 == NULL);
+#endif
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static char *
lib_strncpy (char *s1, char const *s2, size_t n)
@@ -116,6 +130,9 @@ main (void)
{
char y[1];
ASSERT (strncpy (y, NULL, 0) == y);
+
+ volatile_strncpy = null_strncpy;
+ ASSERT (strncpy (y, NULL, 0) == y);
}
return test_exit_status;
diff --git a/tests/test-strndup.c b/tests/test-strndup.c
index 10ad604f24..86ad433cfd 100644
--- a/tests/test-strndup.c
+++ b/tests/test-strndup.c
@@ -23,6 +23,20 @@
#include "macros.h"
+/* Test the prototype in <string.h> + compiler.
+ In GCC < 15 this is a builtin that has the nonnull attribute.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static char *
+null_strndup (char const *s, size_t size)
+{
+ char *p = strndup (s, size);
+#if (! defined __GNUC__ || __GNUC__ >= 15 || defined __clang__) \
+ && (! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__))
+ ASSERT (s == NULL);
+#endif
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static char *
lib_strndup (char const *s, size_t size)
@@ -41,5 +55,8 @@ main (void)
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3322.pdf>. */
ASSERT (strndup (NULL, 0) != NULL);
+ volatile_strndup = null_strndup;
+ ASSERT (strndup (NULL, 0) != NULL);
+
return test_exit_status;
}
diff --git a/tests/test-wcsncat.c b/tests/test-wcsncat.c
index 3aaa68a24f..26f0168e47 100644
--- a/tests/test-wcsncat.c
+++ b/tests/test-wcsncat.c
@@ -25,6 +25,18 @@
#include "macros.h"
+/* Test the prototype in <wchar.h> + compiler.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static wchar_t *
+null_wcsncat (wchar_t *ws1, wchar_t const *ws2, size_t n)
+{
+ wchar_t *p = wcsncat (ws1, ws2, n);
+#if ! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__)
+ ASSERT (ws2 == NULL);
+#endif
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static wchar_t *
lib_wcsncat (wchar_t *ws1, wchar_t const *ws2, size_t n)
@@ -50,6 +62,9 @@ main ()
{
wchar_t y[2] = { L'x', 0 };
ASSERT (wcsncat (y, NULL, 0) == y);
+
+ volatile_wcsncat = null_wcsncat;
+ ASSERT (wcsncat (y, NULL, 0) == y);
}
return test_exit_status;
diff --git a/tests/test-wcsncmp.c b/tests/test-wcsncmp.c
index c43ce8273c..35c7a5ddd0 100644
--- a/tests/test-wcsncmp.c
+++ b/tests/test-wcsncmp.c
@@ -25,6 +25,18 @@ SIGNATURE_CHECK (wcsncmp, int, (const wchar_t *, const wchar_t *, size_t));
#include "macros.h"
+/* Test the prototype in <wchar.h> + compiler.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static int
+null_wcsncmp (wchar_t const *ws1, wchar_t const *ws2, size_t n)
+{
+ int r = wcsncmp (ws1, ws2, n);
+#if ! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__)
+ ASSERT (ws1 == NULL);
+#endif
+ return r;
+}
+
/* Test the library, not the compiler+library. */
static int
lib_wcsncmp (wchar_t const *ws1, wchar_t const *ws2, size_t n)
@@ -196,5 +208,8 @@ main (int argc, char *argv[])
ASSERT (wcsncmp (L"x", NULL, 0) == 0);
ASSERT (wcsncmp (NULL, NULL, 0) == 0);
+ volatile_wcsncmp = null_wcsncmp;
+ ASSERT (wcsncmp (NULL, L"x", 0) == 0);
+
return test_exit_status;
}
diff --git a/tests/test-wcsncpy.c b/tests/test-wcsncpy.c
index 321fabd6ef..e5eb777bab 100644
--- a/tests/test-wcsncpy.c
+++ b/tests/test-wcsncpy.c
@@ -23,6 +23,18 @@
#include "macros.h"
+/* Test the prototype in <wchar.h> + compiler.
+ Some glibc versions use the nonnull attribute, which breaks this test. */
+static wchar_t *
+null_wcsncpy (wchar_t *ws1, wchar_t const *ws2, size_t n)
+{
+ wchar_t *p = wcsncpy (ws1, ws2, n);
+#if ! defined __GLIBC__ || 2 < __GLIBC__ + (99 <= __GLIBC_MINOR__)
+ ASSERT (ws2 == NULL);
+#endif
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static wchar_t *
lib_wcsncpy (wchar_t *ws1, wchar_t const *ws2, size_t n)
@@ -46,6 +58,9 @@ main (void)
{
wchar_t y[1];
ASSERT (wcsncpy (y, NULL, 0) == y);
+
+ volatile_wcsncpy = null_wcsncpy;
+ ASSERT (wcsncpy (y, NULL, 0) == y);
}
return test_exit_status;
diff --git a/tests/test-wmemchr.c b/tests/test-wmemchr.c
index 877f519aa8..04858ba93e 100644
--- a/tests/test-wmemchr.c
+++ b/tests/test-wmemchr.c
@@ -23,6 +23,15 @@
#include "macros.h"
+/* Test the prototype in <wchar.h> + compiler. */
+static wchar_t *
+null_wmemchr (wchar_t const *s, wchar_t wc, size_t n)
+{
+ wchar_t const *p = wmemchr (s, wc, n);
+ ASSERT (s == NULL);
+ return (wchar_t *) p;
+}
+
/* Test the library, not the compiler+library. */
static wchar_t *
lib_wmemchr (wchar_t const *s, wchar_t wc, size_t n)
@@ -41,5 +50,8 @@ main (void)
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3322.pdf>. */
ASSERT (wmemchr (NULL, L'?', 0) == NULL);
+ volatile_wmemchr = null_wmemchr;
+ ASSERT (wmemchr (NULL, L'?', 0) == NULL);
+
return test_exit_status;
}
diff --git a/tests/test-wmemcmp.c b/tests/test-wmemcmp.c
index 6b7564659d..8d68196074 100644
--- a/tests/test-wmemcmp.c
+++ b/tests/test-wmemcmp.c
@@ -25,6 +25,15 @@ SIGNATURE_CHECK (wmemcmp, int, (const wchar_t *, const wchar_t *, size_t));
#include "macros.h"
+/* Test the prototype in <wchar.h> + compiler. */
+static int
+null_wmemcmp (wchar_t const *ws1, wchar_t const *ws2, size_t n)
+{
+ int r = wmemcmp (ws1, ws2, n);
+ ASSERT (ws1 == NULL);
+ return r;
+}
+
/* Test the library, not the compiler+library. */
static int
lib_wmemcmp (wchar_t const *ws1, wchar_t const *ws2, size_t n)
@@ -108,5 +117,8 @@ main (int argc, char *argv[])
ASSERT (wmemcmp (L"x", NULL, 0) == 0);
ASSERT (wmemcmp (NULL, NULL, 0) == 0);
+ volatile_wmemcmp = null_wmemcmp;
+ ASSERT (wmemcmp (NULL, L"x", 0) == 0);
+
return test_exit_status;
}
diff --git a/tests/test-wmemcpy.c b/tests/test-wmemcpy.c
index f5bb779ce8..296a0cae1e 100644
--- a/tests/test-wmemcpy.c
+++ b/tests/test-wmemcpy.c
@@ -23,6 +23,15 @@
#include "macros.h"
+/* Test the prototype in <wchar.h> + compiler. */
+static wchar_t *
+null_wmemcpy (wchar_t *s1, wchar_t const *s2, size_t n)
+{
+ wchar_t *p = wmemcpy (s1, s2, n);
+ ASSERT (s1 == NULL);
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static wchar_t *
lib_wmemcpy (wchar_t *s1, wchar_t const *s2, size_t n)
@@ -48,5 +57,8 @@ main (void)
ASSERT (wmemcpy (y, NULL, 0) == y);
}
+ volatile_wmemcpy = null_wmemcpy;
+ ASSERT (wmemcpy (NULL, L"x", 0) == NULL);
+
return test_exit_status;
}
diff --git a/tests/test-wmemmove.c b/tests/test-wmemmove.c
index 4e0401c37f..3737d62d98 100644
--- a/tests/test-wmemmove.c
+++ b/tests/test-wmemmove.c
@@ -23,6 +23,15 @@
#include "macros.h"
+/* Test the prototype in <wchar.h> + compiler. */
+static wchar_t *
+null_wmemmove (wchar_t *s1, wchar_t const *s2, size_t n)
+{
+ wchar_t *p = wmemmove (s1, s2, n);
+ ASSERT (s1 == NULL);
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static wchar_t *
lib_wmemmove (wchar_t *s1, wchar_t const *s2, size_t n)
@@ -48,5 +57,8 @@ main (void)
ASSERT (wmemmove (y, NULL, 0) == y);
}
+ volatile_wmemmove = null_wmemmove;
+ ASSERT (wmemmove (NULL, L"x", 0) == NULL);
+
return test_exit_status;
}
diff --git a/tests/test-wmemset.c b/tests/test-wmemset.c
index a7fb360932..9d45f4c5a6 100644
--- a/tests/test-wmemset.c
+++ b/tests/test-wmemset.c
@@ -23,6 +23,15 @@
#include "macros.h"
+/* Test the prototype in <wchar.h> + compiler. */
+static wchar_t *
+null_wmemset (wchar_t *ws, wchar_t wc, size_t n)
+{
+ wchar_t *p = wmemset (ws, wc, n);
+ ASSERT (ws == NULL);
+ return p;
+}
+
/* Test the library, not the compiler+library. */
static wchar_t *
lib_wmemset (wchar_t *ws, wchar_t wc, size_t n)
@@ -41,5 +50,8 @@ main (void)
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3322.pdf>. */
ASSERT (wmemset (NULL, L'?', 0) == NULL);
+ volatile_wmemset = null_wmemset;
+ ASSERT (wmemset (NULL, L'?', 0) == NULL);
+
return test_exit_status;
}
--
2.55.0
--MP_/0GE0PZyaCoutYn+kfVmh4mo--