Skip to content

Commit aa2cc4c

Browse files
committed
fix: correctly parse i128::MIN from text Ion (#1016)
* fix: correctly parse `i128::MIN` from text Ion The text Ion parser rejected `i128::MIN` (`-170141183460469231731687303715884105728`) as out of range, even though it is a valid `i128` value. This happened because the parser stripped the sign, parsed the magnitude as `i128`, then negated. Since the magnitude of `i128::MIN` is `i128::MAX + 1`, parsing it as `i128` failed. Fixed by parsing the magnitude as `u128` with proper range validation, then using `wrapping_neg()` to correctly convert to the negative `i128` value. Added boundary value tests for `i32`, `i64`, and `i128` min/max values. * test: add overflow tests for `i128` boundary parsing Add `read_ints_overflow` test to verify the parser correctly rejects integers outside the `i128` range: - `i128::MAX + 1` (positive overflow) - `i128::MIN - 1` (negative overflow)
1 parent bbcd84c commit aa2cc4c

1 file changed

Lines changed: 66 additions & 12 deletions

File tree

‎src/lazy/text/matched.rs‎

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -188,28 +188,44 @@ impl MatchedInt {
188188
// Note: This UTF-8 validation step should be unnecessary as the parser only recognizes
189189
// ASCII integer characters. If this shows up in profiling, we could consider skipping it.
190190
let text = sanitized.as_utf8(matched_input.offset())?;
191-
let int: Int = match i128::from_str_radix(text, self.radix()) {
192-
Ok(i) => i.into(),
191+
// Parse as u128 to handle the full range including i128::MIN, whose magnitude
192+
// exceeds i128::MAX by one.
193+
let magnitude = match u128::from_str_radix(text, self.radix()) {
194+
Ok(m) => m,
193195
Err(parse_int_error) => {
194196
debug_assert!(
195-
// `from_str_radix` can fail for a variety of reasons, but our rules for matching an
196-
// int rule out most of them (empty str, invalid digit, etc). The only ones that should
197-
// happen are overflow and underflow. In those cases, we fall back to using `BigInt`.
198-
parse_int_error.kind() == &IntErrorKind::NegOverflow
199-
|| parse_int_error.kind() == &IntErrorKind::PosOverflow
197+
// `from_str_radix` can fail for a variety of reasons, but our rules for
198+
// matching an int rule out most of them (empty str, invalid digit, etc).
199+
// The only one that should happen for u128 is overflow.
200+
parse_int_error.kind() == &IntErrorKind::PosOverflow
200201
);
201202
return cold_path!(IonResult::decoding_error(format!(
202-
"encountered an int whose value was exceeded the supported range: '{}'",
203+
"encountered an int whose value exceeded the supported range: '{}'",
203204
std::str::from_utf8(matched_input.bytes()).unwrap_or("invalid UTF-8")
204205
)));
205206
}
206207
};
207208

208-
if self.is_negative {
209-
Ok(-int)
209+
const I128_MIN_MAGNITUDE: u128 = i128::MIN.unsigned_abs();
210+
let int: Int = if self.is_negative {
211+
if magnitude > I128_MIN_MAGNITUDE {
212+
return cold_path!(IonResult::decoding_error(format!(
213+
"encountered an int whose value exceeded the supported range: '{}'",
214+
std::str::from_utf8(matched_input.bytes()).unwrap_or("invalid UTF-8")
215+
)));
216+
}
217+
(magnitude.wrapping_neg() as i128).into()
210218
} else {
211-
Ok(int)
212-
}
219+
if magnitude > i128::MAX as u128 {
220+
return cold_path!(IonResult::decoding_error(format!(
221+
"encountered an int whose value exceeded the supported range: '{}'",
222+
std::str::from_utf8(matched_input.bytes()).unwrap_or("invalid UTF-8")
223+
)));
224+
}
225+
(magnitude as i128).into()
226+
};
227+
228+
Ok(int)
213229
}
214230
}
215231

@@ -1248,6 +1264,21 @@ mod tests {
12481264
"-1234567890_1234567890_1234567890",
12491265
Int::from(-1234567890_1234567890_1234567890i128),
12501266
),
1267+
// i32 boundary values
1268+
("2147483647", Int::from(i32::MAX)),
1269+
("-2147483648", Int::from(i32::MIN)),
1270+
// i64 boundary values
1271+
("9223372036854775807", Int::from(i64::MAX)),
1272+
("-9223372036854775808", Int::from(i64::MIN)),
1273+
// i128 boundary values
1274+
(
1275+
"170141183460469231731687303715884105727",
1276+
Int::from(i128::MAX),
1277+
),
1278+
(
1279+
"-170141183460469231731687303715884105728",
1280+
Int::from(i128::MIN),
1281+
),
12511282
];
12521283

12531284
for (input, expected) in tests {
@@ -1256,6 +1287,29 @@ mod tests {
12561287
Ok(())
12571288
}
12581289

1290+
#[test]
1291+
fn read_ints_overflow() {
1292+
fn expect_overflow(data: &str) {
1293+
let encoding_context = EncodingContext::empty();
1294+
let context = encoding_context.get_ref();
1295+
let mut buffer = TextBuffer::new(context, data.as_bytes());
1296+
let matched = peek(TextBuffer::match_int).parse_next(&mut buffer).unwrap();
1297+
let result = matched.read(buffer);
1298+
assert!(result.is_err(), "Expected overflow error for '{data}'");
1299+
}
1300+
1301+
let tests = [
1302+
// i128::MAX + 1 (positive overflow)
1303+
"170141183460469231731687303715884105728",
1304+
// i128::MIN - 1 (negative overflow)
1305+
"-170141183460469231731687303715884105729",
1306+
];
1307+
1308+
for input in tests {
1309+
expect_overflow(input);
1310+
}
1311+
}
1312+
12591313
#[test]
12601314
fn read_timestamps() -> IonResult<()> {
12611315
fn expect_timestamp(data: &str, expected: Timestamp) {

0 commit comments

Comments
 (0)