feat(string): complete string.h + strdup/memccpy

This commit is contained in:
2026-09-03 20:05:49 -04:00
parent 20f514d91c
commit 06d2ec7c34
19 changed files with 1860 additions and 2 deletions
+226 -2
View File
@@ -7,8 +7,14 @@
* String and memory functions, gated by the active compatibility profile * String and memory functions, gated by the active compatibility profile
* (see include/vlibc/features.h). Levels are cumulative: * (see include/vlibc/features.h). Levels are cumulative:
* *
* Level 1 (onlyposix): ISO C core — memcpy, memmove, memset, strlen, strcmp. * Level 1 (onlyposix): ISO C core + POSIX.1-2008 base — memcpy, memmove,
* Level 2 (muslmimic): BSD extensions — strlcpy, strlcat. * memset, memchr, memcmp, strlen, strnlen, strcmp,
* strncmp, strcpy, strncpy, strcat, strncat, strchr,
* strrchr, strspn, strcspn, strpbrk, strstr, strtok,
* strtok_r, strcoll, strxfrm, strdup, strndup,
* strsignal.
* Level 2 (muslmimic): BSD + XSI extensions — strlcpy, strlcat, stpcpy,
* stpncpy, memccpy, strcoll_l, strxfrm_l.
* Level 3 (muslext): GNU extensions — strcasestr. * Level 3 (muslext): GNU extensions — strcasestr.
* *
* This header includes <vlibc/features.h> itself, so the gates below always * This header includes <vlibc/features.h> itself, so the gates below always
@@ -33,6 +39,15 @@ extern "C" {
__attribute__((pure)) size_t __attribute__((pure)) size_t
strlen(const char *s); strlen(const char *s);
/*
* Return the length of s, excluding the terminating NUL, examining at most
* maxlen bytes; the result is at most maxlen even when s is not a proper
* NUL-terminated string.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) size_t
strnlen(const char *s, size_t maxlen);
/* /*
* Compare the strings lhs and rhs; return negative, zero, or positive when * Compare the strings lhs and rhs; return negative, zero, or positive when
* lhs is less than, equal to, or greater than rhs. * lhs is less than, equal to, or greater than rhs.
@@ -41,6 +56,24 @@ strlen(const char *s);
__attribute__((pure)) int __attribute__((pure)) int
strcmp(const char *lhs, const char *rhs); strcmp(const char *lhs, const char *rhs);
/*
* Compare at most n bytes of lhs and rhs, stopping early at the first
* difference or the first NUL; return negative, zero, or positive when lhs
* is less than, equal to, or greater than rhs.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) int
strncmp(const char *lhs, const char *rhs, size_t n);
/*
* Compare s1 and s2 under the active locale's collating sequence; return
* negative, zero, or positive. Only the "C" locale exists so far (locales
* are owned by a later todo), where collation is identical to strcmp.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) int
strcoll(const char *s1, const char *s2);
/* /*
* Copy n bytes from src to dst. The regions must not overlap (restrict). * Copy n bytes from src to dst. The regions must not overlap (restrict).
* No intent attribute: it writes memory. * No intent attribute: it writes memory.
@@ -63,6 +96,157 @@ memmove(void *dst, const void *src, size_t n);
void * void *
memset(void *dst, int c, size_t n); memset(void *dst, int c, size_t n);
/*
* Return a pointer to the first occurrence of c (converted to unsigned
* char) among the first n bytes of s, or NULL when absent.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) void *
memchr(const void *s, int c, size_t n);
/*
* Compare the first n bytes of lhs and rhs as unsigned char; return
* negative, zero, or positive when lhs is less than, equal to, or greater
* than rhs. Unlike strcmp, a NUL byte does not end the comparison.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) int
memcmp(const void *lhs, const void *rhs, size_t n);
/*
* Copy src to dst, including the terminating NUL; return dst. The strings
* must not overlap (restrict).
* No intent attribute: it writes memory.
*/
char *
strcpy(char *restrict dst, const char *restrict src);
/*
* Copy at most n bytes from src to dst. When src is shorter than n the
* remainder of dst is NUL-padded; when src is n bytes or longer the result
* is not NUL-terminated. Return dst.
* No intent attribute: it writes memory.
*/
char *
strncpy(char *restrict dst, const char *restrict src, size_t n);
/*
* Append src (including its NUL) to the end of dst; return dst. The strings
* must not overlap (restrict).
* No intent attribute: it writes memory.
*/
char *
strcat(char *restrict dst, const char *restrict src);
/*
* Append at most n bytes of src to dst and always NUL-terminate; return
* dst.
* No intent attribute: it writes memory.
*/
char *
strncat(char *restrict dst, const char *restrict src, size_t n);
/*
* Return a pointer to the first occurrence of c (converted to char) in s,
* or NULL when absent. The terminating NUL is part of the string, so
* strchr(s, '\0') returns a pointer to it.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) char *
strchr(const char *s, int c);
/*
* Return a pointer to the last occurrence of c (converted to char) in s, or
* NULL when absent. The terminating NUL is part of the string, so
* strrchr(s, '\0') returns a pointer to it.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) char *
strrchr(const char *s, int c);
/*
* Return the length of the initial span of s consisting entirely of bytes
* that occur in accept.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) size_t
strspn(const char *s, const char *accept);
/*
* Return the length of the initial span of s consisting entirely of bytes
* that do NOT occur in reject.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) size_t
strcspn(const char *s, const char *reject);
/*
* Return a pointer to the first byte in s that also occurs in accept, or
* NULL when none occurs.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) char *
strpbrk(const char *s, const char *accept);
/*
* Return a pointer to the first occurrence of needle in haystack, or NULL
* when absent. An empty needle matches haystack itself.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) char *
strstr(const char *haystack, const char *needle);
/*
* Split s into tokens delimited by any byte from sep. On the first call s
* names the string; subsequent calls with NULL continue the same string.
* Leading and consecutive delimiters produce no empty tokens, and the
* delimiter bytes in s are overwritten with NUL. Returns NULL when no token
* remains. strtok keeps its state in private static storage and is not
* thread-safe; strtok_r keeps it in *state and is.
* No intent attribute: strtok mutates private state, strtok_r writes
* through its parameters.
*/
char *
strtok(char *restrict s, const char *restrict sep);
char *
strtok_r(char *restrict s, const char *restrict sep, char **restrict state);
/*
* Transform src under the active locale's collating sequence so that strcmp
* on transformed strings orders them as strcoll would, storing at most n
* bytes of the result in dst (always NUL-terminated when n > 0; nothing is
* written when n == 0). Return the length of the full transformed string,
* excluding the NUL. In the "C" locale the transformation is the identity
* and the return is strlen(src).
* No intent attribute: it writes memory.
*/
size_t
strxfrm(char *restrict dst, const char *restrict src, size_t n);
/*
* Return a heap copy of s (malloc-allocated; release with free), or NULL
* with errno ENOMEM on allocation failure. strndup copies at most n bytes
* and NUL-terminates, so strndup(s, 0) returns the empty string.
* malloc: returns fresh unaliased storage the caller owns.
*/
__attribute__((malloc)) char *
strdup(const char *s);
__attribute__((malloc)) char *
strndup(const char *s, size_t n);
/*
* Return a pointer to a static string describing the signal sig, or a
* formatted "Unknown signal <sig>" string for unrecognized numbers. Never
* returns NULL; distinct known signals yield distinct strings. The text for
* signal 0 is unspecified by POSIX and deliberately not pinned.
*
* No intent attribute: the unknown-signal path writes shared storage.
*/
char *
strsignal(int sig);
#if VLIBC_LEVEL >= 2 #if VLIBC_LEVEL >= 2
/* Level 2 (muslmimic): BSD extensions. */ /* Level 2 (muslmimic): BSD extensions. */
@@ -83,6 +267,46 @@ size_t
strlcat(char *dst, const char *src, size_t size); strlcat(char *dst, const char *src, size_t size);
#endif /* VLIBC_LEVEL >= 2 */ #endif /* VLIBC_LEVEL >= 2 */
#if VLIBC_LEVEL_GE(2)
/* Level 2 (muslmimic): XSI extensions. */
/*
* strcpy/strncpy variants returning a pointer to the terminating NUL of
* dst: stpcpy to the NUL of the copied string, stpncpy to the first NUL
* written (or to dst + n when no NUL was written).
* No intent attribute: they write memory.
*/
char *
stpcpy(char *restrict dst, const char *restrict src);
char *
stpncpy(char *restrict dst, const char *restrict src, size_t n);
/*
* Copy at most n bytes from src to dst, stopping after the first byte equal
* to c (converted to unsigned char); return a pointer to the byte after c
* in dst when found, NULL otherwise. The regions must not overlap
* (restrict).
* No intent attribute: it writes memory.
*/
void *
memccpy(void *restrict dst, const void *restrict src, int c, size_t n);
/*
* Locale-parameterized XSI variants of strcoll/strxfrm. No locale machinery
* exists yet, so the locale argument is accepted and ignored and behavior
* is the "C" locale behavior of the base functions. The locale parameter is
* typed void * for now: locale_t will be an ABI-identical pointer typedef
* defined by <locale.h>, whose todo updates these signatures to the real
* type.
*/
__attribute__((pure)) int
strcoll_l(const char *s1, const char *s2, void *locale);
size_t
strxfrm_l(char *restrict dst, const char *restrict src, size_t n, void *locale);
#endif /* VLIBC_LEVEL_GE(2) */
#if VLIBC_LEVEL >= 3 #if VLIBC_LEVEL >= 3
/* Level 3 (muslext): GNU extensions. */ /* Level 3 (muslext): GNU extensions. */
+30
View File
@@ -0,0 +1,30 @@
#ifndef VLIBC_INTERNAL_MALLOC_H
#define VLIBC_INTERNAL_MALLOC_H
/*
* vlibc — internal allocator seam (todo 8).
*
* The allocator todo (#7) owns the real implementation and provides these
* entry points; library code that must allocate (strdup, strndup, and later
* stdio/regex/...) calls them instead of the public malloc/free so that the
* allocator remains the single allocation implementation. The public
* malloc/free/calloc/realloc of todo 7 must be backed by the same allocator,
* so a block returned by __libc_malloc can be released with public free.
*
* - __libc_malloc(n): allocate n bytes, 16-byte aligned; NULL + errno ENOMEM
* on failure. May return a unique pointer even when n == 0.
* - __libc_free(p): release a block returned by __libc_malloc; NULL is a
* no-op.
*/
#include <stddef.h>
#include "libc.h"
hidden void *
__libc_malloc(size_t n); // NOLINT(bugprone-reserved-identifier)
hidden void
__libc_free(void *p); // NOLINT(bugprone-reserved-identifier)
#endif /* VLIBC_INTERNAL_MALLOC_H */
+40
View File
@@ -0,0 +1,40 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#if VLIBC_LEVEL_GE(2)
/*
* XSI memccpy (todo 8): copy at most n bytes from src to dst, stopping
* after the first byte equal to c (converted to unsigned char). Return a
* pointer to the byte after c in dst when found, NULL otherwise. The
* regions must not overlap (restrict).
*
* See strcpy.c: the copy loop is a recognizable pattern, so disable
* -ftree-loop-distribute-patterns here too.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) void *
memccpy(void *restrict dst, const void *restrict src, int c, size_t n) // NOLINT(bugprone-*)
{
unsigned char *d = dst;
const unsigned char *s = src;
const unsigned char uc = (unsigned char)c;
while (n != 0)
{
*d = *s;
if (*d == uc)
{
return d + 1;
}
d++;
s++;
n--;
}
return NULL;
}
#endif /* VLIBC_LEVEL_GE(2) */
+34
View File
@@ -0,0 +1,34 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Return a pointer to the first occurrence of c (converted to unsigned
* char) among the first n bytes of s, or NULL when absent. Bytes are
* examined as unsigned char so negative c values search for the matching
* 0x80..0xff byte.
*
* See strcpy.c: the scan loop is a pattern GCC's
* -ftree-loop-distribute-patterns can recognize (memchr), so disable that
* transformation to avoid self-recursion.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) void *
memchr(const void *s, int c, size_t n) // NOLINT(bugprone-easily-swappable-parameters)
{
const unsigned char *p = s;
const unsigned char uc = (unsigned char)c;
while (n != 0)
{
if (*p == uc)
{
return (void *)p;
}
p++;
n--;
}
return NULL;
}
+34
View File
@@ -0,0 +1,34 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Compare the first n bytes of lhs and rhs as unsigned char; return
* negative, zero, or positive when lhs is less than, equal to, or greater
* than rhs. Unlike strcmp the comparison never stops at a NUL byte.
*
* See strcpy.c: the compare loop is a pattern GCC's
* -ftree-loop-distribute-patterns can recognize (memcmp), so disable that
* transformation to avoid self-recursion.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) int
memcmp(const void *lhs, const void *rhs, size_t n) // NOLINT(bugprone-easily-swappable-parameters)
{
const unsigned char *l = lhs;
const unsigned char *r = rhs;
while (n != 0)
{
if (*l != *r)
{
return *l < *r ? -1 : 1;
}
l++;
r++;
n--;
}
return 0;
}
+69
View File
@@ -0,0 +1,69 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#if VLIBC_LEVEL_GE(2)
/*
* XSI stpcpy / stpncpy (todo 8): strcpy / strncpy variants that return a
* pointer to the terminating NUL of dst instead of dst itself.
*
* See strcpy.c: the copy loops are recognizable patterns, so disable
* -ftree-loop-distribute-patterns here too.
*/
/*
* Copy src to dst including the terminating NUL; return a pointer to the
* NUL written into dst.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
stpcpy(char *restrict dst, const char *restrict src) // NOLINT(bugprone-*)
{
char *d = dst;
for (;;)
{
*d = *src;
if (*d == '\0')
{
return d;
}
d++;
src++;
}
}
/*
* Copy at most n bytes from src to dst, NUL-padding when src is shorter;
* return a pointer to the first NUL written into dst, or to dst + n when no
* NUL was written.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
stpncpy(char *restrict dst, const char *restrict src, size_t n) // NOLINT(bugprone-*)
{
char *d = dst;
char *end;
/* Copy until src ends or n bytes have been copied. */
while (n != 0 && *src != '\0')
{
*d++ = *src++;
n--;
}
/* The first NUL (or one past the last byte) sits right here. */
end = d;
/* NUL-pad the remainder. */
while (n != 0)
{
*d++ = '\0';
n--;
}
return end;
}
#endif /* VLIBC_LEVEL_GE(2) */
+65
View File
@@ -0,0 +1,65 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Append src (including its terminating NUL) to the end of dst; return dst.
* The strings must not overlap (restrict).
*
* See strcpy.c: the copy loop is a recognizable pattern, so disable
* -ftree-loop-distribute-patterns here too.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
strcat(char *restrict dst, const char *restrict src) // NOLINT(bugprone-*)
{
char *d = dst;
/* Find the terminating NUL of dst. */
while (*d != '\0')
{
d++;
}
/* Append src including its NUL. */
for (;;)
{
*d++ = *src;
if (*src == '\0')
{
return dst;
}
src++;
}
}
/*
* Append at most n bytes of src to dst and always NUL-terminate; return dst.
*
* See strcpy.c: the copy loop is a recognizable pattern, so disable
* -ftree-loop-distribute-patterns here too.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
strncat(char *restrict dst, const char *restrict src, size_t n) // NOLINT(bugprone-*)
{
char *d = dst;
/* Find the terminating NUL of dst. */
while (*d != '\0')
{
d++;
}
/* Append at most n bytes of src, stopping early at its NUL. */
while (n != 0 && *src != '\0')
{
*d++ = *src++;
n--;
}
/* strncat always NUL-terminates. */
*d = '\0';
return dst;
}
+52
View File
@@ -0,0 +1,52 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Return a pointer to the first occurrence of c (converted to char) in s,
* or NULL when absent. The terminating NUL is part of the string, so
* strchr(s, '\0') returns a pointer to it.
*/
char *
strchr(const char *s, int c) // NOLINT(bugprone-easily-swappable-parameters)
{
const char cc = (char)c;
for (;; s++)
{
if (*s == cc)
{
return (char *)s;
}
if (*s == '\0')
{
return NULL;
}
}
}
/*
* Return a pointer to the last occurrence of c (converted to char) in s, or
* NULL when absent. The terminating NUL is part of the string, so
* strrchr(s, '\0') returns a pointer to it.
*/
char *
strrchr(const char *s, int c) // NOLINT(bugprone-easily-swappable-parameters)
{
const char cc = (char)c;
const char *last = NULL;
for (;; s++)
{
if (*s == cc)
{
last = s;
}
if (*s == '\0')
{
return (char *)last;
}
}
}
+79
View File
@@ -0,0 +1,79 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* C-locale collation (todo 49 owns real locales). In the "C" locale the
* collating sequence is the machine collating sequence — byte order — so
* strcoll degenerates to strcmp, and the strxfrm transformation is the
* identity: the transformed form of a string is the string itself.
*/
/*
* Compare s1 and s2 under the active locale's collating sequence and return
* negative, zero, or positive. "C" locale: identical to strcmp.
*/
int
strcoll(const char *s1, const char *s2) // NOLINT(bugprone-easily-swappable-parameters)
{
return strcmp(s1, s2);
}
/*
* Transform src so that strcmp on transformed strings orders them as
* strcoll would, storing at most n bytes of the result in dst (always
* NUL-terminated when n > 0; nothing is written when n == 0). Return the
* length of the full transformed string excluding the NUL — in the "C"
* locale, strlen(src).
*/
size_t
strxfrm(char *restrict dst, const char *restrict src,
size_t n) // NOLINT(bugprone-easily-swappable-parameters)
{
size_t len = strlen(src);
size_t copy;
size_t i;
if (n == 0)
{
return len;
}
copy = len < n ? len : n - 1;
for (i = 0; i < copy; i++)
{
dst[i] = src[i];
}
dst[copy] = '\0';
return len;
}
#if VLIBC_LEVEL_GE(2)
/*
* XSI locale-parameterized variants. No locale machinery exists yet (todo
* 49), so the locale argument is accepted and ignored and the behavior is
* the "C" locale behavior of the base functions. The locale parameter is
* typed void * for now: locale_t will be an ABI-identical pointer typedef
* defined by <locale.h>, and todo 49 updates these signatures to the real
* type.
*/
int
strcoll_l(const char *s1, const char *s2,
void *locale) // NOLINT(bugprone-easily-swappable-parameters)
{
(void)locale;
return strcoll(s1, s2);
}
size_t
strxfrm_l(char *restrict dst, const char *restrict src, size_t n,
void *locale) // NOLINT(bugprone-easily-swappable-parameters)
{
(void)locale;
return strxfrm(dst, src, n);
}
#endif /* VLIBC_LEVEL_GE(2) */
+31
View File
@@ -0,0 +1,31 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Copy src to dst including the terminating NUL; return dst. The strings
* must not overlap (restrict).
*
* The copy loop below is the canonical strcpy idiom; GCC's
* -ftree-loop-distribute-patterns (on by default at -O2 and -O3) can rewrite
* such loops into calls to the recognized string builtins — potentially this
* very function — causing infinite self-recursion. Disable that one
* transformation for this function only.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
strcpy(char *restrict dst, const char *restrict src) // NOLINT(bugprone-*)
{
char *d = dst;
for (;;)
{
*d++ = *src;
if (*src == '\0')
{
return dst;
}
src++;
}
}
+69
View File
@@ -0,0 +1,69 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <errno.h>
#include "../internal/malloc.h"
/*
* strdup / strndup (todo 8).
*
* Both allocate through the internal allocator seam __libc_malloc (see
* src/internal/malloc.h; todo 7 provides the real allocator), so the copies
* are released with the public free. On allocation failure errno is set to
* ENOMEM and NULL is returned.
*/
/*
* Return a heap copy of s, or NULL with errno ENOMEM on allocation failure.
*/
char *
strdup(const char *s)
{
size_t len = strlen(s) + 1;
char *p = __libc_malloc(len);
size_t i;
if (p == NULL)
{
errno = ENOMEM;
return NULL;
}
for (i = 0; i < len; i++)
{
p[i] = s[i];
}
return p;
}
/*
* Return a heap copy of the first n bytes of s, NUL-terminated. When s is
* shorter than n the copy is the whole string; strndup(s, 0) returns the
* empty string. NULL with errno ENOMEM on allocation failure.
*/
char *
strndup(const char *s, size_t n)
{
size_t len = strnlen(s, n);
char *p = __libc_malloc(len + 1);
size_t i;
if (p == NULL)
{
errno = ENOMEM;
return NULL;
}
for (i = 0; i < len; i++)
{
p[i] = s[i];
}
p[len] = '\0';
return p;
}
+36
View File
@@ -0,0 +1,36 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Compare at most n bytes of lhs and rhs as unsigned char, stopping early
* at the first difference or the first NUL of either string. Return
* negative, zero, or positive when lhs is less than, equal to, or greater
* than rhs.
*
* See strcpy.c: the scan/compare loop is a pattern GCC's
* -ftree-loop-distribute-patterns can recognize (strcmp/strncmp), so disable
* that transformation to avoid self-recursion.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) int
strncmp(const char *lhs, const char *rhs, size_t n) // NOLINT(bugprone-easily-swappable-parameters)
{
const unsigned char *l = (const unsigned char *)lhs;
const unsigned char *r = (const unsigned char *)rhs;
while (n != 0 && *l != '\0' && *l == *r)
{
l++;
r++;
n--;
}
if (n == 0 || *l == *r)
{
return 0;
}
return *l < *r ? -1 : 1;
}
+35
View File
@@ -0,0 +1,35 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Copy at most n bytes from src to dst. When src is shorter than n, the
* remainder of dst is filled with NULs; when src is n bytes or longer, no
* NUL is written at all. Return dst.
*
* See strcpy.c: the copy loop is a recognizable pattern, so disable
* -ftree-loop-distribute-patterns here too.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
strncpy(char *restrict dst, const char *restrict src, size_t n) // NOLINT(bugprone-*)
{
char *d = dst;
/* Copy until src ends or n bytes have been copied. */
while (n != 0 && *src != '\0')
{
*d++ = *src++;
n--;
}
/* NUL-pad the remainder (this also terminates a short src). */
while (n != 0)
{
*d++ = '\0';
n--;
}
return dst;
}
+28
View File
@@ -0,0 +1,28 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Return the length of s, excluding the terminating NUL, examining at most
* maxlen bytes: the result is min(strlen(s), maxlen) when s is a proper
* string. Unlike strlen, the scan provably stops at maxlen bytes, which is
* why strndup and friends rely on it for untrusted buffers.
*
* See strcpy.c: the NUL scan is exactly the pattern GCC's
* -ftree-loop-distribute-patterns recognizes as strnlen/strlen, so disable
* that transformation to avoid self-recursion.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) size_t
strnlen(const char *s, size_t maxlen)
{
size_t n = 0;
while (n < maxlen && s[n] != '\0')
{
n++;
}
return n;
}
+136
View File
@@ -0,0 +1,136 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* vlibc — strsignal (todo 8).
*
* Maps a signal number to its description string. The table uses the
* x86_64 Linux asm-generic signal numbers (SIGHUP=1 .. SIGSYS=31), which
* are kernel-ABI facts; <signal.h> (todo 28) will become the canonical home
* for the signal-number names, and this table should then reference those
* names instead of the literal numbers.
*
* Known signals return immutable static string literals. Unrecognized
* numbers — including 0, whose text POSIX deliberately leaves unspecified —
* format as "Unknown signal <N>" into one shared static buffer. That is the
* same single-buffer tradeoff as strerror (see src/errno/strerror.c): the
* unknown path is rare diagnostics, there is no per-thread storage beyond
* the errno TCB slot yet, and strsignal must not depend on malloc. Migrate
* to a per-thread buffer once the TCB supports it.
*/
/*
* One entry per x86_64 asm-generic signal 1..31. The texts are standard
* English descriptions of what each signal means; the wording is vlibc's
* own (POSIX fixes the meanings, not the strings).
*/
static const char *const sigmsg[] = {
[1] = "Hangup",
[2] = "Interrupt",
[3] = "Quit",
[4] = "Illegal instruction",
[5] = "Trace/breakpoint trap",
[6] = "Aborted",
[7] = "Bus error",
[8] = "Floating point exception",
[9] = "Killed",
[10] = "User defined signal 1",
[11] = "Segmentation fault",
[12] = "User defined signal 2",
[13] = "Broken pipe",
[14] = "Alarm clock",
[15] = "Terminated",
[16] = "Stack fault",
[17] = "Child exited",
[18] = "Continued",
[19] = "Stopped (signal)",
[20] = "Stopped",
[21] = "Stopped (tty input)",
[22] = "Stopped (tty output)",
[23] = "Urgent I/O condition",
[24] = "CPU time limit exceeded",
[25] = "File size limit exceeded",
[26] = "Virtual timer expired",
[27] = "Profiling timer expired",
[28] = "Window changed",
[29] = "I/O possible",
[30] = "Power failure",
[31] = "Bad system call",
};
/* The table spans exactly 1..31 (index 0 stays NULL: signal 0 is unknown). */
_Static_assert(sizeof sigmsg / sizeof sigmsg[0] == 32,
"strsignal table must cover signal numbers 1..31");
/* Shared storage for the unknown-signal path (see the file-top comment). */
static char sigbuf[32];
/*
* Write "Unknown signal <sig>" into dst, truncating to cap bytes and always
* NUL-terminating when cap > 0. Hand-rolled so this file needs no stdio.
*/
static void
format_unknown(int sig, char *dst, size_t cap) // NOLINT(bugprone-easily-swappable-parameters)
{
static const char prefix[] = "Unknown signal ";
char digits[12]; /* enough for "-2147483648" */
size_t ndigits;
size_t i;
unsigned long mag;
if (cap == 0)
{
return;
}
/* Absolute value as unsigned long; INT_MIN negates safely in long. */
mag = (unsigned long)(sig < 0 ? -(long)sig : sig);
/* Digits, least significant first. */
ndigits = 0;
do
{
digits[ndigits] = (char)('0' + (int)(mag % 10));
ndigits++;
mag /= 10;
} while (mag != 0);
i = 0;
while (i + 1 < cap && prefix[i] != '\0')
{
dst[i] = prefix[i];
i++;
}
if (i + 1 < cap && sig < 0)
{
dst[i] = '-';
i++;
}
while (i + 1 < cap && ndigits > 0)
{
ndigits--;
dst[i] = digits[ndigits];
i++;
}
dst[i] = '\0';
}
/*
* Return a pointer to a static string describing the signal sig. Never
* returns NULL. Distinct known signals yield distinct strings; signal 0 and
* out-of-range numbers yield "Unknown signal <sig>".
*/
char *
strsignal(int sig)
{
if (sig > 0 && sig < (int)(sizeof sigmsg / sizeof sigmsg[0]))
{
return (char *)sigmsg[sig];
}
format_unknown(sig, sigbuf, sizeof sigbuf);
return sigbuf;
}
+71
View File
@@ -0,0 +1,71 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* True when c appears anywhere in set (including as the NUL of set — but set
* is a string, so scanning stops at that NUL, as intended).
*/
static int
str_in_set(const char *set, char c) // NOLINT(bugprone-easily-swappable-parameters)
{
for (; *set != '\0'; set++)
{
if (*set == c)
{
return 1;
}
}
return 0;
}
/*
* Return the length of the initial span of s consisting entirely of bytes
* that occur in accept.
*/
size_t
strspn(const char *s, const char *accept) // NOLINT(bugprone-easily-swappable-parameters)
{
size_t n = 0;
while (s[n] != '\0' && str_in_set(accept, s[n]))
{
n++;
}
return n;
}
/*
* Return the length of the initial span of s consisting entirely of bytes
* that do NOT occur in reject.
*/
size_t
strcspn(const char *s, const char *reject) // NOLINT(bugprone-easily-swappable-parameters)
{
size_t n = 0;
while (s[n] != '\0' && !str_in_set(reject, s[n]))
{
n++;
}
return n;
}
/*
* Return a pointer to the first byte in s that also occurs in accept, or
* NULL when none occurs.
*/
char *
strpbrk(const char *s, const char *accept) // NOLINT(bugprone-easily-swappable-parameters)
{
for (; *s != '\0'; s++)
{
if (str_in_set(accept, *s))
{
return (char *)s;
}
}
return NULL;
}
+43
View File
@@ -0,0 +1,43 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Return a pointer to the first occurrence of needle in haystack, or NULL
* when absent. An empty needle matches haystack itself.
*
* Naive two-pointer scan: for each position in haystack, compare characters
* until the needle is exhausted (match) or a mismatch occurs (advance). The
* inner loop stops at the NUL terminator of either string, so the comparison
* never reads past the end of haystack when needle is longer than the
* remaining tail.
*/
char *
strstr(const char *haystack, const char *needle) // NOLINT(bugprone-easily-swappable-parameters)
{
if (*needle == '\0')
{
return (char *)haystack;
}
for (; *haystack != '\0'; haystack++)
{
const char *h = haystack;
const char *n = needle;
while (*n != '\0' && *h == *n)
{
h++;
n++;
}
if (*n == '\0')
{
return (char *)haystack;
}
}
return NULL;
}
+73
View File
@@ -0,0 +1,73 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Shared continuation pointer for the non-reentrant strtok. Plain static
* storage: POSIX requires strtok itself to be neither thread-safe nor
* reentrant; callers needing either use strtok_r.
*/
static char *strtok_next;
/*
* Split s into tokens delimited by any byte from sep. On the first call s
* names the string; on later calls with s == NULL the search continues in
* the same string. Leading and consecutive delimiters produce no empty
* tokens, and the delimiter bytes in s are overwritten with NUL. Returns
* NULL when no token remains.
*/
char *
strtok(char *restrict s, const char *restrict sep) // NOLINT(bugprone-easily-swappable-parameters)
{
return strtok_r(s, sep, &strtok_next);
}
/*
* Reentrant strtok: the continuation pointer lives in *state instead of
* private static storage. When a call finds no token (including the first
* call on an empty or all-delimiter string), *state is set to NULL, so a
* later strtok_r(NULL, sep, state) call returns NULL without further reads.
*/
char *
strtok_r(char *restrict s, const char *restrict sep, char **restrict state)
{
char *tok;
if (s == NULL)
{
s = *state;
if (s == NULL)
{
return NULL;
}
}
/* Skip leading delimiters (this also skips the empty tokens that
consecutive delimiters would otherwise produce). */
s += strspn(s, sep);
if (*s == '\0')
{
*state = NULL;
return NULL;
}
tok = s;
/* Find the end of the token. */
s += strcspn(s, sep);
if (*s != '\0')
{
/* Replace the delimiter with NUL and continue after it. */
*s++ = '\0';
}
else
{
/* The token ran to the end of the string. */
s = NULL;
}
*state = s;
return tok;
}
+709
View File
@@ -0,0 +1,709 @@
/*
* vlibc — string.h completion test (todo 8).
*
* Covers the new string search/memory functions end to end:
*
* 1. copy family: strcpy/strncpy (NUL padding), strcat/strncat (always
* NUL-terminated), L2 stpcpy/stpncpy (return the NUL position);
* 2. search family: strchr/strrchr (including the NUL as a match),
* strspn/strcspn/strpbrk, strstr (empty/longer/overlapping needles);
* 3. tokenizer: strtok/strtok_r — empty source yields NULL, consecutive
* delimiters produce NO empty tokens, an all-delimiter string yields
* NULL on the first call, and the delimiter string is never modified;
* 4. collation: strcoll == strcmp and strxfrm (identity transform,
* returns strlen, truncates to n-1 bytes + NUL) — "C" locale only;
* L2 strcoll_l/strxfrm_l ignore the locale argument;
* 5. memory: memchr (incl. negative c), memcmp ordering, L2 memccpy;
* 6. misc: strnlen (stops at maxlen), strncmp (stops at NUL), strdup/
* strndup round-trips, strsignal (distinct non-null strings; unknown
* numbers format as "Unknown signal <N>"; signal 0 is asserted
* distinct + non-null only, per POSIX).
*
* The expected values were captured against the host glibc as the
* ground-truth oracle for the corpus (see the task-8 evidence log); the
* assertions below encode those golden results inline. Two strxfrm cases
* intentionally assert VLIBc's pinned contract (n-1 bytes + NUL, return
* strlen) rather than glibc's: POSIX leaves the dst contents UNSPECIFIED
* when the return value is >= n.
*
* strdup/strndup allocate through the internal __libc_malloc seam
* (src/internal/malloc.h); under make check the real allocator (todo 7)
* provides it. For the STANDALONE verification of this todo the test links
* a throwaway /tmp allocator stub (never committed) — see the evidence log.
*
* With `-p`, a representative sample of results is printed to stdout for
* eyeball diffing; the assertions always run.
*
* No host libc headers are included (the -Iinclude path would shadow GCC's
* internal headers); all output goes through raw SYS_write via
* <vlibc/internal/test.h>. Not yet wired into make check (a later todo).
*
* Standalone build:
* gcc -Iinclude -DVLIBC_LEVEL=2 -std=c23 -Wall -Wextra -pedantic \
* -o /tmp/t8 tests/test_string.c \
* src/string/{strcpy,strncpy,strcat,strchr,strspn,strstr,strtok,strcoll,strdup,strnlen,strncmp,memchr,memcmp,strsignal,stpcpy,memccpy}.c \
* src/string/{strlen,strcmp}.c /tmp/allocstub.c \
* src/internal/errno.c src/internal/syscall_ret.c
*/
#include <string.h>
#include <vlibc/internal/test.h>
#include "../src/internal/malloc.h"
/* ---- shared helpers ---- */
/*
* Collect up to cap tokens from a fresh copy of s using strtok; the copy is
* consumed (delimiters become NUL), matching POSIX strtok semantics.
*/
static int
tok_collect(char *s, const char *sep, char *out[], int cap)
{
char *t;
int n = 0;
t = strtok(s, sep);
while (t != NULL && n < cap)
{
out[n++] = t;
t = strtok(NULL, sep);
}
return n;
}
/* Same, but through strtok_r with an explicit state. */
static int
tokr_collect(char *s, const char *sep, char *out[], int cap)
{
char *t;
char *state = NULL;
int n = 0;
t = strtok_r(s, sep, &state);
while (t != NULL && n < cap)
{
out[n++] = t;
t = strtok_r(NULL, sep, &state);
}
return n;
}
/*
* GCC's -Wstringop-truncation/-Wstringop-overread fire when the source
* length is statically visible at a bounded-copy call site. The truncation
* and oversized-n corpus cases below are exactly what those diagnostics
* guard against — intentionally — so they go through noipa wrappers
* (noipa, not just noinline: GCC's constprop clones ignore noinline at
* -O2 and re-expose the call sites).
*/
static __attribute__((noipa)) char *
strncpy_opaque(char *dst, const char *src, size_t n)
{
return strncpy(dst, src, n);
}
static __attribute__((noipa)) char *
strncat_opaque(char *dst, const char *src, size_t n)
{
return strncat(dst, src, n);
}
static __attribute__((noipa)) char *
strndup_opaque(const char *s, size_t n)
{
return strndup(s, n);
}
/* ---- test functions (one per checked behavior) ---- */
static int
test_strcpy(void)
{
char buf[16];
TEST_ASSERT_TRUE(strcpy(buf, "hello") == buf);
TEST_ASSERT_STREQ(buf, "hello");
TEST_ASSERT_TRUE(strcpy(buf, "") == buf);
TEST_ASSERT_STREQ(buf, "");
return 0;
}
static int
test_strncpy(void)
{
char buf[8];
/* Shorter src: NUL-padded to n. */
memset(buf, 0x41, sizeof buf);
TEST_ASSERT_TRUE(strncpy(buf, "hi", 5) == buf);
TEST_ASSERT_STREQ(buf, "hi");
TEST_ASSERT_EQ(buf[2], '\0');
TEST_ASSERT_EQ(buf[3], '\0');
TEST_ASSERT_EQ(buf[4], '\0');
TEST_ASSERT_EQ(buf[5], 'A'); /* untouched past n */
/* Src n bytes or longer: no NUL written within n. */
memset(buf, 0x41, sizeof buf);
TEST_ASSERT_TRUE(strncpy_opaque(buf, "hello", 3) == buf);
TEST_ASSERT_EQ(buf[0], 'h');
TEST_ASSERT_EQ(buf[1], 'e');
TEST_ASSERT_EQ(buf[2], 'l');
TEST_ASSERT_EQ(buf[3], 'A'); /* not NUL-terminated */
/* n == 0: nothing written. */
memset(buf, 0x41, sizeof buf);
strncpy_opaque(buf, "hello", 0);
TEST_ASSERT_EQ(buf[0], 'A');
return 0;
}
static int
test_strcat(void)
{
char buf[16];
strcpy(buf, "hello");
TEST_ASSERT_TRUE(strcat(buf, " world") == buf);
TEST_ASSERT_STREQ(buf, "hello world");
strcpy(buf, "");
strcat(buf, "x");
TEST_ASSERT_STREQ(buf, "x");
return 0;
}
static int
test_strncat(void)
{
char buf[16];
strcpy(buf, "abc");
TEST_ASSERT_TRUE(strncat_opaque(buf, "defgh", 3) == buf);
TEST_ASSERT_STREQ(buf, "abcdef");
strcpy(buf, "abc");
strncat(buf, "defgh", 10);
TEST_ASSERT_STREQ(buf, "abcdefgh");
strcpy(buf, "abc");
strncat_opaque(buf, "xyz", 0);
TEST_ASSERT_STREQ(buf, "abc");
return 0;
}
static int
test_strchr(void)
{
const char *s = "hello";
TEST_ASSERT_EQ(strchr(s, 'l') - s, 2);
TEST_ASSERT_EQ(strchr(s, 'h') - s, 0);
TEST_ASSERT_NULL(strchr(s, 'z'));
TEST_ASSERT_EQ(strchr(s, '\0') - s, 5);
return 0;
}
static int
test_strrchr(void)
{
const char *s = "hello";
TEST_ASSERT_EQ(strrchr(s, 'l') - s, 3);
TEST_ASSERT_EQ(strrchr(s, 'h') - s, 0);
TEST_ASSERT_NULL(strrchr(s, 'z'));
TEST_ASSERT_EQ(strrchr(s, '\0') - s, 5);
return 0;
}
static int
test_strspn(void)
{
TEST_ASSERT_EQ(strspn("abcba", "abc"), 5);
TEST_ASSERT_EQ(strspn("abcba", "ab"), 2);
TEST_ASSERT_EQ(strspn("abcba", "z"), 0);
TEST_ASSERT_EQ(strspn("", "ab"), 0);
return 0;
}
static int
test_strcspn(void)
{
TEST_ASSERT_EQ(strcspn("abcba", "b"), 1);
TEST_ASSERT_EQ(strcspn("abcba", "z"), 5);
TEST_ASSERT_EQ(strcspn("abcba", "a"), 0);
TEST_ASSERT_EQ(strcspn("", "ab"), 0);
return 0;
}
static int
test_strpbrk(void)
{
const char *s = "hello";
TEST_ASSERT_EQ(strpbrk(s, "aeiou") - s, 1);
TEST_ASSERT_EQ(strpbrk(s, "h") - s, 0);
TEST_ASSERT_NULL(strpbrk(s, "xyz"));
TEST_ASSERT_NULL(strpbrk("", "x"));
return 0;
}
static int
test_strstr(void)
{
const char *h = "hello world";
TEST_ASSERT_EQ(strstr(h, "world") - h, 6);
TEST_ASSERT_EQ(strstr(h, "hello") - h, 0);
TEST_ASSERT_EQ(strstr(h, "o") - h, 4);
TEST_ASSERT_NULL(strstr(h, "helloo"));
TEST_ASSERT_NULL(strstr("hi", "hello")); /* needle longer than haystack */
TEST_ASSERT_TRUE(strstr(h, "") == h); /* empty needle matches haystack */
TEST_ASSERT_EQ(strstr("aaaa", "aaa") - "aaaa", 0); /* overlapping */
TEST_ASSERT_EQ(strstr("banana", "nan") - "banana", 2);
TEST_ASSERT_EQ(strstr("abc", "bc") - "abc", 1);
return 0;
}
static int
test_strtok(void)
{
char buf[32];
char *out[8];
int n;
/* Empty source: first call yields NULL (no tokens). */
strcpy(buf, "");
TEST_ASSERT_NULL(strtok(buf, " "));
/* Consecutive delimiters produce NO empty tokens. */
strcpy(buf, "a,,b");
n = tok_collect(buf, ",", out, 8);
TEST_ASSERT_EQ(n, 2);
TEST_ASSERT_STREQ(out[0], "a");
TEST_ASSERT_STREQ(out[1], "b");
/* All delimiters: first call yields NULL. */
strcpy(buf, ",,,");
TEST_ASSERT_NULL(strtok(buf, ","));
/* Leading + trailing delimiters are skipped. */
strcpy(buf, "xxhelloxx");
n = tok_collect(buf, "x", out, 8);
TEST_ASSERT_EQ(n, 1);
TEST_ASSERT_STREQ(out[0], "hello");
/* No delimiter present: the whole string is one token. */
strcpy(buf, "hello");
n = tok_collect(buf, " ", out, 8);
TEST_ASSERT_EQ(n, 1);
TEST_ASSERT_STREQ(out[0], "hello");
/* Multiple whitespace runs collapse to the same tokens. */
strcpy(buf, "a b c");
n = tok_collect(buf, " ", out, 8);
TEST_ASSERT_EQ(n, 3);
TEST_ASSERT_STREQ(out[0], "a");
TEST_ASSERT_STREQ(out[1], "b");
TEST_ASSERT_STREQ(out[2], "c");
/* The delimiter string is never modified. */
{
static char sep[] = ",;";
char *sep_before = sep;
strcpy(buf, "a,b;c");
(void)tok_collect(buf, sep, out, 8);
TEST_ASSERT_TRUE(sep == sep_before);
TEST_ASSERT_EQ(sep[0], ',');
TEST_ASSERT_EQ(sep[1], ';');
}
return 0;
}
static int
test_strtok_r(void)
{
char buf[32];
char *out[8];
int n;
strcpy(buf, "a,,b");
n = tokr_collect(buf, ",", out, 8);
TEST_ASSERT_EQ(n, 2);
TEST_ASSERT_STREQ(out[0], "a");
TEST_ASSERT_STREQ(out[1], "b");
strcpy(buf, "");
TEST_ASSERT_NULL(strtok_r(buf, " ", &(char *){NULL}));
/* Two interleaved scans keep independent state. */
{
char s1[16];
char s2[16];
char *state1 = NULL;
char *state2 = NULL;
char *t1;
char *t2;
strcpy(s1, "one two");
strcpy(s2, "red green blue");
t1 = strtok_r(s1, " ", &state1);
t2 = strtok_r(s2, " ", &state2);
TEST_ASSERT_STREQ(t1, "one");
TEST_ASSERT_STREQ(t2, "red");
t1 = strtok_r(NULL, " ", &state1);
TEST_ASSERT_STREQ(t1, "two");
t2 = strtok_r(NULL, " ", &state2);
TEST_ASSERT_STREQ(t2, "green");
t1 = strtok_r(NULL, " ", &state1);
TEST_ASSERT_NULL(t1);
t2 = strtok_r(NULL, " ", &state2);
TEST_ASSERT_STREQ(t2, "blue");
}
return 0;
}
static int
test_strcoll(void)
{
TEST_ASSERT_EQ(strcoll("apple", "banana"), strcmp("apple", "banana"));
TEST_ASSERT_EQ(strcoll("apple", "banana") < 0, 1);
TEST_ASSERT_EQ(strcoll("b", "a") > 0, 1);
TEST_ASSERT_EQ(strcoll("same", "same"), 0);
TEST_ASSERT_EQ(strcoll("A", "a"), strcmp("A", "a"));
TEST_ASSERT_EQ(strcoll("", ""), 0);
return 0;
}
static int
test_strxfrm(void)
{
char xf[16];
/* Identity transform, returns strlen(src). */
TEST_ASSERT_EQ(strxfrm(xf, "hello", sizeof xf), 5);
TEST_ASSERT_STREQ(xf, "hello");
/* Truncation: at most n-1 bytes + NUL, return still 5. */
TEST_ASSERT_EQ(strxfrm(xf, "hello", 4), 5);
TEST_ASSERT_STREQ(xf, "hel");
/* n == 0: nothing written, return still 5. */
memset(xf, 0x41, sizeof xf);
TEST_ASSERT_EQ(strxfrm(xf, "hello", 0), 5);
TEST_ASSERT_EQ(xf[0], 'A');
/* n == 1: only the NUL. */
TEST_ASSERT_EQ(strxfrm(xf, "hello", 1), 5);
TEST_ASSERT_EQ(xf[0], '\0');
TEST_ASSERT_EQ(strxfrm(xf, "", sizeof xf), 0);
TEST_ASSERT_STREQ(xf, "");
return 0;
}
static int
test_strdup(void)
{
char src[] = "hello";
char *p = strdup(src);
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "hello");
TEST_ASSERT_TRUE(p != src); /* a real copy, not the source array */
__libc_free(p);
p = strdup("");
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "");
__libc_free(p);
return 0;
}
static int
test_strndup(void)
{
char *p = strndup("abcdef", 3);
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "abc");
__libc_free(p);
p = strndup_opaque("ab", 10); /* n beyond the NUL: whole string */
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "ab");
__libc_free(p);
p = strndup("xyz", 0); /* n == 0: empty string */
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "");
__libc_free(p);
p = strndup("", 0);
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "");
__libc_free(p);
return 0;
}
static int
test_strnlen(void)
{
TEST_ASSERT_EQ(strnlen("abc", 2), 2);
TEST_ASSERT_EQ(strnlen("abc", 10), 3);
TEST_ASSERT_EQ(strnlen("abc", 0), 0);
TEST_ASSERT_EQ(strnlen("", 5), 0);
return 0;
}
static int
test_strncmp(void)
{
TEST_ASSERT_EQ(strncmp("abc", "abc", 3), 0);
TEST_ASSERT_EQ(strncmp("ab", "abc", 2), 0);
TEST_ASSERT_EQ(strncmp("ab", "ac", 1), 0);
TEST_ASSERT_EQ(strncmp("abc", "abd", 3) < 0, 1);
TEST_ASSERT_EQ(strncmp("abd", "abc", 3) > 0, 1);
TEST_ASSERT_EQ(strncmp("x", "y", 0), 0); /* n == 0 */
/* Stops at the first NUL even when n is larger. */
TEST_ASSERT_EQ(strncmp("a\0b", "a\0c", 5), 0);
TEST_ASSERT_EQ(strncmp("a\0b", "a", 5), 0);
return 0;
}
static int
test_memchr(void)
{
const char *s = "hello";
unsigned char high[] = {0x41, 0xff, 0x00};
TEST_ASSERT_EQ((char *)memchr(s, 'l', 5) - s, 2);
TEST_ASSERT_NULL(memchr(s, 'z', 5));
TEST_ASSERT_NULL(memchr(s, 'h', 0)); /* n == 0 */
TEST_ASSERT_EQ((char *)memchr(s, '\0', 6) - s, 5);
/* Negative c searches for the matching high byte. */
TEST_ASSERT_EQ((char *)memchr(high, 0xff, 3) - (char *)high, 1);
return 0;
}
static int
test_memcmp(void)
{
unsigned char hi[] = {0xff, 0x00};
unsigned char lo[] = {0x7f, 0x00};
TEST_ASSERT_EQ(memcmp("abc", "abc", 3), 0);
TEST_ASSERT_EQ(memcmp("abc", "abd", 3) < 0, 1);
TEST_ASSERT_EQ(memcmp("abd", "abc", 3) > 0, 1);
TEST_ASSERT_EQ(memcmp("x", "y", 0), 0); /* n == 0 */
/* Bytes compare as unsigned char: 0xff > 0x7f. */
TEST_ASSERT_EQ(memcmp(hi, lo, 1) > 0, 1);
TEST_ASSERT_EQ(memcmp(lo, hi, 1) < 0, 1);
/* Does not stop at NUL bytes. */
TEST_ASSERT_EQ(memcmp("a\0b", "a\0c", 3) < 0, 1);
return 0;
}
static int
test_strsignal(void)
{
/* Known signals: pinned texts (vlibc's own table, x86_64 numbers). */
TEST_ASSERT_STREQ(strsignal(2), "Interrupt");
TEST_ASSERT_STREQ(strsignal(11), "Segmentation fault");
TEST_ASSERT_STREQ(strsignal(1), "Hangup");
TEST_ASSERT_STREQ(strsignal(9), "Killed");
TEST_ASSERT_STREQ(strsignal(31), "Bad system call");
/* Distinct strings for distinct known signals. */
TEST_ASSERT_TRUE(strsignal(2) != strsignal(11));
TEST_ASSERT_TRUE(strsignal(1) != strsignal(2));
/* Signal 0: non-null and distinct, text deliberately not pinned. */
TEST_ASSERT_TRUE(strsignal(0) != NULL);
TEST_ASSERT_TRUE(strsignal(0) != strsignal(1));
TEST_ASSERT_TRUE(strsignal(0) != strsignal(2));
/* Unknown numbers: formatted, non-null, distinct. */
TEST_ASSERT_STREQ(strsignal(999), "Unknown signal 999");
TEST_ASSERT_STREQ(strsignal(32), "Unknown signal 32");
TEST_ASSERT_TRUE(strsignal(-1) != NULL);
TEST_ASSERT_STREQ(strsignal(-1), "Unknown signal -1");
return 0;
}
static int
test_stpcpy_stpncpy(void)
{
char buf[8];
char *end;
end = stpcpy(buf, "abc");
TEST_ASSERT_EQ(end - buf, 3);
TEST_ASSERT_STREQ(buf, "abc");
TEST_ASSERT_EQ(*end, '\0');
end = stpcpy(buf, "");
TEST_ASSERT_EQ(end - buf, 0);
TEST_ASSERT_EQ(buf[0], '\0');
/* Short src: NUL-padded; return points at the first NUL. */
end = stpncpy(buf, "abc", 8);
TEST_ASSERT_EQ(end - buf, 3);
TEST_ASSERT_STREQ(buf, "abc");
/* Src >= n: no NUL written; return is dst + n. */
memset(buf, 'X', sizeof buf);
end = stpncpy(buf, "abcdefgh", 4);
TEST_ASSERT_EQ(end - buf, 4);
TEST_ASSERT_EQ(buf[0], 'a');
TEST_ASSERT_EQ(buf[3], 'd');
TEST_ASSERT_EQ(buf[4], 'X');
/* n == 0: return dst, nothing written. */
memset(buf, 'X', sizeof buf);
end = stpncpy(buf, "abc", 0);
TEST_ASSERT_EQ(end - buf, 0);
TEST_ASSERT_EQ(buf[0], 'X');
return 0;
}
static int
test_memccpy(void)
{
unsigned char buf[16];
void *p;
p = memccpy(buf, "hello world", ' ', 20);
TEST_ASSERT_EQ((char *)p - (char *)buf, 6); /* one past the ' ' */
TEST_ASSERT_EQ(buf[5], ' ');
/* c never appears within n: NULL, full copy made. */
p = memccpy(buf, "hello", 'z', 20);
TEST_ASSERT_NULL(p);
TEST_ASSERT_EQ(buf[4], 'o');
/* c exists but beyond n: NULL. */
p = memccpy(buf, "hello", 'o', 3);
TEST_ASSERT_NULL(p);
/* n == 0: NULL, nothing written. */
memset(buf, 'X', sizeof buf);
p = memccpy(buf, "hello", 'h', 0);
TEST_ASSERT_NULL(p);
TEST_ASSERT_EQ(buf[0], 'X');
return 0;
}
static int
test_strcoll_l(void)
{
char xf[16];
/* The locale argument is ignored ("C" locale behavior). */
TEST_ASSERT_EQ(strcoll_l("a", "b", NULL), strcoll("a", "b"));
TEST_ASSERT_EQ(strcoll_l("a", "b", NULL) < 0, 1);
TEST_ASSERT_EQ(strcoll_l("x", "x", NULL), 0);
TEST_ASSERT_EQ(strxfrm_l(xf, "hi", sizeof xf, NULL), strxfrm(xf, "hi", sizeof xf));
TEST_ASSERT_STREQ(xf, "hi");
return 0;
}
/* ---- register + runner ---- */
static const struct vlibc_test tests[] = {
{"strcpy", test_strcpy}, {"strncpy", test_strncpy},
{"strcat", test_strcat}, {"strncat", test_strncat},
{"strchr", test_strchr}, {"strrchr", test_strrchr},
{"strspn", test_strspn}, {"strcspn", test_strcspn},
{"strpbrk", test_strpbrk}, {"strstr", test_strstr},
{"strtok", test_strtok}, {"strtok_r", test_strtok_r},
{"strcoll", test_strcoll}, {"strxfrm", test_strxfrm},
{"strdup", test_strdup}, {"strndup", test_strndup},
{"strnlen", test_strnlen}, {"strncmp", test_strncmp},
{"memchr", test_memchr}, {"memcmp", test_memcmp},
{"strsignal", test_strsignal}, {"stpcpy/stpncpy", test_stpcpy_stpncpy},
{"memccpy", test_memccpy}, {"strcoll_l/strxfrm_l", test_strcoll_l},
};
/* -p mode: print a representative sample for eyeball diffing. */
static void
print_sample(void)
{
char buf[32];
char *out[8];
int n;
char *t;
vlibc_test_say(1, "strtok(a,,b, ,)= ");
strcpy(buf, "a,,b");
n = tok_collect(buf, ",", out, 8);
for (int i = 0; i < n; i++)
{
vlibc_test_say(1, "[");
vlibc_test_say(1, out[i]);
vlibc_test_say(1, "]");
}
vlibc_test_say(1, "\nstrxfrm(hello,n=4)=len ");
vlibc_test_say_dec(1, (unsigned long)strxfrm(buf, "hello", 4));
vlibc_test_say(1, " dst=\"");
vlibc_test_say(1, buf);
vlibc_test_say(1, "\"\nstrsignal(2)=\"");
vlibc_test_say(1, strsignal(2));
vlibc_test_say(1, "\" strsignal(0)=\"");
vlibc_test_say(1, strsignal(0));
vlibc_test_say(1, "\" strsignal(999)=\"");
vlibc_test_say(1, strsignal(999));
vlibc_test_say(1, "\"\n");
t = strdup("dup-ok");
vlibc_test_say(1, "strdup(dup-ok)=\"");
vlibc_test_say(1, t);
vlibc_test_say(1, "\"\n");
__libc_free(t);
}
/*
* Own main (not TEST_MAIN): supports the -p print mode. Everything else
* follows the TEST_MAIN contract — same output shape, 0 on all-pass.
*/
int
main(int argc, char **argv)
{
const size_t count = sizeof tests / sizeof tests[0];
size_t passed = 0;
size_t i;
if (argc > 1 && strcmp(argv[1], "-p") == 0)
{
print_sample();
}
for (i = 0; i < count; i++)
{
int before = vlibc_test_failures;
vlibc_test_say(1, "RUN ");
vlibc_test_say(1, tests[i].name);
vlibc_test_say(1, ": ");
if (tests[i].run() == 0 && vlibc_test_failures == before)
{
vlibc_test_say(1, "PASS\n");
passed++;
}
else
{
vlibc_test_say(1, "FAIL\n");
}
}
vlibc_test_say(1, "SUMMARY: ");
vlibc_test_say_dec(1, (unsigned long)passed);
vlibc_test_say(1, "/");
vlibc_test_say_dec(1, (unsigned long)count);
vlibc_test_say(1, " passed, ");
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures);
vlibc_test_say(1, " assertion failure(s)\n");
return passed == count ? 0 : 1;
}