Skip to content

Commit 6bfbfc1

Browse files
committed
Clamp numbers on overflow when parsing. Fix #35, fix #72.
1 parent afa457e commit 6bfbfc1

File tree

2 files changed

+129
-33
lines changed

2 files changed

+129
-33
lines changed

src/tests.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,62 @@ fn line_numbers() {
409409
assert_eq!(input.next_including_whitespace(), Err(()));
410410
}
411411

412+
#[test]
413+
fn overflow() {
414+
use std::iter::repeat;
415+
use std::f32;
416+
417+
let css = r"
418+
2147483646
419+
2147483647
420+
2147483648
421+
10000000000000
422+
1000000000000000000000000000000000000000
423+
1{309 zeros}
424+
425+
-2147483647
426+
-2147483648
427+
-2147483649
428+
-10000000000000
429+
-1000000000000000000000000000000000000000
430+
-1{309 zeros}
431+
432+
3.30282347e+38
433+
3.40282347e+38
434+
3.402824e+38
435+
436+
-3.30282347e+38
437+
-3.40282347e+38
438+
-3.402824e+38
439+
440+
".replace("{309 zeros}", &repeat('0').take(309).collect::<String>());
441+
let mut input = Parser::new(&css);
442+
443+
assert_eq!(input.expect_integer(), Ok(2147483646));
444+
assert_eq!(input.expect_integer(), Ok(2147483647));
445+
assert_eq!(input.expect_integer(), Ok(2147483647)); // Clamp on overflow
446+
assert_eq!(input.expect_integer(), Ok(2147483647));
447+
assert_eq!(input.expect_integer(), Ok(2147483647));
448+
assert_eq!(input.expect_integer(), Ok(2147483647));
449+
450+
assert_eq!(input.expect_integer(), Ok(-2147483647));
451+
assert_eq!(input.expect_integer(), Ok(-2147483648));
452+
assert_eq!(input.expect_integer(), Ok(-2147483648)); // Clamp on overflow
453+
assert_eq!(input.expect_integer(), Ok(-2147483648));
454+
assert_eq!(input.expect_integer(), Ok(-2147483648));
455+
assert_eq!(input.expect_integer(), Ok(-2147483648));
456+
457+
assert_eq!(input.expect_number(), Ok(3.30282347e+38));
458+
assert_eq!(input.expect_number(), Ok(f32::MAX));
459+
assert_eq!(input.expect_number(), Ok(f32::INFINITY));
460+
assert!(f32::MAX != f32::INFINITY);
461+
462+
assert_eq!(input.expect_number(), Ok(-3.30282347e+38));
463+
assert_eq!(input.expect_number(), Ok(f32::MIN));
464+
assert_eq!(input.expect_number(), Ok(f32::NEG_INFINITY));
465+
assert!(f32::MIN != f32::NEG_INFINITY);
466+
}
467+
412468
#[test]
413469
fn line_delimited() {
414470
let mut input = Parser::new(" { foo ; bar } baz;,");

src/tokenizer.rs

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
use std::ops::Range;
88
use std::cell::Cell;
99
use std::char;
10-
use std::num;
10+
use std::num::{self, Float};
1111
use std::ascii::AsciiExt;
1212
use std::borrow::{Cow, ToOwned};
1313
use std::borrow::Cow::{Owned, Borrowed};
14+
use std::i32;
1415

1516
use self::Token::*;
1617

@@ -638,32 +639,51 @@ fn consume_name<'a>(tokenizer: &mut Tokenizer<'a>) -> Cow<'a, str> {
638639
}
639640

640641

