/* * vlibc — numeric string conversions test (todo 11). * * Exercises atoi/atol/atoll/atof, strtol/strtoul/strtoll/strtoull, and * strtof/strtod/strtold against glibc-verified golden expectations (the * corpus was diffed value+endptr+errno against glibc during development; * the literals below are that diff's results): * * 1. base resolution: 0 (decimal/octal/0x-hex auto-detect), 2, 8, 10, * 16, including the "0" / "0x" prefix edge cases ("0" base 0 is an * octal subject of value 0 with the 0 consumed; "0x" base 0 with no * hex digit has no subject at all; base 16 keeps "0" and stops at x); * 2. leading C-locale whitespace and sign handling; * 3. endptr correctness: past the last digit, at the first invalid * character, or at nptr when no subject sequence exists; * 4. exact boundaries without range errors: LONG_MAX / LONG_MIN / * ULONG_MAX / ULLONG_MAX / DBL_MAX; * 5. strtoul negative wrap (value-only, modulo 2^64, C23 7.24.1.4p8); * 6. strtod: "inf"/"infinity"/"nan"/"nan(chars)" (case-insensitive), * hex floats ("0x1.8p1"), "0.1" exact bits, "3.14159"; * 7. ato* wrappers. * * The `-f` failure mode runs the errno-writing scenarios (overflow clamps, * EINVAL bases, HUGE_VAL, underflow-to-zero, hex subnormal). Because this * standalone test is host-linked, vlibc's errno macro addresses glibc's * TCB slot (%fs:0+8) — writing it corrupts the host. errno behavior is * therefore verified only through the C23-mandated return-value clamps * (LONG_MAX/LONG_MIN/HUGE_VAL presence proves ERANGE happened; EINVAL is * proven by the zero return with endptr == nptr), never by reading errno, * and the -f mode exits via a raw SYS_exit_group to skip host cleanup. * * 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 (the make check wiring * is owned by a later todo). */ #include #include #include #include "../include/stdlib.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); } static void say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters) { 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) // NOLINT(bugprone-easily-swappable-parameters) { 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++; } } /* 1. Base resolution and prefix rules. */ static void base_resolution(void) { const char *s; char *e; long v; s = "0x1f"; v = strtol(s, &e, 0); check(v == 31 && e == s + 4, "strtol(\"0x1f\",0)==31, endptr past digits"); s = "0x1f"; v = strtol(s, &e, 16); check(v == 31 && e == s + 4, "strtol(\"0x1f\",16)==31"); s = "0x1F"; check(strtol(s, &e, 0) == 31 && e == s + 4, "strtol(\"0x1F\",0)==31 (upper hex)"); s = "101010"; check(strtol(s, &e, 2) == 42 && e == s + 6, "strtol(\"101010\",2)==42"); s = "777"; check(strtol(s, &e, 8) == 511 && e == s + 3, "strtol(\"777\",8)==511"); s = "42"; check(strtol(s, &e, 10) == 42 && e == s + 2, "strtol(\"42\",10)==42"); s = "ff"; check(strtol(s, &e, 16) == 255 && e == s + 2, "strtol(\"ff\",16)==255"); s = "ff"; v = strtol(s, &e, 10); check(v == 0 && e == s, "strtol(\"ff\",10)==0, endptr at start (no digits)"); s = "0"; v = strtol(s, &e, 0); check(v == 0 && e == s + 1, "strtol(\"0\",0)==0, octal-prefix 0 consumed"); s = "08"; v = strtol(s, &e, 0); check(v == 0 && e == s + 1, "strtol(\"08\",0)==0, subject is the octal 0"); s = "0x"; v = strtol(s, &e, 0); check(v == 0 && e == s, "strtol(\"0x\",0)==0, no subject sequence at all"); s = "0x"; v = strtol(s, &e, 16); check(v == 0 && e == s + 1, "strtol(\"0x\",16)==0, the 0 is the subject"); s = "0xg"; v = strtol(s, &e, 0); check(v == 0 && e == s, "strtol(\"0xg\",0): no hex digit, no subject"); s = "0b101"; v = strtol(s, &e, 0); check(v == 0 && e == s + 1, "strtol(\"0b101\",0)==0, b is not octal"); s = "010"; check(strtol(s, &e, 0) == 8 && e == s + 3, "strtol(\"010\",0)==8 (octal)"); s = "1234"; check(strtol(s, &e, 36) == 49360 && e == s + 4, "strtol(\"1234\",36)==49360"); } /* 2. Whitespace and signs. */ static void whitespace_signs(void) { const char *s = " -42"; char *e; check(strtol(s, &e, 0) == -42 && e == s + 5, "strtol(\" -42\",0)==-42"); s = "\t\n\v\f\r+42x"; check(strtol(s, &e, 10) == 42 && e == s + 8, "strtol(ws \"+42x\")==42, endptr at x"); s = " "; check(strtol(s, &e, 10) == 0 && e == s, "strtol(whitespace only)==0, endptr at start"); s = "+"; check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"+\")==0, endptr at start"); s = "-"; check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"-\")==0, endptr at start"); s = ""; check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"\")==0, endptr at start"); s = "-0x10"; check(strtol(s, &e, 0) == -16 && e == s + 5, "strtol(\"-0x10\",0)==-16"); s = " 0x10"; check(strtol(s, &e, 0) == 16 && e == s + 5, "strtol(\" 0x10\",0)==16"); } /* 3. Endptr lands at the first invalid character. */ static void endptr_positions(void) { const char *s = "12abc"; char *e; check(strtol(s, &e, 10) == 12, "strtol(\"12abc\") value"); check_ptr(e, s + 2, "strtol(\"12abc\") endptr at 'a'"); s = "0x1fg"; check(strtol(s, &e, 0) == 31, "strtol(\"0x1fg\") value"); check_ptr(e, s + 4, "strtol(\"0x1fg\") endptr at 'g'"); s = "3.5"; check(strtol(s, &e, 10) == 3, "strtol(\"3.5\") value"); check_ptr(e, s + 1, "strtol(\"3.5\") endptr at '.'"); } /* 4. Exact representable boundaries set no range error (proven by the * exact return values; errno itself is never read here). */ static void exact_boundaries(void) { check(strtol("9223372036854775807", NULL, 10) == LONG_MAX, "strtol max == LONG_MAX"); check(strtol("-9223372036854775808", NULL, 10) == LONG_MIN, "strtol min == LONG_MIN"); check(strtoll("9223372036854775807", NULL, 10) == LLONG_MAX, "strtoll max == LLONG_MAX"); check(strtoll("-9223372036854775808", NULL, 10) == LLONG_MIN, "strtoll min == LLONG_MIN"); check(strtoul("18446744073709551615", NULL, 10) == ULONG_MAX, "strtoul max == ULONG_MAX"); check(strtoull("18446744073709551615", NULL, 10) == ULLONG_MAX, "strtoull max == ULLONG_MAX"); check(strtol("9223372036854775807z", NULL, 10) == LONG_MAX, "strtol max with tail"); check(strtol("0x7fffffffffffffff", NULL, 0) == LONG_MAX, "strtol hex LONG_MAX"); check(strtol("-0x8000000000000000", NULL, 0) == LONG_MIN, "strtol hex LONG_MIN"); check(strtoul("0xffffffffffffffff", NULL, 0) == ULONG_MAX, "strtoul hex ULONG_MAX"); } /* 5. strtoul negative wrap: value-only negation modulo 2^64 (C23 * 7.24.1.4p8) — never a range error, so this runs in default mode. */ static void unsigned_negative_wrap(void) { check(strtoul("-1", NULL, 10) == ULONG_MAX, "strtoul(\"-1\") == ULONG_MAX (wrap)"); check(strtoull("-1", NULL, 10) == ULLONG_MAX, "strtoull(\"-1\") == ULLONG_MAX (wrap)"); check(strtoul("-4294967296", NULL, 10) == ULONG_MAX - 4294967295UL, "strtoul(-2^32) == 2^64-2^32"); check(strtoul("-9223372036854775808", NULL, 10) == 9223372036854775808UL, "strtoul(-2^63) == 2^63 (no range error)"); } /* 6a. strtod special forms and hex floats. */ static void strtod_special(void) { const char *s = "inf"; char *e; double r; r = strtod(s, &e); check(r == __builtin_inf() && e == s + 3, "strtod(\"inf\") == +infinity"); s = "Infinity"; r = strtod(s, &e); check(r == __builtin_inf() && e == s + 8, "strtod(\"Infinity\") == +infinity"); s = "-INF"; r = strtod(s, &e); check(r == -__builtin_inf() && e == s + 4, "strtod(\"-INF\") == -infinity"); s = "infinityx"; r = strtod(s, &e); check(r == __builtin_inf(), "strtod(\"infinityx\") value"); check_ptr(e, s + 8, "strtod(\"infinityx\") endptr at 'x'"); s = "nan"; r = strtod(s, &e); check(r != r && e == s + 3, "strtod(\"nan\") is NaN, endptr past nan"); s = "NAN(xyz)"; r = strtod(s, &e); check(r != r, "strtod(\"NAN(xyz)\") is NaN"); check_ptr(e, s + 8, "strtod(\"NAN(xyz)\") endptr past ')'"); s = "nan(x"; r = strtod(s, &e); check(r != r, "strtod(\"nan(x\") is NaN"); check_ptr(e, s + 3, "strtod(\"nan(x\") endptr right after nan"); s = "in"; r = strtod(s, &e); check(r == 0.0 && e == s, "strtod(\"in\")==0, endptr at start"); s = "0x1.8p1"; r = strtod(s, &e); check(r == 3.0 && e == s + 7, "strtod(\"0x1.8p1\")==3.0"); s = "0x1p-2"; check(strtod(s, &e) == 0.25 && e == s + 6, "strtod(\"0x1p-2\")==0.25"); s = "0x1.8p1f"; r = strtod(s, &e); check(r == 3.0, "strtod(\"0x1.8p1f\") value"); check_ptr(e, s + 7, "strtod(\"0x1.8p1f\") endptr at 'f'"); s = "0x8.8"; check(strtod(s, &e) == 8.5 && e == s + 5, "strtod(\"0x8.8\")==8.5"); s = "0x.8p1"; check(strtod(s, &e) == 1.0 && e == s + 6, "strtod(\"0x.8p1\")==1.0"); s = "0x1.fffffffffffffp+1023"; check(strtod(s, &e) == DBL_MAX && e == s + 23, "strtod(hex max normal) == DBL_MAX"); s = "0x"; r = strtod(s, &e); check(r == 0.0, "strtod(\"0x\") value"); check_ptr(e, s + 1, "strtod(\"0x\") endptr after the 0 (glibc)"); } /* 6b. strtod decimal conversions. */ static void strtod_decimal(void) { const char *s = "0.1"; char *e; double r; r = strtod(s, &e); check(r == 0x1.999999999999ap-4 && e == s + 3, "strtod(\"0.1\") has exact 0.1 bits"); s = "3.14159"; check(strtod(s, &e) == 3.14159 && e == s + 7, "strtod(\"3.14159\") == 3.14159"); s = "1.5e2"; check(strtod(s, &e) == 150.0 && e == s + 5, "strtod(\"1.5e2\")==150"); s = " -0.5"; check(strtod(s, &e) == -0.5 && e == s + 6, "strtod(\" -0.5\")==-0.5"); s = ".5"; check(strtod(s, &e) == 0.5 && e == s + 2, "strtod(\".5\")==0.5"); s = "1."; check(strtod(s, &e) == 1.0 && e == s + 2, "strtod(\"1.\")==1.0"); s = "."; r = strtod(s, &e); check(r == 0.0 && e == s, "strtod(\".\")==0, endptr at start"); s = "1e"; r = strtod(s, &e); check(r == 1.0 && e == s + 1, "strtod(\"1e\")==1.0, bare e not part of subject"); s = "0"; check(strtod(s, &e) == 0.0 && e == s + 1, "strtod(\"0\")==0.0"); s = "-0"; r = strtod(s, &e); check(r == 0.0 && 1.0 / r == -__builtin_inf(), "strtod(\"-0\") == -0.0"); s = "123456789012345678901234567890"; r = strtod(s, &e); check(r > 0.0 && e == s + 30, "strtod(30-digit integer) positive, endptr at end"); s = "9.999999999999999e22"; check(strtod(s, &e) == 9.999999999999999e22, "strtod(19-digit) exact"); } /* 6c. strtof and strtold basics. */ static void strtof_strtold(void) { const char *s = "0.1"; char *e; check(strtof(s, &e) == 0.1f && e == s + 3, "strtof(\"0.1\") == 0.1f"); s = "inf"; check(strtof(s, &e) == __builtin_inff() && e == s + 3, "strtof(\"inf\") == +inf"); s = "nan"; { float nf = strtof(s, &e); check(nf != nf && e == s + 3, "strtof(\"nan\") is NaN"); } s = "0x1.8p1"; check(strtof(s, &e) == 3.0f && e == s + 7, "strtof(\"0x1.8p1\")==3.0f"); s = "0.1"; check(strtold(s, &e) == 0.1L && e == s + 3, "strtold(\"0.1\") == 0.1L"); s = "3.14159"; check(strtold(s, &e) == 3.14159L && e == s + 7, "strtold(\"3.14159\") == 3.14159L"); s = "1e100"; check(strtold(s, &e) == 1e100L && e == s + 5, "strtold(\"1e100\") == 1e100L (exact)"); s = "0x1.8p1"; check(strtold(s, &e) == 3.0L && e == s + 7, "strtold(\"0x1.8p1\")==3.0L"); s = "nan"; { long double nl = strtold(s, &e); check(nl != nl && e == s + 3, "strtold(\"nan\") is NaN"); } s = "1e999"; { long double big = strtold(s, &e); long double rel = big / 1e999L; check(e == s + 5 && rel > 0.999999999999999L && rel < 1.000000000000001L, "strtold(\"1e999\") within 1e-15 of 1e999 (no range error)"); } } /* 7. ato* wrappers. */ static void ato_wrappers(void) { check(atoi("42") == 42, "atoi(\"42\")==42"); check(atoi(" -17") == -17, "atoi(\" -17\")==-17"); check(atol("2147483648") == 2147483648L, "atol(\"2147483648\")==2147483648"); check(atoll("9223372036854775807") == LLONG_MAX, "atoll max == LLONG_MAX"); check(atof("1.5") == 1.5, "atof(\"1.5\")==1.5"); check(atof(" +2.25e2") == 225.0, "atof(\" +2.25e2\")==225.0"); check(atof("0x1.8p1") == 3.0, "atof(\"0x1.8p1\")==3.0"); check(atof("x") == 0.0, "atof(\"x\")==0.0"); } /* * Failure scenarios (-f): every path that writes errno inside the library. * Because the test is host-linked, those writes land in glibc's TCB slot, * so errno is never read back — the C23 return-value clamps prove the * ERANGE/EINVAL behavior — and the process exits through a raw * SYS_exit_group without running host cleanup. */ static int failure_scenarios(void) { const char *s; char *e; int bad = 0; s = "9223372036854775808"; if (strtol(s, &e, 10) != LONG_MAX || e != s + 19) { bad++; say(2, "FAIL: strtol overflow clamp\n"); } s = "-9223372036854775809"; if (strtol(s, &e, 10) != LONG_MIN || e != s + 20) { bad++; say(2, "FAIL: strtol negative overflow clamp\n"); } s = "9223372036854775808"; if (strtoll(s, &e, 10) != LLONG_MAX || e != s + 19) { bad++; say(2, "FAIL: strtoll overflow clamp\n"); } s = "-9223372036854775809"; if (strtoll(s, &e, 10) != LLONG_MIN || e != s + 20) { bad++; say(2, "FAIL: strtoll negative overflow clamp\n"); } s = "18446744073709551616"; if (strtoul(s, &e, 10) != ULONG_MAX || e != s + 20) { bad++; say(2, "FAIL: strtoul overflow clamp\n"); } s = "18446744073709551617"; if (strtoull(s, &e, 10) != ULLONG_MAX || e != s + 20) { bad++; say(2, "FAIL: strtoull overflow clamp\n"); } s = "-18446744073709551616"; if (strtoul(s, &e, 10) != ULONG_MAX || e != s + 21) { bad++; say(2, "FAIL: strtoul magnitude overflow (2^64) clamp\n"); } s = "123"; if (strtol(s, &e, 1) != 0 || e != s) { bad++; say(2, "FAIL: strtol base 1 (EINVAL): value/endptr\n"); } s = "123"; if (strtol(s, &e, 37) != 0 || e != s) { bad++; say(2, "FAIL: strtol base 37 (EINVAL): value/endptr\n"); } s = "123"; if (strtoul(s, &e, -1) != 0 || e != s) { bad++; say(2, "FAIL: strtoul base -1 (EINVAL): value/endptr\n"); } s = "1e999"; if (strtod(s, &e) != __builtin_inf() || e != s + 5) { bad++; say(2, "FAIL: strtod(\"1e999\") != +inf (HUGE_VAL)\n"); } s = "-1e999"; if (strtod(s, &e) != -__builtin_inf() || e != s + 6) { bad++; say(2, "FAIL: strtod(\"-1e999\") != -inf\n"); } s = "1e-999"; if (strtod(s, &e) != 0.0 || e != s + 6) { bad++; say(2, "FAIL: strtod(\"1e-999\") != 0.0 (underflow)\n"); } s = "0x1.fffffffffffffp1024"; if (strtod(s, &e) != __builtin_inf() || e != s + 22) { bad++; say(2, "FAIL: strtod(hex overflow) != +inf\n"); } s = "0x0.0000000000001p-1022"; if (strtod(s, &e) != DBL_TRUE_MIN || e != s + 23) { bad++; say(2, "FAIL: strtod(hex subnormal) != DBL_TRUE_MIN\n"); } s = "1e999"; if (strtof(s, &e) != __builtin_inff() || e != s + 5) { bad++; say(2, "FAIL: strtof(\"1e999\") != +inf\n"); } s = "1e-999"; if (strtof(s, &e) != 0.0f || e != s + 6) { bad++; say(2, "FAIL: strtof(\"1e-999\") != 0.0f\n"); } s = "1e5000"; if (strtold(s, &e) != __builtin_infl() || e != s + 6) { bad++; say(2, "FAIL: strtold(\"1e5000\") != +inf\n"); } if (bad == 0) { say(1, "PASS: all overflow/EINVAL/underflow 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 */ } base_resolution(); whitespace_signs(); endptr_positions(); exact_boundaries(); unsigned_negative_wrap(); strtod_special(); strtod_decimal(); strtof_strtold(); ato_wrappers(); if (failures > 0) { say(2, "FAILED ("); say_dec(2, (unsigned long)failures); say(2, " check(s))\n"); return 1; } say(1, "all strtol/strtod tests passed\n"); return 0; }