authorgravatar for twostepted@gmail.comTravis Staloch <twostepted@gmail.com> 2023-11-28 13:48:49-08:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2023-12-11 17:49:22+02:00
log5bbacb0c8ce1aa59af5e898e255c544786059d06
treeadbfde693ae4952daabce2aea03a3c6216acc846
parenta817e27c7d459c8bba4b41931a842158bf3df2d0

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

1 files changed, 17 insertions(+), 6 deletions(-)

lib/std/fmt.zig+17-6
...@@ -1789,6 +1789,11 @@ test "parseInt" {...@@ -1789,6 +1789,11 @@ test "parseInt" {
1789 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0b", 0));1789 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0b", 0));
1790 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0o", 0));1790 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0o", 0));
1791 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0x", 0));1791 try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "0x", 0));
1792
1793 // edge cases which previously errored due to base overflowing T
1794 try std.testing.expectEqual(@as(i2, -2), try std.fmt.parseInt(i2, "-10", 2));
1795 try std.testing.expectEqual(@as(i4, -8), try std.fmt.parseInt(i4, "-10", 8));
1796 try std.testing.expectEqual(@as(i5, -16), try std.fmt.parseInt(i5, "-10", 16));
1792}1797}
17931798
1794fn parseWithSign(1799fn parseWithSign(
...@@ -1829,27 +1834,33 @@ fn parseWithSign(...@@ -1829,27 +1834,33 @@ fn parseWithSign(
1829 .neg => math.sub,1834 .neg => math.sub,
1830 };1835 };
18311836
1832 var x: T = 0;1837 // accumulate into U which is always 8 bits or larger. this prevents
1838 // `buf_base` from overflowing T.
1839 const info = @typeInfo(T);
1840 const U = std.meta.Int(info.Int.signedness, @max(8, info.Int.bits));
1841 var x: U = 0;
18331842
1834 if (buf_start[0] == '_' or buf_start[buf_start.len - 1] == '_') return error.InvalidCharacter;1843 if (buf_start[0] == '_' or buf_start[buf_start.len - 1] == '_') return error.InvalidCharacter;
18351844
1836 for (buf_start) |c| {1845 for (buf_start) |c| {
1837 if (c == '_') continue;1846 if (c == '_') continue;
1838 const digit = try charToDigit(c, buf_base);1847 const digit = try charToDigit(c, buf_base);
1839
1840 if (x != 0) {1848 if (x != 0) {
1841 x = try math.mul(T, x, math.cast(T, buf_base) orelse return error.Overflow);1849 x = try math.mul(U, x, math.cast(U, buf_base) orelse return error.Overflow);
1842 } else if (sign == .neg) {1850 } else if (sign == .neg) {
1843 // The first digit of a negative number.1851 // The first digit of a negative number.
1844 // Consider parsing "-4" as an i3.1852 // Consider parsing "-4" as an i3.
1845 // This should work, but positive 4 overflows i3, so we can't cast the digit to T and subtract.1853 // This should work, but positive 4 overflows i3, so we can't cast the digit to T and subtract.
1846 x = math.cast(T, -@as(i8, @intCast(digit))) orelse return error.Overflow;1854 x = math.cast(U, -@as(i8, @intCast(digit))) orelse return error.Overflow;
1847 continue;1855 continue;
1848 }1856 }
1849 x = try add(T, x, math.cast(T, digit) orelse return error.Overflow);1857 x = try add(U, x, math.cast(U, digit) orelse return error.Overflow);
1850 }1858 }
18511859
1852 return x;1860 return if (T == U)
1861 x
1862 else
1863 math.cast(T, x) orelse return error.Overflow;
1853}1864}
18541865
1855/// Parses the string `buf` as unsigned representation in the specified base1866/// Parses the string `buf` as unsigned representation in the specified base