/* * 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 #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; }