From df836f1e2a9fdabcae94e38193347c69e7363934 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Tue, 5 Oct 2021 11:29:44 -0500 Subject: [PATCH] Implement arguments exotic objects (#1522) --- boa/src/builtins/array/mod.rs | 14 +- boa/src/builtins/function/arguments.rs | 272 +++++++++++++++++++ boa/src/builtins/function/mod.rs | 40 +-- boa/src/builtins/intrinsics.rs | 46 ++++ boa/src/builtins/mod.rs | 1 + boa/src/builtins/object/mod.rs | 2 +- boa/src/context.rs | 18 +- boa/src/object/internal_methods/arguments.rs | 260 ++++++++++++++++++ boa/src/object/internal_methods/mod.rs | 1 + boa/src/object/jsobject.rs | 33 ++- boa/src/object/mod.rs | 40 +++ 11 files changed, 668 insertions(+), 59 deletions(-) create mode 100644 boa/src/builtins/function/arguments.rs create mode 100644 boa/src/builtins/intrinsics.rs create mode 100644 boa/src/object/internal_methods/arguments.rs diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 6b67f6addd..cf32720fa1 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -52,11 +52,7 @@ impl BuiltIn for Array { .constructable(false) .build(); - let values_function = FunctionBuilder::native(context, Self::values) - .name("values") - .length(0) - .constructable(false) - .build(); + let values_function = Self::values_intrinsic(context); let array = ConstructorBuilder::with_standard_object( context, @@ -2660,4 +2656,12 @@ impl Array { } } } + + pub(crate) fn values_intrinsic(context: &mut Context) -> JsObject { + FunctionBuilder::native(context, Self::values) + .name("values") + .length(0) + .constructable(false) + .build() + } } diff --git a/boa/src/builtins/function/arguments.rs b/boa/src/builtins/function/arguments.rs new file mode 100644 index 0000000000..a60314860a --- /dev/null +++ b/boa/src/builtins/function/arguments.rs @@ -0,0 +1,272 @@ +use crate::{ + builtins::Array, + environment::lexical_environment::Environment, + object::{FunctionBuilder, JsObject, ObjectData}, + property::PropertyDescriptor, + symbol::{self, WellKnownSymbols}, + syntax::ast::node::FormalParameter, + Context, JsValue, +}; + +use gc::{Finalize, Trace}; +use rustc_hash::FxHashSet; + +#[derive(Debug, Clone, Trace, Finalize)] +pub struct MappedArguments(JsObject); + +impl MappedArguments { + pub(crate) fn parameter_map(&self) -> JsObject { + self.0.clone() + } +} + +#[derive(Debug, Clone, Trace, Finalize)] +pub enum Arguments { + Unmapped, + Mapped(MappedArguments), +} + +impl Arguments { + /// Creates a new unmapped Arguments ordinary object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-createunmappedargumentsobject + pub(crate) fn create_unmapped_arguments_object( + arguments_list: &[JsValue], + context: &mut Context, + ) -> JsObject { + // 1. Let len be the number of elements in argumentsList. + let len = arguments_list.len(); + + // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »). + let obj = context.construct_object(); + + // 3. Set obj.[[ParameterMap]] to undefined. + // skipped because the `Arguments` enum ensures ordinary argument objects don't have a `[[ParameterMap]]` + obj.borrow_mut().data = ObjectData::arguments(Arguments::Unmapped); + + // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), + // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + obj.define_property_or_throw( + "length", + PropertyDescriptor::builder() + .value(len) + .writable(true) + .enumerable(false) + .configurable(true), + context, + ) + .expect("DefinePropertyOrThrow must not fail per the spec"); + + // 5. Let index be 0. + // 6. Repeat, while index < len, + for (index, value) in arguments_list.iter().cloned().enumerate() { + // a. Let val be argumentsList[index]. + // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). + obj.create_data_property_or_throw(index, value, context) + .expect("CreateDataPropertyOrThrow must not fail per the spec"); + + // c. Set index to index + 1. + } + + // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { + // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, + // [[Configurable]]: true }). + obj.define_property_or_throw( + symbol::WellKnownSymbols::iterator(), + PropertyDescriptor::builder() + .value(Array::values_intrinsic(context)) + .writable(true) + .enumerable(false) + .configurable(true), + context, + ) + .expect("DefinePropertyOrThrow must not fail per the spec"); + + let throw_type_error = context.intrinsics().throw_type_error(); + + // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { + // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, + // [[Configurable]]: false }). + obj.define_property_or_throw( + "callee", + PropertyDescriptor::builder() + .get(throw_type_error.clone()) + .set(throw_type_error) + .enumerable(false) + .configurable(false), + context, + ) + .expect("DefinePropertyOrThrow must not fail per the spec"); + + // 9. Return obj. + obj + } + + /// Creates a new mapped Arguments exotic object. + /// + /// + pub(crate) fn create_mapped_arguments_object( + func: &JsObject, + formals: &[FormalParameter], + arguments_list: &[JsValue], + env: &Environment, + context: &mut Context, + ) -> JsObject { + // 1. Assert: formals does not contain a rest parameter, any binding patterns, or any initializers. + // It may contain duplicate identifiers. + // 2. Let len be the number of elements in argumentsList. + let len = arguments_list.len(); + + // 3. Let obj be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[ParameterMap]] »). + // 4. Set obj.[[GetOwnProperty]] as specified in 10.4.4.1. + // 5. Set obj.[[DefineOwnProperty]] as specified in 10.4.4.2. + // 6. Set obj.[[Get]] as specified in 10.4.4.3. + // 7. Set obj.[[Set]] as specified in 10.4.4.4. + // 8. Set obj.[[Delete]] as specified in 10.4.4.5. + // 9. Set obj.[[Prototype]] to %Object.prototype%. + + // 10. Let map be ! OrdinaryObjectCreate(null). + let map = JsObject::empty(); + + // 11. Set obj.[[ParameterMap]] to map. + let obj = JsObject::from_proto_and_data( + context.standard_objects().object_object().prototype(), + ObjectData::arguments(Arguments::Mapped(MappedArguments(map.clone()))), + ); + + // 14. Let index be 0. + // 15. Repeat, while index < len, + for (index, val) in arguments_list.iter().cloned().enumerate() { + // a. Let val be argumentsList[index]. + // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). + obj.create_data_property_or_throw(index, val, context) + .expect("CreateDataPropertyOrThrow must not fail per the spec"); + // c. Set index to index + 1. + } + + // 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), + // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + obj.define_property_or_throw( + "length", + PropertyDescriptor::builder() + .value(len) + .writable(true) + .enumerable(false) + .configurable(true), + context, + ) + .expect("DefinePropertyOrThrow must not fail per the spec"); + + // 17. Let mappedNames be a new empty List. + // using a set to optimize `contains` + let mut mapped_names = FxHashSet::default(); + + // 12. Let parameterNames be the BoundNames of formals. + // 13. Let numberOfParameters be the number of elements in parameterNames. + // 18. Set index to numberOfParameters - 1. + // 19. Repeat, while index ≥ 0, + // a. Let name be parameterNames[index]. + for (index, parameter_name) in formals.iter().map(|fp| fp.name()).enumerate().rev() { + // b. If name is not an element of mappedNames, then + if !mapped_names.contains(parameter_name) { + // i. Add name as an element of the list mappedNames. + mapped_names.insert(parameter_name); + // ii. If index < len, then + if index < len { + // 1. Let g be MakeArgGetter(name, env). + // https://tc39.es/ecma262/#sec-makearggetter + let g = { + // 2. Let getter be ! CreateBuiltinFunction(getterClosure, 0, "", « »). + // 3. NOTE: getter is never directly accessible to ECMAScript code. + // 4. Return getter. + FunctionBuilder::closure_with_captures( + context, + // 1. Let getterClosure be a new Abstract Closure with no parameters that captures + // name and env and performs the following steps when called: + |_, _, captures, context| { + captures.0.get_binding_value(&captures.1, false, context) + }, + (env.clone(), parameter_name.to_owned()), + ) + .length(0) + .name("") + .build() + }; + + // 2. Let p be MakeArgSetter(name, env). + // https://tc39.es/ecma262/#sec-makeargsetter + let p = { + // 2. Let setter be ! CreateBuiltinFunction(setterClosure, 1, "", « »). + // 3. NOTE: setter is never directly accessible to ECMAScript code. + // 4. Return setter. + FunctionBuilder::closure_with_captures( + context, + // 1. Let setterClosure be a new Abstract Closure with parameters (value) that captures + // name and env and performs the following steps when called: + |_, args, captures, context| { + let value = args.get(0).cloned().unwrap_or_default(); + // a. Return env.SetMutableBinding(name, value, false). + captures + .0 + .set_mutable_binding(&captures.1, value, false, context) + .map(|_| JsValue::Undefined) + // Ok(JsValue::Undefined) + }, + (env.clone(), parameter_name.to_owned()), + ) + .length(1) + .name("") + .build() + }; + + // 3. Perform map.[[DefineOwnProperty]](! ToString(𝔽(index)), PropertyDescriptor { + // [[Set]]: p, [[Get]]: g, [[Enumerable]]: false, [[Configurable]]: true }). + map.__define_own_property__( + index.into(), + PropertyDescriptor::builder() + .set(p) + .get(g) + .enumerable(false) + .configurable(true) + .build(), + context, + ) + .expect("[[DefineOwnProperty]] must not fail per the spec"); + } + } + } + + // 20. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { + // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, + // [[Configurable]]: true }). + obj.define_property_or_throw( + WellKnownSymbols::iterator(), + PropertyDescriptor::builder() + .value(Array::values_intrinsic(context)) + .writable(true) + .enumerable(false) + .configurable(true), + context, + ) + .expect("DefinePropertyOrThrow must not fail per the spec"); + + // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { + // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + obj.define_property_or_throw( + "callee", + PropertyDescriptor::builder() + .value(func.clone()) + .writable(true) + .enumerable(false) + .configurable(true), + context, + ) + .expect("DefinePropertyOrThrow must not fail per the spec"); + + // 22. Return obj. + obj + } +} diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 0cc9daf398..658b061390 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -36,6 +36,7 @@ use crate::{ use super::JsArgs; +pub(crate) mod arguments; #[cfg(test)] mod tests; @@ -288,45 +289,6 @@ impl Function { } } -/// Arguments. -/// -/// -pub fn create_unmapped_arguments_object( - arguments_list: &[JsValue], - context: &mut Context, -) -> JsResult { - let len = arguments_list.len(); - let obj = JsObject::empty(); - // Set length - let length = PropertyDescriptor::builder() - .value(len) - .writable(true) - .enumerable(false) - .configurable(true) - .build(); - // Define length as a property - crate::object::internal_methods::ordinary_define_own_property( - &obj, - "length".into(), - length, - context, - )?; - let mut index: usize = 0; - while index < len { - let val = arguments_list.get(index).expect("Could not get argument"); - let prop = PropertyDescriptor::builder() - .value(val.clone()) - .writable(true) - .enumerable(true) - .configurable(true); - - obj.insert(index, prop); - index += 1; - } - - Ok(JsValue::new(obj)) -} - /// Creates a new member function of a `Object` or `prototype`. /// /// A function registered using this macro can then be called from Javascript using: diff --git a/boa/src/builtins/intrinsics.rs b/boa/src/builtins/intrinsics.rs new file mode 100644 index 0000000000..97c7eb762b --- /dev/null +++ b/boa/src/builtins/intrinsics.rs @@ -0,0 +1,46 @@ +use crate::{ + builtins::function::Function, + object::{JsObject, ObjectData}, + property::PropertyDescriptor, + Context, JsResult, JsValue, +}; + +#[derive(Debug, Default)] +pub struct IntrinsicObjects { + throw_type_error: JsObject, +} + +impl IntrinsicObjects { + pub fn init(context: &mut Context) -> IntrinsicObjects { + Self { + throw_type_error: create_throw_type_error(context), + } + } + + pub fn throw_type_error(&self) -> JsObject { + self.throw_type_error.clone() + } +} + +fn create_throw_type_error(context: &mut Context) -> JsObject { + fn throw_type_error(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + context.throw_type_error("invalid type") + } + + let function = JsObject::from_proto_and_data( + context.standard_objects().function_object().prototype(), + ObjectData::function(Function::Native { + function: throw_type_error, + constructable: false, + }), + ); + + let property = PropertyDescriptor::builder() + .writable(false) + .enumerable(false) + .configurable(false); + function.insert_property("name", property.clone().value("ThrowTypeError")); + function.insert_property("length", property.value(0)); + + function +} diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index b6b05808f0..47e1a54577 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -11,6 +11,7 @@ pub mod error; pub mod function; pub mod global_this; pub mod infinity; +pub mod intrinsics; pub mod iterable; pub mod json; pub mod map; diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 9b516ec8c1..fd901975c4 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -471,7 +471,7 @@ impl Object { let o = o.borrow(); match o.kind() { ObjectKind::Array => "Array", - // TODO: Arguments Exotic Objects are currently not supported + ObjectKind::Arguments(_) => "Arguments", ObjectKind::Function(_) => "Function", ObjectKind::Error => "Error", ObjectKind::Boolean(_) => "Boolean", diff --git a/boa/src/context.rs b/boa/src/context.rs index 25114da567..e4ff82b3b1 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -4,6 +4,7 @@ use crate::{ builtins::{ self, function::{Function, NativeFunctionSignature, ThisMode}, + intrinsics::IntrinsicObjects, iterable::IteratorPrototypes, typed_array::TypedArray, }, @@ -88,7 +89,7 @@ pub struct StandardObjects { symbol: StandardConstructor, error: StandardConstructor, type_error: StandardConstructor, - referece_error: StandardConstructor, + reference_error: StandardConstructor, range_error: StandardConstructor, syntax_error: StandardConstructor, eval_error: StandardConstructor, @@ -133,7 +134,7 @@ impl Default for StandardObjects { symbol: StandardConstructor::default(), error: StandardConstructor::default(), type_error: StandardConstructor::default(), - referece_error: StandardConstructor::default(), + reference_error: StandardConstructor::default(), range_error: StandardConstructor::default(), syntax_error: StandardConstructor::default(), eval_error: StandardConstructor::default(), @@ -210,7 +211,7 @@ impl StandardObjects { #[inline] pub fn reference_error_object(&self) -> &StandardConstructor { - &self.referece_error + &self.reference_error } #[inline] @@ -384,6 +385,9 @@ pub struct Context { /// Cached standard objects and their prototypes. standard_objects: StandardObjects, + /// Cached intrinsic objects + intrinsic_objects: IntrinsicObjects, + /// Whether or not strict mode is active. strict: StrictType, @@ -403,6 +407,7 @@ impl Default for Context { iterator_prototypes: IteratorPrototypes::default(), typed_array_constructor: StandardConstructor::default(), standard_objects: Default::default(), + intrinsic_objects: IntrinsicObjects::default(), strict: StrictType::Off, #[cfg(feature = "vm")] vm: Vm { @@ -426,6 +431,7 @@ impl Default for Context { context.typed_array_constructor.prototype = typed_array_constructor_prototype; context.create_intrinsics(); context.iterator_prototypes = IteratorPrototypes::init(&mut context); + context.intrinsic_objects = IntrinsicObjects::init(&mut context); context } } @@ -1060,6 +1066,12 @@ impl Context { &self.standard_objects } + /// Return the intrinsic objects. + #[inline] + pub fn intrinsics(&self) -> &IntrinsicObjects { + &self.intrinsic_objects + } + /// Set the value of trace on the context #[cfg(feature = "vm")] pub fn set_trace(&mut self, trace: bool) { diff --git a/boa/src/object/internal_methods/arguments.rs b/boa/src/object/internal_methods/arguments.rs new file mode 100644 index 0000000000..994fb8a72f --- /dev/null +++ b/boa/src/object/internal_methods/arguments.rs @@ -0,0 +1,260 @@ +use crate::{ + object::JsObject, + property::{DescriptorKind, PropertyDescriptor, PropertyKey}, + Context, JsResult, JsValue, +}; + +use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; + +pub(crate) static ARGUMENTS_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = + InternalObjectMethods { + __get_own_property__: arguments_exotic_get_own_property, + __define_own_property__: arguments_exotic_define_own_property, + __get__: arguments_exotic_get, + __set__: arguments_exotic_set, + __delete__: arguments_exotic_delete, + ..ORDINARY_INTERNAL_METHODS + }; + +/// `[[GetOwnProperty]]` for arguments exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-getownproperty-p +#[inline] +pub(crate) fn arguments_exotic_get_own_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut Context, +) -> JsResult> { + // 1. Let desc be OrdinaryGetOwnProperty(args, P). + // 2. If desc is undefined, return desc. + let desc = if let Some(desc) = super::ordinary_get_own_property(obj, key, context)? { + desc + } else { + return Ok(None); + }; + + // 3. Let map be args.[[ParameterMap]]. + let map = obj + .borrow() + .as_mapped_arguments() + .expect("arguments exotic method must only be callable from arguments objects") + .parameter_map(); + + Ok(Some( + // 4. Let isMapped be ! HasOwnProperty(map, P). + // 5. If isMapped is true, then + if map + .has_own_property(key.clone(), context) + .expect("HasOwnProperty must not fail per the spec") + { + // a. Set desc.[[Value]] to Get(map, P). + PropertyDescriptor::builder() + .value(map.get(key.clone(), context)?) + .maybe_writable(desc.writable()) + .maybe_enumerable(desc.enumerable()) + .maybe_configurable(desc.configurable()) + .build() + } else { + // 6. Return desc. + desc + }, + )) +} + +/// `[[DefineOwnProperty]]` for arguments exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-defineownproperty-p-desc +pub(crate) fn arguments_exotic_define_own_property( + obj: &JsObject, + key: PropertyKey, + desc: PropertyDescriptor, + context: &mut Context, +) -> JsResult { + // 1. Let map be args.[[ParameterMap]]. + let map = obj + .borrow() + .as_mapped_arguments() + .expect("arguments exotic method must only be callable from arguments objects") + .parameter_map(); + + // 2. Let isMapped be HasOwnProperty(map, P). + let is_mapped = map.has_own_property(key.clone(), context)?; + + let new_arg_desc = match desc.kind() { + // 4. If isMapped is true and IsDataDescriptor(Desc) is true, then + // a. If Desc.[[Value]] is not present and Desc.[[Writable]] is present and its + // value is false, then + DescriptorKind::Data { + writable: Some(false), + value: None, + } if is_mapped => + // i. Set newArgDesc to a copy of Desc. + // ii. Set newArgDesc.[[Value]] to Get(map, P). + { + PropertyDescriptor::builder() + .value(map.get(key.clone(), context)?) + .writable(false) + .maybe_enumerable(desc.enumerable()) + .maybe_configurable(desc.configurable()) + .build() + } + + // 3. Let newArgDesc be Desc. + _ => desc.clone(), + }; + + // 5. Let allowed be ? OrdinaryDefineOwnProperty(args, P, newArgDesc). + // 6. If allowed is false, return false. + if !super::ordinary_define_own_property(obj, key.clone(), new_arg_desc, context)? { + return Ok(false); + } + + // 7. If isMapped is true, then + if is_mapped { + // a. If IsAccessorDescriptor(Desc) is true, then + if desc.is_accessor_descriptor() { + // i. Call map.[[Delete]](P). + map.__delete__(&key, context)?; + } + // b. Else, + else { + // i. If Desc.[[Value]] is present, then + if let Some(value) = desc.value() { + // 1. Let setStatus be Set(map, P, Desc.[[Value]], false). + let set_status = map.set(key.clone(), value, false, context); + + // 2. Assert: setStatus is true because formal parameters mapped by argument objects are always writable. + assert_eq!(set_status, Ok(true)) + } + + // ii. If Desc.[[Writable]] is present and its value is false, then + if let Some(false) = desc.writable() { + // 1. Call map.[[Delete]](P). + map.__delete__(&key, context)?; + } + } + } + + // 8. Return true. + Ok(true) +} + +/// `[[Get]]` for arguments exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-get-p-receiver +pub(crate) fn arguments_exotic_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut Context, +) -> JsResult { + // 1. Let map be args.[[ParameterMap]]. + let map = obj + .borrow() + .as_mapped_arguments() + .expect("arguments exotic method must only be callable from arguments objects") + .parameter_map(); + + // 2. Let isMapped be ! HasOwnProperty(map, P). + // 4. Else, + if map + .has_own_property(key.clone(), context) + .expect("HasOwnProperty must not fail per the spec") + { + // a. Assert: map contains a formal parameter mapping for P. + // b. Return Get(map, P). + map.get(key.clone(), context) + + // 3. If isMapped is false, then + } else { + // a. Return ? OrdinaryGet(args, P, Receiver). + super::ordinary_get(obj, key, receiver, context) + } +} + +/// `[[Set]]` for arguments exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-set-p-v-receiver +pub(crate) fn arguments_exotic_set( + obj: &JsObject, + key: PropertyKey, + value: JsValue, + receiver: JsValue, + context: &mut Context, +) -> JsResult { + // 1. If SameValue(args, Receiver) is false, then + // a. Let isMapped be false. + // 2. Else, + if JsValue::same_value(&obj.clone().into(), &receiver) { + // a. Let map be args.[[ParameterMap]]. + let map = obj + .borrow() + .as_mapped_arguments() + .expect("arguments exotic method must only be callable from arguments objects") + .parameter_map(); + + // b. Let isMapped be ! HasOwnProperty(map, P). + // 3. If isMapped is true, then + if map + .has_own_property(key.clone(), context) + .expect("HasOwnProperty must not fail per the spec") + { + // a. Let setStatus be Set(map, P, V, false). + let set_status = map.set(key.clone(), value.clone(), false, context); + + // b. Assert: setStatus is true because formal parameters mapped by argument objects are always writable. + assert_eq!(set_status, Ok(true)); + } + } + + // 4. Return ? OrdinarySet(args, P, V, Receiver). + super::ordinary_set(obj, key, value, receiver, context) +} + +/// `[[Delete]]` for arguments exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-delete-p +pub(crate) fn arguments_exotic_delete( + obj: &JsObject, + key: &PropertyKey, + context: &mut Context, +) -> JsResult { + // 1. Let map be args.[[ParameterMap]]. + let map = obj + .borrow() + .as_mapped_arguments() + .expect("arguments exotic method must only be callable from arguments objects") + .parameter_map(); + + // 2. Let isMapped be ! HasOwnProperty(map, P). + let is_mapped = map + .has_own_property(key.clone(), context) + .expect("HasOwnProperty must not fail per the spec"); + + // 3. Let result be ? OrdinaryDelete(args, P). + let result = super::ordinary_delete(obj, key, context)?; + + // 4. If result is true and isMapped is true, then + if is_mapped && result { + // a. Call map.[[Delete]](P). + map.__delete__(key, context)?; + } + + // 5. Return result. + Ok(result) +} diff --git a/boa/src/object/internal_methods/mod.rs b/boa/src/object/internal_methods/mod.rs index 8a7c99e8c0..4c5cf7b085 100644 --- a/boa/src/object/internal_methods/mod.rs +++ b/boa/src/object/internal_methods/mod.rs @@ -15,6 +15,7 @@ use crate::{ use super::PROTOTYPE; +pub(super) mod arguments; pub(super) mod array; pub(super) mod integer_indexed; pub(super) mod string; diff --git a/boa/src/object/jsobject.rs b/boa/src/object/jsobject.rs index adb66dab4b..68d3f05e82 100644 --- a/boa/src/object/jsobject.rs +++ b/boa/src/object/jsobject.rs @@ -20,12 +20,8 @@ use std::{ #[cfg(not(feature = "vm"))] use crate::{ - builtins::function::{ - create_unmapped_arguments_object, Captures, ClosureFunctionSignature, Function, - NativeFunctionSignature, - }, + builtins::function::{Captures, ClosureFunctionSignature, Function, NativeFunctionSignature}, environment::{ - environment_record_trait::EnvironmentRecordTrait, function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, lexical_environment::Environment, }, @@ -160,7 +156,7 @@ impl JsObject { context: &mut Context, construct: bool, ) -> JsResult { - use crate::context::StandardObjects; + use crate::{builtins::function::arguments::Arguments, context::StandardObjects}; use super::internal_methods::get_prototype_from_constructor; @@ -233,14 +229,21 @@ impl JsObject { )?; let mut arguments_in_parameter_names = false; + let mut is_simple_parameter_list = true; for param in params.iter() { has_parameter_expressions = has_parameter_expressions || param.init().is_some(); arguments_in_parameter_names = arguments_in_parameter_names || param.name() == "arguments"; + is_simple_parameter_list = is_simple_parameter_list + && !param.is_rest_param() + && param.init().is_none() } + // Turn local_env into Environment so it can be cloned + let local_env: Environment = local_env.into(); + // An arguments object is added when all of the following conditions are met // - If not in an arrow function (10.2.11.16) // - If the parameter list does not contain `arguments` (10.2.11.17) @@ -254,14 +257,22 @@ impl JsObject { && !body.function_declared_names().contains("arguments"))) { // Add arguments object - let arguments_obj = create_unmapped_arguments_object(args, context)?; + let arguments_obj = + if context.strict() || body.strict() || !is_simple_parameter_list { + Arguments::create_unmapped_arguments_object(args, context) + } else { + Arguments::create_mapped_arguments_object( + self, params, args, &local_env, context, + ) + }; local_env.create_mutable_binding("arguments", false, true, context)?; - local_env.initialize_binding("arguments", arguments_obj, context)?; + local_env.initialize_binding( + "arguments", + arguments_obj.into(), + context, + )?; } - // Turn local_env into Environment so it can be cloned - let local_env: Environment = local_env.into(); - // Push the environment first so that it will be used by default parameters context.push_environment(local_env.clone()); diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index c2d8563780..87dc77ddc7 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -4,6 +4,7 @@ use crate::{ builtins::{ array::array_iterator::ArrayIterator, array_buffer::ArrayBuffer, + function::arguments::{Arguments, MappedArguments}, function::{Captures, Function, NativeFunctionSignature}, map::map_iterator::MapIterator, map::ordered_map::OrderedMap, @@ -16,6 +17,7 @@ use crate::{ }, context::StandardConstructor, gc::{Finalize, Trace}, + object::internal_methods::arguments::ARGUMENTS_EXOTIC_INTERNAL_METHODS, property::{Attribute, PropertyDescriptor, PropertyKey}, Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, }; @@ -113,6 +115,7 @@ pub enum ObjectKind { Ordinary, Date(Date), Global, + Arguments(Arguments), NativeObject(Box), IntegerIndexed(IntegerIndexed), } @@ -286,6 +289,18 @@ impl ObjectData { } } + /// Create the `Arguments` object data + pub fn arguments(arguments: Arguments) -> Self { + Self { + internal_methods: if matches!(arguments, Arguments::Unmapped) { + &ORDINARY_INTERNAL_METHODS + } else { + &ARGUMENTS_EXOTIC_INTERNAL_METHODS + }, + kind: ObjectKind::Arguments(arguments), + } + } + /// Create the `NativeObject` object data pub fn native_object(native_object: Box) -> Self { Self { @@ -327,6 +342,7 @@ impl Display for ObjectKind { Self::BigInt(_) => "BigInt", Self::Date(_) => "Date", Self::Global => "Global", + Self::Arguments(_) => "Arguments", Self::NativeObject(_) => "NativeObject", Self::IntegerIndexed(_) => "TypedArray", }) @@ -858,6 +874,30 @@ impl Object { ) } + /// Checks if it is an `Arguments` object. + #[inline] + pub fn is_arguments(&self) -> bool { + matches!( + self.data, + ObjectData { + kind: ObjectKind::Arguments(_), + .. + } + ) + } + + /// Gets the mapped arguments data if this is a mapped arguments object. + #[inline] + pub fn as_mapped_arguments(&self) -> Option<&MappedArguments> { + match self.data { + ObjectData { + kind: ObjectKind::Arguments(Arguments::Mapped(ref args)), + .. + } => Some(args), + _ => None, + } + } + /// Gets the typed array data (integer indexed object) if this is a typed array. #[inline] pub fn as_typed_array(&self) -> Option<&IntegerIndexed> {