From 68543e8fb46d0527dcce98e7603bc6986714baa7 Mon Sep 17 00:00:00 2001 From: Iban Eguia Date: Tue, 25 May 2021 16:05:26 +0200 Subject: [PATCH] Fix Array.prototype.filter (#1272) Co-authored-by: tofpie --- boa/src/builtins/array/mod.rs | 107 +++++++++++++++++++++++++++------- boa/src/builtins/json/mod.rs | 3 +- boa/src/builtins/math/mod.rs | 14 ++--- 3 files changed, 94 insertions(+), 30 deletions(-) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 587be1561c..d60f056f3c 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -262,6 +262,51 @@ impl Array { Ok(array_obj_ptr) } + /// Utility function used to specify the creation of a new Array object using a constructor + /// function that is derived from original_array. + /// + /// see: + pub(crate) fn array_species_create( + original_array: &GcObject, + length: u32, + context: &mut Context, + ) -> Result { + if !original_array.is_array() { + return Ok(Self::array_create(length, None, context)); + } + let c = original_array.get( + &"constructor".into(), + original_array.clone().into(), + context, + )?; + // Step 4 is ignored, as there are no different realms for now + let c = if let Some(c) = c.as_object() { + let c = c.get( + &WellKnownSymbols::species().into(), + c.clone().into(), + context, + )?; + if c.is_null_or_undefined() { + Value::undefined() + } else { + c + } + } else { + c + }; + if c.is_undefined() { + return Ok(Self::array_create(length, None, context)); + } + if let Some(c) = c.as_object() { + if !c.is_constructable() { + return context.throw_type_error("Symbol.species must be a constructor"); + } + c.construct(&[Value::from(length)], c.clone().into(), context) + } else { + context.throw_type_error("Symbol.species must be a constructor") + } + } + /// Utility function which takes an existing array object and puts additional /// values on the end, correctly rewriting the length pub(crate) fn add_to_array_object( @@ -1211,37 +1256,57 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.filter /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter pub(crate) fn filter(this: &Value, args: &[Value], context: &mut Context) -> Result { - if args.is_empty() { - return Err(Value::from( - "missing argument 0 when calling function Array.prototype.filter", - )); - } - - let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); + let o = this.to_object(context)?; + let length = o + .get(&"length".into(), Value::from(o.clone()), context)? + .to_length(context)?; + + let callback = args + .get(0) + .map(|a| a.to_object(context)) + .transpose()? + .ok_or_else(|| { + context.construct_type_error( + "missing argument 0 when calling function Array.prototype.filter", + ) + })?; let this_val = args.get(1).cloned().unwrap_or_else(Value::undefined); - let length = this.get_field("length", context)?.to_length(context)?; + if !callback.is_callable() { + return context.throw_type_error("the callback must be callable"); + } - let new = Self::new_array(context); + let mut a = Self::array_species_create(&o, 0, context)? + .as_object() + .expect("array_species_create must create an object"); - let values = (0..length) - .map(|idx| { - let element = this.get_field(idx, context)?; + let mut to = 0u32; + for idx in 0..length { + if o.has_property(&idx.into()) { + let element = o.get(&idx.into(), Value::from(o.clone()), context)?; - let args = [element.clone(), Value::from(idx), new.clone()]; + let args = [element.clone(), Value::from(idx), Value::from(o.clone())]; - let callback_result = context.call(&callback, &this_val, &args)?; + let callback_result = callback.call(&this_val, &args, context)?; if callback_result.to_boolean() { - Ok(Some(element)) - } else { - Ok(None) + if !a.define_own_property( + to, + DataDescriptor::new( + element, + Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, + ) + .into(), + context, + )? { + return context.throw_type_error("cannot set property in array"); + } + to += 1; } - }) - .collect::>>>()?; - let values = values.into_iter().flatten().collect::>(); + } + } - Self::construct_array(&new, &values, context) + Ok(a.into()) } /// Array.prototype.some ( callbackfn [ , thisArg ] ) diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 0d7883ec21..bcf54997bd 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -42,13 +42,12 @@ impl BuiltIn for Json { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let to_string_tag = WellKnownSymbols::to_string_tag(); - let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; let json_object = ObjectInitializer::new(context) - .property(to_string_tag, Self::NAME, attribute) .function(Self::parse, "parse", 2) .function(Self::stringify, "stringify", 3) + .property(to_string_tag, Self::NAME, attribute) .build(); (Self::NAME, json_object.into(), Self::attribute()) diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index 633b24732f..284319e2a0 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -31,12 +31,12 @@ impl BuiltIn for Math { } fn init(context: &mut Context) -> (&'static str, Value, Attribute) { - let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); use std::f64; - let to_string_tag = WellKnownSymbols::to_string_tag(); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; + let string_tag = WellKnownSymbols::to_string_tag(); let object = ObjectInitializer::new(context) .property("E", f64::consts::E, attribute) .property("LN2", f64::consts::LN_2, attribute) @@ -46,11 +46,6 @@ impl BuiltIn for Math { .property("SQRT1_2", 0.5_f64.sqrt(), attribute) .property("SQRT2", f64::consts::SQRT_2, attribute) .property("PI", f64::consts::PI, attribute) - .property( - to_string_tag, - Self::NAME, - Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) .function(Self::abs, "abs", 1) .function(Self::acos, "acos", 1) .function(Self::acosh, "acosh", 1) @@ -86,6 +81,11 @@ impl BuiltIn for Math { .function(Self::tan, "tan", 1) .function(Self::tanh, "tanh", 1) .function(Self::trunc, "trunc", 1) + .property( + string_tag, + Math::NAME, + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) .build(); (Self::NAME, object.into(), Self::attribute())