git: d08296c7ab0d - main - libc: Implement qualifier-preserving standard library functions

From: Faraz Vahedi <kfv_at_FreeBSD.org>
Date: Thu, 13 Aug 2026 16:39:29 UTC
The branch main has been updated by kfv:

URL: https://cgit.FreeBSD.org/src/commit/?id=d08296c7ab0d7bb259bf7b8cdf9ffb819c1929ab

commit d08296c7ab0d7bb259bf7b8cdf9ffb819c1929ab
Author:     Faraz Vahedi <kfv@FreeBSD.org>
AuthorDate: 2026-06-19 21:50:20 +0000
Commit:     Faraz Vahedi <kfv@FreeBSD.org>
CommitDate: 2026-08-13 16:38:49 +0000

    libc: Implement qualifier-preserving standard library functions
    
    Several standard library functions are specified to return an unqualified
    pointer while accepting a pointer to a potentially const-qualified object.
    N3020 addresses this behaviour, discarding qualifiers due to incompatible
    pointer types, by introducing qualifier-preserving macros for the affected
    set of standard library functions.
    
    Add `__qualsel()` helper to `<sys/cdefs.h>`, implemented using the generic
    selection, and define qualifier-preserving macros for that set of functions
    in `<string.h>`, `<wchar.h>`, and `<stdlib.h>`.
    
    Macros are gated on `_STDC_VERSION__ >= 202311L && !__cplusplus`, therefore
    there is no behavioural change for earlier C modes or C++ translation units.
    The kernel is likewise unaffected, as it does not include userland headers.
    
    As function-like macros, they are transparent except at a call site where
    the address-of operator is applied, the macro is suppressed via `#undef`,
    or the identifier appears in parenthesised form; all of which cause the
    underlying function designator to be used instead.
    
    Reviewed by:    fuz
    Approved by:    fuz (mentor)
    MFC after:      1 month
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2288
---
 include/stdlib.h          | 12 ++++++++++
 include/string.h          | 28 ++++++++++++++++++++++++
 include/wchar.h           | 14 ++++++++++++
 lib/libc/stdlib/bsearch.3 |  5 +++--
 lib/libc/string/bstring.3 |  9 ++++++--
 lib/libc/string/memchr.3  | 13 +++++++----
 lib/libc/string/memmem.3  |  5 +++++
 lib/libc/string/strchr.3  | 12 ++++++++--
 lib/libc/string/string.3  | 16 +++++++++-----
 lib/libc/string/strpbrk.3 |  8 +++----
 lib/libc/string/strstr.3  | 21 ++++++++++++++----
 lib/libc/string/wmemchr.3 | 56 ++++++++++++++++++++++++++++++++++++++---------
 sys/sys/cdefs.h           | 21 ++++++++++++++++++
 13 files changed, 187 insertions(+), 33 deletions(-)

diff --git a/include/stdlib.h b/include/stdlib.h
index c1f465e21fd1..d61e2de99693 100644
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -418,4 +418,16 @@ errno_t	 qsort_s(void *, rsize_t, rsize_t,
 __END_DECLS
 __NULLABILITY_PRAGMA_POP
 
+#if defined(__qualsel) && !defined(__cplusplus) && \
+    defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
+#define	bsearch(key, base, nmemb, size, compar)		__qualsel((base),    \
+	(const void *)(bsearch)((key), (base), (nmemb), (size), (compar)),   \
+	(bsearch)((key), (base), (nmemb), (size), (compar)))
+#ifdef __BLOCKS__
+#define	bsearch_b(key, base, nmemb, size, compar)	__qualsel((base),    \
+	(const void *)(bsearch_b)((key), (base), (nmemb), (size), (compar)), \
+	(bsearch_b)((key), (base), (nmemb), (size), (compar)))
+#endif
+#endif
+
 #endif /* !_STDLIB_H_ */
diff --git a/include/string.h b/include/string.h
index e9aa1b55281a..98a0a6396848 100644
--- a/include/string.h
+++ b/include/string.h
@@ -205,4 +205,32 @@ errno_t memset_s(void *, rsize_t, int, rsize_t);
 #endif /* __EXT1_VISIBLE */
 __END_DECLS
 
