Skip to content

Commit b0b312d

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 b0b312d

File tree

2 files changed

+67
-13
lines changed

2 files changed

+67
-13
lines changed

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

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,23 +165,24 @@ impl ExtendedParser for u64 {
165165
ExtendedBigDecimal::BigDecimal(bd) => {
166166
let (digits, scale) = bd.into_bigint_and_scale();
167167
if scale == 0 {
168-
let negative = digits.sign() == Sign::Minus;
168+
let (sign, digits) = digits.into_parts();
169+
169170
match u64::try_from(digits) {
170-
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-
})),
171+
Ok(i) => {
172+
if sign == Sign::Minus {
173+
Ok(!i + 1)
174+
} else {
175+
Ok(i)
176+
}
177+
}
178+
_ => Err(ExtendedParserError::Overflow(u64::MAX)),
177179
}
178180
} else {
179181
// Should not happen.
180182
Err(ExtendedParserError::NotNumeric)
181183
}
182184
}
183-
// TODO: Handle -0 too #7488
184-
// No other case should not happen.
185+
ExtendedBigDecimal::MinusZero => Ok(0),
185186
_ => Err(ExtendedParserError::NotNumeric),
186187
}
187188
}
@@ -500,10 +501,28 @@ mod tests {
500501
fn test_decimal_u64() {
501502
assert_eq!(Ok(123), u64::extended_parse("123"));
502503
assert_eq!(Ok(u64::MAX), u64::extended_parse(&format!("{}", u64::MAX)));
503-
// TODO: We should wrap around here #7488
504+
assert_eq!(Ok(0), u64::extended_parse("-0"));
505+
assert_eq!(Ok(u64::MAX), u64::extended_parse("-1"));
506+
assert_eq!(
507+
Ok(u64::MAX / 2 + 1),
508+
u64::extended_parse("-9223372036854775808") // i64::MIN
509+
);
510+
assert_eq!(
511+
Ok(1123372036854675616),
512+
u64::extended_parse("-17323372036854876000") // 2*i64::MIN
513+
);
514+
assert_eq!(Ok(1), u64::extended_parse("-18446744073709551615")); // -u64::MAX
515+
assert!(matches!(
516+
u64::extended_parse("-18446744073709551616"), // -u64::MAX - 1
517+
Err(ExtendedParserError::Overflow(u64::MAX))
518+
));
519+
assert!(matches!(
520+
u64::extended_parse("-92233720368547758150"),
521+
Err(ExtendedParserError::Overflow(u64::MAX))
522+
));
504523
assert!(matches!(
505-
u64::extended_parse("-123"),
506-
Err(ExtendedParserError::Overflow(0))
524+
u64::extended_parse("-170141183460469231731687303715884105729"),
525+
Err(ExtendedParserError::Overflow(u64::MAX))
507526
));
508527
assert!(matches!(
509528
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)