/* * 989_fmt_scanoverflow_run — drain item F-A: lib/fmt scandigits dropped * the last-digit overflow case. * * scandigits (lib/fmt/fmt.ww) accumulates a format-directive digit run * into an i32 with a Horner loop. The guard was a one-sided * `if (v > 214748364)`, which never fires when v == 214748364 and the * next digit is '8' or '9': v * 10 + 8/9 = 2147483648/2147483649 * overflows i32 and wraps NEGATIVE. A negative explicit arg index then * slips past the signed `idx >= args.len` guard in fprintf (fmt.ww:703) * and `args[idx]` reads far out of bounds -> SIGSEGV. * * The fix completes the canonical Horner pre-multiply guard to the * two-part form `v > MAX/10 || (v == MAX/10 && digit > MAX%10)` * (MAX=2147483647, MAX/10=214748364, MAX%10=7), so the last digit is * rejected and scandigits aborts cleanly via fmtabort (os.exit(255)). * * row | directive | arg | want_exit * -----------------+--------------------+---------+---------- * idx_2147483648 | "{2147483648}" | 42i64 | 255 (clean abort, was SIGSEGV) * idx_2147483649 | "{2147483649}" | 42i64 | 255 (clean abort, was SIGSEGV) * idx_9999999999 | "{9999999999}" | 42i64 | 255 (clear overflow, aborts pre+post) * width_2147483648 | "{:2147483648}" | 42i64 | 255 (width call site, last-digit abort) * prec_2147483647 | "{:.2147483647}" | "ok" | 7 (i32 MAX still PARSES, prints "ok") * * scandigits feeds three directive call sites — index (fmt.ww:690), * width (:259) and precision (:255). idx_*, width_* and prec_* exercise * all three; the last-digit guard must fire identically at each. * * The first two rows are the regression proper: on the pre-fix guard the * program is killed by signal 11 (runwait -> -1), not 255. The valid * boundary row proves the fix does not over-reject i32 MAX itself. * * Rule 10: every row runs on cstage `ww` and (when present) wwstage * `ww_ww`; both drivers compile the same fixed lib/fmt so both must agree. */ #include #include #include #include #include static int runwait(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return -1; /* killed by a signal (e.g. SIGSEGV) */ } struct row { const char *label; const char *src; int want_exit; }; static const struct row rows[] = { { "idx_2147483648", "package main;\n" "import fmt;\n" "import io;\n" "export fn main() i32 = {\n" " let buf: [64]u8;\n" " let r: (str | io.error) = fmt.bsprintf(buf[0:64], \"{2147483648}\", 42i64);\n" " match (r) {\n" " case let s: str => { return 0; };\n" " case let e: io.error => { return 1; };\n" " };\n" "};\n", 255 }, { "idx_2147483649", "package main;\n" "import fmt;\n" "import io;\n" "export fn main() i32 = {\n" " let buf: [64]u8;\n" " let r: (str | io.error) = fmt.bsprintf(buf[0:64], \"{2147483649}\", 42i64);\n" " match (r) {\n" " case let s: str => { return 0; };\n" " case let e: io.error => { return 1; };\n" " };\n" "};\n", 255 }, { "idx_9999999999", "package main;\n" "import fmt;\n" "import io;\n" "export fn main() i32 = {\n" " let buf: [64]u8;\n" " let r: (str | io.error) = fmt.bsprintf(buf[0:64], \"{9999999999}\", 42i64);\n" " match (r) {\n" " case let s: str => { return 0; };\n" " case let e: io.error => { return 1; };\n" " };\n" "};\n", 255 }, { "width_2147483648", "package main;\n" "import fmt;\n" "import io;\n" "export fn main() i32 = {\n" " let buf: [64]u8;\n" " let r: (str | io.error) = fmt.bsprintf(buf[0:64], \"{:2147483648}\", 42i64);\n" " match (r) {\n" " case let s: str => { return 0; };\n" " case let e: io.error => { return 1; };\n" " };\n" "};\n", 255 }, { "prec_2147483647", "package main;\n" "import fmt;\n" "import io;\n" "export fn main() i32 = {\n" " let buf: [64]u8;\n" " let r: (str | io.error) = fmt.bsprintf(buf[0:64], \"{:.2147483647}\", \"ok\");\n" " match (r) {\n" " case let s: str => { if (s.len == 2) { return 7; }; return 8; };\n" " case let e: io.error => { return 1; };\n" " };\n" "};\n", 7 }, }; static int write_source(const char *path, const char *src) { FILE *f = fopen(path, "wb"); if (!f) return -1; fputs(src, f); fclose(f); return 0; } /* ww_ww writes intermediates next to the source (filed task #15); clear * each row's .{combined.ww,s,o} + bare exe. Mirror of 780's. */ static void cleanup_tmp(const char *tmpdir, const char *base) { char p[640]; snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p); snprintf(p, sizeof p, "%s/%s.s", tmpdir, base); unlink(p); snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p); snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p); snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p); rmdir(tmpdir); } static int build_via_driver(const char *driver, const char *tmpdir, const char *cwd, const char *src) { char cmd[2048]; snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s build -I %s/lib %s 2>/dev/null", tmpdir, driver, cwd, src); return runwait(cmd); } static int run_row(const char *driver, const char *cwd, const struct row *r, int seq) { char tmpdir[256], src[512], base[64], outbin[768]; snprintf(tmpdir, sizeof tmpdir, "/tmp/fso_%d_d_%d", getpid(), seq); snprintf(base, sizeof base, "main989so"); snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base); mkdir(tmpdir, 0755); if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -2; } int rc; int br = build_via_driver(driver, tmpdir, cwd, src); if (br == 0) { snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); rc = runwait(outbin); } else { rc = -3; /* build failure: distinct from a run signal */ } cleanup_tmp(tmpdir, base); return rc; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char cwd[256]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; char absbin[512]; if (bin[0] != '/') { snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[640], wdrv[640]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; int wwpresent = (access(wdrv, X_OK) == 0); int seq = 0; for (int i = 0; i < n; i++) { total++; int got = run_row(cdrv, cwd, &rows[i], seq++); if (got != rows[i].want_exit) { fprintf(stderr, "fmt_scanoverflow[cs][%s]: exit=%d want=%d\n", rows[i].label, got, rows[i].want_exit); fail++; } if (wwpresent) { total++; got = run_row(wdrv, cwd, &rows[i], seq++); if (got != rows[i].want_exit) { fprintf(stderr, "fmt_scanoverflow[ww][%s]: exit=%d want=%d\n", rows[i].label, got, rows[i].want_exit); fail++; } } } if (!wwpresent) fprintf(stderr, "fmt_scanoverflow: skip wwstage (no %s)\n", wdrv); if (fail) { fprintf(stderr, "fmt_scanoverflow: %d/%d fixtures failed\n", fail, total); return 1; } printf("fmt_scanoverflow: %d/%d ok\n", total, total); return 0; }