Skip to content

Commit 50aa60d

Browse files
committed
Merge pull request servo#73 from servo/overflow
Clamp to MAX / INFINITY rather than panic on overflow in number parsing
2 parents 7f862d8 + 09b4d51 commit 50aa60d

File tree

6 files changed

+155
-60
lines changed

6 files changed

+155
-60
lines changed

src/color.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -308,18 +308,18 @@ fn parse_color_function(name: &str, arguments: &mut Parser) -> Result<Color, ()>
308308
// Either integers or percentages, but all the same type.
309309
match try!(arguments.next()) {
310310
Token::Number(ref v) if v.int_value.is_some() => {
311-
red = (v.value / 255.) as f32;
311+
red = v.value / 255.;
312312
try!(arguments.expect_comma());
313313
green = try!(arguments.expect_integer()) as f32 / 255.;
314314
try!(arguments.expect_comma());
315315
blue = try!(arguments.expect_integer()) as f32 / 255.;
316316
}
317317
Token::Percentage(ref v) => {
318-
red = v.unit_value as f32;
318+
red = v.unit_value;
319319
try!(arguments.expect_comma());
320-
green = try!(arguments.expect_percentage()) as f32;
320+
green = try!(arguments.expect_percentage());
321321
try!(arguments.expect_comma());
322-
blue = try!(arguments.expect_percentage()) as f32;
322+
blue = try!(arguments.expect_percentage());
323323
}
324324
_ => return Err(())
325325
};
@@ -332,7 +332,7 @@ fn parse_color_function(name: &str, arguments: &mut Parser) -> Result<Color, ()>
332332
let lightness = (try!(arguments.expect_percentage())).max(0.).min(1.);
333333

