Skip to content

Commit eda5b18

Browse files
printf: make negative values wrap around with unsigned/hex format
To convert from negative to unsigned with overflow, we get the two's complement representation of the absolute (unsigned) value.
1 parent e6ff6d5 commit eda5b18

File tree

2 files changed

+69
-12
lines changed

2 files changed

+69
-12
lines changed

src/uucore/src/lib/features/parser/num_parser.rs

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -166,22 +166,26 @@ impl ExtendedParser for u64 {
166166
let (digits, scale) = bd.into_bigint_and_scale();
167167
if scale == 0 {
168168
let negative = digits.sign() == Sign::Minus;
169-
match u64::try_from(digits) {
169+
170+
match u64::try_from(digits.clone()) {
170171
Ok(i) => Ok(i),
171-
_ => Err(ExtendedParserError::Overflow(if negative {
172-
// TODO: We should wrap around here #7488
173-
0
174-
} else {
175-
u64::MAX
176-
})),
172+
_ => {
173+
if negative {
174+
match u64::try_from(digits.into_parts().1) {
175+
Ok(i) => Ok(!i + 1), // two's complement representation
176+
_ => Err(ExtendedParserError::Overflow(u64::MAX)),
177+
}
178+
} else {
179+
Err(ExtendedParserError::Overflow(u64::MAX))
180+
}
181+
}
177182
}
178183
} else {
179184
// Should not happen.
180185
Err(ExtendedParserError::NotNumeric)
181186
}
182187
}
183-
// TODO: Handle -0 too #7488
184-
// No other case should not happen.
188+
ExtendedBigDecimal::MinusZero => Ok(0),
185189
_ => Err(ExtendedParserError::NotNumeric),
186190
}
187191
}
@@ -500,10 +504,28 @@ mod tests {
500504
fn test_decimal_u64() {
501505
assert_eq!(Ok(123), u64::extended_parse("123"));
502506
assert_eq!(Ok(u64::MAX), u64::extended_parse(&format!("{}", u64::MAX)));
503-
// TODO: We should wrap around here #7488
507+
assert_eq!(Ok(0), u64::extended_parse("-0"));
508+
assert_eq!(Ok(u64::MAX), u64::extended_parse("-1"));
509+
assert_eq!(
510+
Ok(u64::MAX / 2 + 1),
511+
u64::extended_parse("-9223372036854775808") // i64::MIN
512+
);
513+
assert_eq!(
514+
Ok(1123372036854675616),
515+
u64::extended_parse("-17323372036854876000") // 2*i64::MIN
516+
);
517+
assert_eq!(Ok(1), u64::extended_parse("-18446744073709551615")); // -u64::MAX
518+
assert!(matches!(
519+
u64::extended_parse("-18446744073709551616"), // -u64::MAX - 1
520+
Err(ExtendedParserError::Overflow(u64::MAX))
521+
));
522+
assert!(matches!(
523+
u64::extended_parse("-92233720368547758150"),
524+
Err(ExtendedParserError::Overflow(u64::MAX))
525+
));
504526
assert!(matches!(
505-
u64::extended_parse("-123"),
506-
Err(ExtendedParserError::Overflow(0))
527+
u64::extended_parse("-170141183460469231731687303715884105729"),
528+
Err(ExtendedParserError::Overflow(u64::MAX))
507529
));
508530
assert!(matches!(
509531
u64::extended_parse(""),

tests/by-util/test_printf.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,41 @@ fn partial_integer() {
668668
.stderr_is("printf: '42x23': value not completely converted\n");
669669
}
670670

671+
#[test]
672+
fn unsigned_hex_negative_wraparound() {
673+
new_ucmd!()
674+
.args(&["%x", "-0b100"])
675+
.succeeds()
676+
.stdout_only("fffffffffffffffc");
677+
678+
new_ucmd!()
679+
.args(&["%x", "-0100"])
680+
.succeeds()
681+
.stdout_only("ffffffffffffffc0");
682+
683+
new_ucmd!()
684+
.args(&["%x", "-100"])
685+
.succeeds()
686+
.stdout_only("ffffffffffffff9c");
687+
688+
new_ucmd!()
689+
.args(&["%x", "-0x100"])
690+
.succeeds()
691+
.stdout_only("ffffffffffffff00");
692+
693+
new_ucmd!()
694+
.args(&["%x", "-92233720368547758150"])
695+
.fails_with_code(1)
696+
.stdout_is("ffffffffffffffff")
697+
.stderr_is("printf: '-92233720368547758150': Numerical result out of range\n");
698+
699+
new_ucmd!()
700+
.args(&["%u", "-1002233720368547758150"])
701+
.fails_with_code(1)
702+
.stdout_is("18446744073709551615")
703+
.stderr_is("printf: '-1002233720368547758150': Numerical result out of range\n");
704+
}
705+
671706
#[test]
672707
fn test_overflow() {
673708
new_ucmd!()

0 commit comments

Comments
 (0)