fix(std-io): make uint128 decimal formatting safe (#2924)

* fix(std-io): make uint128 decimal formatting safe and add all-base
numeric coverage
---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
This commit is contained in:
Fernando López Guevara
2026-02-11 19:50:16 -03:00
committed by GitHub
parent 7665720264
commit f079fa82b2
5 changed files with 243 additions and 34 deletions

View File

@@ -13,8 +13,7 @@ interface Printable
fn usz? to_format(Formatter* formatter) @optional;
}
faultdef BUFFER_EXCEEDED, INTERNAL_BUFFER_EXCEEDED, INVALID_FORMAT,
NOT_ENOUGH_ARGUMENTS, INVALID_ARGUMENT;
faultdef BUFFER_EXCEEDED, INTERNAL_BUFFER_EXCEEDED, INVALID_FORMAT, NOT_ENOUGH_ARGUMENTS, INVALID_ARGUMENT;
alias OutputFn = fn void?(void* buffer, char c);
alias FloatType = double;
@@ -34,14 +33,14 @@ macro bool is_struct_with_default_print($Type)
*>
macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
{
var $Type = $typeof(value);
usz total = f.print("{ ")!;
$foreach $i, $member : $Type.membersof:
$if $i > 0:
total += f.print(", ")!;
$endif
$if $member.nameof != "":
total += f.printf("%s: ", $member.nameof)!;
var $Type = $typeof(value);
usz total = f.print("{ ")!;
$foreach $i, $member : $Type.membersof:
$if $i > 0:
total += f.print(", ")!;
$endif
$if $member.nameof != "":
total += f.printf("%s: ", $member.nameof)!;
$endif
$if ($force_dump &&& ($member.typeid.kindof == STRUCT || $member.typeid.kindof == BITSTRUCT)) |||
is_struct_with_default_print($member.typeid):
@@ -351,12 +350,12 @@ macro usz? @wrap_bad(Formatter* f, #action)
switch (err)
{
case BUFFER_EXCEEDED:
case INTERNAL_BUFFER_EXCEEDED:
return f.first_err(err)~;
default:
case INTERNAL_BUFFER_EXCEEDED:
return f.first_err(err)~;
default:
err = f.first_err(INVALID_ARGUMENT);
f.out_substr("<INVALID>")!;
return err~;
f.out_substr("<INVALID>")!;
return err~;
}
}
return len;
@@ -489,7 +488,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
self.flags.uppercase = true;
nextcase;
case 'h':
char[] out @noinit;
char[] out @noinit;
switch (current.type)
{
case char[]:

View File

@@ -541,7 +541,6 @@ const char[201] DIGIT_PAIRS @private =
"08182838485868788898"
"09192939495969798999";
fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
{
char[PRINTF_NTOA_BUFFER_SIZE] buf @noinit;
@@ -574,10 +573,10 @@ fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
while (value >= 10)
{
if (len + 1 >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
char digit = (char)(value % 100);
char digit = (char)(value % 100U);
buf[len:2] = DIGIT_PAIRS[2 * digit:2];
len += 2;
value /= 100;
value /= 100U;
}
if (value > 0)
{
@@ -593,16 +592,16 @@ fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
value >>= 4;
}
while (value);
case 8:
do
{
if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
buf[len++] = '0' + (char)value & 0x7;
value >>= 3;
}
while (value);
default:
unreachable();
case 8:
do
{
if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
buf[len++] = '0' + (char)value & 0x7;
value >>= 3;
}
while (value);
default:
unreachable();
}
}
return self.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
@@ -684,9 +683,9 @@ fn usz? Formatter.out_char(&self, any arg) @private
usz len = 1;
// pre padding
if (!self.flags.left)
{
len += self.pad(' ', self.width, len)!;
}
{
len += self.pad(' ', self.width, len)!;
}
// char output
Char32 c = types::any_to_int(arg, uint) ?? 0xFFFD;
@@ -708,8 +707,8 @@ fn usz? Formatter.out_char(&self, any arg) @private
self.out((char)(0x80 | (c & 0x3F)))!;
}
if (self.flags.left)
{
len += self.pad(' ', self.width, len)!;
{
len += self.pad(' ', self.width, len)!;
}
return len;
}

View File

@@ -32,6 +32,7 @@
- Reallocating overaligned memory with the LibcAllocator was unsafe.
- Using [] or .foo on $$ functions would not raise error but instead crash
- Improved underlining errors/warnings when unicode is used. #2887
- Fix std::io::Formatter integer issue for large uint128 decimal values.
## 0.7.9 Change list

View File

@@ -8,6 +8,13 @@ fn usz? Foo.to_format(&self, Formatter *f) @dynamic
return f.printf("Foo[%d]", self.a);
}
enum FormatterEnum
{
ABC,
BCD,
EFG,
}
fn void test_ref() @test
{
Foo* f = &&(Foo){ 8 };
@@ -42,3 +49,25 @@ fn void test_ref() @test
defer free(s3);
assert(s3 == "1.235e+04 8.765e-04 1.235e+06 12.35 12.30 1.000 3.141592653589793");
}
fn void test_uint128_decimal_formatting() @test
{
uint128 v = 0xFFEEDDCC_BBAA9988_77665544_33221100;
String s = string::format(mem, "%d", v);
defer free(s);
test::eq(s, "340193404210632335760508365704335069440");
String s2 = string::format(mem, "%d", uint128.max);
defer free(s2);
test::eq(s2, "340282366920938463463374607431768211455");
}
fn void test_mixed_format_with_uint128() @test
{
int a = 1234;
uint128 b = 0xFFEEDDCC_BBAA9988_77665544_33221100;
FormatterEnum e = BCD;
String s = string::format(mem, "a: %s, b: %d, foo: %s", a, b, e);
defer free(s);
test::eq(s, "a: 1234, b: 340193404210632335760508365704335069440, foo: BCD");
}

