Browse Source

Implement `Proxy` object (#1664)

* Implement `Proxy` object

* Restucture `Proxy` struct fields

* Apply some suggestions
pull/1672/head
raskad 3 years ago committed by GitHub
parent
commit
f66324cdf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      boa/src/builtins/mod.rs
  2. 105
      boa/src/builtins/object/mod.rs
  3. 188
      boa/src/builtins/proxy/mod.rs
  4. 7
      boa/src/context.rs
  5. 5
      boa/src/object/internal_methods/mod.rs
  6. 1024
      boa/src/object/internal_methods/proxy.rs
  7. 2
      boa/src/object/internal_methods/string.rs
  8. 95
      boa/src/object/mod.rs
  9. 43
      boa/src/object/operations.rs
  10. 2
      boa/src/property/mod.rs
  11. 21
      boa/src/syntax/ast/node/operator/assign/mod.rs
  12. 8
      boa/src/syntax/ast/node/operator/unary_op/mod.rs
  13. 1
      test_ignore.txt

3
boa/src/builtins/mod.rs

@ -19,6 +19,7 @@ pub mod math;
pub mod nan; pub mod nan;
pub mod number; pub mod number;
pub mod object; pub mod object;
pub mod proxy;
pub mod reflect; pub mod reflect;
pub mod regexp; pub mod regexp;
pub mod set; pub mod set;
@ -44,6 +45,7 @@ pub(crate) use self::{
number::Number, number::Number,
object::for_in_iterator::ForInIterator, object::for_in_iterator::ForInIterator,
object::Object as BuiltInObjectObject, object::Object as BuiltInObjectObject,
proxy::Proxy,
reflect::Reflect, reflect::Reflect,
regexp::RegExp, regexp::RegExp,
set::set_iterator::SetIterator, set::set_iterator::SetIterator,
@ -122,6 +124,7 @@ pub fn init(context: &mut Context) {
Math, Math,
Json, Json,
Array, Array,
Proxy,
ArrayBuffer, ArrayBuffer,
BigInt, BigInt,
Boolean, Boolean,

105
boa/src/builtins/object/mod.rs

@ -18,9 +18,9 @@ use crate::{
context::StandardObjects, context::StandardObjects,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, 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, symbol::WellKnownSymbols,
value::JsValue, value::JsValue,
BoaProfiler, Context, JsResult, BoaProfiler, Context, JsResult,
@ -170,16 +170,17 @@ impl Object {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let object = args.get_or_undefined(0).to_object(context)?; // 1. Let obj be ? ToObject(O).
if let Some(key) = args.get(1) { let obj = args.get_or_undefined(0).to_object(context)?;
let key = key.to_property_key(context)?;
if let Some(desc) = object.__get_own_property__(&key, context)? { // 2. Let key be ? ToPropertyKey(P).
return Ok(Self::from_property_descriptor(desc, context)); 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 )` /// `Object.getOwnPropertyDescriptors( object )`
@ -202,9 +203,7 @@ impl Object {
for key in object.borrow().properties().keys() { for key in object.borrow().properties().keys() {
let descriptor = { let descriptor = {
let desc = object let desc = object.__get_own_property__(&key, context)?;
.__get_own_property__(&key, context)?
.expect("Expected property to be on object.");
Self::from_property_descriptor(desc, context) Self::from_property_descriptor(desc, context)
}; };
@ -228,40 +227,64 @@ impl Object {
/// [ECMAScript reference][spec] /// [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-frompropertydescriptor /// [spec]: https://tc39.es/ecma262/#sec-frompropertydescriptor
fn from_property_descriptor(desc: PropertyDescriptor, context: &mut Context) -> JsValue { pub(crate) fn from_property_descriptor(
let mut descriptor = ObjectInitializer::new(context); desc: Option<PropertyDescriptor>,
context: &mut Context,
// TODO: use CreateDataPropertyOrThrow ) -> 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() { // 5. If Desc has a [[Writable]] field, then
DescriptorKind::Data { value, writable } => { if let Some(writable) = desc.writable() {
if let Some(value) = value { // a. Perform ! CreateDataPropertyOrThrow(obj, "writable", Desc.[[Writable]]).
descriptor.property("value", value.clone(), Attribute::all()); 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 } => { // 7. If Desc has a [[Set]] field, then
if let Some(get) = get { if let Some(set) = desc.set() {
descriptor.property("get", get.clone(), Attribute::all()); // 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() { // 9. If Desc has a [[Configurable]] field, then
descriptor.property("enumerable", enumerable, Attribute::all()); 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() { // 10. Return obj.
descriptor.property("configurable", configurable, Attribute::all()); obj.into()
}
} }
descriptor.build().into()
} }
/// Uses the SameValue algorithm to check equality of objects /// Uses the SameValue algorithm to check equality of objects
@ -273,6 +296,10 @@ impl Object {
} }
/// Get the `prototype` of an 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<JsValue> { pub fn get_prototype_of(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue> {
if args.is_empty() { if args.is_empty() {
return ctx.throw_type_error( return ctx.throw_type_error(

188
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<JsValue> {
// 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<JsValue> {
// 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<JsValue> {
// 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())
}
}

7
boa/src/context.rs

@ -79,6 +79,7 @@ impl StandardConstructor {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct StandardObjects { pub struct StandardObjects {
object: StandardConstructor, object: StandardConstructor,
proxy: StandardConstructor,
function: StandardConstructor, function: StandardConstructor,
array: StandardConstructor, array: StandardConstructor,
bigint: StandardConstructor, bigint: StandardConstructor,
@ -115,6 +116,7 @@ impl Default for StandardObjects {
fn default() -> Self { fn default() -> Self {
Self { Self {
object: StandardConstructor::default(), object: StandardConstructor::default(),
proxy: StandardConstructor::default(),
function: StandardConstructor::default(), function: StandardConstructor::default(),
array: StandardConstructor::default(), array: StandardConstructor::default(),
bigint: StandardConstructor::default(), bigint: StandardConstructor::default(),
@ -164,6 +166,11 @@ impl StandardObjects {
&self.object &self.object
} }
#[inline]
pub fn proxy_object(&self) -> &StandardConstructor {
&self.proxy
}
#[inline] #[inline]
pub fn function_object(&self) -> &StandardConstructor { pub fn function_object(&self) -> &StandardConstructor {
&self.function &self.function

5
boa/src/object/internal_methods/mod.rs

@ -20,6 +20,7 @@ pub(super) mod array;
pub(super) mod bound_function; pub(super) mod bound_function;
pub(super) mod function; pub(super) mod function;
pub(super) mod integer_indexed; pub(super) mod integer_indexed;
pub(super) mod proxy;
pub(super) mod string; pub(super) mod string;
impl JsObject { impl JsObject {
@ -749,10 +750,10 @@ pub(crate) fn ordinary_own_property_keys(
pub(crate) fn is_compatible_property_descriptor( pub(crate) fn is_compatible_property_descriptor(
extensible: bool, extensible: bool,
desc: PropertyDescriptor, desc: PropertyDescriptor,
current: PropertyDescriptor, current: Option<PropertyDescriptor>,
) -> bool { ) -> bool {
// 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current). // 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` /// Abstract operation `ValidateAndApplyPropertyDescriptor`

1024
boa/src/object/internal_methods/proxy.rs

File diff suppressed because it is too large Load Diff

2
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( Ok(super::is_compatible_property_descriptor(
extensible, extensible,
desc, desc,
string_desc, Some(string_desc),
)) ))
} else { } else {
// 4. Return ! OrdinaryDefineOwnProperty(S, P, Desc). // 4. Return ! OrdinaryDefineOwnProperty(S, P, Desc).

95
boa/src/object/mod.rs

@ -9,6 +9,7 @@ use crate::{
map::map_iterator::MapIterator, map::map_iterator::MapIterator,
map::ordered_map::OrderedMap, map::ordered_map::OrderedMap,
object::for_in_iterator::ForInIterator, object::for_in_iterator::ForInIterator,
proxy::Proxy,
regexp::regexp_string_iterator::RegExpStringIterator, regexp::regexp_string_iterator::RegExpStringIterator,
set::ordered_set::OrderedSet, set::ordered_set::OrderedSet,
set::set_iterator::SetIterator, set::set_iterator::SetIterator,
@ -39,6 +40,10 @@ use self::internal_methods::{
}, },
function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS}, function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS},
integer_indexed::INTEGER_INDEXED_EXOTIC_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, string::STRING_EXOTIC_INTERNAL_METHODS,
InternalObjectMethods, ORDINARY_INTERNAL_METHODS, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
}; };
@ -122,6 +127,7 @@ pub enum ObjectKind {
Symbol(JsSymbol), Symbol(JsSymbol),
Error, Error,
Ordinary, Ordinary,
Proxy(Proxy),
Date(Date), Date(Date),
Global, Global,
Arguments(Arguments), 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 /// Create the `Date` object data
pub fn date(date: Date) -> Self { pub fn date(date: Date) -> Self {
Self { Self {
@ -363,6 +383,7 @@ impl Display for ObjectKind {
Self::Symbol(_) => "Symbol", Self::Symbol(_) => "Symbol",
Self::Error => "Error", Self::Error => "Error",
Self::Ordinary => "Ordinary", Self::Ordinary => "Ordinary",
Self::Proxy(_) => "Proxy",
Self::Boolean(_) => "Boolean", Self::Boolean(_) => "Boolean",
Self::Number(_) => "Number", Self::Number(_) => "Number",
Self::BigInt(_) => "BigInt", 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. /// Gets the prototype instance of this object.
#[inline] #[inline]
pub fn prototype(&self) -> &JsPrototype { 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] #[inline]
pub fn is<T>(&self) -> bool pub fn is<T>(&self) -> bool
where where
@ -1069,7 +1124,7 @@ impl Object {
/// Inserts a field in the object `properties` without checking if it's writable. /// 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 /// 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] #[inline]
pub fn insert_property<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor> pub fn insert_property<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor>
where where
@ -1273,8 +1328,8 @@ impl<'context> FunctionBuilder<'context> {
.writable(false) .writable(false)
.enumerable(false) .enumerable(false)
.configurable(true); .configurable(true);
function.insert_property("name", property.clone().value(self.name.clone())); function.insert_property("length", property.clone().value(self.length));
function.insert_property("length", property.value(self.length)); function.insert_property("name", property.value(self.name.clone()));
function function
} }
@ -1395,6 +1450,7 @@ pub struct ConstructorBuilder<'context> {
context: &'context mut Context, context: &'context mut Context,
constructor_function: NativeFunctionSignature, constructor_function: NativeFunctionSignature,
constructor_object: JsObject, constructor_object: JsObject,
constructor_has_prototype: bool,
prototype: JsObject, prototype: JsObject,
name: JsString, name: JsString,
length: usize, length: usize,
@ -1410,6 +1466,7 @@ impl Debug for ConstructorBuilder<'_> {
.field("name", &self.name) .field("name", &self.name)
.field("length", &self.length) .field("length", &self.length)
.field("constructor", &self.constructor_object) .field("constructor", &self.constructor_object)
.field("constructor_has_prototype", &self.constructor_has_prototype)
.field("prototype", &self.prototype) .field("prototype", &self.prototype)
.field("inherit", &self.inherit) .field("inherit", &self.inherit)
.field("callable", &self.callable) .field("callable", &self.callable)
@ -1433,6 +1490,7 @@ impl<'context> ConstructorBuilder<'context> {
constructor: true, constructor: true,
inherit: None, inherit: None,
custom_prototype: None, custom_prototype: None,
constructor_has_prototype: true,
} }
} }
@ -1446,6 +1504,7 @@ impl<'context> ConstructorBuilder<'context> {
context, context,
constructor_function: constructor, constructor_function: constructor,
constructor_object: object.constructor, constructor_object: object.constructor,
constructor_has_prototype: true,
prototype: object.prototype, prototype: object.prototype,
length: 0, length: 0,
name: JsString::default(), name: JsString::default(),
@ -1669,6 +1728,15 @@ impl<'context> ConstructorBuilder<'context> {
self 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. /// Return the current context.
#[inline] #[inline]
pub fn context(&mut self) -> &'_ mut Context { pub fn context(&mut self) -> &'_ mut Context {
@ -1710,14 +1778,17 @@ impl<'context> ConstructorBuilder<'context> {
.prototype(), .prototype(),
); );
} }
constructor.insert_property(
PROTOTYPE, if self.constructor_has_prototype {
PropertyDescriptor::builder() constructor.insert_property(
.value(self.prototype.clone()) PROTOTYPE,
.writable(false) PropertyDescriptor::builder()
.enumerable(false) .value(self.prototype.clone())
.configurable(false), .writable(false)
); .enumerable(false)
.configurable(false),
);
}
} }
{ {

43
boa/src/object/operations.rs

@ -534,6 +534,34 @@ impl JsObject {
Ok(properties) 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<K>(&self, key: K, context: &mut Context) -> JsResult<Option<JsObject>>
where
K: Into<PropertyKey>,
{
// 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: GetFunctionRealm
// todo: CopyDataProperties // todo: CopyDataProperties
@ -584,21 +612,14 @@ impl JsValue {
/// - [EcmaScript reference][spec] /// - [EcmaScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-getmethod /// [spec]: https://tc39.es/ecma262/#sec-getmethod
#[inline]
pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<Option<JsObject>> pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<Option<JsObject>>
where where
K: Into<PropertyKey>, K: Into<PropertyKey>,
{ {
// 1. Assert: IsPropertyKey(P) is true. // Note: The spec specifies this function for JsValue.
// 2. Let func be ? GetV(V, P). // The main part of the function is implemented for JsObject.
match &self.get_v(key, context)? { self.to_object(context)?.get_method(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")),
}
} }
/// It is used to create List value whose elements are provided by the indexed properties of /// It is used to create List value whose elements are provided by the indexed properties of

2
boa/src/property/mod.rs

@ -473,7 +473,7 @@ impl From<PropertyDescriptorBuilder> for PropertyDescriptor {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-ispropertykey /// [spec]: https://tc39.es/ecma262/#sec-ispropertykey
#[derive(Trace, Finalize, PartialEq, Debug, Clone)] #[derive(Trace, Finalize, PartialEq, Debug, Clone, Eq, Hash)]
pub enum PropertyKey { pub enum PropertyKey {
String(JsString), String(JsString),
Symbol(JsSymbol), Symbol(JsSymbol),

21
boa/src/syntax/ast/node/operator/assign/mod.rs

@ -67,14 +67,27 @@ impl Executable for Assign {
} }
} }
Node::GetConstField(ref get_const_field) => { Node::GetConstField(ref get_const_field) => {
let val_obj = get_const_field.obj().run(context)?; let value = get_const_field.obj().run(context)?;
val_obj.set_field(get_const_field.field(), val.clone(), false, 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) => { 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 field = get_field.field().run(context)?;
let key = field.to_property_key(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",
);
}
} }
_ => (), _ => (),
} }

8
boa/src/syntax/ast/node/operator/unary_op/mod.rs

@ -109,10 +109,14 @@ impl Executable for UnaryOp {
Node::GetField(ref get_field) => { Node::GetField(ref get_field) => {
let obj = get_field.obj().run(context)?; let obj = get_field.obj().run(context)?;
let field = &get_field.field().run(context)?; let field = &get_field.field().run(context)?;
let res = obj let delete_status = obj
.to_object(context)? .to_object(context)?
.__delete__(&field.to_property_key(context)?, 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. // TODO: implement delete on references.
Node::Identifier(_) => JsValue::new(false), Node::Identifier(_) => JsValue::new(false),

1
test_ignore.txt

@ -8,6 +8,7 @@ feature:DataView
feature:SharedArrayBuffer feature:SharedArrayBuffer
feature:resizable-arraybuffer feature:resizable-arraybuffer
feature:Temporal feature:Temporal
feature: tail-call-optimization
//feature:generators //feature:generators
//feature:async-iteration //feature:async-iteration
//feature:class //feature:class

Loading…
Cancel
Save