From f66324cdf54357e92dfcd80f28419b365e410147 Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Thu, 14 Oct 2021 18:19:09 +0200 Subject: [PATCH] Implement `Proxy` object (#1664) * Implement `Proxy` object * Restucture `Proxy` struct fields * Apply some suggestions --- boa/src/builtins/mod.rs | 3 + boa/src/builtins/object/mod.rs | 105 +- boa/src/builtins/proxy/mod.rs | 188 +++ boa/src/context.rs | 7 + boa/src/object/internal_methods/mod.rs | 5 +- boa/src/object/internal_methods/proxy.rs | 1024 +++++++++++++++++ boa/src/object/internal_methods/string.rs | 2 +- boa/src/object/mod.rs | 95 +- boa/src/object/operations.rs | 43 +- boa/src/property/mod.rs | 2 +- .../syntax/ast/node/operator/assign/mod.rs | 21 +- .../syntax/ast/node/operator/unary_op/mod.rs | 8 +- test_ignore.txt | 1 + 13 files changed, 1432 insertions(+), 72 deletions(-) create mode 100644 boa/src/builtins/proxy/mod.rs create mode 100644 boa/src/object/internal_methods/proxy.rs diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 47e1a54577..f31e5defd4 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -19,6 +19,7 @@ pub mod math; pub mod nan; pub mod number; pub mod object; +pub mod proxy; pub mod reflect; pub mod regexp; pub mod set; @@ -44,6 +45,7 @@ pub(crate) use self::{ number::Number, object::for_in_iterator::ForInIterator, object::Object as BuiltInObjectObject, + proxy::Proxy, reflect::Reflect, regexp::RegExp, set::set_iterator::SetIterator, @@ -122,6 +124,7 @@ pub fn init(context: &mut Context) { Math, Json, Array, + Proxy, ArrayBuffer, BigInt, Boolean, diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 8acc178c4a..0998b8f55f 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -18,9 +18,9 @@ use crate::{ context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - IntegrityLevel, JsObject, ObjectData, ObjectInitializer, ObjectKind, + IntegrityLevel, JsObject, ObjectData, ObjectKind, }, - property::{Attribute, DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind}, + property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, value::JsValue, BoaProfiler, Context, JsResult, @@ -170,16 +170,17 @@ impl Object { args: &[JsValue], context: &mut Context, ) -> JsResult { - let object = args.get_or_undefined(0).to_object(context)?; - if let Some(key) = args.get(1) { - let key = key.to_property_key(context)?; + // 1. Let obj be ? ToObject(O). + let obj = args.get_or_undefined(0).to_object(context)?; - if let Some(desc) = object.__get_own_property__(&key, context)? { - return Ok(Self::from_property_descriptor(desc, context)); - } - } + // 2. Let key be ? ToPropertyKey(P). + let key = args.get_or_undefined(1).to_property_key(context)?; - Ok(JsValue::undefined()) + // 3. Let desc be ? obj.[[GetOwnProperty]](key). + let desc = obj.__get_own_property__(&key, context)?; + + // 4. Return FromPropertyDescriptor(desc). + Ok(Self::from_property_descriptor(desc, context)) } /// `Object.getOwnPropertyDescriptors( object )` @@ -202,9 +203,7 @@ impl Object { for key in object.borrow().properties().keys() { let descriptor = { - let desc = object - .__get_own_property__(&key, context)? - .expect("Expected property to be on object."); + let desc = object.__get_own_property__(&key, context)?; Self::from_property_descriptor(desc, context) }; @@ -228,40 +227,64 @@ impl Object { /// [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-frompropertydescriptor - fn from_property_descriptor(desc: PropertyDescriptor, context: &mut Context) -> JsValue { - let mut descriptor = ObjectInitializer::new(context); - - // TODO: use CreateDataPropertyOrThrow + pub(crate) fn from_property_descriptor( + desc: Option, + context: &mut Context, + ) -> JsValue { + match desc { + // 1. If Desc is undefined, return undefined. + None => JsValue::undefined(), + Some(desc) => { + // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). + // 3. Assert: obj is an extensible ordinary object with no own properties. + let obj = context.construct_object(); + + // 4. If Desc has a [[Value]] field, then + if let Some(value) = desc.value() { + // a. Perform ! CreateDataPropertyOrThrow(obj, "value", Desc.[[Value]]). + obj.create_data_property_or_throw("value", value, context) + .expect("CreateDataPropertyOrThrow cannot fail here"); + } - match desc.kind() { - DescriptorKind::Data { value, writable } => { - if let Some(value) = value { - descriptor.property("value", value.clone(), Attribute::all()); + // 5. If Desc has a [[Writable]] field, then + if let Some(writable) = desc.writable() { + // a. Perform ! CreateDataPropertyOrThrow(obj, "writable", Desc.[[Writable]]). + obj.create_data_property_or_throw("writable", writable, context) + .expect("CreateDataPropertyOrThrow cannot fail here"); } - if let Some(writable) = writable { - descriptor.property("writable", *writable, Attribute::all()); + + // 6. If Desc has a [[Get]] field, then + if let Some(get) = desc.get() { + // a. Perform ! CreateDataPropertyOrThrow(obj, "get", Desc.[[Get]]). + obj.create_data_property_or_throw("get", get, context) + .expect("CreateDataPropertyOrThrow cannot fail here"); } - } - DescriptorKind::Accessor { get, set } => { - if let Some(get) = get { - descriptor.property("get", get.clone(), Attribute::all()); + + // 7. If Desc has a [[Set]] field, then + if let Some(set) = desc.set() { + // a. Perform ! CreateDataPropertyOrThrow(obj, "set", Desc.[[Set]]). + obj.create_data_property_or_throw("set", set, context) + .expect("CreateDataPropertyOrThrow cannot fail here"); } - if let Some(set) = set { - descriptor.property("set", set.clone(), Attribute::all()); + + // 8. If Desc has an [[Enumerable]] field, then + if let Some(enumerable) = desc.enumerable() { + // a. Perform ! CreateDataPropertyOrThrow(obj, "enumerable", Desc.[[Enumerable]]). + obj.create_data_property_or_throw("enumerable", enumerable, context) + .expect("CreateDataPropertyOrThrow cannot fail here"); } - } - _ => {} - } - if let Some(enumerable) = desc.enumerable() { - descriptor.property("enumerable", enumerable, Attribute::all()); - } + // 9. If Desc has a [[Configurable]] field, then + if let Some(configurable) = desc.configurable() { + // a. Perform ! CreateDataPropertyOrThrow(obj, "configurable", Desc.[[Configurable]]). + obj.create_data_property_or_throw("configurable", configurable, context) + .expect("CreateDataPropertyOrThrow cannot fail here"); + } - if let Some(configurable) = desc.configurable() { - descriptor.property("configurable", configurable, Attribute::all()); + // 10. Return obj. + obj.into() + } } - - descriptor.build().into() } /// Uses the SameValue algorithm to check equality of objects @@ -273,6 +296,10 @@ impl Object { } /// Get the `prototype` of an object. + /// + /// [More information][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof pub fn get_prototype_of(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult { if args.is_empty() { return ctx.throw_type_error( diff --git a/boa/src/builtins/proxy/mod.rs b/boa/src/builtins/proxy/mod.rs new file mode 100644 index 0000000000..14df5cb9da --- /dev/null +++ b/boa/src/builtins/proxy/mod.rs @@ -0,0 +1,188 @@ +//! This module implements the global `Proxy` object. +//! +//! The `Proxy` object enables you to create a proxy for another object, +//! which can intercept and redefine fundamental operations for that object. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! - [MDN documentation][mdn] +//! +//! [spec]: https://tc39.es/ecma262/#sec-proxy-objects +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy + +use crate::{ + builtins::{BuiltIn, JsArgs}, + gc::{Finalize, Trace}, + object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData}, + property::Attribute, + BoaProfiler, Context, JsResult, JsValue, +}; + +/// Javascript `Proxy` object. +#[derive(Debug, Clone, Trace, Finalize)] +pub struct Proxy { + // (target, handler) + data: Option<(JsObject, JsObject)>, +} + +impl BuiltIn for Proxy { + const NAME: &'static str = "Proxy"; + + const ATTRIBUTE: Attribute = Attribute::WRITABLE + .union(Attribute::NON_ENUMERABLE) + .union(Attribute::CONFIGURABLE); + + fn init(context: &mut Context) -> JsValue { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + ConstructorBuilder::with_standard_object( + context, + Self::constructor, + context.standard_objects().proxy_object().clone(), + ) + .name(Self::NAME) + .length(Self::LENGTH) + .constructor_has_prototype(false) + .static_method(Self::revocable, "revocable", 2) + .build() + .into() + } +} + +impl Proxy { + const LENGTH: usize = 2; + + fn new(target: JsObject, handler: JsObject) -> Self { + Self { + data: Some((target, handler)), + } + } + + /// This is an internal method only built for usage in the proxy internal methods. + /// + /// It returns the (target, handler) of the proxy. + pub(crate) fn try_data(&self, context: &mut Context) -> JsResult<(JsObject, JsObject)> { + self.data.clone().ok_or_else(|| { + context.construct_type_error("Proxy object has empty handler and target") + }) + } + + /// `28.2.1.1 Proxy ( target, handler )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-proxy-target-handler + pub(crate) fn constructor( + new_target: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. If NewTarget is undefined, throw a TypeError exception. + if new_target.is_undefined() { + return context.throw_type_error("Proxy constructor called on undefined new target"); + } + + // 2. Return ? ProxyCreate(target, handler). + Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context) + } + + // `10.5.14 ProxyCreate ( target, handler )` + // + // More information: + // - [ECMAScript reference][spec] + // + // [spec]: https://tc39.es/ecma262/#sec-proxycreate + fn create(target: &JsValue, handler: &JsValue, context: &mut Context) -> JsResult { + // 1. If Type(target) is not Object, throw a TypeError exception. + let target = target.as_object().ok_or_else(|| { + context.construct_type_error("Proxy constructor called with non-object handler") + })?; + + // 2. If Type(handler) is not Object, throw a TypeError exception. + let handler = handler.as_object().ok_or_else(|| { + context.construct_type_error("Proxy constructor called with non-object handler") + })?; + + // 3. Let P be ! MakeBasicObject(« [[ProxyHandler]], [[ProxyTarget]] »). + // 4. Set P's essential internal methods, except for [[Call]] and [[Construct]], to the definitions specified in 10.5. + // 5. If IsCallable(target) is true, then + // a. Set P.[[Call]] as specified in 10.5.12. + // b. If IsConstructor(target) is true, then + // i. Set P.[[Construct]] as specified in 10.5.13. + // 6. Set P.[[ProxyTarget]] to target. + // 7. Set P.[[ProxyHandler]] to handler. + let p = JsObject::from_proto_and_data( + context.standard_objects().object_object().prototype(), + ObjectData::proxy( + Self::new(target.clone(), handler.clone()), + target.is_callable(), + target.is_constructor(), + ), + ); + + // 8. Return P. + Ok(p.into()) + } + + /// `28.2.2.1 Proxy.revocable ( target, handler )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable + fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let p be ? ProxyCreate(target, handler). + let p = Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context)?; + + // 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »). + // 4. Set revoker.[[RevocableProxy]] to p. + let revoker = FunctionBuilder::closure_with_captures( + context, + |_, _, revocable_proxy, _| { + // a. Let F be the active function object. + // b. Let p be F.[[RevocableProxy]]. + // c. If p is null, return undefined. + if revocable_proxy.is_null() { + return Ok(JsValue::undefined()); + } + + let p = revocable_proxy + .as_object() + .expect("[[RevocableProxy]] must be an object or null"); + + // e. Assert: p is a Proxy object. + // f. Set p.[[ProxyTarget]] to null. + // g. Set p.[[ProxyHandler]] to null. + p.borrow_mut() + .as_proxy_mut() + .expect("[[RevocableProxy]] must be a proxy object") + .data = None; + + // d. Set F.[[RevocableProxy]] to null. + *revocable_proxy = JsValue::Null; + + // h. Return undefined. + Ok(JsValue::undefined()) + }, + p.clone(), + ) + .build(); + + // 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%). + let result = context.construct_object(); + + // 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p). + result + .create_data_property_or_throw("proxy", p, context) + .expect("CreateDataPropertyOrThrow cannot fail here"); + + // 7. Perform ! CreateDataPropertyOrThrow(result, "revoke", revoker). + result + .create_data_property_or_throw("revoke", revoker, context) + .expect("CreateDataPropertyOrThrow cannot fail here"); + + // 8. Return result. + Ok(result.into()) + } +} diff --git a/boa/src/context.rs b/boa/src/context.rs index 636da4b277..1bbf3d2aa7 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -79,6 +79,7 @@ impl StandardConstructor { #[derive(Debug, Clone)] pub struct StandardObjects { object: StandardConstructor, + proxy: StandardConstructor, function: StandardConstructor, array: StandardConstructor, bigint: StandardConstructor, @@ -115,6 +116,7 @@ impl Default for StandardObjects { fn default() -> Self { Self { object: StandardConstructor::default(), + proxy: StandardConstructor::default(), function: StandardConstructor::default(), array: StandardConstructor::default(), bigint: StandardConstructor::default(), @@ -164,6 +166,11 @@ impl StandardObjects { &self.object } + #[inline] + pub fn proxy_object(&self) -> &StandardConstructor { + &self.proxy + } + #[inline] pub fn function_object(&self) -> &StandardConstructor { &self.function diff --git a/boa/src/object/internal_methods/mod.rs b/boa/src/object/internal_methods/mod.rs index e26c9ca259..4c74440650 100644 --- a/boa/src/object/internal_methods/mod.rs +++ b/boa/src/object/internal_methods/mod.rs @@ -20,6 +20,7 @@ pub(super) mod array; pub(super) mod bound_function; pub(super) mod function; pub(super) mod integer_indexed; +pub(super) mod proxy; pub(super) mod string; impl JsObject { @@ -749,10 +750,10 @@ pub(crate) fn ordinary_own_property_keys( pub(crate) fn is_compatible_property_descriptor( extensible: bool, desc: PropertyDescriptor, - current: PropertyDescriptor, + current: Option, ) -> bool { // 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current). - validate_and_apply_property_descriptor(None, extensible, desc, Some(current)) + validate_and_apply_property_descriptor(None, extensible, desc, current) } /// Abstract operation `ValidateAndApplyPropertyDescriptor` diff --git a/boa/src/object/internal_methods/proxy.rs b/boa/src/object/internal_methods/proxy.rs new file mode 100644 index 0000000000..136e86613f --- /dev/null +++ b/boa/src/object/internal_methods/proxy.rs @@ -0,0 +1,1024 @@ +use crate::{ + builtins::{array, object::Object}, + object::{InternalObjectMethods, JsObject, JsPrototype}, + property::{PropertyDescriptor, PropertyKey}, + value::Type, + Context, JsResult, JsValue, +}; +use rustc_hash::FxHashSet; + +/// Definitions of the internal object methods for array exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects +pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods = + InternalObjectMethods { + __get_prototype_of__: proxy_exotic_get_prototype_of, + __set_prototype_of__: proxy_exotic_set_prototype_of, + __is_extensible__: proxy_exotic_is_extensible, + __prevent_extensions__: proxy_exotic_prevent_extensions, + __get_own_property__: proxy_exotic_get_own_property, + __define_own_property__: proxy_exotic_define_own_property, + __has_property__: proxy_exotic_has_property, + __get__: proxy_exotic_get, + __set__: proxy_exotic_set, + __delete__: proxy_exotic_delete, + __own_property_keys__: proxy_exotic_own_property_keys, + __call__: None, + __construct__: None, + }; + +pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods = + InternalObjectMethods { + __get_prototype_of__: proxy_exotic_get_prototype_of, + __set_prototype_of__: proxy_exotic_set_prototype_of, + __is_extensible__: proxy_exotic_is_extensible, + __prevent_extensions__: proxy_exotic_prevent_extensions, + __get_own_property__: proxy_exotic_get_own_property, + __define_own_property__: proxy_exotic_define_own_property, + __has_property__: proxy_exotic_has_property, + __get__: proxy_exotic_get, + __set__: proxy_exotic_set, + __delete__: proxy_exotic_delete, + __own_property_keys__: proxy_exotic_own_property_keys, + __call__: Some(proxy_exotic_call), + __construct__: None, + }; + +pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods = + InternalObjectMethods { + __get_prototype_of__: proxy_exotic_get_prototype_of, + __set_prototype_of__: proxy_exotic_set_prototype_of, + __is_extensible__: proxy_exotic_is_extensible, + __prevent_extensions__: proxy_exotic_prevent_extensions, + __get_own_property__: proxy_exotic_get_own_property, + __define_own_property__: proxy_exotic_define_own_property, + __has_property__: proxy_exotic_has_property, + __get__: proxy_exotic_get, + __set__: proxy_exotic_set, + __delete__: proxy_exotic_delete, + __own_property_keys__: proxy_exotic_own_property_keys, + __call__: Some(proxy_exotic_call), + __construct__: Some(proxy_exotic_construct), + }; + +/// `10.5.1 [[GetPrototypeOf]] ( )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof +pub(crate) fn proxy_exotic_get_prototype_of( + obj: &JsObject, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "getPrototypeOf"). + let trap = if let Some(trap) = handler.get_method("getPrototypeOf", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? target.[[GetPrototypeOf]](). + return target.__get_prototype_of__(context); + }; + + // 7. Let handlerProto be ? Call(trap, handler, « target »). + let handler_proto = trap.call(&handler.into(), &[target.clone().into()], context)?; + + // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception. + let handler_proto = match &handler_proto { + JsValue::Object(obj) => Some(obj.clone()), + JsValue::Null => None, + _ => { + return Err(context.construct_type_error("Proxy trap result is neither object nor null")) + } + }; + + // 9. Let extensibleTarget be ? IsExtensible(target). + // 10. If extensibleTarget is true, return handlerProto. + if target.is_extensible(context)? { + return Ok(handler_proto); + } + + // 11. Let targetProto be ? target.[[GetPrototypeOf]](). + let target_proto = target.__get_prototype_of__(context)?; + + // 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError exception. + if handler_proto != target_proto { + return Err(context.construct_type_error("Proxy trap returned unexpected prototype")); + } + + // 13. Return handlerProto. + Ok(handler_proto) +} + +/// `10.5.2 [[SetPrototypeOf]] ( V )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v +#[inline] +pub(crate) fn proxy_exotic_set_prototype_of( + obj: &JsObject, + val: JsPrototype, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "setPrototypeOf"). + let trap = if let Some(trap) = handler.get_method("setPrototypeOf", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? target.[[SetPrototypeOf]](V). + return target.__set_prototype_of__(val, context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, V »)). + // 8. If booleanTrapResult is false, return false. + if !trap + .call( + &handler.into(), + &[ + target.clone().into(), + val.clone().map_or(JsValue::Null, |obj| obj.into()), + ], + context, + )? + .to_boolean() + { + return Ok(false); + } + + // 9. Let extensibleTarget be ? IsExtensible(target). + // 10. If extensibleTarget is true, return true. + if target.is_extensible(context)? { + return Ok(true); + } + + // 11. Let targetProto be ? target.[[GetPrototypeOf]](). + let target_proto = target.__get_prototype_of__(context)?; + + // 12. If SameValue(V, targetProto) is false, throw a TypeError exception. + if val != target_proto { + return Err(context.construct_type_error("Proxy trap failed to set prototype")); + } + + // 13. Return true. + Ok(true) +} + +/// `10.5.3 [[IsExtensible]] ( )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-isextensible +#[inline] +pub(crate) fn proxy_exotic_is_extensible(obj: &JsObject, context: &mut Context) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "isExtensible"). + let trap = if let Some(trap) = handler.get_method("isExtensible", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? IsExtensible(target). + return target.is_extensible(context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)). + let boolean_trap_result = trap + .call(&handler.into(), &[target.clone().into()], context)? + .to_boolean(); + + // 8. Let targetResult be ? IsExtensible(target). + let target_result = target.is_extensible(context)?; + + // 9. If SameValue(booleanTrapResult, targetResult) is false, throw a TypeError exception. + if boolean_trap_result != target_result { + return Err(context.construct_type_error("Proxy trap returned unexpected extensible value")); + } + + // 10. Return booleanTrapResult. + Ok(boolean_trap_result) +} + +/// `10.5.4 [[PreventExtensions]] ( )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions +#[inline] +pub(crate) fn proxy_exotic_prevent_extensions( + obj: &JsObject, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "preventExtensions"). + let trap = if let Some(trap) = handler.get_method("preventExtensions", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? target.[[PreventExtensions]](). + return target.__prevent_extensions__(context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)). + let boolean_trap_result = trap + .call(&handler.into(), &[target.clone().into()], context)? + .to_boolean(); + + // 8. If booleanTrapResult is true, then + if boolean_trap_result && target.is_extensible(context)? { + // a. Let extensibleTarget be ? IsExtensible(target). + // b. If extensibleTarget is true, throw a TypeError exception. + return Err(context.construct_type_error("Proxy trap failed to set extensible")); + } + + // 9. Return booleanTrapResult. + Ok(boolean_trap_result) +} + +/// `10.5.5 [[GetOwnProperty]] ( P )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p +#[inline] +pub(crate) fn proxy_exotic_get_own_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut Context, +) -> JsResult> { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor"). + let trap = if let Some(trap) = handler.get_method("getOwnPropertyDescriptor", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? target.[[GetOwnProperty]](P). + return target.__get_own_property__(key, context); + }; + + // 7. Let trapResultObj be ? Call(trap, handler, « target, P »). + let trap_result_obj = trap.call( + &handler.into(), + &[target.clone().into(), key.clone().into()], + context, + )?; + + // 8. If Type(trapResultObj) is neither Object nor Undefined, throw a TypeError exception. + if !trap_result_obj.is_object() && !trap_result_obj.is_undefined() { + return Err( + context.construct_type_error("Proxy trap result is neither object nor undefined") + ); + } + + // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). + let target_desc = target.__get_own_property__(key, context)?; + + // 10. If trapResultObj is undefined, then + if trap_result_obj.is_undefined() { + if let Some(desc) = target_desc { + // b. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + if !desc.expect_configurable() { + return Err(context.construct_type_error( + "Proxy trap result is undefined adn target result is not configurable", + )); + } + + // c. Let extensibleTarget be ? IsExtensible(target). + // d. If extensibleTarget is false, throw a TypeError exception. + if !target.is_extensible(context)? { + return Err(context.construct_type_error( + "Proxy trap result is undefined and target is not extensible", + )); + // e. Return undefined. + } else { + return Ok(None); + } + } else { + // a. If targetDesc is undefined, return undefined. + return Ok(None); + } + } + + // 11. Let extensibleTarget be ? IsExtensible(target). + let extensible_target = target.is_extensible(context)?; + + // 12. Let resultDesc be ? ToPropertyDescriptor(trapResultObj). + let result_desc = trap_result_obj.to_property_descriptor(context)?; + + // 13. Call CompletePropertyDescriptor(resultDesc). + let result_desc = result_desc.complete_property_descriptor(); + + // 14. Let valid be IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc). + // 15. If valid is false, throw a TypeError exception. + if !super::is_compatible_property_descriptor( + extensible_target, + result_desc.clone(), + target_desc.clone(), + ) { + return Err(context.construct_type_error("Proxy trap returned unexpected property")); + } + + // 16. If resultDesc.[[Configurable]] is false, then + if !result_desc.expect_configurable() { + // a. If targetDesc is undefined or targetDesc.[[Configurable]] is true, then + match &target_desc { + Some(desc) if !desc.expect_configurable() => { + // b. If resultDesc has a [[Writable]] field and resultDesc.[[Writable]] is false, then + if let Some(false) = result_desc.writable() { + // i. If targetDesc.[[Writable]] is true, throw a TypeError exception. + if desc.expect_writable() { + return Err( + context.construct_type_error("Proxy trap result is writable and not configurable while target result is not configurable") + ); + } + } + } + // i. Throw a TypeError exception. + _ => { + return Err(context.construct_type_error( + "Proxy trap result is not configurable and target result is undefined", + )) + } + } + } + + // 17. Return resultDesc. + Ok(Some(result_desc)) +} + +/// `10.5.6 [[DefineOwnProperty]] ( P, Desc )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc +#[inline] +pub(crate) fn proxy_exotic_define_own_property( + obj: &JsObject, + key: PropertyKey, + desc: PropertyDescriptor, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "defineProperty"). + let trap = if let Some(trap) = handler.get_method("defineProperty", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? target.[[DefineOwnProperty]](P, Desc). + return target.__define_own_property__(key, desc, context); + }; + + // 7. Let descObj be FromPropertyDescriptor(Desc). + let desc_obj = Object::from_property_descriptor(Some(desc.clone()), context); + + // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, descObj »)). + // 9. If booleanTrapResult is false, return false. + if !trap + .call( + &handler.into(), + &[target.clone().into(), key.clone().into(), desc_obj], + context, + )? + .to_boolean() + { + return Ok(false); + } + + // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). + let target_desc = target.__get_own_property__(&key, context)?; + + // 11. Let extensibleTarget be ? IsExtensible(target). + let extensible_target = target.is_extensible(context)?; + + // 12. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, then + let setting_config_false = matches!(desc.configurable(), Some(false)); + + match target_desc { + // 14. If targetDesc is undefined, then + None => { + // a. If extensibleTarget is false, throw a TypeError exception. + if !extensible_target { + return Err(context.construct_type_error("Proxy trap failed to set property")); + } + + // b. If settingConfigFalse is true, throw a TypeError exception. + if setting_config_false { + return Err(context.construct_type_error("Proxy trap failed to set property")); + } + } + // 15. Else, + Some(target_desc) => { + // a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, targetDesc) is false, throw a TypeError exception. + if !super::is_compatible_property_descriptor( + extensible_target, + desc.clone(), + Some(target_desc.clone()), + ) { + return Err( + context.construct_type_error("Proxy trap set property to unexpected value") + ); + } + + // b. If settingConfigFalse is true and targetDesc.[[Configurable]] is true, throw a TypeError exception. + if setting_config_false && target_desc.expect_configurable() { + return Err(context.construct_type_error( + "Proxy trap set property with unexpected configurable field", + )); + } + + // c. If IsDataDescriptor(targetDesc) is true, targetDesc.[[Configurable]] is false, and targetDesc.[[Writable]] is true, then + if target_desc.is_data_descriptor() + && !target_desc.expect_configurable() + && target_desc.expect_writable() + { + // i. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, throw a TypeError exception. + if let Some(writable) = desc.writable() { + if !writable { + return Err(context.construct_type_error( + "Proxy trap set property with unexpected writable field", + )); + } + } + } + } + } + + // 16. Return true. + Ok(true) +} + +/// `10.5.7 [[HasProperty]] ( P )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-hasproperty-p +#[inline] +pub(crate) fn proxy_exotic_has_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "has"). + let trap = if let Some(trap) = handler.get_method("has", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? target.[[HasProperty]](P). + return target.has_property(key.clone(), context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)). + let boolean_trap_result = trap + .call( + &handler.into(), + &[target.clone().into(), key.clone().into()], + context, + )? + .to_boolean(); + + // 8. If booleanTrapResult is false, then + if !boolean_trap_result { + // a. Let targetDesc be ? target.[[GetOwnProperty]](P). + let target_desc = target.__get_own_property__(key, context)?; + + // b. If targetDesc is not undefined, then + if let Some(target_desc) = target_desc { + // i. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + if !target_desc.expect_configurable() { + return Err(context.construct_type_error("Proxy trap returned unexpected property")); + } + + // ii. Let extensibleTarget be ? IsExtensible(target). + // iii. If extensibleTarget is false, throw a TypeError exception. + if !target.is_extensible(context)? { + return Err(context.construct_type_error("Proxy trap returned unexpected property")); + } + } + } + + // 9. Return booleanTrapResult. + Ok(boolean_trap_result) +} + +/// `10.5.8 [[Get]] ( P, Receiver )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver +#[inline] +pub(crate) fn proxy_exotic_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "get"). + let trap = if let Some(trap) = handler.get_method("get", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? target.[[Get]](P, Receiver). + return target.__get__(key, receiver, context); + }; + + // 7. Let trapResult be ? Call(trap, handler, « target, P, Receiver »). + let trap_result = trap.call( + &handler.into(), + &[target.clone().into(), key.clone().into(), receiver], + context, + )?; + + // 8. Let targetDesc be ? target.[[GetOwnProperty]](P). + let target_desc = target.__get_own_property__(key, context)?; + + // 9. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then + if let Some(target_desc) = target_desc { + if !target_desc.expect_configurable() { + // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then + if target_desc.is_data_descriptor() && !target_desc.expect_writable() { + // i. If SameValue(trapResult, targetDesc.[[Value]]) is false, throw a TypeError exception. + if !JsValue::same_value(&trap_result, target_desc.expect_value()) { + return Err(context + .construct_type_error("Proxy trap returned unexpected data descriptor")); + } + } + + // b. If IsAccessorDescriptor(targetDesc) is true and targetDesc.[[Get]] is undefined, then + if target_desc.is_accessor_descriptor() && target_desc.expect_get().is_undefined() { + // i. If trapResult is not undefined, throw a TypeError exception. + if !trap_result.is_undefined() { + return Err(context.construct_type_error( + "Proxy trap returned unexpected accessor descriptor", + )); + } + } + } + } + + // 10. Return trapResult. + Ok(trap_result) +} + +/// `10.5.9 [[Set]] ( P, V, Receiver )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver +#[inline] +pub(crate) fn proxy_exotic_set( + obj: &JsObject, + key: PropertyKey, + value: JsValue, + receiver: JsValue, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "set"). + let trap = if let Some(trap) = handler.get_method("set", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? target.[[Set]](P, V, Receiver). + return target.__set__(key, value, receiver, context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, V, Receiver »)). + // 8. If booleanTrapResult is false, return false. + if !trap + .call( + &handler.into(), + &[target.clone().into(), value.clone(), receiver], + context, + )? + .to_boolean() + { + return Ok(false); + } + + // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). + let target_desc = target.__get_own_property__(&key, context)?; + + // 10. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then + if let Some(target_desc) = target_desc { + if !target_desc.expect_configurable() { + // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then + if target_desc.is_data_descriptor() && !target_desc.expect_writable() { + // i. If SameValue(V, targetDesc.[[Value]]) is false, throw a TypeError exception. + if !JsValue::same_value(&value, target_desc.expect_value()) { + return Err( + context.construct_type_error("Proxy trap set unexpected data descriptor") + ); + } + } + + // b. If IsAccessorDescriptor(targetDesc) is true, then + if target_desc.is_accessor_descriptor() { + // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. + match target_desc.set() { + None | Some(&JsValue::Undefined) => { + return Err(context.construct_type_error( + "Proxy trap set unexpected accessor descriptor", + )); + } + _ => {} + } + } + } + } + + // 11. Return true. + Ok(true) +} + +/// `10.5.10 [[Delete]] ( P )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-delete-p +#[inline] +pub(crate) fn proxy_exotic_delete( + obj: &JsObject, + key: &PropertyKey, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "deleteProperty"). + let trap = if let Some(trap) = handler.get_method("deleteProperty", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? target.[[Delete]](P). + return target.__delete__(key, context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)). + // 8. If booleanTrapResult is false, return false. + if !trap + .call( + &handler.into(), + &[target.clone().into(), key.clone().into()], + context, + )? + .to_boolean() + { + return Ok(false); + } + + // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). + match target.__get_own_property__(key, context)? { + // 10. If targetDesc is undefined, return true. + None => return Ok(true), + // 11. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + Some(target_desc) => { + if !target_desc.expect_configurable() { + return Err(context.construct_type_error("Proxy trap failed to delete property")); + } + } + } + + // 12. Let extensibleTarget be ? IsExtensible(target). + // 13. If extensibleTarget is false, throw a TypeError exception. + if !target.is_extensible(context)? { + return Err(context.construct_type_error("Proxy trap failed to delete property")); + } + + // 14. Return true. + Ok(true) +} + +/// `10.5.11 [[OwnPropertyKeys]] ( )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys +#[inline] +pub(crate) fn proxy_exotic_own_property_keys( + obj: &JsObject, + context: &mut Context, +) -> JsResult> { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "ownKeys"). + let trap = if let Some(trap) = handler.get_method("ownKeys", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? target.[[OwnPropertyKeys]](). + return target.__own_property_keys__(context); + }; + + // 7. Let trapResultArray be ? Call(trap, handler, « target »). + let trap_result_array = trap.call(&handler.into(), &[target.clone().into()], context)?; + + // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, « String, Symbol »). + let trap_result_raw = + trap_result_array.create_list_from_array_like(&[Type::String, Type::Symbol], context)?; + + // 9. If trapResult contains any duplicate entries, throw a TypeError exception. + let mut unchecked_result_keys: FxHashSet = FxHashSet::default(); + let mut trap_result = Vec::new(); + for value in &trap_result_raw { + match value { + JsValue::String(s) => { + if !unchecked_result_keys.insert(s.clone().into()) { + return Err(context.construct_type_error( + "Proxy trap result contains duplicate string property keys", + )); + } + trap_result.push(s.clone().into()) + } + JsValue::Symbol(s) => { + if !unchecked_result_keys.insert(s.clone().into()) { + return Err(context.construct_type_error( + "Proxy trap result contains duplicate symbol property keys", + )); + } + trap_result.push(s.clone().into()) + } + _ => {} + } + } + + // 10. Let extensibleTarget be ? IsExtensible(target). + let extensible_target = target.is_extensible(context)?; + + // 11. Let targetKeys be ? target.[[OwnPropertyKeys]](). + // 12. Assert: targetKeys is a List of property keys. + // 13. Assert: targetKeys contains no duplicate entries. + let target_keys = target.__own_property_keys__(context)?; + + // 14. Let targetConfigurableKeys be a new empty List. + // 15. Let targetNonconfigurableKeys be a new empty List. + let mut target_configurable_keys = Vec::new(); + let mut target_nonconfigurable_keys = Vec::new(); + + // 16. For each element key of targetKeys, do + for key in target_keys { + // a. Let desc be ? target.[[GetOwnProperty]](key). + match target.__get_own_property__(&key, context)? { + // b. If desc is not undefined and desc.[[Configurable]] is false, then + Some(desc) if !desc.expect_configurable() => { + // i. Append key as an element of targetNonconfigurableKeys. + target_nonconfigurable_keys.push(key); + } + // c. Else, + _ => { + // i. Append key as an element of targetConfigurableKeys. + target_configurable_keys.push(key); + } + } + } + + // 17. If extensibleTarget is true and targetNonconfigurableKeys is empty, then + if extensible_target && target_nonconfigurable_keys.is_empty() { + // a. Return trapResult. + return Ok(trap_result); + } + + // 18. Let uncheckedResultKeys be a List whose elements are the elements of trapResult. + // 19. For each element key of targetNonconfigurableKeys, do + for key in target_nonconfigurable_keys { + // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. + // b. Remove key from uncheckedResultKeys. + if !unchecked_result_keys.remove(&key) { + return Err(context.construct_type_error( + "Proxy trap failed to return all non-configurable property keys", + )); + } + } + + // 20. If extensibleTarget is true, return trapResult. + if extensible_target { + return Ok(trap_result); + } + + // 21. For each element key of targetConfigurableKeys, do + for key in target_configurable_keys { + // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. + // b. Remove key from uncheckedResultKeys. + if !unchecked_result_keys.remove(&key) { + return Err(context.construct_type_error( + "Proxy trap failed to return all configurable property keys", + )); + } + } + + // 22. If uncheckedResultKeys is not empty, throw a TypeError exception. + if !unchecked_result_keys.is_empty() { + return Err(context.construct_type_error("Proxy trap failed to return all property keys")); + } + + // 23. Return trapResult. + Ok(trap_result) +} + +/// `10.5.12 [[Call]] ( thisArgument, argumentsList )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist +fn proxy_exotic_call( + obj: &JsObject, + this: &JsValue, + args: &[JsValue], + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Let trap be ? GetMethod(handler, "apply"). + let trap = if let Some(trap) = handler.get_method("apply", context)? { + trap + // 6. If trap is undefined, then + } else { + // a. Return ? Call(target, thisArgument, argumentsList). + return target.call(this, args, context); + }; + + // 7. Let argArray be ! CreateArrayFromList(argumentsList). + let arg_array = array::Array::create_array_from_list(args.to_vec(), context); + + // 8. Return ? Call(trap, handler, « target, thisArgument, argArray »). + trap.call( + &handler.into(), + &[target.clone().into(), this.clone(), arg_array.into()], + context, + ) +} + +/// `10.5.13 [[Construct]] ( argumentsList, newTarget )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget +fn proxy_exotic_construct( + obj: &JsObject, + args: &[JsValue], + new_target: &JsValue, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .borrow() + .as_proxy() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data(context)?; + + // 5. Assert: IsConstructor(target) is true. + assert!(target.is_constructor()); + + // 6. Let trap be ? GetMethod(handler, "construct"). + let trap = if let Some(trap) = handler.get_method("construct", context)? { + trap + // 7. If trap is undefined, then + } else { + // a. Return ? Construct(target, argumentsList, newTarget). + return target.construct(args, new_target, context); + }; + + // 8. Let argArray be ! CreateArrayFromList(argumentsList). + let arg_array = array::Array::create_array_from_list(args.to_vec(), context); + + // 9. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »). + let new_obj = trap.call( + &handler.into(), + &[target.clone().into(), arg_array.into(), new_target.clone()], + context, + )?; + + // 10. If Type(newObj) is not Object, throw a TypeError exception. + if !new_obj.is_object() { + return Err( + context.construct_type_error("Proxy trap constructor returned non-object value") + ); + } + + // 11. Return newObj. + Ok(new_obj) +} diff --git a/boa/src/object/internal_methods/string.rs b/boa/src/object/internal_methods/string.rs index eb23743d5d..ed65f14964 100644 --- a/boa/src/object/internal_methods/string.rs +++ b/boa/src/object/internal_methods/string.rs @@ -69,7 +69,7 @@ pub(crate) fn string_exotic_define_own_property( Ok(super::is_compatible_property_descriptor( extensible, desc, - string_desc, + Some(string_desc), )) } else { // 4. Return ! OrdinaryDefineOwnProperty(S, P, Desc). diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index a22a970a97..df2b80e232 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -9,6 +9,7 @@ use crate::{ map::map_iterator::MapIterator, map::ordered_map::OrderedMap, object::for_in_iterator::ForInIterator, + proxy::Proxy, regexp::regexp_string_iterator::RegExpStringIterator, set::ordered_set::OrderedSet, set::set_iterator::SetIterator, @@ -39,6 +40,10 @@ use self::internal_methods::{ }, function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS}, integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, + proxy::{ + PROXY_EXOTIC_INTERNAL_METHODS_ALL, PROXY_EXOTIC_INTERNAL_METHODS_BASIC, + PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL, + }, string::STRING_EXOTIC_INTERNAL_METHODS, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, }; @@ -122,6 +127,7 @@ pub enum ObjectKind { Symbol(JsSymbol), Error, Ordinary, + Proxy(Proxy), Date(Date), Global, Arguments(Arguments), @@ -298,6 +304,20 @@ impl ObjectData { } } + /// Create the `Proxy` object data + pub fn proxy(proxy: Proxy, call: bool, construct: bool) -> Self { + Self { + kind: ObjectKind::Proxy(proxy), + internal_methods: if call && construct { + &PROXY_EXOTIC_INTERNAL_METHODS_ALL + } else if call { + &PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL + } else { + &PROXY_EXOTIC_INTERNAL_METHODS_BASIC + }, + } + } + /// Create the `Date` object data pub fn date(date: Date) -> Self { Self { @@ -363,6 +383,7 @@ impl Display for ObjectKind { Self::Symbol(_) => "Symbol", Self::Error => "Error", Self::Ordinary => "Ordinary", + Self::Proxy(_) => "Proxy", Self::Boolean(_) => "Boolean", Self::Number(_) => "Number", Self::BigInt(_) => "BigInt", @@ -950,6 +971,40 @@ impl Object { ) } + /// Checks if it's an proxy object. + #[inline] + pub fn is_proxy(&self) -> bool { + matches!( + self.data, + ObjectData { + kind: ObjectKind::Proxy(_), + .. + } + ) + } + + #[inline] + pub fn as_proxy(&self) -> Option<&Proxy> { + match self.data { + ObjectData { + kind: ObjectKind::Proxy(ref proxy), + .. + } => Some(proxy), + _ => None, + } + } + + #[inline] + pub fn as_proxy_mut(&mut self) -> Option<&mut Proxy> { + match self.data { + ObjectData { + kind: ObjectKind::Proxy(ref mut proxy), + .. + } => Some(proxy), + _ => None, + } + } + /// Gets the prototype instance of this object. #[inline] pub fn prototype(&self) -> &JsPrototype { @@ -998,7 +1053,7 @@ impl Object { } } - /// Reeturn `true` if it is a native object and the native type is `T`. + /// Return `true` if it is a native object and the native type is `T`. #[inline] pub fn is(&self) -> bool where @@ -1069,7 +1124,7 @@ impl Object { /// Inserts a field in the object `properties` without checking if it's writable. /// /// If a field was already in the object with the same name that a `Some` is returned - /// with that field, otherwise None is retuned. + /// with that field, otherwise None is returned. #[inline] pub fn insert_property(&mut self, key: K, property: P) -> Option where @@ -1273,8 +1328,8 @@ impl<'context> FunctionBuilder<'context> { .writable(false) .enumerable(false) .configurable(true); - function.insert_property("name", property.clone().value(self.name.clone())); - function.insert_property("length", property.value(self.length)); + function.insert_property("length", property.clone().value(self.length)); + function.insert_property("name", property.value(self.name.clone())); function } @@ -1395,6 +1450,7 @@ pub struct ConstructorBuilder<'context> { context: &'context mut Context, constructor_function: NativeFunctionSignature, constructor_object: JsObject, + constructor_has_prototype: bool, prototype: JsObject, name: JsString, length: usize, @@ -1410,6 +1466,7 @@ impl Debug for ConstructorBuilder<'_> { .field("name", &self.name) .field("length", &self.length) .field("constructor", &self.constructor_object) + .field("constructor_has_prototype", &self.constructor_has_prototype) .field("prototype", &self.prototype) .field("inherit", &self.inherit) .field("callable", &self.callable) @@ -1433,6 +1490,7 @@ impl<'context> ConstructorBuilder<'context> { constructor: true, inherit: None, custom_prototype: None, + constructor_has_prototype: true, } } @@ -1446,6 +1504,7 @@ impl<'context> ConstructorBuilder<'context> { context, constructor_function: constructor, constructor_object: object.constructor, + constructor_has_prototype: true, prototype: object.prototype, length: 0, name: JsString::default(), @@ -1669,6 +1728,15 @@ impl<'context> ConstructorBuilder<'context> { self } + /// Specify whether the constructor function has a 'prototype' property. + /// + /// Default is `true` + #[inline] + pub fn constructor_has_prototype(&mut self, constructor_has_prototype: bool) -> &mut Self { + self.constructor_has_prototype = constructor_has_prototype; + self + } + /// Return the current context. #[inline] pub fn context(&mut self) -> &'_ mut Context { @@ -1710,14 +1778,17 @@ impl<'context> ConstructorBuilder<'context> { .prototype(), ); } - constructor.insert_property( - PROTOTYPE, - PropertyDescriptor::builder() - .value(self.prototype.clone()) - .writable(false) - .enumerable(false) - .configurable(false), - ); + + if self.constructor_has_prototype { + constructor.insert_property( + PROTOTYPE, + PropertyDescriptor::builder() + .value(self.prototype.clone()) + .writable(false) + .enumerable(false) + .configurable(false), + ); + } } { diff --git a/boa/src/object/operations.rs b/boa/src/object/operations.rs index 921029cc12..7c207b9e4c 100644 --- a/boa/src/object/operations.rs +++ b/boa/src/object/operations.rs @@ -534,6 +534,34 @@ impl JsObject { Ok(properties) } + /// Abstract operation `GetMethod ( V, P )` + /// + /// Retrieves the value of a specific property, when the value of the property is expected to be a function. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-getmethod + pub(crate) fn get_method(&self, key: K, context: &mut Context) -> JsResult> + where + K: Into, + { + // Note: The spec specifies this function for JsValue. + // It is implemented for JsObject for convenience. + + // 1. Assert: IsPropertyKey(P) is true. + // 2. Let func be ? GetV(V, P). + match &self.__get__(&key.into(), self.clone().into(), context)? { + // 3. If func is either undefined or null, return undefined. + JsValue::Undefined | JsValue::Null => Ok(None), + // 5. Return func. + JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())), + // 4. If IsCallable(func) is false, throw a TypeError exception. + _ => Err(context + .construct_type_error("value returned for property of object is not a function")), + } + } + // todo: GetFunctionRealm // todo: CopyDataProperties @@ -584,21 +612,14 @@ impl JsValue { /// - [EcmaScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-getmethod + #[inline] pub(crate) fn get_method(&self, key: K, context: &mut Context) -> JsResult> where K: Into, { - // 1. Assert: IsPropertyKey(P) is true. - // 2. Let func be ? GetV(V, P). - match &self.get_v(key, context)? { - // 3. If func is either undefined or null, return undefined. - JsValue::Undefined | JsValue::Null => Ok(None), - // 5. Return func. - JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())), - // 4. If IsCallable(func) is false, throw a TypeError exception. - _ => Err(context - .construct_type_error("value returned for property of object is not a function")), - } + // Note: The spec specifies this function for JsValue. + // The main part of the function is implemented for JsObject. + self.to_object(context)?.get_method(key, context) } /// It is used to create List value whose elements are provided by the indexed properties of diff --git a/boa/src/property/mod.rs b/boa/src/property/mod.rs index 625151a561..0cbb1f3df7 100644 --- a/boa/src/property/mod.rs +++ b/boa/src/property/mod.rs @@ -473,7 +473,7 @@ impl From for PropertyDescriptor { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ispropertykey -#[derive(Trace, Finalize, PartialEq, Debug, Clone)] +#[derive(Trace, Finalize, PartialEq, Debug, Clone, Eq, Hash)] pub enum PropertyKey { String(JsString), Symbol(JsSymbol), diff --git a/boa/src/syntax/ast/node/operator/assign/mod.rs b/boa/src/syntax/ast/node/operator/assign/mod.rs index d2af1b6fc1..56a5ce3a1d 100644 --- a/boa/src/syntax/ast/node/operator/assign/mod.rs +++ b/boa/src/syntax/ast/node/operator/assign/mod.rs @@ -67,14 +67,27 @@ impl Executable for Assign { } } Node::GetConstField(ref get_const_field) => { - let val_obj = get_const_field.obj().run(context)?; - val_obj.set_field(get_const_field.field(), val.clone(), false, context)?; + let value = get_const_field.obj().run(context)?; + let obj = value.to_object(context)?; + let succeeded = + obj.__set__(get_const_field.field().into(), val.clone(), value, context)?; + if !succeeded && context.strict() { + return context.throw_type_error( + "Assignment to read-only properties is not allowed in strict mode", + ); + } } Node::GetField(ref get_field) => { - let object = get_field.obj().run(context)?; + let value = get_field.obj().run(context)?; + let obj = value.to_object(context)?; let field = get_field.field().run(context)?; let key = field.to_property_key(context)?; - object.set_field(key, val.clone(), false, context)?; + let succeeded = obj.__set__(key, val.clone(), value, context)?; + if !succeeded && context.strict() { + return context.throw_type_error( + "Assignment to read-only properties is not allowed in strict mode", + ); + } } _ => (), } diff --git a/boa/src/syntax/ast/node/operator/unary_op/mod.rs b/boa/src/syntax/ast/node/operator/unary_op/mod.rs index 24069c96a3..d0622aeae6 100644 --- a/boa/src/syntax/ast/node/operator/unary_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/unary_op/mod.rs @@ -109,10 +109,14 @@ impl Executable for UnaryOp { Node::GetField(ref get_field) => { let obj = get_field.obj().run(context)?; let field = &get_field.field().run(context)?; - let res = obj + let delete_status = obj .to_object(context)? .__delete__(&field.to_property_key(context)?, context)?; - return Ok(JsValue::new(res)); + if !delete_status && context.strict() { + return context.throw_type_error("Cannot delete property"); + } else { + JsValue::new(delete_status) + } } // TODO: implement delete on references. Node::Identifier(_) => JsValue::new(false), diff --git a/test_ignore.txt b/test_ignore.txt index 6438694689..489e486edd 100644 --- a/test_ignore.txt +++ b/test_ignore.txt @@ -8,6 +8,7 @@ feature:DataView feature:SharedArrayBuffer feature:resizable-arraybuffer feature:Temporal +feature: tail-call-optimization //feature:generators //feature:async-iteration //feature:class