Skip to content

Commit 153f464

Browse files
committed
uucore: parser: num_parser: Return error if no digit has been parsed
This is mostly important when parsing digits like `.` and `0x.` where we should return an error. Fixes #7684.
1 parent 148b341 commit 153f464

File tree

1 file changed

+52
-6
lines changed

1 file changed

+52
-6
lines changed

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

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -456,12 +456,12 @@ pub(crate) fn parse<'a>(
456456

457457
// Parse the integral part of the number
458458
let mut chars = rest.chars().enumerate().fuse().peekable();
459-
let mut digits = BigUint::zero();
459+
let mut digits: Option<BigUint> = None;
460460
let mut scale = 0u64;
461461
let mut exponent = BigInt::zero();
462462
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
463463
chars.next();
464-
digits = digits * base as u8 + d;
464+
digits = Some(digits.unwrap_or_default() * base as u8 + d);
465465
}
466466

467467
// Parse fractional/exponent part of the number for supported bases.
@@ -472,7 +472,7 @@ pub(crate) fn parse<'a>(
472472
chars.next();
473473
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
474474
chars.next();
475-
(digits, scale) = (digits * base as u8 + d, scale + 1);
475+
(digits, scale) = (Some(digits.unwrap_or_default() * base as u8 + d), scale + 1);
476476
}
477477
}
478478

@@ -509,15 +509,17 @@ pub(crate) fn parse<'a>(
509509
}
510510
}
511511

512-
// If nothing has been parsed, check if this is a special value, or declare the parsing unsuccessful
513-
if let Some((0, _)) = chars.peek() {
512+
// If no digit has been parsed, check if this is a special value, or declare the parsing unsuccessful
513+
if digits.is_none() {
514514
return if target == ParseTarget::Integral {
515515
Err(ExtendedParserError::NotNumeric)
516516
} else {
517517
parse_special_value(unsigned, negative, allowed_suffixes)
518518
};
519519
}
520520

521+
let mut digits = digits.unwrap();
522+
521523
if let Some((_, ch)) = chars.peek() {
522524
if let Some(times) = allowed_suffixes
523525
.iter()
@@ -529,7 +531,8 @@ pub(crate) fn parse<'a>(
529531
}
530532
}
531533

532-
let ebd_result = construct_extended_big_decimal(digits, negative, base, scale, exponent);
534+
let ebd_result =
535+
construct_extended_big_decimal(digits, negative, base, scale, exponent);
533536

534537
// Return what has been parsed so far. If there are extra characters, mark the
535538
// parsing as a partial match.
@@ -625,6 +628,15 @@ mod tests {
625628
i64::extended_parse(&format!("{}", i64::MIN as i128 - 1)),
626629
Err(ExtendedParserError::Overflow(i64::MIN))
627630
));
631+
632+
assert!(matches!(
633+
i64::extended_parse(""),
634+
Err(ExtendedParserError::NotNumeric)
635+
));
636+
assert!(matches!(
637+
i64::extended_parse("."),
638+
Err(ExtendedParserError::NotNumeric)
639+
));
628640
}
629641

630642
#[test]
@@ -811,6 +823,16 @@ mod tests {
811823
ExtendedBigDecimal::extended_parse(&format!("-0e{}", i64::MIN + 2)),
812824
Ok(ExtendedBigDecimal::MinusZero)
813825
);
826+
827+
/* Invalid numbers */
828+
assert_eq!(
829+
Err(ExtendedParserError::NotNumeric),
830+
ExtendedBigDecimal::extended_parse("")
831+
);
832+
assert_eq!(
833+
Err(ExtendedParserError::NotNumeric),
834+
ExtendedBigDecimal::extended_parse(".")
835+
);
814836
}
815837

816838
#[test]
@@ -887,6 +909,16 @@ mod tests {
887909
ExtendedBigDecimal::MinusZero
888910
))
889911
));
912+
913+
// TODO: GNU coreutils treats these 2 as partial match.
914+
assert_eq!(
915+
Err(ExtendedParserError::NotNumeric),
916+
ExtendedBigDecimal::extended_parse("0x")
917+
);
918+
assert_eq!(
919+
Err(ExtendedParserError::NotNumeric),
920+
ExtendedBigDecimal::extended_parse("0x.")
921+
);
890922
}
891923

892924
#[test]
@@ -935,6 +967,20 @@ mod tests {
935967
ebd == ExtendedBigDecimal::zero(),
936968
_ => false,
937969
});
970+
971+
assert!(match ExtendedBigDecimal::extended_parse("0b") {
972+
Err(ExtendedParserError::PartialMatch(ebd, "b")) => ebd == ExtendedBigDecimal::zero(),
973+
_ => false,
974+
});
975+
assert!(match ExtendedBigDecimal::extended_parse("0b.") {
976+
Err(ExtendedParserError::PartialMatch(ebd, "b.")) => ebd == ExtendedBigDecimal::zero(),
977+
_ => false,
978+
});
979+
// TODO: GNU coreutils treats this as partial match.
980+
assert_eq!(
981+
Err(ExtendedParserError::NotNumeric),
982+
u64::extended_parse("0b")
983+
);
938984
}
939985

940986
#[test]

0 commit comments

Comments
 (0)