+#if defined(__qualsel) && !defined(__cplusplus) && \
+    defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
+#define	memchr(b, c, n)		__qualsel((b),				\
+	(const void *)(memchr)((b), (c), (n)), (memchr)((b), (c), (n)))
+#define	strchr(s, c)		__qualsel((s),				\
+	(const char *)(strchr)((s), (c)), (strchr)((s), (c)))
+#define	strpbrk(s, charset)	__qualsel((s),				\
+	(const char *)(strpbrk)((s), (charset)), (strpbrk)((s), (charset)))
+#define	strrchr(s, c)		__qualsel((s),				\
+	(const char *)(strrchr)((s), (c)), (strrchr)((s), (c)))
+#define	strstr(s, find)		__qualsel((s),				\
+	(const char *)(strstr)((s), (find)), (strstr)((s), (find)))
+#if __BSD_VISIBLE
+#define	memmem(b, blen, pat, plen)	__qualsel((b),			\
+	(const void *)(memmem)((b), (blen), (pat), (plen)),		\
+	(memmem)((b), (blen), (pat), (plen)))
+#define	memrchr(b, c, n)	__qualsel((b),				\
+	(const void *)(memrchr)((b), (c), (n)), (memrchr)((b), (c), (n)))
+#define	strcasestr(s, find)	__qualsel((s),				\
+	(const char *)(strcasestr)((s), (find)), (strcasestr)((s), (find)))
+#define	strchrnul(s, c)		__qualsel((s),				\
+	(const char *)(strchrnul)((s), (c)), (strchrnul)((s), (c)))
+#define	strnstr(s, find, slen)	__qualsel((s),				\
+	(const char *)(strnstr)((s), (find), (slen)),			\
+	(strnstr)((s), (find), (slen)))
+#endif /* __BSD_VISIBLE */
+#endif
+
 #endif /* _STRING_H_ */
diff --git a/include/wchar.h b/include/wchar.h
index bf07062935d5..1447d083a889 100644
--- a/include/wchar.h
+++ b/include/wchar.h
@@ -246,4 +246,18 @@ size_t	wcslcpy(wchar_t *, const wchar_t *, size_t);
 #endif
 __END_DECLS
 
+#if defined(__qualsel) && !defined(__cplusplus) && \
+    defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
+#define	wcschr(s, c)		__qualsel((s),				\
+	(const wchar_t *)(wcschr)((s), (c)), (wcschr)((s), (c)))
+#define	wcspbrk(s, set)		__qualsel((s),				\
+	(const wchar_t *)(wcspbrk)((s), (set)), (wcspbrk)((s), (set)))
+#define	wcsrchr(s, c)		__qualsel((s),				\
+	(const wchar_t *)(wcsrchr)((s), (c)), (wcsrchr)((s), (c)))
+#define	wcsstr(s, find)		__qualsel((s),				\
+	(const wchar_t *)(wcsstr)((s), (find)), (wcsstr)((s), (find)))
+#define	wmemchr(s, c, n)	__qualsel((s),				\
+	(const wchar_t *)(wmemchr)((s), (c), (n)), (wmemchr)((s), (c), (n)))
+#endif
+
 #endif /* !_WCHAR_H_ */
diff --git a/lib/libc/stdlib/bsearch.3 b/lib/libc/stdlib/bsearch.3
index 712be0f98381..68c0cae5154a 100644
--- a/lib/libc/stdlib/bsearch.3
+++ b/lib/libc/stdlib/bsearch.3
@@ -29,7 +29,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd July 17, 2019
+.Dd June 21, 2026
 .Dt BSEARCH 3
 .Os
 .Sh NAME
@@ -153,4 +153,5 @@ main(void)
 The
 .Fn bsearch
 function conforms to
-.St -isoC .
+.St -isoC-2023 ,
+where it is specified as a qualifier-preserving function.
diff --git a/lib/libc/string/bstring.3 b/lib/libc/string/bstring.3
index dd89af44fc4a..25168404029b 100644
--- a/lib/libc/string/bstring.3
+++ b/lib/libc/string/bstring.3
@@ -27,7 +27,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd December 5, 2023
+.Dd June 21, 2026
 .Dt BSTRING 3
 .Os
 .Sh NAME
@@ -88,7 +88,6 @@ See the specific manual pages for more information.
 .Xr memset 3
 .Sh STANDARDS
 The functions
-.Fn memchr ,
 .Fn memcmp ,
 .Fn memcpy ,
 .Fn memmove ,
@@ -96,6 +95,12 @@ and
 .Fn memset
 conform to
 .St -isoC .
