From f45a02c9382a7f8581cae51a650433eb12a5ea72 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Sat, 15 Aug 2026 16:20:30 +0800 Subject: [PATCH] testing/libc/arch_libc: Test memccpy and stpncpy. Neither is covered here, and both are overridable, so a machine or libc implementation of either goes in unmeasured and unchecked. memccpy is checked with the search character present, where the copy stops just past it and the result points there, and absent, where the whole length is copied and the result is NULL. stpncpy is checked against every capacity from zero to four past the length, for the content, the zero padding beyond the terminator, and the returned pointer, which is the terminator when the string fits and one past the end when it does not. Both sweep all sixty four source and destination alignment pairs, and both are added to the benchmark, which now covers nineteen functions. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- testing/libc/arch_libc/Kconfig | 8 + testing/libc/arch_libc/arch_libc_bench.c | 10 + testing/libc/arch_libc/arch_libc_test_main.c | 189 +++++++++++++++++++ 3 files changed, 207 insertions(+) diff --git a/testing/libc/arch_libc/Kconfig b/testing/libc/arch_libc/Kconfig index fec4ba9c1..b90019a7f 100644 --- a/testing/libc/arch_libc/Kconfig +++ b/testing/libc/arch_libc/Kconfig @@ -43,6 +43,14 @@ config TESTING_ARCH_LIBC_STRCPY bool "test strcpy" default y +config TESTING_ARCH_LIBC_MEMCCPY + bool "test memccpy" + default y + +config TESTING_ARCH_LIBC_STPNCPY + bool "test stpncpy" + default y + config TESTING_ARCH_LIBC_STRLCPY bool "test strlcpy" default y diff --git a/testing/libc/arch_libc/arch_libc_bench.c b/testing/libc/arch_libc/arch_libc_bench.c index d73fd48f9..af5bb4dd9 100644 --- a/testing/libc/arch_libc/arch_libc_bench.c +++ b/testing/libc/arch_libc/arch_libc_bench.c @@ -92,6 +92,8 @@ #define OP_STPCPY 14 #define OP_STRNCPY 15 #define OP_STRCAT 16 +#define OP_MEMCCPY 17 +#define OP_STPNCPY 18 /**************************************************************************** * Private Types @@ -134,6 +136,8 @@ static const struct bench_op_s g_ops[] = {"stpcpy", OP_STPCPY, true, true}, {"strncpy", OP_STRNCPY, true, true}, {"strcat", OP_STRCAT, true, true}, + {"memccpy", OP_MEMCCPY, true, false}, + {"stpncpy", OP_STPNCPY, true, true}, }; static const size_t g_sizes[] = @@ -324,6 +328,12 @@ static void bench_one(FAR const struct bench_op_s *o, size_t n, case OP_STRNCPY: strncpy(d, s, n); break; + case OP_MEMCCPY: + g_sink += (uintptr_t)memccpy(d, s, '~', n); + break; + case OP_STPNCPY: + g_sink += (uintptr_t)stpncpy(d, s, n); + break; case OP_STRCAT: /* Appending to what the last turn appended would grow the diff --git a/testing/libc/arch_libc/arch_libc_test_main.c b/testing/libc/arch_libc/arch_libc_test_main.c index 0f14f967c..1382bd62e 100644 --- a/testing/libc/arch_libc/arch_libc_test_main.c +++ b/testing/libc/arch_libc/arch_libc_test_main.c @@ -792,6 +792,183 @@ static void speed_strlcpy(void) #endif #endif +/**************************************************************************** + * Name: test_memccpy + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMCCPY +static int test_memccpy(void) +{ + int size; + int fail = 0; + int ai; + int i; + + printf("Testing memccpy...\n"); + + for (ai = 0; ai < 64; ai++) + { + int da = ai / 8; + int sa = ai % 8; + + for (size = 1; size <= 64; size++) + { + FAR char *p; + int at = size / 2; + + fill_pattern(g_buf1 + sa, size); + g_buf1[sa + at] = '#'; + memset(g_buf2, 0x5a, sizeof(g_buf2)); + + /* The character is present, so the copy stops just past it */ + + p = memccpy(g_buf2 + da, g_buf1 + sa, '#', size); + if (p != g_buf2 + da + at + 1 || + memcmp(g_buf2 + da, g_buf1 + sa, at + 1) != 0 || + (unsigned char)g_buf2[da + at + 1] != 0x5a) + { + printf(" FAIL found: sa=%d da=%d size=%d\n", sa, da, size); + fail++; + } + + /* The character is absent, so the whole length is copied */ + + for (i = 0; i < size; i++) + { + g_buf1[sa + i] = 'A' + (i % 26); + } + + memset(g_buf2, 0x5a, sizeof(g_buf2)); + p = memccpy(g_buf2 + da, g_buf1 + sa, '#', size); + if (p != NULL || memcmp(g_buf2 + da, g_buf1 + sa, size) != 0 || + (unsigned char)g_buf2[da + size] != 0x5a) + { + printf(" FAIL absent: sa=%d da=%d size=%d\n", sa, da, size); + fail++; + } + } + } + + printf("memccpy: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +#ifdef ARCH_LIBC_HAVE_PERF +static void speed_memccpy(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)memccpy(g_buf2, g_buf1, '#', 128); + } + + end = perf_gettime(); + printf("memccpy(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif +#endif + +/**************************************************************************** + * Name: test_stpncpy + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_STPNCPY +static int test_stpncpy(void) +{ + int size; + int fail = 0; + int ai; + int cap; + int i; + + printf("Testing stpncpy...\n"); + + for (ai = 0; ai < 64; ai++) + { + int da = ai / 8; + int sa = ai % 8; + + for (size = 1; size <= 48; size++) + { + fill_pattern(g_buf1 + sa, size); + g_buf1[sa + size] = '\0'; + + for (cap = 0; cap <= size + 4; cap++) + { + FAR char *p; + FAR char *want; + + memset(g_buf2, 0x5a, sizeof(g_buf2)); + p = stpncpy(g_buf2 + da, g_buf1 + sa, cap); + + /* Up to the terminator is copied, the rest is padded, and + * the result points at the terminator or one past the end. + */ + + want = size < cap ? g_buf2 + da + size : g_buf2 + da + cap; + if (p != want) + { + printf(" FAIL ret: sa=%d da=%d size=%d cap=%d\n", + sa, da, size, cap); + fail++; + continue; + } + + for (i = 0; i < cap; i++) + { + char expect = i < size ? g_buf1[sa + i] : '\0'; + + if (g_buf2[da + i] != expect) + { + printf(" FAIL content: sa=%d da=%d size=%d cap=%d\n", + sa, da, size, cap); + fail++; + break; + } + } + + if ((unsigned char)g_buf2[da + cap] != 0x5a) + { + printf(" FAIL overrun: sa=%d da=%d size=%d cap=%d\n", + sa, da, size, cap); + fail++; + } + } + } + } + + printf("stpncpy: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +#ifdef ARCH_LIBC_HAVE_PERF +static void speed_stpncpy(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)stpncpy(g_buf2, g_buf1, 128); + } + + end = perf_gettime(); + printf("stpncpy(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif +#endif + /**************************************************************************** * Name: test_strchr ****************************************************************************/ @@ -1545,6 +1722,18 @@ int main(int argc, FAR char *argv[]) fail += test_strcpy(); speed_strcpy(); #endif +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMCCPY + fail += test_memccpy(); +# ifdef ARCH_LIBC_HAVE_PERF + speed_memccpy(); +# endif +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STPNCPY + fail += test_stpncpy(); +# ifdef ARCH_LIBC_HAVE_PERF + speed_stpncpy(); +# endif +#endif #ifdef CONFIG_TESTING_ARCH_LIBC_STRLCPY fail += test_strlcpy(); # ifdef ARCH_LIBC_HAVE_PERF