Files
vlibc/tests/test_inttypes.c

403 lines
12 KiB
C

/*
* vlibc — inttypes.h test (todo 14).
*
* Covers three surfaces, all without printf (which does not exist yet —
* todo 16 owns it):
*
* 1. PRI/SCN macro literals: each macro must expand to a string literal
* with the complete conversion specifier for the x86_64 LP64 target
* (long = 64-bit), compared with vlibc's own strcmp, never through a
* host printf. Design rule under test: the fprintf macros carry the
* PROMOTED argument's modifier (PRId8 == "d", PRId64 == "ld") while
* the fscanf macros carry the exact-width pointer modifier (SCNd8 ==
* "hhd", SCNd64 == "ld").
* 2. strtoimax/strtoumax: thin ABI-identical delegations to strtol/
* strtoul (intmax_t is long on LP64), including the C23 7.8.2.3
* unsigned-wrap contract (strtoumax("-1") == UINTMAX_MAX with no
* range error) and imaxabs/imaxdiv.
* 3. wcstoimax/wcstoumax: the self-contained wide scan, verified
* value + endptr (wchar_t *) against the narrow-engine semantics.
*
* The `-f` failure mode runs the errno-writing scenarios (overflow clamps,
* EINVAL bases). This standalone test is host-linked, so vlibc's errno
* macro would write glibc's TCB slot (%fs:0+8) — errno is never read
* back; the C23 return-value clamps (INTMAX_MAX/INTMAX_MIN/UINTMAX_MAX)
* prove the ERANGE behavior, EINVAL is proven by the zero return with
* endptr == nptr, and -f exits through a raw SYS_exit_group to skip host
* cleanup. imaxabs(INTMAX_MIN) is deliberately untested: the wrap is
* implementation-defined.
*
* No host headers: under -Iinclude the vlibc headers shadow GCC's
* internal ones, so all output goes through raw SYS_write. Not part of the
* library proper; compiled manually for this todo (make check wiring is
* owned by a later todo).
*/
/* Link-trap note: strtoimax/strtoumax delegate to strtol/strtoul — standalone
* links MUST include src/stdlib/strtox.c + strtox_impl.c (+ strtod.c) or host
* glibc silently satisfies them and -f's EINVAL-base checks fail. */
#include <stdint.h>
#include "../include/inttypes.h"
#include "../include/string.h"
#include "../src/internal/syscall.h"
static int failures;
static void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
static void
say_dec(int fd, unsigned long v)
{
char buf[24];
int i = (int)sizeof(buf);
buf[--i] = '\0';
do
{
buf[--i] = (char)('0' + (v % 10));
v /= 10;
} while (v != 0);
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
}
static void
check(int cond, const char *what)
{
if (cond)
{
say(1, "PASS: ");
say(1, what);
say(1, "\n");
}
else
{
say(2, "FAIL: ");
say(2, what);
say(2, "\n");
failures++;
}
}
static void
check_ptr(const char *got, const char *want, const char *what)
{
if (got == want)
{
say(1, "PASS: ");
say(1, what);
say(1, "\n");
}
else
{
say(2, "FAIL: ");
say(2, what);
say(2, " (endptr off by ");
say_dec(2, (unsigned long)(got - want));
say(2, ")\n");
failures++;
}
}
// NOLINTEND(bugprone-easily-swappable-parameters)
/* noipa: keep the comparison a real strcmp call so GCC cannot fold the
* literal contents away at -O2 (test_malloc.c proxy precedent). */
static __attribute__((noipa)) int
str_eq(const char *a, const char *b)
{
return strcmp(a, b) == 0;
}
/* 1. PRI/SCN macro literals (acceptance: a macro expands to a literal,
* not via printf). */
static void
macro_literals(void)
{
const char *s;
s = PRIu64;
check(str_eq(s, "lu"), "PRIu64 == \"lu\" (LP64 ABI)");
s = PRId64;
check(str_eq(s, "ld"), "PRId64 == \"ld\"");
s = PRIi64;
check(str_eq(s, "li"), "PRIi64 == \"li\"");
s = PRIx64;
check(str_eq(s, "lx"), "PRIx64 == \"lx\"");
s = PRIX64;
check(str_eq(s, "lX"), "PRIX64 == \"lX\"");
s = PRIuMAX;
check(str_eq(s, "lu"), "PRIuMAX == \"lu\" (intmax_t is long)");
s = PRIdMAX;
check(str_eq(s, "ld"), "PRIdMAX == \"ld\"");
s = PRIdPTR;
check(str_eq(s, "ld"), "PRIdPTR == \"ld\"");
s = PRIxPTR;
check(str_eq(s, "lx"), "PRIxPTR == \"lx\"");
s = SCNu64;
check(str_eq(s, "lu"), "SCNu64 == \"lu\"");
s = SCNd64;
check(str_eq(s, "ld"), "SCNd64 == \"ld\"");
s = SCNx64;
check(str_eq(s, "lx"), "SCNx64 == \"lx\"");
/* Sub-int widths: PRI uses the promoted argument's modifier, SCN the
* exact-width pointer modifier. */
s = PRId8;
check(str_eq(s, "d"), "PRId8 == \"d\" (int8_t promotes to int)");
s = SCNd8;
check(str_eq(s, "hhd"), "SCNd8 == \"hhd\" (pointer to int8_t)");
s = PRId16;
check(str_eq(s, "d"), "PRId16 == \"d\" (int16_t promotes to int)");
s = SCNd16;
check(str_eq(s, "hd"), "SCNd16 == \"hd\"");
s = SCNuLEAST8;
check(str_eq(s, "hhu"), "SCNuLEAST8 == \"hhu\"");
s = PRIdLEAST64;
check(str_eq(s, "ld"), "PRIdLEAST64 == \"ld\"");
/* Fastest types: int_fast8_t is signed char, int_fast16/32/64_t are
* long on x86_64. */
s = PRIdFAST8;
check(str_eq(s, "d"), "PRIdFAST8 == \"d\" (signed char promotes)");
s = PRIdFAST16;
check(str_eq(s, "ld"), "PRIdFAST16 == \"ld\" (long)");
s = SCNdFAST8;
check(str_eq(s, "hhd"), "SCNdFAST8 == \"hhd\"");
s = SCNdFAST16;
check(str_eq(s, "ld"), "SCNdFAST16 == \"ld\"");
}
/* 2a. strtoimax/strtoumax happy paths (no errno writes). */
static void
strtoimax_happy(void)
{
const char *s = " -42";
char *e;
intmax_t v = strtoimax(s, &e, 0);
check(v == -42, "strtoimax(\" -42\",0) value");
check_ptr(e, s + 5, "strtoimax(\" -42\",0) endptr past digits");
s = "42xyz";
v = strtoimax(s, &e, 0);
check(v == 42, "strtoimax(\"42xyz\") value");
check_ptr(e, s + 2, "strtoimax(\"42xyz\") endptr at 'x'");
s = "0x1f";
check(strtoimax(s, &e, 0) == 31 && e == s + 4, "strtoimax(\"0x1f\",0)==31");
s = "18446744073709551615";
check(strtoumax(s, &e, 10) == UINTMAX_MAX && e == s + 20,
"strtoumax(UINTMAX_MAX digits) exact, no range error");
s = " 42";
check(strtoumax(s, &e, 10) == 42 && e == s + 4, "strtoumax(\" 42\")==42");
/* C23 7.8.2.3: a minus sign negates in the return type modulo 2^64 and
* is never a range error by itself (errno stays 0 — proven by value
* only, never by reading errno in this host-linked test). */
s = "-1";
check(strtoumax(s, &e, 10) == UINTMAX_MAX, "strtoumax(\"-1\")==UINTMAX_MAX (wrap, no errno)");
s = "-4294967296";
check(strtoumax(s, &e, 10) == UINTMAX_MAX - 4294967295UL, "strtoumax(-2^32)==2^64-2^32");
}
/* 2b. imaxabs/imaxdiv. imaxabs(INTMAX_MIN) is deliberately skipped: the
* wrap is implementation-defined and UB-adjacent. */
static void
imax_abs_div(void)
{
check(imaxabs(-42) == 42 && imaxabs(42) == 42 && imaxabs(0) == 0, "imaxabs basics");
check(imaxabs(INTMAX_MAX) == INTMAX_MAX, "imaxabs(INTMAX_MAX)");
{
imaxdiv_t r = imaxdiv(7, 3);
check(r.quot == 2 && r.rem == 1, "imaxdiv(7,3) == {2,1}");
}
{
imaxdiv_t r = imaxdiv(-7, 3);
check(r.quot == -2 && r.rem == -1, "imaxdiv(-7,3) == {-2,-1} (trunc toward zero)");
}
{
imaxdiv_t r = imaxdiv(7, -3);
check(r.quot == -2 && r.rem == 1, "imaxdiv(7,-3) == {-2,1}");
}
}
/* 3. wcstoimax/wcstoumax: wide scan mirrors the narrow engine. */
static void
wcsto_happy(void)
{
const wchar_t *ws = L" -42";
wchar_t *e;
intmax_t v = wcstoimax(ws, &e, 0);
check(v == -42, "wcstoimax(L\" -42\",0) value");
check(e == ws + 5, "wcstoimax(L\" -42\",0) endptr past digits");
ws = L"4x";
v = wcstoimax(ws, &e, 10);
check(v == 4, "wcstoimax(L\"4x\") value");
check(e == ws + 1, "wcstoimax(L\"4x\") endptr past L'4'");
ws = L"42abc";
v = wcstoimax(ws, &e, 0);
check(v == 42, "wcstoimax(L\"42abc\") value");
check(e == ws + 2, "wcstoimax(L\"42abc\") endptr at 'a'");
ws = L"-0x10";
check(wcstoimax(ws, &e, 0) == -16 && e == ws + 5, "wcstoimax(L\"-0x10\",0)==-16");
ws = L"18446744073709551615";
check(wcstoumax(ws, &e, 10) == UINTMAX_MAX && e == ws + 20,
"wcstoumax(UINTMAX_MAX digits) exact, no range error");
ws = L"-1";
check(wcstoumax(ws, &e, 10) == UINTMAX_MAX, "wcstoumax(L\"-1\")==UINTMAX_MAX (wrap)");
ws = L"\t\n\v\f\r +42x";
check(wcstoumax(ws, &e, 10) == 42 && e == ws + 9, "wcstoumax wide whitespace+sign");
ws = L" ";
check(wcstoimax(ws, &e, 10) == 0 && e == ws, "wcstoimax(whitespace only) endptr at start");
ws = L"0x";
check(wcstoimax(ws, &e, 0) == 0 && e == ws, "wcstoimax(L\"0x\",0): no subject");
}
/*
* Failure scenarios (-f): every path that writes errno inside the library.
* errno is never read back (host TCB hazard) — the C23 return-value clamps
* prove ERANGE, endptr == nptr with a zero return proves EINVAL — and the
* process exits through a raw SYS_exit_group.
*/
static int
failure_scenarios(void)
{
const char *s;
char *e;
int bad = 0;
s = "18446744073709551616";
if (strtoumax(s, &e, 0) != UINTMAX_MAX || e != s + 20)
{
bad++;
say(2, "FAIL: strtoumax overflow clamp\n");
}
s = "9223372036854775808";
if (strtoimax(s, &e, 10) != INTMAX_MAX || e != s + 19)
{
bad++;
say(2, "FAIL: strtoimax overflow clamp\n");
}
s = "-9223372036854775809";
if (strtoimax(s, &e, 10) != INTMAX_MIN || e != s + 20)
{
bad++;
say(2, "FAIL: strtoimax negative overflow clamp\n");
}
s = "-18446744073709551616";
if (strtoumax(s, &e, 10) != UINTMAX_MAX || e != s + 21)
{
bad++;
say(2, "FAIL: strtoumax magnitude 2^64 overflow clamp\n");
}
s = "123";
if (strtoimax(s, &e, 1) != 0 || e != s)
{
bad++;
say(2, "FAIL: strtoimax base 1 (EINVAL)\n");
}
s = "123";
if (strtoumax(s, &e, 37) != 0 || e != s)
{
bad++;
say(2, "FAIL: strtoumax base 37 (EINVAL)\n");
}
s = "123";
if (strtoumax(s, &e, -1) != 0 || e != s)
{
bad++;
say(2, "FAIL: strtoumax base -1 (EINVAL)\n");
}
{
const wchar_t *ws = L"18446744073709551616";
wchar_t *we;
if (wcstoumax(ws, &we, 0) != UINTMAX_MAX || we != ws + 20)
{
bad++;
say(2, "FAIL: wcstoumax overflow clamp\n");
}
ws = L"9223372036854775808";
if (wcstoimax(ws, &we, 10) != INTMAX_MAX || we != ws + 19)
{
bad++;
say(2, "FAIL: wcstoimax overflow clamp\n");
}
ws = L"-9223372036854775809";
if (wcstoimax(ws, &we, 10) != INTMAX_MIN || we != ws + 20)
{
bad++;
say(2, "FAIL: wcstoimax negative overflow clamp\n");
}
ws = L"123";
if (wcstoimax(ws, &we, 37) != 0 || we != ws)
{
bad++;
say(2, "FAIL: wcstoimax base 37 (EINVAL)\n");
}
}
if (bad == 0)
{
say(1, "PASS: all overflow/EINVAL failure scenarios\n");
}
return bad > 0 ? 1 : 0;
}
int
main(int argc, char **argv)
{
int rc;
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
{
rc = failure_scenarios();
__syscall1(SYS_exit_group, rc);
return rc; /* not reached */
}
macro_literals();
strtoimax_happy();
imax_abs_div();
wcsto_happy();
if (failures > 0)
{
say(2, "FAILED (");
say_dec(2, (unsigned long)failures);
say(2, " check(s))\n");
return 1;
}
say(1, "all inttypes tests passed\n");
return 0;
}