libc: Implement bsearch_s(), document bsearch_b(), and add unit tests

- Implement bsearch_s() as per §K.3.6.3.2 in C23, first specified
  in C11.  It behaves identically to bsearch(), except the callback
  is called with a third argument, context, which is passed through
  from the caller, and it also performs runtime constraint checking
  on its arguments.
- Document bsearch_b(), bsearch_s(), and add history section
- Add rudimentary unit tests for bsearch(), bsearch_b(), and bsearch_s()

Reviewed by:	dteske, fuz
Approved by:    dteske (mentor), fuz (mentor)
MFC after:	1 month
Differential Revision:	https://reviews.freebsd.org/D58876
This commit is contained in:
Faraz Vahedi committed 2026-08-22 18:31:27 +03:30
1 parent b5c1ab6c8f
commit d59c7ea270
11 files changed
+503 -6

No files matched your search

+10
View File
@@ -411,6 +411,9 @@ _Noreturn void abort_handler_s(const char * __restrict, void * __restrict,
/* K3.6.1.3 */
void ignore_handler_s(const char * __restrict, void * __restrict, errno_t);
/* K.3.6.3.2 */
void *bsearch_s(const void *, const void *, rsize_t, rsize_t,
int (*)(const void *, const void *, void *), void *);
/* K.3.6.3.3 */
errno_t qsort_s(void *, rsize_t, rsize_t,
int (*)(const void *, const void *, void *), void *);
#endif /* __EXT1_VISIBLE */
@@ -428,6 +431,13 @@ __NULLABILITY_PRAGMA_POP
(const void *)(bsearch_b)((key), (base), (nmemb), (size), (compar)), \
(bsearch_b)((key), (base), (nmemb), (size), (compar)))
#endif
#if __EXT1_VISIBLE
#define bsearch_s(key, base, nmemb, size, compar, context) \
__qualsel((base), \
(const void *)(bsearch_s)((key), (base), (nmemb), (size), \
(compar), (context)), \
(bsearch_s)((key), (base), (nmemb), (size), (compar), (context)))
#endif
#endif
#endif /* !_STDLIB_H_ */
+3
View File
@@ -13,6 +13,7 @@ MISRCS+= \
atoll.c \
bsearch.c \
bsearch_b.c \
bsearch_s.c \
cxa_thread_atexit.c \
cxa_thread_atexit_impl.c \
div.c \
@@ -109,6 +110,8 @@ MLINKS+=abs.3 labs.3 \
abs.3 llabs.3 \
abs.3 imaxabs.3
MLINKS+=atol.3 atoll.3
MLINKS+=bsearch.3 bsearch_b.3 \
bsearch.3 bsearch_s.3
MLINKS+=div.3 ldiv.3 \
div.3 lldiv.3 \
div.3 imaxdiv.3
+1
View File
@@ -132,6 +132,7 @@ FBSD_1.8 {
};
FBSD_1.9 {
bsearch_s;
memalignment;
recallocarray;
strfromd;
+103 -5
View File
@@ -29,11 +29,13 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd June 21, 2026
.Dd August 15, 2026
.Dt BSEARCH 3
.Os
.Sh NAME
.Nm bsearch
.Nm bsearch ,
.Nm bsearch_b ,
.Nm bsearch_s
.Nd binary search of a sorted table
.Sh LIBRARY
.Lb libc
@@ -41,6 +43,11 @@
.In stdlib.h
.Ft "QVoid *"
.Fn bsearch "const void *key" "QVoid *base" "size_t nmemb" "size_t size" "int (*compar) (const void *, const void *)"
.Ft "QVoid *"
.Fn bsearch_b "const void *key" "QVoid *base" "size_t nmemb" "size_t size" "int (^compar) (const void *, const void *)"
.Fd #define __STDC_WANT_LIB_EXT1__ 1
.Ft "QVoid *"
.Fn bsearch_s "const void *key" "QVoid *base" "rsize_t nmemb" "rsize_t size" "int (*compar) (const void *, const void *, void *)" "void *context"
.Sh DESCRIPTION
The
.Fn bsearch
@@ -74,11 +81,68 @@ sample function in
.Xr qsort 3
for a comparison function that is also compatible with
.Fn bsearch .
.Pp
The
.Fn bsearch_b
function behaves identically to
.Fn bsearch ,
except the callback
.Fa compar
takes a block pointer instead of a function pointer.
.Pp
The
.Fn bsearch_s
function behaves identically to
.Fn bsearch ,
except the callback
.Fa compar
is called with a third argument,
.Fa context ,
which is passed through from the caller.
Runtime-constraint violation occurs if:
.Bl -bullet
.It
.Fa nmemb
or
.Fa size
is greater than
.Dv RSIZE_MAX
.It
.Fa nmemb
is not zero and any of
.Fa key ,
.Fa base ,
or
.Fa compar
is a null pointer
.El
.Pp
On a runtime-constraint violation, the runtime-constraint handler is
invoked,
.Fn bsearch_s
does not search the array, and a null pointer is returned.
Note that the handler is called before
.Fn bsearch_s
returns, and the handler function might not return.
If
.Fa nmemb
is zero, the comparison function is not called, no match is found,
and
.Fa key ,
.Fa base ,
and
.Fa compar
may be null pointers.
.Sh RETURN VALUES
The
.Fn bsearch
function returns a pointer to a matching member of the array, or a null
.Fn bsearch ,
.Fn bsearch_b ,
and
.Fn bsearch_s
functions return a pointer to a matching member of the array, or a null
pointer if no match is found.
.Fn bsearch_s
also returns a null pointer if there is a runtime-constraint violation.
If two members compare as equal, which member is matched is unspecified.
.Sh EXAMPLES
A sample program that searches people by age in a sorted array:
@@ -147,7 +211,8 @@ main(void)
.Sh SEE ALSO
.Xr db 3 ,
.Xr lsearch 3 ,
.Xr qsort 3
.Xr qsort 3 ,
.Xr set_constraint_handler_s 3
.\" .Xr tsearch 3
.Sh STANDARDS
The
@@ -155,3 +220,36 @@ The
function conforms to
.St -isoC-2023 ,
where it is specified as a qualifier-preserving function.
.Pp
The
.Fn bsearch_s
function conforms to
.St -isoC-2023 ,
section K.3.6.3.2.
Like
.Fn bsearch ,
it is specified as a qualifier-preserving function.
.Sh HISTORY
The
.Fn bsearch ,
initially specified in the
.St -svid1 ,
first appeared in
.At V.2
and later in
.Bx 4.3 .
It was first standardized in
.St -isoC .
.Pp
The
.Fn bsearch_b
function first appeared in Mac OS X and was later added to
.Fx 11.0
as an extension.
.Pp
The
.Fn bsearch_s
function first appeared in
.Fx 16.0 .
It was first standardized in
.St -isoC-2011 .
+38
View File
@@ -32,10 +32,18 @@
#include <stddef.h>
#include <stdlib.h>
#ifdef I_AM_BSEARCH_S
#include <errno.h>
#include <stdint.h>
#include "libc_private.h"
#endif
#ifdef I_AM_BSEARCH_B
#include "block_abi.h"
#define COMPAR(x,y) CALL_BLOCK(compar, x, y)
typedef DECLARE_BLOCK(int, compar_block, const void *, const void *);
#elif defined(I_AM_BSEARCH_S)
#define COMPAR(x,y) compar((x), (y), context)
#else
#define COMPAR(x,y) compar(x, y)
#endif
@@ -60,6 +68,10 @@ typedef DECLARE_BLOCK(int, compar_block, const void *, const void *);
void *
bsearch_b(const void *key, const void *base0, size_t nmemb, size_t size,
compar_block compar)
#elif defined(I_AM_BSEARCH_S)
void *
bsearch_s(const void *key, const void *base0, rsize_t nmemb, rsize_t size,
int (*compar)(const void *, const void *, void *), void *context)
#else
void *
bsearch(const void *key, const void *base0, size_t nmemb, size_t size,
@@ -71,6 +83,32 @@ bsearch(const void *key, const void *base0, size_t nmemb, size_t size,
int cmp;
const void *p;
#ifdef I_AM_BSEARCH_S
if (nmemb > RSIZE_MAX) {
__throw_constraint_handler_s("bsearch_s : nmemb > RSIZE_MAX",
EINVAL);
return (NULL);
} else if (size > RSIZE_MAX) {
__throw_constraint_handler_s("bsearch_s : size > RSIZE_MAX",
EINVAL);
return (NULL);
} else if (nmemb != 0) {
if (key == NULL) {
__throw_constraint_handler_s("bsearch_s : key == NULL",
EINVAL);
return (NULL);
} else if (base0 == NULL) {
__throw_constraint_handler_s("bsearch_s : base == NULL",
EINVAL);
return (NULL);
} else if (compar == NULL) {
__throw_constraint_handler_s("bsearch_s : compar == NULL",
EINVAL);
return (NULL);
}
}
#endif
for (lim = nmemb; lim != 0; lim >>= 1) {
p = base + (lim >> 1) * size;
cmp = COMPAR(key, p);
+7
View File
@@ -0,0 +1,7 @@
/*
* Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#define I_AM_BSEARCH_S
#include "bsearch.c"
+6 -1
View File
@@ -1,5 +1,10 @@
.include <src.opts.mk>
ATF_TESTS_C+= bsearch_test
.if ${COMPILER_FEATURES:Mblocks}
ATF_TESTS_C+= bsearch_b_test
.endif
ATF_TESTS_C+= bsearch_s_test
ATF_TESTS_C+= clearenv_test
ATF_TESTS_C+= cxa_atexit_test
ATF_TESTS_C+= dynthr_test
@@ -66,7 +71,7 @@ CFLAGS+= -I${.CURDIR}
LIBADD.cxa_thread_atexit_test+= pthread
# Tests that require blocks support
.for t in qsort_b_test
.for t in bsearch_b_test qsort_b_test
CFLAGS.${t}.c+= -fblocks
LIBADD.${t}+= BlocksRuntime
.endfor
+58
View File
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
/*
* Test for bsearch_b() routine.
*/
#include <stdlib.h>
#include "test-search.h"
#define THUNK 42
static void *
do_bsearch_b(const int *key, const int *base, size_t n, void *ctx)
{
int thunk = *(int *)ctx;
return (bsearch_b(key, base, n, sizeof(int),
^(const void *a, const void *b) {
ATF_REQUIRE_EQ(thunk, THUNK);
return (searchhelp(a, b));
}));
}
ATF_TC_WITHOUT_HEAD(bsearch_b_test);
ATF_TC_BODY(bsearch_b_test, tc)
{
int testvector[SVEC_LEN];
int thunk = THUNK;
int key, j;
for (j = 0; j <= SVEC_LEN; j++) {
if (j == 0) {
key = 0;
ATF_CHECK(bsearch_b(&key, testvector, 0,
sizeof(testvector[0]),
^(const void *a __unused, const void *b __unused) {
atf_tc_fail(
"comparison block invoked unexpectedly");
return (0);
}) == NULL);
continue;
}
check_sorted_search(do_bsearch_b, &thunk, testvector,
(size_t)j);
}
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, bsearch_b_test);
return (atf_no_error());
}
+134
View File
@@ -0,0 +1,134 @@
/*
* Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
/*
* Test for bsearch_s() routine.
*/
#include <stdint.h>
#include <stdlib.h>
#define THUNK 42
#include "test-search.h"
static errno_t error_code;
static int compar_calls;
static int
searchhelp_s(const void *a, const void *b, void *thunk)
{
compar_calls++;
if (thunk != NULL)
ATF_REQUIRE_EQ(*(int *)thunk, THUNK);
return (searchhelp(a, b));
}
static void
constraint_handler(const char * restrict msg __unused,
void * restrict ptr __unused, errno_t error)
{
error_code = error;
}
static void
expect_viol(const void *key, const void *base, rsize_t nmemb, rsize_t size,
int (*compar)(const void *, const void *, void *), void *thunk)
{
error_code = 0;
compar_calls = 0;
ATF_CHECK(bsearch_s(key, base, nmemb, size, compar, thunk) == NULL);
ATF_CHECK(error_code > 0);
ATF_CHECK_EQ(compar_calls, 0);
}
static void *
do_bsearch_s(const int *key, const int *base, size_t n, void *ctx)
{
return (bsearch_s(key, base, n, sizeof(int), searchhelp_s, ctx));
}
ATF_TC_WITHOUT_HEAD(bsearch_s_constraints);
ATF_TC_BODY(bsearch_s_constraints, tc)
{
int thunk = THUNK;
int key = 4;
int b[] = { 4, 7, 81 };
set_constraint_handler_s(constraint_handler);
expect_viol(&key, b, -1, sizeof(int), searchhelp_s, &thunk);
expect_viol(&key, b, RSIZE_MAX + 1, sizeof(int), searchhelp_s, &thunk);
expect_viol(&key, b, nitems(b), -1, searchhelp_s, &thunk);
expect_viol(&key, b, nitems(b), RSIZE_MAX + 1, searchhelp_s, &thunk);
expect_viol(NULL, b, nitems(b), sizeof(int), searchhelp_s, &thunk);
expect_viol(&key, NULL, 1, sizeof(int), searchhelp_s, &thunk);
expect_viol(&key, b, nitems(b), sizeof(int), NULL, &thunk);
/* size > RSIZE_MAX is a violation even when nmemb is zero. */
expect_viol(&key, b, 0, RSIZE_MAX + 1, searchhelp_s, &thunk);
}
ATF_TC_WITHOUT_HEAD(bsearch_s_nmemb_zero);
ATF_TC_BODY(bsearch_s_nmemb_zero, tc)
{
int thunk = THUNK;
int key = 4;
int b[] = { 4, 7, 81 };
error_code = 0;
compar_calls = 0;
set_constraint_handler_s(constraint_handler);
ATF_CHECK(bsearch_s(&key, b, 0, sizeof(int), searchhelp_s,
&thunk) == NULL);
ATF_CHECK(error_code == 0);
ATF_CHECK_EQ(compar_calls, 0);
ATF_CHECK(bsearch_s(NULL, NULL, 0, 0, NULL, NULL) == NULL);
ATF_CHECK(error_code == 0);
}
ATF_TC_WITHOUT_HEAD(bsearch_s_h);
ATF_TC_BODY(bsearch_s_h, tc)
{
int thunk = THUNK;
int b[] = { 4, 7, 81 };
int key = 7;
int *found;
error_code = 0;
compar_calls = 0;
set_constraint_handler_s(constraint_handler);
found = bsearch_s(&key, b, nitems(b), sizeof(int), searchhelp_s,
&thunk);
ATF_CHECK(error_code == 0);
ATF_CHECK(compar_calls > 0);
ATF_CHECK(found == &b[1]);
compar_calls = 0;
found = bsearch_s(&key, b, nitems(b), sizeof(int), searchhelp_s, NULL);
ATF_CHECK(found == &b[1]);
ATF_CHECK(compar_calls > 0);
}
ATF_TC_WITHOUT_HEAD(bsearch_s_test);
ATF_TC_BODY(bsearch_s_test, tc)
{
int testvector[SVEC_LEN];
int thunk = THUNK;
int j;
for (j = 1; j <= SVEC_LEN; j++)
check_sorted_search(do_bsearch_s, &thunk, testvector,
(size_t)j);
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, bsearch_s_constraints);
ATF_TP_ADD_TC(tp, bsearch_s_nmemb_zero);
ATF_TP_ADD_TC(tp, bsearch_s_h);
ATF_TP_ADD_TC(tp, bsearch_s_test);
return (atf_no_error());
}
+92
View File
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
/*
* Test for bsearch() routine.
*/
#include <stdlib.h>
#include "test-search.h"
static const void *expected_key;
static const void *expected_base;
static size_t expected_nmemb;
static int
searchhelp_check(const void *a, const void *b)
{
const char *p, *base;
ATF_CHECK(a == expected_key);
p = b;
base = expected_base;
ATF_CHECK(((size_t)(p - base) % sizeof(int)) == 0);
ATF_CHECK(p >= base);
ATF_CHECK(p < base + expected_nmemb * sizeof(int));
return (searchhelp(a, b));
}
static int
searchhelp_never(const void *a __unused, const void *b __unused)
{
atf_tc_fail("comparison function invoked unexpectedly");
return (0);
}
static void *
do_bsearch(const int *key, const int *base, size_t n, void *ctx __unused)
{
expected_key = key;
return (bsearch(key, base, n, sizeof(int), searchhelp_check));
}
ATF_TC_WITHOUT_HEAD(bsearch_test);
ATF_TC_BODY(bsearch_test, tc)
{
int testvector[SVEC_LEN];
int key, j;
for (j = 0; j <= SVEC_LEN; j++) {
if (j == 0) {
key = 0;
ATF_CHECK(bsearch(&key, testvector, 0,
sizeof(testvector[0]), searchhelp_never) == NULL);
continue;
}
expected_base = testvector;
expected_nmemb = (size_t)j;
check_sorted_search(do_bsearch, NULL, testvector, (size_t)j);
}
}
ATF_TC_WITHOUT_HEAD(bsearch_duplicates);
ATF_TC_BODY(bsearch_duplicates, tc)
{
int d[] = { 1, 2, 2, 2, 3 };
int e[] = { 7, 7, 7 };
int key, *found;
key = 2;
found = bsearch(&key, d, nitems(d), sizeof(d[0]), searchhelp);
ATF_REQUIRE(found != NULL);
ATF_CHECK(found >= &d[1] && found <= &d[3]);
ATF_CHECK_EQ(*found, 2);
key = 7;
found = bsearch(&key, e, nitems(e), sizeof(e[0]), searchhelp);
ATF_REQUIRE(found != NULL);
ATF_CHECK(found >= &e[0] && found <= &e[2]);
ATF_CHECK_EQ(*found, 7);
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, bsearch_test);
ATF_TP_ADD_TC(tp, bsearch_duplicates);
return (atf_no_error());
}
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#ifndef _TEST_SEARCH_H
#define _TEST_SEARCH_H
#include <sys/param.h>
#include <stddef.h>
#include <atf-c.h>
#define SVEC_LEN 1024
typedef void *search_int_t(const int *, const int *, size_t, void *);
static int
searchhelp(const void *a, const void *b)
{
const int *oa = a, *ob = b;
return ((*oa > *ob) - (*oa < *ob));
}
/*
* Fill v[i] = i, then confirm every element is found and -1 and n are not.
*/
static void
check_sorted_search(search_int_t *search, void *ctx, int *v, size_t n)
{
size_t i;
int key;
for (i = 0; i < n; i++)
v[i] = (int)i;
for (i = 0; i < n; i++) {
key = v[i];
ATF_CHECK(search(&key, v, n, ctx) == &v[i]);
}
if (n != 0) {
key = -1;
ATF_CHECK(search(&key, v, n, ctx) == NULL);
key = (int)n;
ATF_CHECK(search(&key, v, n, ctx) == NULL);
}
}
#endif /* !_TEST_SEARCH_H */