From 5bbacb0c8ce1aa59af5e898e255c544786059d06 Mon Sep 17 00:00:00 2001 From: Travis Staloch <1562827+travisstaloch@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:48:49 -0800 Subject: [PATCH] fmt.parseWithSign(): prevent edge case overflows previously when T was smaller than 8 bits, it was possible for base to overflow T (because base is a u8). this patch prevents this by accumulating into a U rather than T which is at least 8 bits wide. this is the best way i could think of to maintain performance. this will only affect parsing of integers less than 8 bits by adding one additional cast at return. additionally, this patch may be slightly slower to return an error for integers less than 8 bits which overflow because it will accumulate a few more digits before the overflow check at return. * add tests which previously overflowed when they shouldn't have closes #18157 --- lib/std/fmt.zig | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index c93b8363a7..e7b66dc703 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1789,6 +1789,11 @@ test "parseInt" { try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0b", 0)); try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0o", 0)); try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0x", 0)); + + // edge cases which previously errored due to base overflowing T + try std.testing.expectEqual(@as(i2, -2), try std.fmt.parseInt(i2, "-10", 2)); + try std.testing.expectEqual(@as(i4, -8), try std.fmt.parseInt(i4, "-10", 8)); + try std.testing.expectEqual(@as(i5, -16), try std.fmt.parseInt(i5, "-10", 16)); } fn parseWithSign( @@ -1829,27 +1834,33 @@ fn parseWithSign( .neg => math.sub, }; - var x: T = 0; + // accumulate into U which is always 8 bits or larger. this prevents + // `buf_base` from overflowing T. + const info = @typeInfo(T); + const U = std.meta.Int(info.Int.signedness, @max(8, info.Int.bits)); + var x: U = 0; if (buf_start[0] == '_' or buf_start[buf_start.len - 1] == '_') return error.InvalidCharacter; for (buf_start) |c| { if (c == '_') continue; const digit = try charToDigit(c, buf_base); - if (x != 0) { - x = try math.mul(T, x, math.cast(T, buf_base) orelse return error.Overflow); + x = try math.mul(U, x, math.cast(U, buf_base) orelse return error.Overflow); } else if (sign == .neg) { // The first digit of a negative number. // Consider parsing "-4" as an i3. // This should work, but positive 4 overflows i3, so we can't cast the digit to T and subtract. - x = math.cast(T, -@as(i8, @intCast(digit))) orelse return error.Overflow; + x = math.cast(U, -@as(i8, @intCast(digit))) orelse return error.Overflow; continue; } - x = try add(T, x, math.cast(T, digit) orelse return error.Overflow); + x = try add(U, x, math.cast(U, digit) orelse return error.Overflow); } - return x; + return if (T == U) + x + else + math.cast(T, x) orelse return error.Overflow; } /// Parses the string `buf` as unsigned representation in the specified base