View File

@@ -0,0 +1,181 @@
module std::io;
macro void expect_d($value, $expected, $label)
{
String s = string::format(mem, "%d", $value);
defer free(s);
assert(s == $expected, "got '%s'; want '%s' for %s", s, $expected, $label);
}
macro void expect_x($value, $expected, $label)
{
String s = string::format(mem, "%x", $value);
defer free(s);
assert(s == $expected, "got '%s'; want '%s' for %s", s, $expected, $label);
}
macro void expect_o($value, $expected, $label)
{
String s = string::format(mem, "%o", $value);
defer free(s);
assert(s == $expected, "got '%s'; want '%s' for %s", s, $expected, $label);
}
macro void expect_b($value, $expected, $label)
{
String s = string::format(mem, "%b", $value);
defer free(s);
assert(s == $expected, "got '%s'; want '%s' for %s", s, $expected, $label);
}
macro void expect_all_bases($value, $d, $x, $o, $b, $label)
{
expect_d($value, $d, $label);
expect_x($value, $x, $label);
expect_o($value, $o, $label);
expect_b($value, $b, $label);
}
fn void printf_numbers_all_bases_per_type() @test
{
expect_all_bases((ichar)-42, "-42", "-2a", "-52", "-101010", "ichar");
expect_all_bases((short)-42, "-42", "-2a", "-52", "-101010", "short");
expect_all_bases((int)-42, "-42", "-2a", "-52", "-101010", "int");
expect_all_bases((long)-42, "-42", "-2a", "-52", "-101010", "long");
expect_all_bases((int128)-42, "-42", "-2a", "-52", "-101010", "int128");
expect_all_bases((iptr)-42, "-42", "-2a", "-52", "-101010", "iptr");
expect_all_bases((isz)-42, "-42", "-2a", "-52", "-101010", "isz");
expect_all_bases((char)42, "42", "2a", "52", "101010", "char");
expect_all_bases((ushort)42, "42", "2a", "52", "101010", "ushort");
expect_all_bases((uint)42, "42", "2a", "52", "101010", "uint");
expect_all_bases((ulong)42, "42", "2a", "52", "101010", "ulong");
expect_all_bases((uint128)42, "42", "2a", "52", "101010", "uint128");
expect_all_bases((uptr)42, "42", "2a", "52", "101010", "uptr");
expect_all_bases((usz)42, "42", "2a", "52", "101010", "usz");
}
fn void printf_numbers_decimal_ranges() @test
{
expect_d((ichar)-128, "-128", "ichar min");
expect_d((ichar)127, "127", "ichar max");
expect_d((short)-32768, "-32768", "short min");
expect_d((short)32767, "32767", "short max");
expect_d(int.min, "-2147483648", "int min");
expect_d(int.max, "2147483647", "int max");
expect_d(long.min, "-9223372036854775808", "long min");
expect_d(long.max, "9223372036854775807", "long max");
expect_d(int128.min, "-170141183460469231731687303715884105728", "int128 min");
expect_d(int128.max, "170141183460469231731687303715884105727", "int128 max");
expect_d((char)0, "0", "char min");
expect_d((char)255, "255", "char max");
expect_d((ushort)0, "0", "ushort min");
expect_d(ushort.max, "65535", "ushort max");
expect_d((uint)0, "0", "uint min");
expect_d(uint.max, "4294967295", "uint max");
expect_d((ulong)0, "0", "ulong min");
expect_d(ulong.max, "18446744073709551615", "ulong max");
expect_d((uint128)0, "0", "uint128 min");
expect_d(uint128.max, "340282366920938463463374607431768211455", "uint128 max");
if (bitsizeof(iptr) == 64)
{
expect_d(iptr.min, "-9223372036854775808", "iptr min");
expect_d(iptr.max, "9223372036854775807", "iptr max");
}
else
{
expect_d(iptr.min, "-2147483648", "iptr min");
expect_d(iptr.max, "2147483647", "iptr max");
}
if (bitsizeof(isz) == 64)
{
expect_d(isz.min, "-9223372036854775808", "isz min");
expect_d(isz.max, "9223372036854775807", "isz max");
}
else
{
expect_d(isz.min, "-2147483648", "isz min");
expect_d(isz.max, "2147483647", "isz max");
}
if (bitsizeof(uptr) == 64)
{
expect_d((uptr)0, "0", "uptr min");
expect_d(uptr.max, "18446744073709551615", "uptr max");
}
else
{
expect_d((uptr)0, "0", "uptr min");
expect_d(uptr.max, "4294967295", "uptr max");
}
if (bitsizeof(usz) == 64)
{
expect_d((usz)0, "0", "usz min");
expect_d(usz.max, "18446744073709551615", "usz max");
}
else
{
expect_d((usz)0, "0", "usz min");
expect_d(usz.max, "4294967295", "usz max");
}
expect_d(ulong.max, "18446744073709551615", "ulong max fast decimal path");
expect_d((uint128)ulong.max + 1, "18446744073709551616", "uint128 safe decimal path");
}
fn void printf_numbers_radix_formats() @test
{
expect_x((char)255, "ff", "char max hex");
expect_o((char)255, "377", "char max oct");
expect_b((char)255, "11111111", "char max bin");
expect_x((ushort)65535, "ffff", "ushort max hex");
expect_o((ushort)65535, "177777", "ushort max oct");
expect_b((ushort)65535, "1111111111111111", "ushort max bin");
expect_x(uint.max, "ffffffff", "uint max hex");
expect_o(uint.max, "37777777777", "uint max oct");
expect_b(uint.max, "11111111111111111111111111111111", "uint max bin");
expect_x(ulong.max, "ffffffffffffffff", "ulong max hex");
expect_o(ulong.max, "1777777777777777777777", "ulong max oct");
expect_x(uint128.max, "ffffffffffffffffffffffffffffffff", "uint128 max hex");
expect_o((uint128)0xFFEEDDCC_BBAA9988_77665544_33221100,
"3777355671456725231420735462524206310410400",
"uint128 sample oct");
expect_x((uint128)0xFFEEDDCC_BBAA9988_77665544_33221100,
"ffeeddccbbaa99887766554433221100",
"uint128 sample hex");
if (bitsizeof(uptr) == 64)
{
expect_x(uptr.max, "ffffffffffffffff", "uptr max hex");
}
else
{
expect_x(uptr.max, "ffffffff", "uptr max hex");
}
if (bitsizeof(usz) == 64)
{
expect_x(usz.max, "ffffffffffffffff", "usz max hex");
}
else
{
expect_x(usz.max, "ffffffff", "usz max hex");
}
}