399 lines
11 KiB
C
399 lines
11 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).
|
|
*/
|
|
|
|
#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;
|
|
}
|