Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed a bunch of test262 panics (#817) #1046

Merged
merged 3 commits into from
Jan 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions boa/src/builtins/array/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1364,9 +1364,9 @@ fn get_relative_end() {

#[test]
fn array_length_is_not_enumerable() {
let mut context = Context::new();
let context = Context::new();

let array = Array::new_array(&mut context).unwrap();
let array = Array::new_array(&context).unwrap();
let desc = array.get_property("length").unwrap();
assert!(!desc.enumerable());
}
136 changes: 96 additions & 40 deletions boa/src/builtins/number/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
value::{AbstractRelation, IntegerOrInfinity, Value},
BoaProfiler, Context, Result,
};
use num_traits::float::FloatCore;
use num_traits::{float::FloatCore, Num};

mod conversions;

Expand Down Expand Up @@ -651,54 +651,110 @@ impl Number {
///
/// [spec]: https://tc39.es/ecma262/#sec-parseint-string-radix
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt
pub(crate) fn parse_int(_: &Value, args: &[Value], _ctx: &mut Context) -> Result<Value> {
if let (Some(val), r) = (args.get(0), args.get(1)) {
let mut radix = if let Some(rx) = r {
if let Value::Integer(i) = rx {
*i as u32
} else {
// Handling a second argument that isn't an integer but was provided so cannot be defaulted.
return Ok(Value::from(f64::NAN));
}
pub(crate) fn parse_int(_: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
if let (Some(val), radix) = (args.get(0), args.get(1)) {
// 1. Let inputString be ? ToString(string).
let input_string = val.to_string(context)?;

// 2. Let S be ! TrimString(inputString, start).
let mut var_s = input_string.trim();

// 3. Let sign be 1.
// 4. If S is not empty and the first code unit of S is the code unit 0x002D (HYPHEN-MINUS),
// set sign to -1.
let sign = if !var_s.is_empty() && var_s.starts_with('\u{002D}') {
-1
} else {
// No second argument provided therefore radix is unknown
0
1
};

match val {
Value::String(s) => {
// Attempt to infer radix from given string.

if radix == 0 {
if s.starts_with("0x") || s.starts_with("0X") {
return if let Ok(i) = i32::from_str_radix(&s[2..], 16) {
Ok(Value::integer(i))
} else {
// String can't be parsed.
Ok(Value::from(f64::NAN))
};
} else {
radix = 10
};
}
// 5. If S is not empty and the first code unit of S is the code unit 0x002B (PLUS SIGN) or
// the code unit 0x002D (HYPHEN-MINUS), remove the first code unit from S.
if !var_s.is_empty() {
var_s = var_s
.strip_prefix(&['\u{002B}', '\u{002D}'][..])
.unwrap_or(var_s);
}

if let Ok(i) = i32::from_str_radix(s, radix) {
Ok(Value::integer(i))
} else {
// String can't be parsed.
Ok(Value::from(f64::NAN))
}
// 6. Let R be ℝ(? ToInt32(radix)).
let mut var_r = radix.cloned().unwrap_or_default().to_i32(context)?;

// 7. Let stripPrefix be true.
let mut strip_prefix = true;

// 8. If R ≠ 0, then
if var_r != 0 {
// a. If R < 2 or R > 36, return NaN.
if !(2..=36).contains(&var_r) {
return Ok(Value::nan());
}
Value::Integer(i) => Ok(Value::integer(*i)),
Value::Rational(f) => Ok(Value::integer(*f as i32)),
_ => {
// Wrong argument type to parseInt.
Ok(Value::from(f64::NAN))

// b. If R ≠ 16, set stripPrefix to false.
if var_r != 16 {
strip_prefix = false
}
} else {
// 9. Else,
// a. Set R to 10.
var_r = 10;
}

// 10. If stripPrefix is true, then
// a. If the length of S is at least 2 and the first two code units of S are either "0x" or "0X", then
// i. Remove the first two code units from S.
// ii. Set R to 16.
if strip_prefix
&& var_s.len() >= 2
&& (var_s.starts_with("0x") || var_s.starts_with("0X"))
{
var_s = var_s.split_at(2).1;

var_r = 16;
}

// 11. If S contains a code unit that is not a radix-R digit, let end be the index within S of the
// first such code unit; otherwise, let end be the length of S.
let end = if let Some(index) = var_s.find(|c: char| !c.is_digit(var_r as u32)) {
index
} else {
var_s.len()
};

// 12. Let Z be the substring of S from 0 to end.
let var_z = var_s.split_at(end).0;

// 13. If Z is empty, return NaN.
if var_z.is_empty() {
return Ok(Value::nan());
}

// 14. Let mathInt be the integer value that is represented by Z in radix-R notation, using the
// letters A-Z and a-z for digits with values 10 through 35. (However, if R is 10 and Z contains
// more than 20 significant digits, every significant digit after the 20th may be replaced by a
// 0 digit, at the option of the implementation; and if R is not 2, 4, 8, 10, 16, or 32, then
// mathInt may be an implementation-approximated value representing the integer value that is
// represented by Z in radix-R notation.)
let math_int = u64::from_str_radix(var_z, var_r as u32).map_or_else(
|_| f64::from_str_radix(var_z, var_r as u32).expect("invalid_float_conversion"),
|i| i as f64,
);

// 15. If mathInt = 0, then
// a. If sign = -1, return -0𝔽.
// b. Return +0𝔽.
if math_int == 0_f64 {
if sign == -1 {
return Ok(Value::rational(-0_f64));
} else {
return Ok(Value::rational(0_f64));
}
}

// 16. Return 𝔽(sign × mathInt).
Ok(Value::rational(f64::from(sign) * math_int))
} else {
// Not enough arguments to parseInt.
Ok(Value::from(f64::NAN))
Ok(Value::nan())
}
}

Expand Down
2 changes: 1 addition & 1 deletion boa/src/builtins/number/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ fn parse_int_float() {
fn parse_int_float_str() {
let mut context = Context::new();

assert_eq!(&forward(&mut context, "parseInt(\"100.5\")"), "NaN");
assert_eq!(&forward(&mut context, "parseInt(\"100.5\")"), "100");
}

#[test]
Expand Down
7 changes: 6 additions & 1 deletion boa/src/builtins/regexp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,12 @@ impl RegExp {
pub(crate) fn to_string(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
let (body, flags) = if let Some(object) = this.as_object() {
let object = object.borrow();
let regex = object.as_regexp().unwrap();
let regex = object.as_regexp().ok_or_else(|| {
context.construct_type_error(format!(
"Method RegExp.prototype.toString called on incompatible receiver {}",
this.display()
))
})?;
(regex.original_source.clone(), regex.flags.clone())
} else {
return context.throw_type_error(format!(
Expand Down
2 changes: 1 addition & 1 deletion boa/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ impl Context {
let key = field.to_property_key(self)?;
Ok(get_field.obj().run(self)?.set_field(key, value, self)?)
}
_ => panic!("TypeError: invalid assignment to {}", node),
_ => self.throw_type_error(format!("invalid assignment to {}", node)),
}
}

Expand Down
2 changes: 1 addition & 1 deletion boa/src/syntax/ast/node/operator/unary_op/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl Executable for UnaryOp {
| Node::New(_)
| Node::Object(_)
| Node::UnaryOp(_) => Value::boolean(true),
_ => panic!("SyntaxError: wrong delete argument {}", self),
_ => return context.throw_syntax_error(format!("wrong delete argument {}", self)),
},
op::UnaryOp::TypeOf => Value::from(x.get_type().as_str()),
})
Expand Down
15 changes: 11 additions & 4 deletions boa/src/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,16 @@ impl Value {
Self::Boolean(b) => Ok(JSONValue::Bool(b)),
Self::Object(ref obj) => obj.to_json(context),
Self::String(ref str) => Ok(JSONValue::String(str.to_string())),
Self::Rational(num) => Ok(JSONValue::Number(
JSONNumber::from_str(&Number::to_native_string(num)).unwrap(),
)),
Self::Rational(num) => {
if num.is_finite() {
Ok(JSONValue::Number(
JSONNumber::from_str(&Number::to_native_string(num))
.expect("invalid number found"),
))
} else {
Ok(JSONValue::Null)
}
}
Self::Integer(val) => Ok(JSONValue::Number(JSONNumber::from(val))),
Self::BigInt(_) => {
Err(context.construct_type_error("BigInt value can't be serialized in JSON"))
Expand Down Expand Up @@ -716,7 +723,7 @@ impl Value {
///
/// This function is equivalent to `value | 0` in JavaScript
///
/// See: <https://tc39.es/ecma262/#sec-toint32>
/// See: <https://tc39.es/ecma262/#sec-touint32>
pub fn to_u32(&self, context: &mut Context) -> Result<u32> {
// This is the fast path, if the value is Integer we can just return it.
if let Value::Integer(number) = *self {
Expand Down
2 changes: 2 additions & 0 deletions boa/src/value/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ fn hash_undefined() {
}

#[test]
#[allow(clippy::eq_op)]
fn hash_rational() {
let value1 = Value::rational(1.0);
let value2 = Value::rational(1.0);
Expand All @@ -157,6 +158,7 @@ fn hash_rational() {
}

#[test]
#[allow(clippy::eq_op)]
fn hash_object() {
let object1 = Value::object(Object::default());
assert_eq!(object1, object1);
Expand Down
2 changes: 1 addition & 1 deletion boa_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ edition = "2018"
[dependencies]
Boa = { path = "../boa", features = ["console"] }
wasm-bindgen = "0.2.69"
getrandom = { version = "0.2.1", features = ["js"]}
getrandom = { version = "0.2.1", features = ["js"] }

[lib]
crate-type = ["cdylib", "lib"]
Expand Down
Loading