334334
// http://www.w3.org/TR/css3-color/#hsl-color
335-
fn hue_to_rgb(m1: f64, m2: f64, mut h: f64) -> f64 {
335+
fn hue_to_rgb(m1: f32, m2: f32, mut h: f32) -> f32 {
336336
if h < 0. { h += 1. }
337337
if h > 1. { h -= 1. }
338338

@@ -344,14 +344,14 @@ fn parse_color_function(name: &str, arguments: &mut Parser) -> Result<Color, ()>
344344
let m2 = if lightness <= 0.5 { lightness * (saturation + 1.) }
345345
else { lightness + saturation - lightness * saturation };
346346
let m1 = lightness * 2. - m2;
347-
red = hue_to_rgb(m1, m2, hue + 1. / 3.) as f32;
348-
green = hue_to_rgb(m1, m2, hue) as f32;
349-
blue = hue_to_rgb(m1, m2, hue - 1. / 3.) as f32;
347+
red = hue_to_rgb(m1, m2, hue + 1. / 3.);
348+
green = hue_to_rgb(m1, m2, hue);
349+
blue = hue_to_rgb(m1, m2, hue - 1. / 3.);
350350
}
351351

352352
let alpha = if has_alpha {
353353
try!(arguments.expect_comma());
354-
(try!(arguments.expect_number())).max(0.).min(1.) as f32
354+
(try!(arguments.expect_number())).max(0.).min(1.)
355355
} else {
356356
1.
357357
};

src/nth.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ fn parse_b(input: &mut Parser, a: i32) -> Result<(i32, i32), ()> {
5757
match input.next() {
5858
Ok(Token::Delim('+')) => parse_signless_b(input, a, 1),
5959
Ok(Token::Delim('-')) => parse_signless_b(input, a, -1),
60-
Ok(Token::Number(ref value)) if value.signed => {
60+
Ok(Token::Number(ref value)) if value.has_sign => {
6161
Ok((a, try!(value.int_value.ok_or(())) as i32))
6262
}
6363
_ => {
@@ -69,7 +69,7 @@ fn parse_b(input: &mut Parser, a: i32) -> Result<(i32, i32), ()> {
6969

7070
fn parse_signless_b(input: &mut Parser, a: i32, b_sign: i32) -> Result<(i32, i32), ()> {
7171
match try!(input.next()) {
72-
Token::Number(ref value) if !value.signed => {
72+
Token::Number(ref value) if !value.has_sign => {
7373
Ok((a, b_sign * (try!(value.int_value.ok_or(())) as i32)))
7474
}
7575
_ => Err(())

src/parser.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ impl<'i, 't> Parser<'i, 't> {
512512

513513
/// Parse a <number-token> and return the integer value.
514514
#[inline]
515-
pub fn expect_number(&mut self) -> Result<f64, ()> {
515+
pub fn expect_number(&mut self) -> Result<f32, ()> {
516516
match try!(self.next()) {
517517
Token::Number(NumericValue { value, .. }) => Ok(value),
518518
_ => Err(())
@@ -521,7 +521,7 @@ impl<'i, 't> Parser<'i, 't> {
521521

522522
/// Parse a <number-token> that does not have a fractional part, and return the integer value.
523523
#[inline]
524-
pub fn expect_integer(&mut self) -> Result<i64, ()> {
524+
pub fn expect_integer(&mut self) -> Result<i32, ()> {
525525
match try!(self.next()) {
526526
Token::Number(NumericValue { int_value, .. }) => int_value.ok_or(()),
527527
_ => Err(())
@@ -531,7 +531,7 @@ impl<'i, 't> Parser<'i, 't> {
531531
/// Parse a <percentage-token> and return the value.
532532
/// `0%` and `100%` map to `0.0` and `1.0` (not `100.0`), respectively.
533533
#[inline]
534-
pub fn expect_percentage(&mut self) -> Result<f64, ()> {
534+
pub fn expect_percentage(&mut self) -> Result<f32, ()> {
535535
match try!(self.next()) {
536536
Token::Percentage(PercentageValue { unit_value, .. }) => Ok(unit_value),
537537
_ => Err(())

src/serializer.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub trait ToCss {
4848
fn write_numeric<W>(value: NumericValue, dest: &mut W) -> text_writer::Result
4949
where W: TextWriter {
5050
// `value.value >= 0` is true for negative 0.
51-
if value.signed && value.value.is_sign_positive() {
51+
if value.has_sign && value.value.is_sign_positive() {
5252
try!(dest.write_str("+"));
5353
}
5454

@@ -93,11 +93,11 @@ impl<'a> ToCss for Token<'a> {
9393
Token::Delim(value) => try!(dest.write_char(value)),
9494

9595
Token::Number(value) => try!(write_numeric(value, dest)),
96-
Token::Percentage(PercentageValue { unit_value, int_value, signed }) => {
96+
Token::Percentage(PercentageValue { unit_value, int_value, has_sign }) => {
9797
let value = NumericValue {
9898
value: unit_value * 100.,
9999
int_value: int_value,
100-
signed: signed,
100+
has_sign: has_sign,
101101
};
102102
try!(write_numeric(value, dest));
103103
try!(dest.write_char('%'));

src/tests.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,62 @@ fn line_numbers() {
397397
assert_eq!(input.next_including_whitespace(), Err(()));
398398
}
399399

400+
#[test]
401+
fn overflow() {
402+
use std::iter::repeat;
403+
use std::f32;
404+
405+
let css = r"
406+
2147483646
407+
2147483647
408+
2147483648
409+
10000000000000
410+
1000000000000000000000000000000000000000
411+
1{309 zeros}
412+
413+
-2147483647
414+
-2147483648
415+
-2147483649
416+
-10000000000000
417+
-1000000000000000000000000000000000000000
418+
-1{309 zeros}
419+
420+
3.30282347e+38
421+
3.40282347e+38
422+
3.402824e+38
423+
424+
-3.30282347e+38
425+
-3.40282347e+38
426+
-3.402824e+38
427+
428+
".replace("{309 zeros}", &repeat('0').take(309).collect::<String>());
429+
let mut input = Parser::new(&css);
430+
431+
assert_eq!(input.expect_integer(), Ok(2147483646));
432+
assert_eq!(input.expect_integer(), Ok(2147483647));
433+
assert_eq!(input.expect_integer(), Ok(2147483647)); // Clamp on overflow
434+
assert_eq!(input.expect_integer(), Ok(2147483647));
435+
assert_eq!(input.expect_integer(), Ok(2147483647));
436+
assert_eq!(input.expect_integer(), Ok(2147483647));
437+
438+
assert_eq!(input.expect_integer(), Ok(-2147483647));
439+
assert_eq!(input.expect_integer(), Ok(-2147483648));
440+
assert_eq!(input.expect_integer(), Ok(-2147483648)); // Clamp on overflow
441+
assert_eq!(input.expect_integer(), Ok(-2147483648));
442+
assert_eq!(input.expect_integer(), Ok(-2147483648));
443+
assert_eq!(input.expect_integer(), Ok(-2147483648));
444+
445+
assert_eq!(input.expect_number(), Ok(3.30282347e+38));
446+
assert_eq!(input.expect_number(), Ok(f32::MAX));
447+
assert_eq!(input.expect_number(), Ok(f32::INFINITY));
448+
assert!(f32::MAX != f32::INFINITY);
449+
450+
assert_eq!(input.expect_number(), Ok(-3.30282347e+38));
451+
assert_eq!(input.expect_number(), Ok(f32::MIN));
452+
assert_eq!(input.expect_number(), Ok(f32::NEG_INFINITY));
453+
assert!(f32::MIN != f32::NEG_INFINITY);
454+
}
455+
400456
#[test]
401457
fn line_delimited() {
402458
let mut input = Parser::new(" { foo ; bar } baz;,");
@@ -533,11 +589,11 @@ fn one_component_value_to_json(token: Token, input: &mut Parser) -> Json {
533589
Token::Delim(value) => value.to_string().to_json(),
534590

535591
Token::Number(value) => Json::Array(vec!["number".to_json()] + &*numeric(value)),
536-
Token::Percentage(PercentageValue { unit_value, int_value, signed }) => Json::Array(
592+
Token::Percentage(PercentageValue { unit_value, int_value, has_sign }) => Json::Array(
537593
vec!["percentage".to_json()] + &*numeric(NumericValue {
538594
value: unit_value * 100.,
539595
int_value: int_value,
540-
signed: signed,
596+
has_sign: has_sign,
541597
})),
542598
Token::Dimension(value, unit) => Json::Array(
543599
vec!["dimension".to_json()] + &*numeric(value) + &[unit.to_json()][..]),

src/tokenizer.rs

Lines changed: 80 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::char;
1010
use std::ascii::AsciiExt;
1111
use std::borrow::{Cow, ToOwned};
1212
use std::borrow::Cow::{Owned, Borrowed};
13+
use std::i32;
1314

1415
use self::Token::*;
1516

@@ -177,29 +178,29 @@ impl<'a> Token<'a> {
177178
#[derive(PartialEq, Debug, Copy, Clone)]
178179
pub struct NumericValue {
179180
/// The value as a float
180-
pub value: f64,
181+
pub value: f32,
181182

182183
/// If the origin source did not include a fractional part, the value as an integer.
183-
pub int_value: Option<i64>,
184+
pub int_value: Option<i32>,
184185

185186
/// Whether the number had a `+` or `-` sign.
186187
///
187188
/// This is used is some cases like the <An+B> micro syntax. (See the `parse_nth` function.)
188-
pub signed: bool,
189+
pub has_sign: bool,
189190
}
190191

191192

192193
/// The numeric value of `Percentage` tokens.
193194
#[derive(PartialEq, Debug, Copy, Clone)]
194195
pub struct PercentageValue {
195196
/// The value as a float, divided by 100 so that the nominal range is 0.0 to 1.0.
196-
pub unit_value: f64,
197+
pub unit_value: f32,
197198

198199
/// If the origin source did not include a fractional part, the value as an integer. It is **not** divided by 100.
199-
pub int_value: Option<i64>,
200+
pub int_value: Option<i32>,
200201

201202
/// Whether the number had a `+` or `-` sign.
202-
pub signed: bool,
203+
pub has_sign: bool,
203204
}
204205

205206

@@ -653,32 +654,51 @@ fn consume_name<'a>(tokenizer: &mut Tokenizer<'a>) -> Cow<'a, str> {
653654
}
654655

655656

656-
fn consume_digits(tokenizer: &mut Tokenizer) {
657-
while !tokenizer.is_eof() {
658-
match tokenizer.next_char() {
659-
'0'...'9' => tokenizer.advance(1),
660-
_ => break
661-
}
662-
}
663-
}
664-
665-
666657
fn consume_numeric<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> {
667658
// Parse [+-]?\d*(\.\d+)?([eE][+-]?\d+)?
668659
// But this is always called so that there is at least one digit in \d*(\.\d+)?
669-
let start_pos = tokenizer.position();
670-
let mut is_integer = true;
671-
let signed = matches!(tokenizer.next_char(), '-' | '+');
672-
if signed {
660+
661+
// Do all the math in f64 so that large numbers overflow to +/-inf
662+
// and i32::{MIN, MAX} are within range.
663+
664+
let (has_sign, sign) = match tokenizer.next_char() {
665+
'-' => (true, -1.),
666+
'+' => (true, 1.),
667+
_ => (false, 1.),
668+
};
669+
if has_sign {
673670
tokenizer.advance(1);
674671
}
675-
consume_digits(tokenizer);
672+
673+
let mut integral_part: f64 = 0.;
674+
while let Some(digit) = tokenizer.next_char().to_digit(10) {
675+
integral_part = integral_part * 10. + digit as f64;
676+
tokenizer.advance(1);
677+
if tokenizer.is_eof() {
678+
break
679+
}
680+
}
681+
682+
let mut is_integer = true;
683+
684+
let mut fractional_part: f64 = 0.;
676685
if tokenizer.has_at_least(1) && tokenizer.next_char() == '.'
677686
&& matches!(tokenizer.char_at(1), '0'...'9') {
678687
is_integer = false;
679-
tokenizer.advance(2); // '.' and first digit
680-
consume_digits(tokenizer);
688+
tokenizer.advance(1); // Consume '.'
689+
let mut divisor = 10.;
690+
while let Some(digit) = tokenizer.next_char().to_digit(10) {
691+
fractional_part += digit as f64 / divisor;
692+
divisor *= 10.;
693+
tokenizer.advance(1);
694+
if tokenizer.is_eof() {
695+
break
696+
}
697+
}
681698
}
699+
700+
let mut value = sign * (integral_part + fractional_part);
701+
682702
if (
683703
tokenizer.has_at_least(1)
684704
&& matches!(tokenizer.next_char(), 'e' | 'E')
@@ -690,37 +710,56 @@ fn consume_numeric<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> {
690710
&& matches!(tokenizer.char_at(2), '0'...'9')
691711
) {
692712
is_integer = false;
693-
tokenizer.advance(2); // 'e' or 'E', and sign or first digit
694-
consume_digits(tokenizer);
695-
}
696-
let (value, int_value) = {
697-
let mut repr = tokenizer.slice_from(start_pos);
698-
// Remove any + sign as int::parse() does not parse them.
699-
if repr.starts_with("+") {
700-
repr = &repr[1..]
713+
tokenizer.advance(1);
714+
let (has_sign, sign) = match tokenizer.next_char() {
715+
'-' => (true, -1.),
716+
'+' => (true, 1.),
717+
_ => (false, 1.),
718+
};
719+
if has_sign {
720+
tokenizer.advance(1);
721+
}
722+
let mut exponent: f64 = 0.;
723+
while let Some(digit) = tokenizer.next_char().to_digit(10) {
724+
exponent = exponent * 10. + digit as f64;
725+
tokenizer.advance(1);
726+
if tokenizer.is_eof() {
727+
break
728+
}
701729
}
702-
// TODO: handle overflow
703-
(repr.parse::<f64>().unwrap(), if is_integer {
704-
Some(repr.parse::<i64>().unwrap())
730+
value *= f64::powf(10., sign * exponent);
731+
}
732+
733+
let int_value = if is_integer {
734+
Some(if value >= i32::MAX as f64 {
735+
i32::MAX
736+
} else if value <= i32::MIN as f64 {
737+
i32::MIN
705738
} else {
706-
None
739+
value as i32
707740
})
741+
} else {
742+
None
708743
};
744+
709745
if !tokenizer.is_eof() && tokenizer.next_char() == '%' {
710746
tokenizer.advance(1);
711747
return Percentage(PercentageValue {
712-
unit_value: value / 100.,
748+
unit_value: value as f32 / 100.,
713749
int_value: int_value,
714-
signed: signed,
750+
has_sign: has_sign,
715751
})
716752
}
717753
let value = NumericValue {
718-
value: value,
754+
value: value as f32,
719755
int_value: int_value,
720-
signed: signed,
756+
has_sign: has_sign,
721757
};
722-
if is_ident_start(tokenizer) { Dimension(value, consume_name(tokenizer)) }
723-
else { Number(value) }
758+
if is_ident_start(tokenizer) {
759+
Dimension(value, consume_name(tokenizer))
760+
} else {
761+
Number(value)
762+
}
724763
}
725764

726765

0 commit comments

Comments
 (0)