Skip to content

Commit 10af943

Browse files
relrelbHerschel
authored andcommitted
avm2: Minor parseInt()/parseFloat() tweaks
Alongside comment wordings, fix handling of non-`u8` characters by replacing `as u8` conversions with `u8::try_from()`, that doesn't wrap around, but rather fails gracefully.
1 parent d3c64d4 commit 10af943

File tree

1 file changed

+56
-29
lines changed

1 file changed

+56
-29
lines changed

core/src/avm2/globals/toplevel.rs

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub fn is_nan<'gc>(
6363
/// insensitive), or decimal otherwise.
6464
/// `strict` tells whether to fail on trailing garbage, or ignore it.
6565
fn string_to_int(mut s: &WStr, mut radix: i32, strict: bool) -> f64 {
66-
// Allow leading whitespace characters.
66+
// Allow leading whitespace.
6767
skip_spaces(&mut s);
6868

6969
let is_negative = parse_sign(&mut s);
@@ -92,25 +92,31 @@ fn string_to_int(mut s: &WStr, mut radix: i32, strict: bool) -> f64 {
9292
// Actual number parsing.
9393
let mut result = 0.0;
9494
let start = s;
95-
s = s.trim_start_matches(|c| match (c as u8 as char).to_digit(radix as u32) {
96-
Some(digit) => {
97-
result *= f64::from(radix);
98-
result += f64::from(digit);
99-
true
95+
s = s.trim_start_matches(|c| {
96+
match u8::try_from(c)
97+
.ok()
98+
.and_then(|c| char::from(c).to_digit(radix as u32))
99+
{
100+
Some(digit) => {
101+
result *= f64::from(radix);
102+
result += f64::from(digit);
103+
true
104+
}
105+
None => false,
100106
}
101-
None => false,
102107
});
103108

104109
// Fail if we got no digits.
105-
if s == start {
110+
// TODO: Compare by reference instead?
111+
if s.len() == start.len() {
106112
return f64::NAN;
107113
}
108114

109115
if strict {
110-
// Allow trailing whitespace characters.
116+
// Allow trailing whitespace.
111117
skip_spaces(&mut s);
112118

113-
// If we got digits, but we're in strict mode and not at end of string, fail.
119+
// Fail if we got digits, but we're in strict mode and not at end of string.
114120
if !s.is_empty() {
115121
return f64::NAN;
116122
}
@@ -143,7 +149,7 @@ pub fn parse_int<'gc>(
143149
Ok(result.into())
144150
}
145151

146-
/// Strips leading whitespace characters.
152+
/// Strips leading whitespace.
147153
fn skip_spaces(s: &mut &WStr) {
148154
*s = s.trim_start_matches(|c| {
149155
matches!(
@@ -173,49 +179,62 @@ fn parse_sign(s: &mut &WStr) -> bool {
173179
/// This function might fail for some invalid inputs, by returning `None`.
174180
///
175181
/// `strict` typically tells whether to behave like `Number()` or `parseFloat()`:
176-
/// * `strict == true` fails on trailing garbage, but interprets blank strings (which are empty or consist only of whitespace characters) as zero.
182+
/// * `strict == true` fails on trailing garbage, but interprets blank strings (which are empty or consist only of whitespace) as zero.
177183
/// * `strict == false` ignores trailing garbage, but fails on blank strings.
178184
fn string_to_f64(mut s: &WStr, swf_version: u8, strict: bool) -> Option<f64> {
179-
// Allow leading whitespace characters.
185+
fn is_ascii_digit(c: u16) -> bool {
186+
u8::try_from(c).map_or(false, |c| c.is_ascii_digit())
187+
}
188+
189+
// Allow leading whitespace.
180190
skip_spaces(&mut s);
181191

192+
// Handle blank strings as described above.
182193
if s.is_empty() {
183-
// A blank string. Handle it as described above.
184194
return if strict { Some(0.0) } else { None };
185195
}
186196

197+
// Parse sign.
187198
let is_negative = parse_sign(&mut s);
188199
let after_sign = s;
189200

190201
// Count digits before decimal point.
191-
s = s.trim_start_matches(|c| (c as u8).is_ascii_digit());
202+
s = s.trim_start_matches(is_ascii_digit);
192203
let mut total_digits = after_sign.len() - s.len();
193204

194205
// Count digits after decimal point.
195206
if let Some(after_dot) = s.strip_prefix(b'.') {
196207
s = after_dot;
197-
s = s.trim_start_matches(|c| (c as u8).is_ascii_digit());
208+
s = s.trim_start_matches(is_ascii_digit);
198209
total_digits += after_dot.len() - s.len();
199210
}
200211

201212
// Handle exponent.
202213
let mut exponent: i32 = 0;
203214
if let Some(after_e) = s.strip_prefix(b"eE".as_ref()) {
204215
s = after_e;
216+
217+
// Parse exponent sign.
205218
let exponent_is_negative = parse_sign(&mut s);
206219

207220
// Fail if string ends with "e-" with no exponent value specified.
208221
if exponent_is_negative && s.is_empty() {
209222
return None;
210223
}
211224

212-
s = s.trim_start_matches(|c| match (c as u8 as char).to_digit(10) {
213-
Some(digit) => {
214-
exponent = exponent.wrapping_mul(10);
215-
exponent = exponent.wrapping_add(digit as i32);
216-
true
225+
// Parse exponent itself.
226+
s = s.trim_start_matches(|c| {
227+
match u8::try_from(c)
228+
.ok()
229+
.and_then(|c| char::from(c).to_digit(10))
230+
{
231+
Some(digit) => {
232+
exponent = exponent.wrapping_mul(10);
233+
exponent = exponent.wrapping_add(digit as i32);
234+
true
235+
}
236+
None => false,
217237
}
218-
None => false,
219238
});
220239

221240
// Apply exponent sign.
@@ -224,18 +243,19 @@ fn string_to_f64(mut s: &WStr, swf_version: u8, strict: bool) -> Option<f64> {
224243
}
225244
}
226245

227-
// Allow trailing whitespace characters.
246+
// Allow trailing whitespace.
228247
skip_spaces(&mut s);
229248

230-
// If we got no digits, check for Infinity/-Infinity, else fail.
249+
// If we got no digits, check for Infinity/-Infinity. Otherwise fail.
231250
if total_digits == 0 {
232251
if let Some(after_infinity) = s.strip_prefix(WStr::from_units(b"Infinity")) {
233252
s = after_infinity;
234253

235-
// Allow end of string or a whitespace, and fail otherwise.
254+
// Allow end of string or a whitespace. Otherwise fail.
236255
if !s.is_empty() {
237256
skip_spaces(&mut s);
238-
if s == after_infinity {
257+
// TODO: Compare by reference instead?
258+
if s.len() == after_infinity.len() {
239259
return None;
240260
}
241261
}
@@ -250,7 +270,7 @@ fn string_to_f64(mut s: &WStr, swf_version: u8, strict: bool) -> Option<f64> {
250270
return None;
251271
}
252272

253-
// If we got digits, but we're in strict mode and not at end of string (or at null character), fail.
273+
// Fail if we got digits, but we're in strict mode and not at end of string or at a null character.
254274
if strict && !s.is_empty() && !s.starts_with(b'\0') {
255275
return None;
256276
}
@@ -262,12 +282,16 @@ fn string_to_f64(mut s: &WStr, swf_version: u8, strict: bool) -> Option<f64> {
262282
after_sign
263283
};
264284

285+
// Finally, calculate the result.
265286
let mut result = if total_digits > 15 {
266287
// With more than 15 digits, avmplus uses integer arithmetic to avoid rounding errors.
267288
let mut result: i64 = 0;
268289
let mut decimal_digits = -1;
269290
for c in s {
270-
if let Some(digit) = (c as u8 as char).to_digit(10) {
291+
if let Some(digit) = u8::try_from(c)
292+
.ok()
293+
.and_then(|c| char::from(c).to_digit(10))
294+
{
271295
if decimal_digits != -1 {
272296
decimal_digits += 1;
273297
}
@@ -294,7 +318,10 @@ fn string_to_f64(mut s: &WStr, swf_version: u8, strict: bool) -> Option<f64> {
294318
let mut result = 0.0;
295319
let mut decimal_digits = -1;
296320
for c in s {
297-
if let Some(digit) = (c as u8 as char).to_digit(10) {
321+
if let Some(digit) = u8::try_from(c)
322+
.ok()
323+
.and_then(|c| char::from(c).to_digit(10))
324+
{
298325
if decimal_digits != -1 {
299326
decimal_digits += 1;
300327
}

0 commit comments

Comments
 (0)