aboutsummaryrefslogtreecommitdiff
path: root/lib/libc/tests/stdlib
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc/tests/stdlib')
-rw-r--r--lib/libc/tests/stdlib/Makefile13
-rw-r--r--lib/libc/tests/stdlib/clearenv_test.c1
-rw-r--r--lib/libc/tests/stdlib/cxa_atexit_test.c132
-rw-r--r--lib/libc/tests/stdlib/cxa_thread_atexit_nothr_test.cc1
-rw-r--r--lib/libc/tests/stdlib/cxa_thread_atexit_test.cc1
-rw-r--r--lib/libc/tests/stdlib/dynthr_mod/Makefile2
-rw-r--r--lib/libc/tests/stdlib/dynthr_mod/dynthr_mod.c1
-rw-r--r--lib/libc/tests/stdlib/dynthr_test.c1
-rw-r--r--lib/libc/tests/stdlib/getenv_r_test.c69
-rw-r--r--lib/libc/tests/stdlib/heapsort_test.c1
-rw-r--r--lib/libc/tests/stdlib/libatexit/Makefile11
-rw-r--r--lib/libc/tests/stdlib/libatexit/libatexit.cc67
-rw-r--r--lib/libc/tests/stdlib/libc_exit_test.c154
-rw-r--r--lib/libc/tests/stdlib/mergesort_test.c1
-rw-r--r--lib/libc/tests/stdlib/qsort_bench.c113
-rw-r--r--lib/libc/tests/stdlib/qsort_r_compat_test.c1
-rw-r--r--lib/libc/tests/stdlib/qsort_r_test.c1
-rw-r--r--lib/libc/tests/stdlib/qsort_s_test.c1
-rw-r--r--lib/libc/tests/stdlib/qsort_test.c1
-rw-r--r--lib/libc/tests/stdlib/set_constraint_handler_s_test.c1
-rw-r--r--lib/libc/tests/stdlib/strfmon_test.c1
-rw-r--r--lib/libc/tests/stdlib/tsearch_test.c1
22 files changed, 555 insertions, 20 deletions
diff --git a/lib/libc/tests/stdlib/Makefile b/lib/libc/tests/stdlib/Makefile
index a2a6420aba41..9d84becfbd1f 100644
--- a/lib/libc/tests/stdlib/Makefile
+++ b/lib/libc/tests/stdlib/Makefile
@@ -1,17 +1,20 @@
-
.include <src.opts.mk>
ATF_TESTS_C+= clearenv_test
+ATF_TESTS_C+= cxa_atexit_test
ATF_TESTS_C+= dynthr_test
+ATF_TESTS_C+= getenv_r_test
ATF_TESTS_C+= heapsort_test
+ATF_TESTS_C+= libc_exit_test
ATF_TESTS_C+= mergesort_test
ATF_TESTS_C+= qsort_test
-.if ${COMPILER_TYPE} == "clang"
+.if ${COMPILER_FEATURES:Mblocks}
ATF_TESTS_C+= qsort_b_test
.endif
ATF_TESTS_C+= qsort_r_compat_test
ATF_TESTS_C+= qsort_r_test
ATF_TESTS_C+= qsort_s_test
+ATF_TESTS_C+= qsort_bench
ATF_TESTS_C+= set_constraint_handler_s_test
ATF_TESTS_C+= strfmon_test
ATF_TESTS_C+= tsearch_test
@@ -58,11 +61,9 @@ PROGS+= h_getopt h_getopt_long
CFLAGS+= -I${.CURDIR}
-CXXSTD.cxa_thread_atexit_test= c++11
-CXXSTD.cxa_thread_atexit_nothr_test= c++11
LIBADD.cxa_thread_atexit_test+= pthread
-# Tests that requires Blocks feature
+# Tests that require blocks support
.for t in qsort_b_test
CFLAGS.${t}.c+= -fblocks
LIBADD.${t}+= BlocksRuntime
@@ -75,8 +76,10 @@ LDFLAGS.$t+= -L${LIBNETBSD_OBJDIR}
LIBADD.${t}+= netbsd util
.endfor
+LIBADD.libc_exit_test+= pthread
LIBADD.strtod_test+= m
SUBDIR+= dynthr_mod
+SUBDIR+= libatexit
.include <bsd.test.mk>
diff --git a/lib/libc/tests/stdlib/clearenv_test.c b/lib/libc/tests/stdlib/clearenv_test.c
index 369d64b2e3b4..003535a00060 100644
--- a/lib/libc/tests/stdlib/clearenv_test.c
+++ b/lib/libc/tests/stdlib/clearenv_test.c
@@ -29,7 +29,6 @@
* Test for clearenv(3) routine.
*/
-#include <sys/cdefs.h>
#include <atf-c.h>
#include <stdio.h>
diff --git a/lib/libc/tests/stdlib/cxa_atexit_test.c b/lib/libc/tests/stdlib/cxa_atexit_test.c
new file mode 100644
index 000000000000..7e2cafbce850
--- /dev/null
+++ b/lib/libc/tests/stdlib/cxa_atexit_test.c
@@ -0,0 +1,132 @@
+/*-
+ * Copyright (c) 2025 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/wait.h>
+
+#include <dlfcn.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <atf-c.h>
+
+#define ARBITRARY_EXIT_CODE 42
+
+static char *
+get_shlib(const char *srcdir)
+{
+ char *shlib;
+
+ shlib = NULL;
+ if (asprintf(&shlib, "%s/libatexit.so", srcdir) < 0)
+ atf_tc_fail("failed to construct path to libatexit.so");
+ return (shlib);
+}
+
+static void
+run_test(const atf_tc_t *tc, bool with_fatal_atexit, bool with_exit)
+{
+ pid_t p;
+ void (*set_fatal_atexit)(bool);
+ void (*set_exit_code)(int);
+ void *hdl;
+ char *shlib;
+
+ shlib = get_shlib(atf_tc_get_config_var(tc, "srcdir"));
+
+ hdl = dlopen(shlib, RTLD_LAZY);
+ ATF_REQUIRE_MSG(hdl != NULL, "dlopen: %s", dlerror());
+
+ free(shlib);
+
+ if (with_fatal_atexit) {
+ set_fatal_atexit = dlsym(hdl, "set_fatal_atexit");
+ ATF_REQUIRE_MSG(set_fatal_atexit != NULL,
+ "set_fatal_atexit: %s", dlerror());
+ }
+ if (with_exit) {
+ set_exit_code = dlsym(hdl, "set_exit_code");
+ ATF_REQUIRE_MSG(set_exit_code != NULL, "set_exit_code: %s",
+ dlerror());
+ }
+
+ p = atf_utils_fork();
+ if (p == 0) {
+ /*
+ * Don't let the child clobber the results file; stderr/stdout
+ * have been replaced by atf_utils_fork() to capture it. We're
+ * intentionally using exit() instead of _exit() here to run
+ * __cxa_finalize at exit, otherwise we'd just leave it be.
+ */
+ closefrom(3);
+
+ if (with_fatal_atexit)
+ set_fatal_atexit(true);
+ if (with_exit)
+ set_exit_code(ARBITRARY_EXIT_CODE);
+
+ dlclose(hdl);
+
+ /*
+ * If the dtor was supposed to exit (most cases), then we should
+ * not have made it to this point. If it's not supposed to
+ * exit, then we just exit with success here because we might
+ * be expecting either a clean exit or a signal on our way out
+ * as the final __cxa_finalize tries to run a callback in the
+ * unloaded DSO.
+ */
+ if (with_exit)
+ exit(1);
+ exit(0);
+ }
+
+ dlclose(hdl);
+ atf_utils_wait(p, with_exit ? ARBITRARY_EXIT_CODE : 0, "", "");
+}
+
+ATF_TC_WITHOUT_HEAD(simple_cxa_atexit);
+ATF_TC_BODY(simple_cxa_atexit, tc)
+{
+ /*
+ * This test exits in a global object's dtor so that we check for our
+ * dtor being run at dlclose() time. If it isn't, then the forked child
+ * will have a chance to exit(1) after dlclose() to raise a failure.
+ */
+ run_test(tc, false, true);
+}
+
+ATF_TC_WITHOUT_HEAD(late_cxa_atexit);
+ATF_TC_BODY(late_cxa_atexit, tc)
+{
+ /*
+ * This test creates another global object during a __cxa_atexit handler
+ * invocation. It's been observed in the wild that we weren't executing
+ * it, then the DSO gets torn down and it was executed at application
+ * exit time instead. In the best case scenario we would crash if
+ * something else hadn't been mapped there.
+ */
+ run_test(tc, true, false);
+}
+
+ATF_TC_WITHOUT_HEAD(late_cxa_atexit_ran);
+ATF_TC_BODY(late_cxa_atexit_ran, tc)
+{
+ /*
+ * This is a slight variation of the previous test where we trigger an
+ * exit() in our late-registered __cxa_atexit handler so that we can
+ * ensure it was ran *before* dlclose() finished and not through some
+ * weird chain of events afterwards.
+ */
+ run_test(tc, true, true);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, simple_cxa_atexit);
+ ATF_TP_ADD_TC(tp, late_cxa_atexit);
+ ATF_TP_ADD_TC(tp, late_cxa_atexit_ran);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/stdlib/cxa_thread_atexit_nothr_test.cc b/lib/libc/tests/stdlib/cxa_thread_atexit_nothr_test.cc
index 8f9c6348e0b0..0b3b9497a6bd 100644
--- a/lib/libc/tests/stdlib/cxa_thread_atexit_nothr_test.cc
+++ b/lib/libc/tests/stdlib/cxa_thread_atexit_nothr_test.cc
@@ -25,7 +25,6 @@
* SUCH DAMAGE.
*/
-#include <sys/cdefs.h>
#include <dlfcn.h>
#include <atf-c++.hpp>
#include <cstdio>
diff --git a/lib/libc/tests/stdlib/cxa_thread_atexit_test.cc b/lib/libc/tests/stdlib/cxa_thread_atexit_test.cc
index d2e1369c91dd..628a70b510d1 100644
--- a/lib/libc/tests/stdlib/cxa_thread_atexit_test.cc
+++ b/lib/libc/tests/stdlib/cxa_thread_atexit_test.cc
@@ -24,7 +24,6 @@
* SUCH DAMAGE.
*/
-#include <sys/cdefs.h>
#include <dlfcn.h>
#include <atf-c++.hpp>
#include <cstdio>
diff --git a/lib/libc/tests/stdlib/dynthr_mod/Makefile b/lib/libc/tests/stdlib/dynthr_mod/Makefile
index 3c6330cb712f..3602e81e5be1 100644
--- a/lib/libc/tests/stdlib/dynthr_mod/Makefile
+++ b/lib/libc/tests/stdlib/dynthr_mod/Makefile
@@ -1,8 +1,8 @@
-
SHLIB_NAME= dynthr_mod.so
SHLIBDIR= ${TESTSDIR}
SRCS= dynthr_mod.c
LIBADD= pthread
+PACKAGE= tests
TESTSDIR:= ${TESTSBASE}/${RELDIR:C/libc\/tests/libc/:H}
diff --git a/lib/libc/tests/stdlib/dynthr_mod/dynthr_mod.c b/lib/libc/tests/stdlib/dynthr_mod/dynthr_mod.c
index 21c9e19d4468..eccffcd0bd49 100644
--- a/lib/libc/tests/stdlib/dynthr_mod/dynthr_mod.c
+++ b/lib/libc/tests/stdlib/dynthr_mod/dynthr_mod.c
@@ -31,7 +31,6 @@
* may be omitted in redistributions.
*/
-#include <sys/cdefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
diff --git a/lib/libc/tests/stdlib/dynthr_test.c b/lib/libc/tests/stdlib/dynthr_test.c
index 7c22499c7567..5b0af718afc7 100644
--- a/lib/libc/tests/stdlib/dynthr_test.c
+++ b/lib/libc/tests/stdlib/dynthr_test.c
@@ -31,7 +31,6 @@
* may be omitted in redistributions.
*/
-#include <sys/cdefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
diff --git a/lib/libc/tests/stdlib/getenv_r_test.c b/lib/libc/tests/stdlib/getenv_r_test.c
new file mode 100644
index 000000000000..8085b92b1064
--- /dev/null
+++ b/lib/libc/tests/stdlib/getenv_r_test.c
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <atf-c.h>
+
+ATF_TC_WITHOUT_HEAD(getenv_r_ok);
+ATF_TC_BODY(getenv_r_ok, tc)
+{
+ const char *ident = atf_tc_get_ident(tc);
+ char buf[256];
+
+ ATF_REQUIRE_EQ(0, setenv("ATF_TC_IDENT", ident, 1));
+ ATF_REQUIRE_EQ(0, getenv_r("ATF_TC_IDENT", buf, sizeof(buf)));
+ ATF_REQUIRE_STREQ(ident, buf);
+}
+
+ATF_TC_WITHOUT_HEAD(getenv_r_einval);
+ATF_TC_BODY(getenv_r_einval, tc)
+{
+ char buf[256];
+
+ errno = 0;
+ ATF_REQUIRE_EQ(-1, getenv_r(NULL, buf, sizeof(buf)));
+ ATF_REQUIRE_EQ(EINVAL, errno);
+ errno = 0;
+ ATF_REQUIRE_EQ(-1, getenv_r("", buf, sizeof(buf)));
+ ATF_REQUIRE_EQ(EINVAL, errno);
+ errno = 0;
+ ATF_REQUIRE_EQ(-1, getenv_r("A=B", buf, sizeof(buf)));
+ ATF_REQUIRE_EQ(EINVAL, errno);
+}
+
+ATF_TC_WITHOUT_HEAD(getenv_r_enoent);
+ATF_TC_BODY(getenv_r_enoent, tc)
+{
+ char buf[256];
+
+ errno = 0;
+ ATF_REQUIRE_EQ(-1, getenv_r("no such variable", buf, sizeof(buf)));
+ ATF_REQUIRE_EQ(ENOENT, errno);
+}
+
+ATF_TC_WITHOUT_HEAD(getenv_r_erange);
+ATF_TC_BODY(getenv_r_erange, tc)
+{
+ const char *ident = atf_tc_get_ident(tc);
+ char buf[256];
+
+ ATF_REQUIRE_EQ(0, setenv("ATF_TC_IDENT", ident, 1));
+ errno = 0;
+ ATF_REQUIRE_EQ(-1, getenv_r("ATF_TC_IDENT", buf, strlen(ident)));
+ ATF_REQUIRE_EQ(ERANGE, errno);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, getenv_r_ok);
+ ATF_TP_ADD_TC(tp, getenv_r_einval);
+ ATF_TP_ADD_TC(tp, getenv_r_enoent);
+ ATF_TP_ADD_TC(tp, getenv_r_erange);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/stdlib/heapsort_test.c b/lib/libc/tests/stdlib/heapsort_test.c
index 875326354e0f..b6747ff5d3aa 100644
--- a/lib/libc/tests/stdlib/heapsort_test.c
+++ b/lib/libc/tests/stdlib/heapsort_test.c
@@ -28,7 +28,6 @@
* Test for heapsort() routine.
*/
-#include <sys/cdefs.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/lib/libc/tests/stdlib/libatexit/Makefile b/lib/libc/tests/stdlib/libatexit/Makefile
new file mode 100644
index 000000000000..9ba04c77af62
--- /dev/null
+++ b/lib/libc/tests/stdlib/libatexit/Makefile
@@ -0,0 +1,11 @@
+SHLIB_CXX= libatexit
+SHLIB_NAME= libatexit.so
+SHLIB_MAJOR= 1
+SHLIBDIR= ${TESTSDIR}
+PACKAGE= tests
+SRCS= libatexit.cc
+
+TESTSDIR:= ${TESTSBASE}/${RELDIR:C/libc\/tests/libc/:H}
+
+
+.include <bsd.lib.mk>
diff --git a/lib/libc/tests/stdlib/libatexit/libatexit.cc b/lib/libc/tests/stdlib/libatexit/libatexit.cc
new file mode 100644
index 000000000000..bb286c97e421
--- /dev/null
+++ b/lib/libc/tests/stdlib/libatexit/libatexit.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2025 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ */
+
+#include <unistd.h>
+
+static int exit_code = -1;
+static bool fatal_atexit;
+
+extern "C" {
+ void set_fatal_atexit(bool);
+ void set_exit_code(int);
+}
+
+void
+set_fatal_atexit(bool fexit)
+{
+ fatal_atexit = fexit;
+}
+
+void
+set_exit_code(int code)
+{
+ exit_code = code;
+}
+
+struct other_object {
+ ~other_object() {
+
+ /*
+ * In previous versions of our __cxa_atexit handling, we would
+ * never actually execute this handler because it's added during
+ * ~object() below; __cxa_finalize would never revisit it. We
+ * will allow the caller to configure us to exit with a certain
+ * exit code so that it can run us twice: once to ensure we
+ * don't crash at the end, and again to make sure the handler
+ * actually ran.
+ */
+ if (exit_code != -1)
+ _exit(exit_code);
+ }
+};
+
+void
+create_staticobj()
+{
+ static other_object obj;
+}
+
+struct object {
+ ~object() {
+ /*
+ * If we're doing the fatal_atexit behavior (i.e., create an
+ * object that will add its own dtor for __cxa_finalize), then
+ * we don't exit here.
+ */
+ if (fatal_atexit)
+ create_staticobj();
+ else if (exit_code != -1)
+ _exit(exit_code);
+ }
+};
+
+static object obj;
diff --git a/lib/libc/tests/stdlib/libc_exit_test.c b/lib/libc/tests/stdlib/libc_exit_test.c
new file mode 100644
index 000000000000..12965261bdb3
--- /dev/null
+++ b/lib/libc/tests/stdlib/libc_exit_test.c
@@ -0,0 +1,154 @@
+/*-
+ * Copyright (c) 2023 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/wait.h>
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+static void
+func_a(void)
+{
+ if (write(STDOUT_FILENO, "a", 1) != 1)
+ _Exit(1);
+}
+
+static void
+func_b(void)
+{
+ if (write(STDOUT_FILENO, "b", 1) != 1)
+ _Exit(1);
+}
+
+static void
+func_c(void)
+{
+ if (write(STDOUT_FILENO, "c", 1) != 1)
+ _Exit(1);
+}
+
+static void
+child(void)
+{
+ /* this will be received by the parent */
+ printf("hello, ");
+ fflush(stdout);
+ /* this won't, because quick_exit() does not flush */
+ printf("world");
+ /* these will be called in reverse order, producing "abc" */
+ if (at_quick_exit(func_c) != 0 ||
+ at_quick_exit(func_b) != 0 ||
+ at_quick_exit(func_a) != 0)
+ _Exit(1);
+ quick_exit(0);
+}
+
+ATF_TC_WITHOUT_HEAD(quick_exit);
+ATF_TC_BODY(quick_exit, tc)
+{
+ char buf[100] = "";
+ ssize_t len;
+ int p[2], wstatus = 0;
+ pid_t pid;
+
+ ATF_REQUIRE(pipe(p) == 0);
+ pid = fork();
+ if (pid == 0) {
+ if (dup2(p[1], STDOUT_FILENO) < 0)
+ _Exit(1);
+ (void)close(p[1]);
+ (void)close(p[0]);
+ child();
+ _Exit(1);
+ }
+ ATF_REQUIRE_MSG(pid > 0,
+ "expect fork() to succeed");
+ ATF_CHECK_EQ_MSG(pid, waitpid(pid, &wstatus, 0),
+ "expect to collect child process");
+ ATF_CHECK_EQ_MSG(0, wstatus,
+ "expect child to exit cleanly");
+ ATF_CHECK_MSG((len = read(p[0], buf, sizeof(buf))) > 0,
+ "expect to receive output from child");
+ ATF_CHECK_STREQ("hello, abc", buf);
+}
+
+static void
+myatexit1(void)
+{
+ exit(12);
+}
+
+ATF_TC_WITHOUT_HEAD(recursive_exit1);
+ATF_TC_BODY(recursive_exit1, tc)
+{
+ pid_t pid;
+ int wstatus;
+
+ pid = fork();
+ if (pid == 0) {
+ atexit(myatexit1);
+ exit(1);
+ }
+ ATF_REQUIRE_MSG(pid > 0,
+ "expect fork() to succeed");
+ ATF_CHECK_EQ_MSG(pid, waitpid(pid, &wstatus, 0),
+ "expect to collect child process");
+ ATF_CHECK(WIFEXITED(wstatus));
+ ATF_CHECK_EQ(WEXITSTATUS(wstatus), 12);
+}
+
+static pthread_barrier_t barrier;
+
+static void
+myatexit2(void)
+{
+ pthread_barrier_wait(&barrier);
+ exit(12);
+}
+
+static void *
+mythreadexit(void *arg)
+{
+ pthread_barrier_wait(&barrier);
+ exit(15);
+}
+
+ATF_TC_WITHOUT_HEAD(recursive_exit2);
+ATF_TC_BODY(recursive_exit2, tc)
+{
+ pid_t pid;
+ int wstatus;
+
+ pid = fork();
+ if (pid == 0) {
+ pthread_t thr;
+
+ atexit(myatexit2);
+
+ pthread_barrier_init(&barrier, NULL, 2);
+ pthread_create(&thr, NULL, mythreadexit, NULL);
+
+ exit(1);
+ }
+ ATF_REQUIRE_MSG(pid > 0,
+ "expect fork() to succeed");
+ ATF_CHECK_EQ_MSG(pid, waitpid(pid, &wstatus, 0),
+ "expect to collect child process");
+ ATF_CHECK(WIFEXITED(wstatus));
+ ATF_CHECK_EQ(WEXITSTATUS(wstatus), 12);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, quick_exit);
+ ATF_TP_ADD_TC(tp, recursive_exit1);
+ ATF_TP_ADD_TC(tp, recursive_exit2);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/stdlib/mergesort_test.c b/lib/libc/tests/stdlib/mergesort_test.c
index e57def84e3ab..156d6bb7c2c3 100644
--- a/lib/libc/tests/stdlib/mergesort_test.c
+++ b/lib/libc/tests/stdlib/mergesort_test.c
@@ -28,7 +28,6 @@
* Test for mergesort() routine.
*/
-#include <sys/cdefs.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/lib/libc/tests/stdlib/qsort_bench.c b/lib/libc/tests/stdlib/qsort_bench.c
new file mode 100644
index 000000000000..5f2cfae40140
--- /dev/null
+++ b/lib/libc/tests/stdlib/qsort_bench.c
@@ -0,0 +1,113 @@
+/*-
+ * Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <atf-c.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+/*-
+ * Measures qsort(3) runtime with pathological input and verify that it
+ * stays close to N * log2(N).
+ *
+ * Thanks to Vivian Hussey for the proof of concept.
+ *
+ * The input we construct is similar to a sweep from 0 to N where each
+ * half, except for the first element, has been reversed; for instance,
+ * with N = 8, we get { 0, 3, 2, 1, 4, 8, 7, 6 }. This triggers a bug in
+ * the BSD qsort(3) where it will switch to insertion sort if the pivots
+ * are sorted.
+ *
+ * This article goes into more detail about the bug and its origin:
+ *
+ * https://www.raygard.net/2022/02/26/Re-engineering-a-qsort-part-3
+ *
+ * With this optimization (the `if (swap_cnt == 0)` block), qsort(3) needs
+ * roughly N * N / 4 comparisons to sort our pathological input. Without
+ * it, it needs only a little more than N * log2(N) comparisons.
+ */
+
+/* we stop testing once a single takes longer than this */
+#define MAXRUNSECS 10
+
+static bool debugging;
+
+static uintmax_t ncmp;
+
+static int
+intcmp(const void *a, const void *b)
+{
+ ncmp++;
+ return ((*(int *)a > *(int *)b) - (*(int *)a < *(int *)b));
+}
+
+static void
+qsort_bench(int log2n)
+{
+ uintmax_t n = 1LLU << log2n;
+ int *buf;
+
+ /* fill an array with a pathological pattern */
+ ATF_REQUIRE(buf = malloc(n * sizeof(*buf)));
+ buf[0] = 0;
+ buf[n / 2] = n / 2;
+ for (unsigned int i = 1; i < n / 2; i++) {
+ buf[i] = n / 2 - i;
+ buf[n / 2 + i] = n - i;
+ }
+
+ ncmp = 0;
+ qsort(buf, n, sizeof(*buf), intcmp);
+
+ /* check result and free array */
+ if (debugging) {
+ for (unsigned int i = 1; i < n; i++) {
+ ATF_REQUIRE_MSG(buf[i] > buf[i - 1],
+ "array is not sorted");
+ }
+ }
+ free(buf);
+
+ /* check that runtime does not exceed N² */
+ ATF_CHECK_MSG(ncmp / n < n,
+ "runtime %ju exceeds N² for N = %ju", ncmp, n);
+
+ /* check that runtime does not exceed N log N by much */
+ ATF_CHECK_MSG(ncmp / n <= log2n + 1,
+ "runtime %ju exceeds N log N for N = %ju", ncmp, n);
+}
+
+ATF_TC_WITHOUT_HEAD(qsort_bench);
+ATF_TC_BODY(qsort_bench, tc)
+{
+ struct timespec t0, t1;
+ uintmax_t tus;
+
+ for (int i = 10; i <= 30; i++) {
+ clock_gettime(CLOCK_UPTIME, &t0);
+ qsort_bench(i);
+ clock_gettime(CLOCK_UPTIME, &t1);
+ tus = t1.tv_sec * 1000000 + t1.tv_nsec / 1000;
+ tus -= t0.tv_sec * 1000000 + t0.tv_nsec / 1000;
+ if (debugging) {
+ fprintf(stderr, "N = 2^%d in %ju.%06jus\n",
+ i, tus / 1000000, tus % 1000000);
+ }
+ /* stop once an individual run exceeds our limit */
+ if (tus / 1000000 >= MAXRUNSECS)
+ break;
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ debugging = !getenv("__RUNNING_INSIDE_ATF_RUN") &&
+ isatty(STDERR_FILENO);
+ ATF_TP_ADD_TC(tp, qsort_bench);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/stdlib/qsort_r_compat_test.c b/lib/libc/tests/stdlib/qsort_r_compat_test.c
index d2734c8678de..d7b06615292e 100644
--- a/lib/libc/tests/stdlib/qsort_r_compat_test.c
+++ b/lib/libc/tests/stdlib/qsort_r_compat_test.c
@@ -29,7 +29,6 @@
* Test for historical qsort_r(3) routine.
*/
-#include <sys/cdefs.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/lib/libc/tests/stdlib/qsort_r_test.c b/lib/libc/tests/stdlib/qsort_r_test.c
index 962a3792beb4..446d63279fbf 100644
--- a/lib/libc/tests/stdlib/qsort_r_test.c
+++ b/lib/libc/tests/stdlib/qsort_r_test.c
@@ -29,7 +29,6 @@
* Test for qsort_r(3) routine.
*/
-#include <sys/cdefs.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/lib/libc/tests/stdlib/qsort_s_test.c b/lib/libc/tests/stdlib/qsort_s_test.c
index 9682d447edb7..c3210caf24e1 100644
--- a/lib/libc/tests/stdlib/qsort_s_test.c
+++ b/lib/libc/tests/stdlib/qsort_s_test.c
@@ -30,7 +30,6 @@
* Test for qsort_s(3) routine.
*/
-#include <sys/cdefs.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/lib/libc/tests/stdlib/qsort_test.c b/lib/libc/tests/stdlib/qsort_test.c
index 1a0587a94ade..a6202e55fd62 100644
--- a/lib/libc/tests/stdlib/qsort_test.c
+++ b/lib/libc/tests/stdlib/qsort_test.c
@@ -28,7 +28,6 @@
* Test for qsort() routine.
*/
-#include <sys/cdefs.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/lib/libc/tests/stdlib/set_constraint_handler_s_test.c b/lib/libc/tests/stdlib/set_constraint_handler_s_test.c
index b718b4fc11fd..87db1f71ed4d 100644
--- a/lib/libc/tests/stdlib/set_constraint_handler_s_test.c
+++ b/lib/libc/tests/stdlib/set_constraint_handler_s_test.c
@@ -23,7 +23,6 @@
* SUCH DAMAGE.
*/
-#include <sys/cdefs.h>
#include <assert.h>
#include <stdlib.h>
diff --git a/lib/libc/tests/stdlib/strfmon_test.c b/lib/libc/tests/stdlib/strfmon_test.c
index f092c071724a..86f6256dba0b 100644
--- a/lib/libc/tests/stdlib/strfmon_test.c
+++ b/lib/libc/tests/stdlib/strfmon_test.c
@@ -24,7 +24,6 @@
* SUCH DAMAGE.
*/
-#include <sys/cdefs.h>
#include <sys/param.h>
#include <locale.h>
diff --git a/lib/libc/tests/stdlib/tsearch_test.c b/lib/libc/tests/stdlib/tsearch_test.c
index 6dc53b3f6f81..ee9deec588cb 100644
--- a/lib/libc/tests/stdlib/tsearch_test.c
+++ b/lib/libc/tests/stdlib/tsearch_test.c
@@ -23,7 +23,6 @@
* SUCH DAMAGE.
*/
-#include <sys/cdefs.h>
#include <atf-c.h>
#define _SEARCH_PRIVATE
#include <search.h>