+.Pp
+The
+.Fn memchr
+function conforms to
+.St -isoC-2023 ,
+where it is specified as a qualifier-preserving function.
 .Sh HISTORY
 The functions
 .Fn bzero
diff --git a/lib/libc/string/memchr.3 b/lib/libc/string/memchr.3
index c50e932d3382..da96256e976d 100644
--- a/lib/libc/string/memchr.3
+++ b/lib/libc/string/memchr.3
@@ -29,7 +29,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd April 9, 2008
+.Dd June 21, 2026
 .Dt MEMCHR 3
 .Os
 .Sh NAME
@@ -92,13 +92,18 @@ bytes.
 .Sh STANDARDS
 The
 .Fn memchr
-function
-conforms to
-.St -isoC .
+function conforms to
+.St -isoC-2023 ,
+where it is specified as a qualifier-preserving function.
 .Pp
 The
 .Fn memrchr
 function is a GNU extension and conforms to no standard.
+Like the qualifier-preserving functions in
+.St -isoC-2023 ,
+.Fx implements
+.Fn memrchr
+as qualifier-preserving as well.
 .Sh HISTORY
 The
 .Fn memrchr
diff --git a/lib/libc/string/memmem.3 b/lib/libc/string/memmem.3
index 8e8aa6dafa99..ca236839e755 100644
--- a/lib/libc/string/memmem.3
+++ b/lib/libc/string/memmem.3
@@ -71,6 +71,11 @@ is returned.
 .Fn memmem
 conforms to
 .St -p1003.1-2024 .
+Like the qualifier-preserving functions in
+.St -isoC-2023 ,
+.Fx implements
+.Fn memmem
+as qualifier-preserving as well.
 .Sh HISTORY
 The
 .Fn memmem
diff --git a/lib/libc/string/strchr.3 b/lib/libc/string/strchr.3
index 45179a0001fc..a2f5b32c32c9 100644
--- a/lib/libc/string/strchr.3
+++ b/lib/libc/string/strchr.3
@@ -29,7 +29,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd February 13, 2013
+.Dd June 21, 2026
 .Dt STRCHR 3
 .Os
 .Sh NAME
@@ -109,12 +109,20 @@ The functions
 and
 .Fn strrchr
 conform to
-.St -isoC .
+.St -isoC-2023 ,
+where they are specified as qualifier-preserving functions.
+.Pp
 The function
 .Fn strchrnul
 is a
 .Tn GNU
 extension.
+Like the qualifier-preserving functions in
+.St -isoC-2023 ,
+.Fx
+implements
+.Fn strchrnul
+as qualifier-preserving as well.
 .Sh HISTORY
 The
 .Fn strchrnul
diff --git a/lib/libc/string/string.3 b/lib/libc/string/string.3
index 3ffea6ce0369..c91f6317b5db 100644
--- a/lib/libc/string/string.3
+++ b/lib/libc/string/string.3
@@ -27,7 +27,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd September 2, 2023
+.Dd June 21, 2026
 .Dt STRING 3
 .Os
 .Sh NAME
@@ -136,20 +136,26 @@ for size limitations.
 The
 .Fn strcat ,
 .Fn strncat ,
-.Fn strchr ,
-.Fn strrchr ,
 .Fn strcmp ,
 .Fn strncmp ,
 .Fn strcpy ,
 .Fn strncpy ,
 .Fn strerror ,
 .Fn strlen ,
-.Fn strpbrk ,
 .Fn strspn ,
 .Fn strcspn ,
-.Fn strstr ,
 and
 .Fn strtok
 functions
 conform to
 .St -isoC .
+.Pp
+The
+.Fn strchr ,
+.Fn strrchr ,
+.Fn strpbrk ,
+and
+.Fn strstr
+functions conform to
+.St -isoC-2023 ,
+where they are specified as qualifier-preserving functions.
diff --git a/lib/libc/string/strpbrk.3 b/lib/libc/string/strpbrk.3
index cdccf0da957f..fd63aacfa965 100644
--- a/lib/libc/string/strpbrk.3
+++ b/lib/libc/string/strpbrk.3
@@ -29,7 +29,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd June 4, 1993
+.Dd June 21, 2026
 .Dt STRPBRK 3
 .Os
 .Sh NAME
@@ -69,6 +69,6 @@ returns NULL.
 .Sh STANDARDS
 The
 .Fn strpbrk
