git: d59c7ea2701f - main - libc: Implement bsearch_s(), document bsearch_b(), and add unit tests
Faraz Vahedi <[email protected]>
| Newsgroups | gmane.os.freebsd.devel.cvs.src |
|---|---|
| Message-ID | <6a89ba7a.41a1c.392c4c5e__21677.1081600868$1787411080$gmane$org@gitrepo.freebsd.org> |
The branch main has been updated by kfv: URL: https://cgit.FreeBSD.org/src/commit/?id=d59c7ea2701fe7b73b32eef49a7c712ef38de5a0 commit d59c7ea2701fe7b73b32eef49a7c712ef38de5a0 Author: Faraz Vahedi <[email protected]> AuthorDate: 2026-08-22 15:01:27 +0000 Commit: Faraz Vahedi <[email protected]> CommitDate: 2026-08-22 15:01:27 +0000 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 --- include/stdlib.h | 10 +++ lib/libc/stdlib/Makefile.inc | 3 + lib/libc/stdlib/Symbol.map | 1 + lib/libc/stdlib/bsearch.3 | 108 ++++++++++++++++++++++++-- lib/libc/stdlib/bsearch.c | 38 ++++++++++ lib/libc/stdlib/bsearch_s.c | 7 ++ lib/libc/tests/stdlib/Makefile | 7 +- lib/libc/tests/stdlib/bsearch_b_test.c | 58 ++++++++++++++ lib/libc/tests/stdlib/bsearch_s_test.c | 134 +++++++++++++++++++++++++++++++++ lib/libc/tests/stdlib/bsearch_test.c | 92 ++++++++++++++++++++++ lib/libc/tests/stdlib/test-search.h | 51 +++++++++++++ 11 files changed, 503 insertions(+), 6 deletions(-) diff --git a/include/stdlib.h b/include/stdlib.h index d61e2de99693..a384956eb435 100644 --- a/include/stdlib.h +++ b/include/stdlib.h @@ -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_ */ diff --git a/lib/libc/stdlib/Makefile.inc b/lib/libc/stdlib/Makefile.inc index 7ff6a6c46ea5..497f163acf96 100644 --- a/lib/libc/stdlib/Makefile.inc +++ b/lib/libc/stdlib/Makefile.inc @@ -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 diff --git a/lib/libc/stdlib/Symbol.map b/lib/libc/stdlib/Symbol.map index df4922f1df60..be063fea8707 100644 --- a/lib/libc/stdlib/Symbol.map +++ b/lib/libc/stdlib/Symbol.map @@ -132,6 +132,7 @@ FBSD_1.8 { }; FBSD_1.9 { + bsearch_s; memalignment; recallocarray; strfromd; diff --git a/lib/libc/stdlib/bsearch.3 b/lib/libc/stdlib/bsearch.3 index 4d1e7b1171b9..814b71028af0 100644 --- a/lib/libc/stdlib/bsearch.3 +++ b/lib/libc/stdlib/bsearch.3 @@ -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 . diff --git a/lib/libc/stdlib/bsearch.c b/lib/libc/stdlib/bsearch.c index 96c728e1c997..6bfd44d4df6d 100644 --- a/lib/libc/stdlib/bsearch.c +++ b/lib/libc/stdlib/bsearch.c @@ -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); diff --git a/lib/libc/stdlib/bsearch_s.c b/lib/libc/stdlib/bsearch_s.c new file mode 100644 index 000000000000..03722ffe6370 --- /dev/null +++ b/lib/libc/stdlib/bsearch_s.c @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2026 Faraz Vahedi <[email protected]> + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#define I_AM_BSEARCH_S +#include "bsearch.c" diff --git a/lib/libc/tests/stdlib/Makefile b/lib/libc/tests/stdlib/Makefile index 1a0206bcdab9..00836dd0259c 100644 --- a/lib/libc/tests/stdlib/Makefile +++ b/lib/libc/tests/stdlib/Makefile @@ -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 diff --git a/lib/libc/tests/stdlib/bsearch_b_test.c b/lib/libc/tests/stdlib/bsearch_b_test.c new file mode 100644 index 000000000000..4c04a723f639 --- /dev/null +++ b/lib/libc/tests/stdlib/bsearch_b_test.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2026 Faraz Vahedi <[email protected]> + * + * 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()); +} diff --git a/lib/libc/tests/stdlib/bsearch_s_test.c b/lib/libc/tests/stdlib/bsearch_s_test.c new file mode 100644 index 000000000000..b41343400ba3 --- /dev/null +++ b/lib/libc/tests/stdlib/bsearch_s_test.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2026 Faraz Vahedi <[email protected]> + * + * 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()); +} diff --git a/lib/libc/tests/stdlib/bsearch_test.c b/lib/libc/tests/stdlib/bsearch_test.c new file mode 100644 index 000000000000..6da8096b85a9 --- /dev/null +++ b/lib/libc/tests/stdlib/bsearch_test.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2026 Faraz Vahedi <[email protected]> + * + * 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()); +} diff --git a/lib/libc/tests/stdlib/test-search.h b/lib/libc/tests/stdlib/test-search.h new file mode 100644 index 000000000000..3d805585eaae --- /dev/null +++ b/lib/libc/tests/stdlib/test-search.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026 Faraz Vahedi <[email protected]> + * + * 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 */