641-
fn consume_digits(tokenizer: &mut Tokenizer) {
642-
while !tokenizer.is_eof() {
643-
match tokenizer.next_char() {
644-
'0'...'9' => tokenizer.advance(1),
645-
_ => break
646-
}
647-
}
648-
}
649-
650-
651642
fn consume_numeric<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> {
652643
// Parse [+-]?\d*(\.\d+)?([eE][+-]?\d+)?
653644
// But this is always called so that there is at least one digit in \d*(\.\d+)?
654-
let start_pos = tokenizer.position();
655-
let mut is_integer = true;
656-
let has_sign = matches!(tokenizer.next_char(), '-' | '+');
645+
646+
// Do all the math in f64 so that large numbers overflow to +/-inf
647+
// and i32::{MIN, MAX} are within range.
648+
649+
let (has_sign, sign) = match tokenizer.next_char() {
650+
'-' => (true, -1.),
651+
'+' => (true, 1.),
652+
_ => (false, 1.),
653+
};
657654
if has_sign {
658655
tokenizer.advance(1);
659656
}
660-
consume_digits(tokenizer);
657+
658+
let mut integral_part: f64 = 0.;
659+
while let Some(digit) = tokenizer.next_char().to_digit(10) {
660+
integral_part = integral_part * 10. + digit as f64;
661+
tokenizer.advance(1);
662+
if tokenizer.is_eof() {
663+
break
664+
}
665+
}
666+
667+
let mut is_integer = true;
668+
669+
let mut fractional_part: f64 = 0.;
661670
if tokenizer.has_at_least(1) && tokenizer.next_char() == '.'
662671
&& matches!(tokenizer.char_at(1), '0'...'9') {
663672
is_integer = false;
664-
tokenizer.advance(2); // '.' and first digit
665-
consume_digits(tokenizer);
673+
tokenizer.advance(1); // '.' and first digit
674+
let mut divisor = 10.;
675+
while let Some(digit) = tokenizer.next_char().to_digit(10) {
676+
fractional_part += digit as f64 / divisor;
677+
divisor *= 10.;
678+
tokenizer.advance(1);
679+
if tokenizer.is_eof() {
680+
break
681+
}
682+
}
666683
}
684+
685+
let mut value = sign * (integral_part + fractional_part);
686+
667687
if (
668688
tokenizer.has_at_least(1)
669689
&& matches!(tokenizer.next_char(), 'e' | 'E')
@@ -675,37 +695,57 @@ fn consume_numeric<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> {
675695
&& matches!(tokenizer.char_at(2), '0'...'9')
676696
) {
677697
is_integer = false;
678-
tokenizer.advance(2); // 'e' or 'E', and sign or first digit
679-
consume_digits(tokenizer);
680-
}
681-
let (value, int_value) = {
682-
let mut repr = tokenizer.slice_from(start_pos);
683-
// Remove any + sign as int::parse() does not parse them.
684-
if repr.starts_with("+") {
685-
repr = &repr[1..]
698+
tokenizer.advance(1);
699+
let (has_sign, sign) = match tokenizer.next_char() {
700+
'-' => (true, -1.),
701+
'+' => (true, 1.),
702+
_ => (false, 1.),
703+
};
704+
if has_sign {
705+
tokenizer.advance(1);
706+
}
707+
let mut exponent: f64 = 0.;
708+
while let Some(digit) = tokenizer.next_char().to_digit(10) {
709+
exponent = exponent * 10. + digit as f64;
710+
tokenizer.advance(1);
711+
if tokenizer.is_eof() {
712+
break
713+
}
686714
}
687-
// TODO: handle overflow
688-
(repr.parse::<f32>().unwrap(), if is_integer {
689-
Some(repr.parse::<i32>().unwrap())
715+
value *= Float::powf(10., sign * exponent);
716+
}
717+
718+
let int_value = if is_integer {
719+
Some(if value >= i32::MAX as f64 {
720+
i32::MAX
721+
} else if value <= i32::MIN as f64 {
722+
i32::MIN
690723
} else {
691-
None
724+
value as i32
692725
})
726+
} else {
727+
None
693728
};
729+
694730
if !tokenizer.is_eof() && tokenizer.next_char() == '%' {
695731
tokenizer.advance(1);
696732
return Percentage(PercentageValue {
697-
unit_value: value / 100.,
733+
unit_value: value as f32 / 100.,
698734
int_value: int_value,
699735
has_sign: has_sign,
700736
})
701737
}
702738
let value = NumericValue {
703-
value: value,
739+
value: value as f32,
704740
int_value: int_value,
705741
has_sign: has_sign,
706742
};
707-
if is_ident_start(tokenizer) { Dimension(value, consume_name(tokenizer)) }
708-
else { Number(value) }
743+
if is_ident_start(tokenizer) {
744+
Dimension(value, consume_name(tokenizer))
745+
}
746+
else {
747+
Number(value)
748+
}
709749
}
710750

711751

0 commit comments

Comments
 (0)