-function
-conforms to
-.St -isoC .
+function conforms to
+.St -isoC-2023 ,
+where it is specified as a qualifier-preserving function.
diff --git a/lib/libc/string/strstr.3 b/lib/libc/string/strstr.3
index 8957388db535..e04e882eec6a 100644
--- a/lib/libc/string/strstr.3
+++ b/lib/libc/string/strstr.3
@@ -30,7 +30,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd October 11, 2001
+.Dd June 21, 2026
 .Dt STRSTR 3
 .Os
 .Sh NAME
@@ -147,9 +147,22 @@ ptr = strnstr(largestring, smallstring, 4);
 .Sh STANDARDS
 The
 .Fn strstr
-function
-conforms to
-.St -isoC .
+function conforms to
+.St -isoC-2023 ,
+where it is specified as a qualifier-preserving function.
+.Pp
+The
+.Fn strcasestr
+function is a
+.Tn GNU
+extension, and the
+.Fn strnstr
+function is a
+.Bx
+extension.
+Like the qualifier-preserving functions in
+.St -isoC-2023 ,
+.Fx implements them as qualifier-preserving as well.
 .Sh HISTORY
 The
 .Fn strnstr
diff --git a/lib/libc/string/wmemchr.3 b/lib/libc/string/wmemchr.3
index c1701facb7d5..fddb59a1f49f 100644
--- a/lib/libc/string/wmemchr.3
+++ b/lib/libc/string/wmemchr.3
@@ -154,21 +154,57 @@ counterpart, such as
 .Xr strspn 3 ,
 .Xr strstr 3
 .Sh STANDARDS
-These functions conform to
-.St -isoC-99 ,
-with the exception of
+Functions
+.Fn wmemcmp ,
+.Fn wmemcpy ,
+.Fn wmemmove ,
+.Fn wmemset ,
+.Fn wcscat ,
+.Fn wcscmp ,
+.Fn wcscpy ,
+.Fn wcscspn ,
+.Fn wcslen ,
+.Fn wcsncat ,
+.Fn wcsncmp ,
+.Fn wcsncpy ,
+and
+.Fn wcsspn
+conform to
+.St -isoC-99 .
+.Pp
+Functions
+.Fn wmemchr ,
+.Fn wcschr ,
+.Fn wcspbrk ,
+.Fn wcsrchr ,
+and
+.Fn wcsstr
+conform to
+.St -isoC-2023 ,
+where they are specified as qualifier-preserving functions.
+.Pp
+Functions
 .Fn wcpcpy ,
 .Fn wcpncpy ,
 .Fn wcscasecmp ,
 .Fn wcsdup ,
 .Fn wcsncasecmp ,
 and
-.Fn wcsnlen ,
-which conform to
-.St -p1003.1-2008 ;
+.Fn wcsnlen
+conform to
+.St -p1003.1-2008 ,
+and functions
+.Fn wcslcat
 and
-.Fn wcslcat ,
 .Fn wcslcpy ,
-and
-.Fn wmempcpy ,
-which are extensions.
+first introduced by
+.Ox ,
+conform to
+.St -p1003.1-2024 .
+.Pp
+Function
+.Fn wmempcpy
+is a
+.Tn GNU
+extensions.
+
diff --git a/sys/sys/cdefs.h b/sys/sys/cdefs.h
index d0a34f1bdfc0..22e2888d80af 100644
--- a/sys/sys/cdefs.h
+++ b/sys/sys/cdefs.h
@@ -227,6 +227,27 @@
 	    __typeof(((void)0, (expr))), t), yes, no)
 #endif
 
+/*
+ * __qualsel() is the building block for C23 qualifier-preserving macros
+ * as proposed in N3020: it selects cexpr when the pointer expression p
+ * references a const-qualified object, and expr otherwise.
+ *
+ * The conditional operator's composite-type rule collapses every
+ * pointer-to-const-object type onto the single "const void *"" association,
+ * so callers passing any such pointer are matched even though _Generic()
+ * otherwise compares types exactly.
+ *
+ * The (__uintptr_t) round-trip only suppresses -Wcast-qual on the throwaway
+ * second operand.
+ */
+#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || \
+    __has_extension(c_generic_selections)
+#define	__qualsel(p, cexpr, expr)					\
+	_Generic(1 ? (p) : (void *)(__uintptr_t)(p),			\
+	    const void *: (cexpr),					\
+	    default: (expr))
+#endif
+
 /*
  * C99 Static array indices in function parameter declarations.  Syntax such as:
  * void bar(int myArray[static 10]);