fmt: scandigits rejects the i32-overflowing last digit (F-A)

The one-sided guard `v > 214748364` never fired for the last digit:
at v==214748364 a next digit of '8'/'9' made `v*10+digit` overflow
i32 and wrap negative, slipping past the signed args-index bound
check at fmt.ww:703 -> OOB arg read -> SIGSEGV on any format
directive carrying an over-i32 digit run (index, width or precision).

Complete it to the canonical two-part pre-multiply Horner guard
(MAX/10, MAX%10). Hand-rolled in signed i32, not Hare scan_sz's
unsigned post-multiply wrap-check (ref/hare/strconv/stou.ha:60),
which would be signed-overflow UB-class here; noted at the site.

Table-driven subprocess test over all three scandigits call sites,
5 rows x both stages; reverting the guard reproduces exit=139.
This commit is contained in:
2026-06-14 11:46:10 +09:00
parent 33f940e17c
commit 1752305be7
3 changed files with 247 additions and 1 deletions

View File

@@ -221,7 +221,10 @@ fn scandigits(s: str, pos: *i32) i32 = {
return v;
};
any = true;
if (v > 214748364) { fmtabort(); };
// PRE-multiply guard (i32 MAX 2147483647, /10, %10): Hare's
// scan_sz uses stoz's UNSIGNED post-multiply wrap-check
// (stou.ha:60 `n < old`), UB-class here in signed i32.
if (v > 214748364 || (v == 214748364 && (c - 48u8): i32 > 7)) { fmtabort(); };
v = v * 10 + (c - 48u8): i32;
*pos += 1;
};