strconv: reject overflow before arithmetic
This commit is contained in:
@@ -48,8 +48,6 @@
|
|||||||
|
|
||||||
package strconv;
|
package strconv;
|
||||||
|
|
||||||
import os;
|
|
||||||
|
|
||||||
// ref/hare/strconv/decimal.ha:5.
|
// ref/hare/strconv/decimal.ha:5.
|
||||||
def maxshift: u8 = 60u8;
|
def maxshift: u8 = 60u8;
|
||||||
|
|
||||||
@@ -59,7 +57,7 @@ def decimal_point_range: u16 = 2047u16;
|
|||||||
// ref/hare/strconv/decimal.ha:8-26. Field layout 1:1. The 800-digit
|
// ref/hare/strconv/decimal.ha:8-26. Field layout 1:1. The 800-digit
|
||||||
// bound covers subnormal doubles (min exp -1074, max mantissa 4e16
|
// bound covers subnormal doubles (min exp -1074, max mantissa 4e16
|
||||||
// → at most 767 digits; 800 leaves headroom).
|
// → at most 767 digits; 800 leaves headroom).
|
||||||
export type decimal = struct {
|
type decimal = struct {
|
||||||
digits: [800]u8,
|
digits: [800]u8,
|
||||||
nd: size,
|
nd: size,
|
||||||
dp: i32,
|
dp: i32,
|
||||||
|
|||||||
@@ -54,7 +54,6 @@
|
|||||||
package strconv;
|
package strconv;
|
||||||
|
|
||||||
import math;
|
import math;
|
||||||
import os;
|
|
||||||
|
|
||||||
// ref/hare/strconv/ftos_ryu.ha:33. (hi:lo) >> s, low 64 bits. Hare's
|
// ref/hare/strconv/ftos_ryu.ha:33. (hi:lo) >> s, low 64 bits. Hare's
|
||||||
// "TODO: use 128-bit integers" — ww has no u128; pure-u64 decomposition.
|
// "TODO: use 128-bit integers" — ww has no u128; pure-u64 decomposition.
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
package strconv_test;
|
package strconv_test;
|
||||||
|
|
||||||
import strconv;
|
import strconv;
|
||||||
import os;
|
|
||||||
import math;
|
import math;
|
||||||
|
|
||||||
|
|
||||||
@@ -43,6 +42,15 @@ fn rt64(x: f64) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn rt32(x: f32) bool = {
|
||||||
|
match (stof32(f32tos(x), base.DEC)) {
|
||||||
|
case let v: f32 => { return math.f32bits(v) == math.f32bits(x); };
|
||||||
|
case let e: invalid => { return false; };
|
||||||
|
case let e: overflow => { return false; };
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
// ftos_test.ha:57-64 (G/void/NONE-equivalent) — fixed-point renders.
|
// ftos_test.ha:57-64 (G/void/NONE-equivalent) — fixed-point renders.
|
||||||
@test fn f64tos_fixed() void = {
|
@test fn f64tos_fixed() void = {
|
||||||
assert(!(!chk(13.37, "13.37")));
|
assert(!(!chk(13.37, "13.37")));
|
||||||
@@ -136,6 +144,16 @@ fn chkf32(n: f32, want: str) bool = {
|
|||||||
assert(!(!chkf32(maxnorm, "3.4028235e38")));
|
assert(!(!chkf32(maxnorm, "3.4028235e38")));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@test fn f32_roundtrip_boundaries() void = {
|
||||||
|
assert(!(!rt32(math.f32frombits(0x00000001u32))));
|
||||||
|
assert(!(!rt32(math.f32frombits(0x007FFFFFu32))));
|
||||||
|
assert(!(!rt32(math.f32frombits(0x00800000u32))));
|
||||||
|
assert(!(!rt32(math.f32frombits(0x00800001u32))));
|
||||||
|
assert(!(!rt32(math.f32frombits(0x7F7FFFFFu32))));
|
||||||
|
assert(!(!rt32(math.f32frombits(0x807FFFFFu32))));
|
||||||
|
assert(!(!rt32(math.f32frombits(0x80800000u32))));
|
||||||
|
};
|
||||||
|
|
||||||
// ftos_test.ha:11/15/26 — zero, ±infinity, nan (f32 bit patterns).
|
// ftos_test.ha:11/15/26 — zero, ±infinity, nan (f32 bit patterns).
|
||||||
@test fn f32tos_special() void = {
|
@test fn f32tos_special() void = {
|
||||||
assert(!(!chkf32(0.0f32, "0")));
|
assert(!(!chkf32(0.0f32, "0")));
|
||||||
@@ -162,6 +180,10 @@ fn chkf32(n: f32, want: str) bool = {
|
|||||||
assert(!(!rt64(90071992547409915.0))); // 17-digit halfway pair
|
assert(!(!rt64(90071992547409915.0))); // 17-digit halfway pair
|
||||||
assert(!(!rt64(90071992547409925.0)));
|
assert(!(!rt64(90071992547409925.0)));
|
||||||
assert(!(!rt64(math.f64frombits(0x0000000000000001u64)))); // F64_MIN_SUBNORMAL (5e-324)
|
assert(!(!rt64(math.f64frombits(0x0000000000000001u64)))); // F64_MIN_SUBNORMAL (5e-324)
|
||||||
|
assert(!(!rt64(math.f64frombits(0x000FFFFFFFFFFFFFu64)))); // F64_MAX_SUBNORMAL
|
||||||
assert(!(!rt64(math.f64frombits(0x0010000000000000u64)))); // F64_MIN_NORMAL
|
assert(!(!rt64(math.f64frombits(0x0010000000000000u64)))); // F64_MIN_NORMAL
|
||||||
|
assert(!(!rt64(math.f64frombits(0x0010000000000001u64)))); // next normal
|
||||||
|
assert(!(!rt64(math.f64frombits(0x800FFFFFFFFFFFFFu64)))); // negative max subnormal
|
||||||
|
assert(!(!rt64(math.f64frombits(0x8010000000000000u64)))); // negative min normal
|
||||||
assert(!(!rt64(math.f64frombits(0x7FEFFFFFFFFFFFFFu64)))); // F64_MAX_NORMAL
|
assert(!(!rt64(math.f64frombits(0x7FEFFFFFFFFFFFFFu64)))); // F64_MAX_NORMAL
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -129,6 +129,9 @@ fn ck_uint_ovf(id: i32, s: str, b: base) void = {
|
|||||||
|
|
||||||
cki_ovf(7, "9223372036854775808", base.DEC);
|
cki_ovf(7, "9223372036854775808", base.DEC);
|
||||||
cki_ovf(8, "-9223372036854775809", base.DEC);
|
cki_ovf(8, "-9223372036854775809", base.DEC);
|
||||||
|
// The last multiply wraps to a value larger than its prefix, so a
|
||||||
|
// post-arithmetic `n < old` check misses this overflow.
|
||||||
|
cki_ovf(19, "21000000000000000000", base.DEC);
|
||||||
|
|
||||||
cki(9, "0", base.DEC, 0);
|
cki(9, "0", base.DEC, 0);
|
||||||
cki(10, "1", base.DEC, 1);
|
cki(10, "1", base.DEC, 1);
|
||||||
@@ -168,6 +171,7 @@ fn ck_uint_ovf(id: i32, s: str, b: base) void = {
|
|||||||
cku_ovf(35, "18446744073709551616", base.DEC);
|
cku_ovf(35, "18446744073709551616", base.DEC);
|
||||||
cku_ovf(36, "184467440737095516150", base.DEC);
|
cku_ovf(36, "184467440737095516150", base.DEC);
|
||||||
cku_ovf(37, "-1", base.DEC);
|
cku_ovf(37, "-1", base.DEC);
|
||||||
|
cku_ovf(46, "21000000000000000000", base.DEC);
|
||||||
|
|
||||||
cku(38, "0", base.DEC, 0u64);
|
cku(38, "0", base.DEC, 0u64);
|
||||||
cku(39, "1", base.DEC, 1u64);
|
cku(39, "1", base.DEC, 1u64);
|
||||||
@@ -207,6 +211,87 @@ fn ck_uint_ovf(id: i32, s: str, b: base) void = {
|
|||||||
ck_uint(62, "110101", base.BIN, 53u64: uint); // 0b110101
|
ck_uint(62, "110101", base.BIN, 53u64: uint); // 0b110101
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@test fn test_narrow_parse_boundaries() void = {
|
||||||
|
match (stoi16("32767", base.DEC)) {
|
||||||
|
case let v: i16 => assert(v == 32767i16);
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => abort();
|
||||||
|
};
|
||||||
|
match (stoi16("-32768", base.DEC)) {
|
||||||
|
case let v: i16 => assert(v == -32768i16);
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => abort();
|
||||||
|
};
|
||||||
|
match (stoi16("32768", base.DEC)) {
|
||||||
|
case let v: i16 => abort();
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => { };
|
||||||
|
};
|
||||||
|
match (stoi16("-32769", base.DEC)) {
|
||||||
|
case let v: i16 => abort();
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => { };
|
||||||
|
};
|
||||||
|
|
||||||
|
match (stoi8("127", base.DEC)) {
|
||||||
|
case let v: i8 => assert(v == 127i8);
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => abort();
|
||||||
|
};
|
||||||
|
match (stoi8("-128", base.DEC)) {
|
||||||
|
case let v: i8 => assert(v == -128i8);
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => abort();
|
||||||
|
};
|
||||||
|
match (stoi8("128", base.DEC)) {
|
||||||
|
case let v: i8 => abort();
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => { };
|
||||||
|
};
|
||||||
|
match (stoi8("-129", base.DEC)) {
|
||||||
|
case let v: i8 => abort();
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => { };
|
||||||
|
};
|
||||||
|
|
||||||
|
match (stou16("65535", base.DEC)) {
|
||||||
|
case let v: u16 => assert(v == 65535u16);
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => abort();
|
||||||
|
};
|
||||||
|
match (stou16("65536", base.DEC)) {
|
||||||
|
case let v: u16 => abort();
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => { };
|
||||||
|
};
|
||||||
|
match (stou8("255", base.DEC)) {
|
||||||
|
case let v: u8 => assert(v == 255u8);
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => abort();
|
||||||
|
};
|
||||||
|
match (stou8("256", base.DEC)) {
|
||||||
|
case let v: u8 => abort();
|
||||||
|
case let e: invalid => abort();
|
||||||
|
case let e: overflow => { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn test_strerror_static() void = {
|
||||||
|
let inv: invalid = 0: invalid;
|
||||||
|
let ie: error = inv;
|
||||||
|
let ia: str = strerror(ie);
|
||||||
|
let ib: str = strerror(ie);
|
||||||
|
assert(ia.ptr == ib.ptr);
|
||||||
|
assert(streq(ia, "input is not a valid number"));
|
||||||
|
|
||||||
|
let ov: overflow;
|
||||||
|
let oe: error = ov;
|
||||||
|
let oa: str = strerror(oe);
|
||||||
|
let ob: str = strerror(oe);
|
||||||
|
assert(oa.ptr == ob.ptr);
|
||||||
|
assert(streq(oa, "input number doesn't fit target type"));
|
||||||
|
};
|
||||||
|
|
||||||
// Verbatim ports of ref/hare/strconv/utos.ha:74-103 (utos/utos_bases) and
|
// Verbatim ports of ref/hare/strconv/utos.ha:74-103 (utos/utos_bases) and
|
||||||
// itos.ha:54-87 (itos/itos_bases) — Hare's format tests are flat assert
|
// itos.ha:54-87 (itos/itos_bases) — Hare's format tests are flat assert
|
||||||
// sequences too. Radix-literal inputs are written in DECIMAL (ww value
|
// sequences too. Radix-literal inputs are written in DECIMAL (ww value
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ package strconv;
|
|||||||
|
|
||||||
import ascii;
|
import ascii;
|
||||||
import math;
|
import math;
|
||||||
import os;
|
|
||||||
import strings;
|
import strings;
|
||||||
|
|
||||||
// ref/hare/strconv/ftos_ryu.ha:12. 64×64→128 result halves.
|
// ref/hare/strconv/ftos_ryu.ha:12. 64×64→128 result halves.
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ package strconv;
|
|||||||
|
|
||||||
import ascii;
|
import ascii;
|
||||||
import bytes;
|
import bytes;
|
||||||
import os;
|
|
||||||
import strings;
|
import strings;
|
||||||
|
|
||||||
// invalid — input wasn't a valid number in the requested format.
|
// invalid — input wasn't a valid number in the requested format.
|
||||||
@@ -224,14 +223,12 @@ fn parseint(s: str, b: base) ((bool, u64) | invalid | overflow) = {
|
|||||||
return i: invalid;
|
return i: invalid;
|
||||||
};
|
};
|
||||||
|
|
||||||
let old: u64 = n;
|
// Check before multiplying: a wrapped value is not necessarily
|
||||||
|
// smaller than the preceding prefix.
|
||||||
n = n * nb;
|
if (n > (18446744073709551615u64 - digit) / nb) {
|
||||||
n = n + digit;
|
|
||||||
|
|
||||||
if (n < old) {
|
|
||||||
return overflow{};
|
return overflow{};
|
||||||
};
|
};
|
||||||
|
n = n * nb + digit;
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
@@ -403,13 +400,12 @@ export fn stoz(s: str, b: base) (size | invalid | overflow) = {
|
|||||||
// both"); ftos.ww's f64tos is the live one. f32tos follows in fold-5b
|
// both"); ftos.ww's f64tos is the live one. f32tos follows in fold-5b
|
||||||
// (task #67, gated on the #143 f32-arg-push cgen fix).
|
// (task #67, gated on the #143 f32-arg-push cgen fix).
|
||||||
|
|
||||||
// strerror — convert an strconv error to a user-readable string.
|
// strerror — convert a strconv error to a user-readable string.
|
||||||
// Returns owned str; release via os.free. Mirrors Hare's
|
// The returned string has static storage and must not be freed.
|
||||||
// strconv::strerror.
|
|
||||||
export fn strerror(e: error) str = {
|
export fn strerror(e: error) str = {
|
||||||
match (e) {
|
match (e) {
|
||||||
case let v: invalid => return strings.dup("input is not a valid number");
|
case let v: invalid => return "input is not a valid number";
|
||||||
case let v: overflow => return strings.dup("input number doesn't fit target type");
|
case let v: overflow => return "input number doesn't fit target type";
|
||||||
};
|
};
|
||||||
return strings.dup("");
|
return "";
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user