feat(string): complete string.h + strdup/memccpy
This commit is contained in:
+226
-2
@@ -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. */
|
||||||
|
|
||||||
|
|||||||
@@ -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 */
|
||||||
@@ -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) */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user