git: 8b5d77bbcbd9 - main - libc/tests/string: add a more comprehensive unit test for strrchr()

From: Robert Clausecker <fuz_at_FreeBSD.org>
Date: Wed, 08 Apr 2026 13:22:35 UTC
The branch main has been updated by fuz:

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

commit 8b5d77bbcbd98e684226950be1c779e108059d8d
Author:     Robert Clausecker <fuz@FreeBSD.org>
AuthorDate: 2026-03-22 21:39:42 +0000
Commit:     Robert Clausecker <fuz@FreeBSD.org>
CommitDate: 2026-04-08 13:21:50 +0000

    libc/tests/string: add a more comprehensive unit test for strrchr()
    
    The unit tests are patterned after those for memrchr().
    This catches the issue found in 293915.
    
    PR:             293915
    Reviewed by:    strajabot
    Reported by:    safonov.paul@gmail.com
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D56037
---
 lib/libc/tests/string/Makefile       |   2 +
 lib/libc/tests/string/strrchr_test.c | 156 +++++++++++++++++++++++++++++++++++
 2 files changed, 158 insertions(+)

diff --git a/lib/libc/tests/string/Makefile b/lib/libc/tests/string/Makefile
index a019939c30af..a4d23b2dcfe1 100644
--- a/lib/libc/tests/string/Makefile
+++ b/lib/libc/tests/string/Makefile
@@ -20,6 +20,7 @@ ATF_TESTS_C+=		strcmp2_test
 ATF_TESTS_C+=		strcspn_test
 ATF_TESTS_C+=		strerror2_test
 ATF_TESTS_C+=		strlcpy_test
+ATF_TESTS_C+=		strrchr2_test
 ATF_TESTS_C+=		strspn_test
 ATF_TESTS_C+=		strverscmp_test
 ATF_TESTS_C+=		strxfrm_test
@@ -49,6 +50,7 @@ NETBSD_ATF_TESTS_C+=	swab_test
 SRCS.memset2_test=	memset_test.c
 SRCS.strcmp2_test=	strcmp_test.c
 SRCS.strerror2_test=	strerror_test.c
+SRCS.strrchr2_test=	strrchr_test.c
 
 .include "../Makefile.netbsd-tests"
 
diff --git a/lib/libc/tests/string/strrchr_test.c b/lib/libc/tests/string/strrchr_test.c
new file mode 100644
index 000000000000..1c3d912ec3f8
--- /dev/null
+++ b/lib/libc/tests/string/strrchr_test.c
@@ -0,0 +1,156 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023, 2026 Robert Clausecker <fuz@FreeBSD.org>
+ *
+ * Adapted from memrchr_test.c.
+ */
+
+#include <sys/cdefs.h>
+
+#include <dlfcn.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <atf-c.h>
+
+static char *(*strrchr_fn)(const char *, int);
+
+/*
+ * Check that when looking for the character NUL, we find the
+ * string terminator, and not some NUL character after it.
+ */
+ATF_TC_WITHOUT_HEAD(nul);
+ATF_TC_BODY(nul, tc)
+{
+	size_t i, j, k;
+	char buf[1+15+64]; /* offset [0+15] + 64 buffer bytes + sentinels */
+
+	buf[0] = '\0';
+	memset(buf + 1, '-', sizeof(buf) - 1);
+
+	for (i = 0; i < 16; i++)
+		for (j = 0; j < 64; j++)
+			for (k = j; k < 64; k++) {
+				buf[i + j + 1] = '\0';
+				buf[i + k + 1] = '\0';
+				ATF_CHECK_EQ(strrchr_fn(buf + i + 1, '\0'), buf + i + j + 1);
+				buf[i + j + 1] = '-';
+				buf[i + k + 1] = '-';
+			}
+}
+
+/*
+ * Check that if the character 'X' does not occur in the string
+ * (but occurs before and after it), we correctly return NULL.
+ */
+ATF_TC_WITHOUT_HEAD(not_found);
+ATF_TC_BODY(not_found, tc)
+{
+	size_t i, j;
+	char buf[1+15+64+2]; /* offset [0..15] + 64 buffer bytes + sentinels */
+
+	buf[0] = 'X';
+	memset(buf + 1, '-', sizeof(buf) - 1);
+
+	for (i = 0; i < 16; i++)
+		for (j = 0; j < 64; j++) {
+			buf[i + j + 1] = '\0';
+			buf[i + j + 2] = 'X';
+			ATF_CHECK_EQ(strrchr_fn(buf + i + 1, 'X'), NULL);
+			buf[i + j + 1] = '-';
+			buf[i + j + 2] = '-';
+		}
+}
+
+static void
+do_found_test(char buf[], size_t first, size_t second)
+{
+	/* invariant: first <= second */
+
+	buf[first] = 'X';
+	buf[second] = 'X';
+	ATF_CHECK_EQ(strrchr_fn(buf, 'X'), buf + second);
+	buf[first] = '-';
+	buf[second] = '-';
+}
+
+/*
+ * Check that if the character 'X' occurs in the string multiple
+ * times (i. e. twice), its last encounter is returned.
+ */
+ATF_TC_WITHOUT_HEAD(found);
+ATF_TC_BODY(found, tc)
+{
+	size_t i, j, k, l;
+	char buf[1+15+64+2];
+
+	buf[0] = 'X';
+	memset(buf + 1, '-', sizeof(buf) - 1);
+
+	for (i = 0; i < 16; i++)
+		for (j = 0; j < 64; j++)
+			for (k = 0; k < j; k++)
+				for (l = 0; l <= k; l++) {
+					buf[i + j + 1] = '\0';
+					buf[i + j + 2] = 'X';
+					do_found_test(buf + i + 1, l, k);
+					buf[i + j + 1] = '-';
+					buf[i + j + 2] = '-';
+				}
+}
+
+static void
+do_values_test(char buf[], size_t len, size_t i, int c)
+{
+	/* sentinels */
+	buf[-1] = c;
+	buf[len] = '\0';
+	buf[len + 1] = 'c';
+
+	/* fill the string with some other character, but not with NUL */
+	memset(buf, c == UCHAR_MAX ? c - 1 : c + 1, len);
+
+	if (i < len) {
+		buf[i] = c;
+		ATF_CHECK_EQ(strrchr_fn(buf, c), buf + i);
+	} else
+		ATF_CHECK_EQ(strrchr_fn(buf, c), c == 0 ? buf + len : NULL);
+}
+
+/*
+ * Check that the character is found regardless of its value.
+ * This catches arithmetic (overflow) errors in incorrect SWAR
+ * implementations of byte-parallel character matching.
+ */
+ATF_TC_WITHOUT_HEAD(values);
+ATF_TC_BODY(values, tc)
+{
+	size_t i, j, k;
+	int c;
+	char buf[1+15+64+2];
+
+	for (i = 0; i < 16; i++)
+		for (j = 0; j < 64; j++)
+			for (k = 0; k <= j; k++)
+				for (c = 0; c <= UCHAR_MAX; c++)
+					do_values_test(buf + i + 1, j, k, c);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+	void *dl_handle;
+
+	dl_handle = dlopen(NULL, RTLD_LAZY);
+	strrchr_fn = dlsym(dl_handle, "test_strrchr");
+	if (strrchr_fn == NULL)
+		strrchr_fn = strrchr;
+
+	ATF_TP_ADD_TC(tp, nul);
+	ATF_TP_ADD_TC(tp, not_found);
+	ATF_TP_ADD_TC(tp, found);
+	ATF_TP_ADD_TC(tp, values);
+
+	return (atf_no_error());
+}