From cbf07824cb33a8be0a103cd90f7e121bbf5e0f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Tue, 27 Dec 2022 19:22:44 +0000 Subject: [PATCH] Cleanup `Context` APIs (#2504) Just a general cleanup of the APIs of our `Context`. - Reordered the `pub` and `pub(crate)/fn` methods to have a clear separation between our public and private APIs. - Removed the call method and added it to `JsValue` instead, which semantically makes a bit more sense. - Removed the `construct_object` method, and added an utility method `new` to `JsObject` instead. - Rewrote some patterns I found while rewriting the calls of the removed function. --- boa_engine/src/builtins/array/mod.rs | 142 +++--- boa_engine/src/builtins/array/tests.rs | 2 +- boa_engine/src/builtins/array_buffer/mod.rs | 15 +- boa_engine/src/builtins/date/mod.rs | 2 +- boa_engine/src/builtins/function/arguments.rs | 7 +- boa_engine/src/builtins/function/tests.rs | 5 +- boa_engine/src/builtins/iterable/mod.rs | 46 +- boa_engine/src/builtins/json/mod.rs | 4 +- boa_engine/src/builtins/object/mod.rs | 22 +- boa_engine/src/builtins/promise/mod.rs | 113 +++-- .../src/builtins/promise/promise_job.rs | 16 +- boa_engine/src/builtins/proxy/mod.rs | 2 +- boa_engine/src/builtins/regexp/mod.rs | 97 ++-- boa_engine/src/builtins/set/mod.rs | 2 +- boa_engine/src/builtins/string/mod.rs | 62 ++- .../typed_array/integer_indexed_object.rs | 37 +- boa_engine/src/builtins/typed_array/mod.rs | 4 +- boa_engine/src/context/intrinsics.rs | 6 +- boa_engine/src/context/mod.rs | 413 ++++++++---------- .../src/object/builtins/jsarraybuffer.rs | 15 +- boa_engine/src/object/builtins/jsproxy.rs | 2 +- .../src/object/internal_methods/global.rs | 4 +- boa_engine/src/object/internal_methods/mod.rs | 4 +- boa_engine/src/object/jsobject.rs | 58 ++- boa_engine/src/object/mod.rs | 6 +- boa_engine/src/object/operations.rs | 31 +- boa_engine/src/value/serde_json.rs | 3 +- boa_engine/src/value/tests.rs | 6 +- boa_engine/src/vm/code_block.rs | 3 +- boa_engine/src/vm/opcode/binary_ops/mod.rs | 6 +- boa_engine/src/vm/opcode/generator/mod.rs | 10 +- boa_engine/src/vm/opcode/get/name.rs | 4 +- boa_engine/src/vm/opcode/push/object.rs | 4 +- boa_examples/src/bin/closures.rs | 2 +- 34 files changed, 550 insertions(+), 605 deletions(-) diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index b0c6f4fc58..b3a71155e8 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -426,77 +426,9 @@ impl Array { }; // 4. Let usingIterator be ? GetMethod(items, @@iterator). - let using_iterator = items - .get_method(WellKnownSymbols::iterator(), context)? - .map(JsValue::from); + let using_iterator = items.get_method(WellKnownSymbols::iterator(), context)?; - if let Some(using_iterator) = using_iterator { - // 5. If usingIterator is not undefined, then - - // a. If IsConstructor(C) is true, then - // i. Let A be ? Construct(C). - // b. Else, - // i. Let A be ? ArrayCreate(0en). - let a = match this.as_constructor() { - Some(constructor) => constructor.construct(&[], None, context)?, - _ => Self::array_create(0, None, context)?, - }; - - // c. Let iteratorRecord be ? GetIterator(items, sync, usingIterator). - let iterator_record = - items.get_iterator(context, Some(IteratorHint::Sync), Some(using_iterator))?; - - // d. Let k be 0. - // e. Repeat, - // i. If k ≥ 2^53 - 1 (MAX_SAFE_INTEGER), then - // ... - // x. Set k to k + 1. - for k in 0..9_007_199_254_740_991_u64 { - // iii. Let next be ? IteratorStep(iteratorRecord). - let next = iterator_record.step(context)?; - - // iv. If next is false, then - let Some(next) = next else { - // 1. Perform ? Set(A, "length", 𝔽(k), true). - a.set("length", k, true, context)?; - - // 2. Return A. - return Ok(a.into()); - }; - - // v. Let nextValue be ? IteratorValue(next). - let next_value = next.value(context)?; - - // vi. If mapping is true, then - let mapped_value = if let Some(mapfn) = mapping { - // 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »). - let mapped_value = mapfn.call(this_arg, &[next_value, k.into()], context); - - // 2. IfAbruptCloseIterator(mappedValue, iteratorRecord). - if_abrupt_close_iterator!(mapped_value, iterator_record, context) - } else { - // vii. Else, let mappedValue be nextValue. - next_value - }; - - // viii. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue). - let define_status = a.create_data_property_or_throw(k, mapped_value, context); - - // ix. IfAbruptCloseIterator(defineStatus, iteratorRecord). - if_abrupt_close_iterator!(define_status, iterator_record, context); - } - - // NOTE: The loop above has to return before it reaches iteration limit, - // which is why it's safe to have this as the fallback return - // - // 1. Let error be ThrowCompletion(a newly created TypeError object). - let error = Err(JsNativeError::typ() - .with_message("Invalid array length") - .into()); - - // 2. Return ? IteratorClose(iteratorRecord, error). - iterator_record.close(error, context) - } else { + let Some(using_iterator) = using_iterator else { // 6. NOTE: items is not an Iterable so assume it is an array-like object. // 7. Let arrayLike be ! ToObject(items). let array_like = items @@ -541,8 +473,74 @@ impl Array { a.set("length", len, true, context)?; // 14. Return A. - Ok(a.into()) + return Ok(a.into()); + }; + + // 5. If usingIterator is not undefined, then + + // a. If IsConstructor(C) is true, then + // i. Let A be ? Construct(C). + // b. Else, + // i. Let A be ? ArrayCreate(0en). + let a = match this.as_constructor() { + Some(constructor) => constructor.construct(&[], None, context)?, + _ => Self::array_create(0, None, context)?, + }; + + // c. Let iteratorRecord be ? GetIterator(items, sync, usingIterator). + let iterator_record = + items.get_iterator(context, Some(IteratorHint::Sync), Some(using_iterator))?; + + // d. Let k be 0. + // e. Repeat, + // i. If k ≥ 2^53 - 1 (MAX_SAFE_INTEGER), then + // ... + // x. Set k to k + 1. + for k in 0..9_007_199_254_740_991_u64 { + // iii. Let next be ? IteratorStep(iteratorRecord). + let next = iterator_record.step(context)?; + + // iv. If next is false, then + let Some(next) = next else { + // 1. Perform ? Set(A, "length", 𝔽(k), true). + a.set("length", k, true, context)?; + + // 2. Return A. + return Ok(a.into()); + }; + + // v. Let nextValue be ? IteratorValue(next). + let next_value = next.value(context)?; + + // vi. If mapping is true, then + let mapped_value = if let Some(mapfn) = mapping { + // 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »). + let mapped_value = mapfn.call(this_arg, &[next_value, k.into()], context); + + // 2. IfAbruptCloseIterator(mappedValue, iteratorRecord). + if_abrupt_close_iterator!(mapped_value, iterator_record, context) + } else { + // vii. Else, let mappedValue be nextValue. + next_value + }; + + // viii. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue). + let define_status = a.create_data_property_or_throw(k, mapped_value, context); + + // ix. IfAbruptCloseIterator(defineStatus, iteratorRecord). + if_abrupt_close_iterator!(define_status, iterator_record, context); } + + // NOTE: The loop above has to return before it reaches iteration limit, + // which is why it's safe to have this as the fallback return + // + // 1. Let error be ThrowCompletion(a newly created TypeError object). + let error = Err(JsNativeError::typ() + .with_message("Invalid array length") + .into()); + + // 2. Return ? IteratorClose(iteratorRecord, error). + iterator_record.close(error, context) } /// `Array.isArray( arg )` @@ -2957,7 +2955,7 @@ impl Array { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@unscopables pub(crate) fn unscopables_intrinsic(context: &mut Context) -> JsObject { // 1. Let unscopableList be OrdinaryObjectCreate(null). - let unscopable_list = JsObject::empty(); + let unscopable_list = JsObject::with_null_proto(); // 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true). unscopable_list .create_data_property_or_throw("at", true, context) diff --git a/boa_engine/src/builtins/array/tests.rs b/boa_engine/src/builtins/array/tests.rs index 67bda0387d..3944623dac 100644 --- a/boa_engine/src/builtins/array/tests.rs +++ b/boa_engine/src/builtins/array/tests.rs @@ -1411,7 +1411,7 @@ fn array_spread_non_iterable() { try { const array2 = [...5]; } catch (err) { - err.name === "TypeError" && err.message === "Value is not callable" + err.name === "TypeError" && err.message === "value with type `number` is not iterable" } "#; assert_eq!(forward(&mut context, init), "true"); diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 8246964f24..43c35357e3 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -340,19 +340,20 @@ impl ArrayBuffer { StandardConstructors::array_buffer, context, )?; - let obj = context.construct_object(); - obj.set_prototype(prototype.into()); // 2. Let block be ? CreateByteDataBlock(byteLength). let block = create_byte_data_block(byte_length)?; // 3. Set obj.[[ArrayBufferData]] to block. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. - obj.borrow_mut().data = ObjectData::array_buffer(Self { - array_buffer_data: Some(block), - array_buffer_byte_length: byte_length, - array_buffer_detach_key: JsValue::Undefined, - }); + let obj = JsObject::from_proto_and_data( + prototype, + ObjectData::array_buffer(Self { + array_buffer_data: Some(block), + array_buffer_byte_length: byte_length, + array_buffer_detach_key: JsValue::Undefined, + }), + ); // 5. Return obj. Ok(obj) diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index 84df5a77b2..835541dc80 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -1234,7 +1234,7 @@ impl Date { // 4. Return ? Invoke(O, "toISOString"). let func = o.get("toISOString", context)?; - context.call(&func, &o.into(), &[]) + func.call(this, &[], context) } /// [`Date.prototype.toLocaleDateString()`][spec]. diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index d84f6a3861..ac1407697e 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -77,11 +77,12 @@ impl Arguments { 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(Self::Unmapped); + let obj = JsObject::from_proto_and_data( + context.intrinsics().constructors().object().prototype(), + ObjectData::arguments(Self::Unmapped), + ); // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index 8b290bd012..6518d6e7ef 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -1,7 +1,7 @@ use crate::{ error::JsNativeError, forward, forward_val, js_string, - object::FunctionBuilder, + object::{FunctionBuilder, JsObject}, property::{Attribute, PropertyDescriptor}, string::utf16, Context, JsNativeErrorKind, @@ -230,7 +230,8 @@ fn closure_capture_clone() { let mut context = Context::default(); let string = js_string!("Hello"); - let object = context.construct_object(); + let object = JsObject::with_object_proto(&mut context); + object .define_property_or_throw( "key", diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index 6b2be2ccb8..f9d7d70888 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -136,7 +136,7 @@ pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Conte // 1. Assert: Type(done) is Boolean. // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). - let obj = context.construct_object(); + let obj = JsObject::with_object_proto(context); // 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value). obj.create_data_property_or_throw("value", value, context) @@ -169,13 +169,13 @@ impl JsValue { &self, context: &mut Context, hint: Option, - method: Option, + method: Option, ) -> JsResult { // 1. If hint is not present, set hint to sync. let hint = hint.unwrap_or(IteratorHint::Sync); // 2. If method is not present, then - let method = if let Some(method) = method { + let method = if method.is_some() { method } else { // a. If hint is async, then @@ -184,17 +184,15 @@ impl JsValue { if let Some(method) = self.get_method(WellKnownSymbols::async_iterator(), context)? { - method.into() + Some(method) } else { // ii. If method is undefined, then // 1. Let syncMethod be ? GetMethod(obj, @@iterator). - let sync_method = self - .get_method(WellKnownSymbols::iterator(), context)? - .map_or(Self::Undefined, Self::from); + let sync_method = self.get_method(WellKnownSymbols::iterator(), context)?; // 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod). let sync_iterator_record = - self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method))?; + self.get_iterator(context, Some(IteratorHint::Sync), sync_method)?; // 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord). return Ok(AsyncFromSyncIterator::create(sync_iterator_record, context)); @@ -202,17 +200,22 @@ impl JsValue { } else { // b. Otherwise, set method to ? GetMethod(obj, @@iterator). self.get_method(WellKnownSymbols::iterator(), context)? - .map_or(Self::Undefined, Self::from) } - }; + } + .ok_or_else(|| { + JsNativeError::typ().with_message(format!( + "value with type `{}` is not iterable", + self.type_of() + )) + })?; // 3. Let iterator be ? Call(method, obj). - let iterator = context.call(&method, self, &[])?; + let iterator = method.call(self, &[], context)?; // 4. If Type(iterator) is not Object, throw a TypeError exception. - let iterator_obj = iterator - .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("the iterator is not an object"))?; + let iterator_obj = iterator.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("returned iterator is not an object") + })?; // 5. Let nextMethod be ? GetV(iterator, "next"). let next_method = iterator.get_v("next", context)?; @@ -500,20 +503,15 @@ impl IteratorRecord { pub(crate) fn iterable_to_list( context: &mut Context, items: &JsValue, - method: Option, + method: Option, ) -> JsResult> { let _timer = Profiler::global().start_event("iterable_to_list", "iterator"); // 1. If method is present, then - let iterator_record = if let Some(method) = method { - // a. Let iteratorRecord be ? GetIterator(items, sync, method). - items.get_iterator(context, Some(IteratorHint::Sync), Some(method))? - } else { - // 2. Else, - - // a. Let iteratorRecord be ? GetIterator(items, sync). - items.get_iterator(context, Some(IteratorHint::Sync), None)? - }; + // a. Let iteratorRecord be ? GetIterator(items, sync, method). + // 2. Else, + // a. Let iteratorRecord be ? GetIterator(items, sync). + let iterator_record = items.get_iterator(context, Some(IteratorHint::Sync), method)?; // 3. Let values be a new empty List. let mut values = Vec::new(); diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index f0da88dc87..01d7085867 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -203,7 +203,7 @@ impl Json { // 11. If IsCallable(reviver) is true, then if let Some(obj) = args.get_or_undefined(1).as_callable() { // a. Let root be ! OrdinaryObjectCreate(%Object.prototype%). - let root = context.construct_object(); + let root = JsObject::with_object_proto(context); // b. Let rootName be the empty String. // c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered). @@ -435,7 +435,7 @@ impl Json { }; // 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%). - let wrapper = context.construct_object(); + let wrapper = JsObject::with_object_proto(context); // 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value). wrapper diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index bbc64f8d64..a582298f4e 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -124,18 +124,24 @@ impl Object { args: &[JsValue], context: &mut Context, ) -> JsResult { + // 1. If NewTarget is neither undefined nor the active function object, then if !new_target.is_undefined() { + // a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%"). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::object, context)?; let object = JsObject::from_proto_and_data(prototype, ObjectData::ordinary()); return Ok(object.into()); } - if let Some(arg) = args.get(0) { - if !arg.is_null_or_undefined() { - return Ok(arg.to_object(context)?.into()); - } + + let value = args.get_or_undefined(0); + + // 2. If value is undefined or null, return OrdinaryObjectCreate(%Object.prototype%). + if value.is_null_or_undefined() { + Ok(JsObject::with_object_proto(context).into()) + } else { + // 3. Return ! ToObject(value). + value.to_object(context).map(JsValue::from) } - Ok(context.construct_object().into()) } /// `get Object.prototype.__proto__` @@ -471,7 +477,7 @@ impl Object { let own_keys = obj.__own_property_keys__(context)?; // 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%). - let descriptors = context.construct_object(); + let descriptors = JsObject::with_object_proto(context); // 4. For each element key of ownKeys, do for key in own_keys { @@ -512,7 +518,7 @@ impl Object { // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 3. Assert: obj is an extensible ordinary object with no own properties. - let obj = context.construct_object(); + let obj = JsObject::with_object_proto(context); // 4. If Desc has a [[Value]] field, then if let Some(value) = desc.value() { @@ -1242,7 +1248,7 @@ impl Object { // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 3. Assert: obj is an extensible ordinary object with no own properties. - let obj = context.construct_object(); + let obj = JsObject::with_object_proto(context); // 4. Let closure be a new Abstract Closure with parameters (key, value) that captures // obj and performs the following steps when called: diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index ce82c47fa4..a49150fc82 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -41,11 +41,9 @@ macro_rules! if_abrupt_reject_promise { Err(err) => { let err = err.to_opaque($context); // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). - $context.call( - &$capability.reject().clone().into(), - &JsValue::undefined(), - &[err], - )?; + $capability + .reject() + .call(&JsValue::undefined(), &[err], $context)?; // b. Return capability.[[Promise]]. return Ok($capability.promise().clone().into()); @@ -297,8 +295,8 @@ impl BuiltIn for Promise { #[derive(Debug)] struct ResolvingFunctionsRecord { - resolve: JsValue, - reject: JsValue, + resolve: JsFunction, + reject: JsFunction, } impl Promise { @@ -327,14 +325,11 @@ impl Promise { .into()); } - let executor = args.get_or_undefined(0); - // 2. If IsCallable(executor) is false, throw a TypeError exception. - if !executor.is_callable() { - return Err(JsNativeError::typ() - .with_message("Promise executor is not callable") - .into()); - } + let executor = args + .get_or_undefined(0) + .as_callable() + .ok_or_else(|| JsNativeError::typ().with_message("Promise executor is not callable"))?; // 3. Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »). let promise = @@ -358,20 +353,22 @@ impl Promise { let resolving_functions = Self::create_resolving_functions(&promise, context); // 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ). - let completion = context.call( - executor, + let completion = executor.call( &JsValue::Undefined, &[ - resolving_functions.resolve, - resolving_functions.reject.clone(), + resolving_functions.resolve.clone().into(), + resolving_functions.reject.clone().into(), ], + context, ); // 10. If completion is an abrupt completion, then if let Err(e) = completion { let e = e.to_opaque(context); // a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »). - context.call(&resolving_functions.reject, &JsValue::Undefined, &[e])?; + resolving_functions + .reject + .call(&JsValue::Undefined, &[e], context)?; } // 11. Return promise. @@ -809,7 +806,7 @@ impl Promise { // 8. Let remainingElementsCount be F.[[RemainingElements]]. // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = context.construct_object(); + let obj = JsObject::with_object_proto(context); // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled"). obj.create_data_property_or_throw("status", "fulfilled", context) @@ -893,7 +890,7 @@ impl Promise { // 8. Let remainingElementsCount be F.[[RemainingElements]]. // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = context.construct_object(); + let obj = JsObject::with_object_proto(context); // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected"). obj.create_data_property_or_throw("status", "rejected", context) @@ -1429,8 +1426,6 @@ impl Promise { .build(); // 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }. - let resolve = resolve.conv::(); - let reject = reject.conv::(); ResolvingFunctionsRecord { resolve, reject } } @@ -1589,7 +1584,7 @@ impl Promise { &mut iterator_record, c, &promise_capability, - &promise_resolve.into(), + &promise_resolve, context, ); @@ -1625,7 +1620,7 @@ impl Promise { iterator_record: &mut IteratorRecord, constructor: &JsValue, result_capability: &PromiseCapability, - promise_resolve: &JsValue, + promise_resolve: &JsObject, context: &mut Context, ) -> JsResult { // 1. Repeat, @@ -1654,7 +1649,7 @@ impl Promise { let next_value = next_value?; // h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). - let next_promise = context.call(promise_resolve, constructor, &[next_value])?; + let next_promise = promise_resolve.call(constructor, &[next_value], context)?; // i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »). next_promise.invoke( @@ -1694,11 +1689,10 @@ impl Promise { let promise_capability = PromiseCapability::new(c, context)?; // 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »). - context.call( - &promise_capability.reject.clone().into(), - &JsValue::undefined(), - &[r.clone()], - )?; + + promise_capability + .reject + .call(&JsValue::undefined(), &[r.clone()], context)?; // 4. Return promiseCapability.[[Promise]]. Ok(promise_capability.promise.clone().into()) @@ -1779,26 +1773,34 @@ impl Promise { let promise = this; // 2. If Type(promise) is not Object, throw a TypeError exception. - let Some(promise_obj) = promise.as_object() else { + let Some(promise) = promise.as_object() else { return Err(JsNativeError::typ() .with_message("finally called with a non-object promise") .into()); }; // 3. Let C be ? SpeciesConstructor(promise, %Promise%). - let c = promise_obj.species_constructor(StandardConstructors::promise, context)?; + let c = promise.species_constructor(StandardConstructors::promise, context)?; // 4. Assert: IsConstructor(C) is true. debug_assert!(c.is_constructor()); let on_finally = args.get_or_undefined(0); - // 5. If IsCallable(onFinally) is false, then - let (then_finally, catch_finally) = if on_finally.is_callable() { + let Some(on_finally) = on_finally.as_callable() else { + // 5. If IsCallable(onFinally) is false, then + // a. Let thenFinally be onFinally. + // b. Let catchFinally be onFinally. + // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). + let then = promise.get("then", context)?; + return then.call(this, &[on_finally.clone(), on_finally.clone()], context); + }; + + let (then_finally, catch_finally) = { /// Capture object for the `thenFinallyClosure` abstract closure. #[derive(Debug, Trace, Finalize)] struct FinallyCaptures { - on_finally: JsValue, + on_finally: JsObject, c: JsObject, } @@ -1815,7 +1817,9 @@ impl Promise { let value = args.get_or_undefined(0); // i. Let result be ? Call(onFinally, undefined). - let result = context.call(&captures.on_finally, &JsValue::undefined(), &[])?; + let result = captures + .on_finally + .call(&JsValue::undefined(), &[], context)?; // ii. Let promise be ? PromiseResolve(C, result). let promise = Self::promise_resolve(captures.c.clone(), result, context)?; @@ -1860,7 +1864,9 @@ impl Promise { let reason = args.get_or_undefined(0); // i. Let result be ? Call(onFinally, undefined). - let result = context.call(&captures.on_finally, &JsValue::undefined(), &[])?; + let result = captures + .on_finally + .call(&JsValue::undefined(), &[], context)?; // ii. Let promise be ? PromiseResolve(C, result). let promise = Self::promise_resolve(captures.c.clone(), result, context)?; @@ -1892,16 +1898,12 @@ impl Promise { // d. Let catchFinally be CreateBuiltinFunction(catchFinallyClosure, 1, "", « »). let catch_finally = catch_finally_closure.length(1).name("").build(); - (then_finally.into(), catch_finally.into()) // TODO - } else { - // 6. Else, - // a. Let thenFinally be onFinally. - // b. Let catchFinally be onFinally. - (on_finally.clone(), on_finally.clone()) + (then_finally.into(), catch_finally.into()) }; // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). - promise.invoke("then", &[then_finally, catch_finally], context) + let then = promise.get("then", context)?; + then.call(this, &[then_finally, catch_finally], context) } /// `Promise.prototype.then ( onFulfilled, onRejected )` @@ -2081,11 +2083,9 @@ impl Promise { let promise_capability = PromiseCapability::new(&c.into(), context)?; // 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »). - context.call( - &promise_capability.resolve.clone().into(), - &JsValue::undefined(), - &[x], - )?; + promise_capability + .resolve + .call(&JsValue::Undefined, &[x], context)?; // 4. Return promiseCapability.[[Promise]]. Ok(promise_capability.promise.clone().into()) @@ -2109,13 +2109,10 @@ impl Promise { let promise_resolve = promise_constructor.get("resolve", context)?; // 2. If IsCallable(promiseResolve) is false, throw a TypeError exception. - promise_resolve.as_callable().map_or_else( - || { - Err(JsNativeError::typ() - .with_message("retrieving a non-callable promise resolver") - .into()) - }, - |promise_resolve| Ok(promise_resolve.clone()), - ) + promise_resolve.as_callable().cloned().ok_or_else(|| { + JsNativeError::typ() + .with_message("retrieving a non-callable promise resolver") + .into() + }) } } diff --git a/boa_engine/src/builtins/promise/promise_job.rs b/boa_engine/src/builtins/promise/promise_job.rs index af97c6887b..9938c3534d 100644 --- a/boa_engine/src/builtins/promise/promise_job.rs +++ b/boa_engine/src/builtins/promise/promise_job.rs @@ -83,13 +83,13 @@ impl PromiseJob { // h. If handlerResult is an abrupt completion, then Err(value) => { // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). - context.call(&reject.clone().into(), &JsValue::Undefined, &[value]) + reject.call(&JsValue::Undefined, &[value], context) } // i. Else, Ok(value) => { // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). - context.call(&resolve.clone().into(), &JsValue::Undefined, &[value]) + resolve.call(&JsValue::Undefined, &[value], context) } } } @@ -138,8 +138,8 @@ impl PromiseJob { let then_call_result = then.call_job_callback( thenable, &[ - resolving_functions.resolve, - resolving_functions.reject.clone(), + resolving_functions.resolve.clone().into(), + resolving_functions.reject.clone().into(), ], context, ); @@ -148,11 +148,9 @@ impl PromiseJob { if let Err(value) = then_call_result { let value = value.to_opaque(context); // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). - return context.call( - &resolving_functions.reject, - &JsValue::Undefined, - &[value], - ); + return resolving_functions + .reject + .call(&JsValue::Undefined, &[value], context); } // d. Return ? thenCallResult. diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 9a771a4e18..cfdb5aa273 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -173,7 +173,7 @@ impl Proxy { let revoker = Self::revoker(p.clone(), context); // 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%). - let result = context.construct_object(); + let result = JsObject::with_object_proto(context); // 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p). result diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 0e25211b62..1bec42a4d7 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -975,7 +975,7 @@ impl RegExp { let named_groups = match_value.named_groups(); let groups = if named_groups.clone().count() > 0 { // a. Let groups be ! OrdinaryObjectCreate(null). - let groups = JsObject::empty(); + let groups = JsObject::with_null_proto(); // Perform 27.f here // f. If the ith capture of R was defined with a GroupName, then @@ -1237,17 +1237,14 @@ impl RegExp { let length_arg_str = arg_str.len(); // 5. Let functionalReplace be IsCallable(replaceValue). - let mut replace_value = args.get_or_undefined(1).clone(); - let functional_replace = replace_value - .as_object() - .map(JsObject::is_callable) - .unwrap_or_default(); - - // 6. If functionalReplace is false, then - if !functional_replace { + let replace_value = args.get_or_undefined(1).clone(); + let replace = if let Some(f) = replace_value.as_callable() { + Ok(f) + } else { + // 6. If functionalReplace is false, then // a. Set replaceValue to ? ToString(replaceValue). - replace_value = replace_value.to_string(context)?.into(); - } + Err(replace_value.to_string(context)?) + }; // 7. Let global be ! ToBoolean(? Get(rx, "global")). let global = rx.get("global", context)?.to_boolean(); @@ -1356,47 +1353,49 @@ impl RegExp { let mut named_captures = result.get("groups", context)?; // k. If functionalReplace is true, then - // l. Else, - let replacement = if functional_replace { - // i. Let replacerArgs be « matched ». - let mut replacer_args = vec![JsValue::new(matched)]; - - // ii. Append in List order the elements of captures to the end of the List replacerArgs. - replacer_args.extend(captures); - - // iii. Append 𝔽(position) and S to replacerArgs. - replacer_args.push(position.into()); - replacer_args.push(arg_str.clone().into()); - - // iv. If namedCaptures is not undefined, then - if !named_captures.is_undefined() { - // 1. Append namedCaptures as the last element of replacerArgs. - replacer_args.push(named_captures); - } - - // v. Let replValue be ? Call(replaceValue, undefined, replacerArgs). - let repl_value = - context.call(&replace_value, &JsValue::undefined(), &replacer_args)?; + let replacement = match replace { + Ok(replace_fn) => { + // i. Let replacerArgs be « matched ». + let mut replacer_args = vec![JsValue::new(matched)]; + + // ii. Append in List order the elements of captures to the end of the List replacerArgs. + replacer_args.extend(captures); + + // iii. Append 𝔽(position) and S to replacerArgs. + replacer_args.push(position.into()); + replacer_args.push(arg_str.clone().into()); + + // iv. If namedCaptures is not undefined, then + if !named_captures.is_undefined() { + // 1. Append namedCaptures as the last element of replacerArgs. + replacer_args.push(named_captures); + } - // vi. Let replacement be ? ToString(replValue). - repl_value.to_string(context)? - } else { - // i. If namedCaptures is not undefined, then - if !named_captures.is_undefined() { - // 1. Set namedCaptures to ? ToObject(namedCaptures). - named_captures = named_captures.to_object(context)?.into(); + // v. Let replValue be ? Call(replaceValue, undefined, replacerArgs). + // vi. Let replacement be ? ToString(replValue). + replace_fn + .call(&JsValue::undefined(), &replacer_args, context)? + .to_string(context)? } + // l. Else, + Err(ref replace_str) => { + // i. If namedCaptures is not undefined, then + if !named_captures.is_undefined() { + // 1. Set namedCaptures to ? ToObject(namedCaptures). + named_captures = named_captures.to_object(context)?.into(); + } - // ii. Let replacement be ? GetSubstitution(matched, S, position, captures, namedCaptures, replaceValue). - string::get_substitution( - &matched, - &arg_str, - position, - &captures, - &named_captures, - &replace_value.to_string(context)?, - context, - )? + // ii. Let replacement be ? GetSubstitution(matched, S, position, captures, namedCaptures, replaceValue). + string::get_substitution( + &matched, + &arg_str, + position, + &captures, + &named_captures, + replace_str, + context, + )? + } }; // m. If position ≥ nextSourcePosition, then diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 928ba7b7d7..1ae8d34e0f 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -371,7 +371,7 @@ impl Set { .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?; if let Some(arguments) = arguments { - context.call(callback_arg, &this_arg, &arguments)?; + callback_arg.call(&this_arg, &arguments, context)?; } index += 1; diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index be9bfc62e4..e1ea55b6d3 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -938,10 +938,7 @@ impl String { let search_str = search_value.to_string(context)?; // 5. Let functionalReplace be IsCallable(replaceValue). - let functional_replace = replace_value - .as_object() - .map(JsObject::is_callable) - .unwrap_or_default(); + let replace_obj = replace_value.as_callable(); // 6. If functionalReplace is false, then // a. Set replaceValue to ? ToString(replaceValue). @@ -960,13 +957,13 @@ impl String { // 11. If functionalReplace is true, then // 12. Else, - let replacement = if functional_replace { + let replacement = if let Some(replace_fn) = replace_obj { // a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)). - context + replace_fn .call( - replace_value, &JsValue::undefined(), &[search_str.into(), position.into(), this_str.clone().into()], + context, )? .to_string(context)? } else { @@ -1060,17 +1057,12 @@ impl String { let search_string = search_value.to_string(context)?; // 5. Let functionalReplace be IsCallable(replaceValue). - let functional_replace = replace_value - .as_object() - .map(JsObject::is_callable) - .unwrap_or_default(); - - let replace_value_string = if functional_replace { - None + let replace = if let Some(f) = replace_value.as_callable() { + Ok(f) } else { - // a. Set replaceValue to ? ToString(replaceValue). // 6. If functionalReplace is false, then - Some(replace_value.to_string(context)?) + // a. Set replaceValue to ? ToString(replaceValue). + Err(replace_value.to_string(context)?) }; // 7. Let searchLength be the length of searchString. @@ -1106,35 +1098,35 @@ impl String { let preserved = &string[end_of_last_match..p]; // c. Else, - let replacement = if let Some(ref replace_value) = replace_value_string { + let replacement = match replace { + // b. If functionalReplace is true, then + Ok(replace_fn) => { + // i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)). + replace_fn + .call( + &JsValue::undefined(), + &[ + search_string.clone().into(), + p.into(), + string.clone().into(), + ], + context, + )? + .to_string(context)? + } // i. Assert: Type(replaceValue) is String. // ii. Let captures be a new empty List. // iii. Let replacement be ! GetSubstitution(searchString, string, p, captures, undefined, replaceValue). - get_substitution( + Err(ref replace_str) => get_substitution( &search_string, &string, p, &[], &JsValue::undefined(), - replace_value, + replace_str, context, ) - .expect("GetSubstitution should never fail here.") - } - // b. If functionalReplace is true, then - else { - // i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)). - context - .call( - replace_value, - &JsValue::undefined(), - &[ - search_string.clone().into(), - p.into(), - string.clone().into(), - ], - )? - .to_string(context)? + .expect("GetSubstitution should never fail here."), }; // d. Set result to the string-concatenation of result, preserved, and replacement. diff --git a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs index 681fd38f10..05538d6075 100644 --- a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs +++ b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs @@ -8,11 +8,7 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects -use crate::{ - builtins::typed_array::TypedArrayKind, - object::{JsObject, ObjectData}, - Context, -}; +use crate::{builtins::typed_array::TypedArrayKind, object::JsObject}; use boa_gc::{Finalize, Trace}; /// Type of the array content. @@ -50,37 +46,6 @@ impl IntegerIndexed { } } - /// `IntegerIndexedObjectCreate ( prototype )` - /// - /// Create a new `JsObject` from a prototype and a `IntegerIndexedObject` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-integerindexedobjectcreate - pub(super) fn create(prototype: JsObject, data: Self, context: &Context) -> JsObject { - // 1. Let internalSlotsList be « [[Prototype]], [[Extensible]], [[ViewedArrayBuffer]], - // [[TypedArrayName]], [[ContentType]], [[ByteLength]], [[ByteOffset]], - // [[ArrayLength]] ». - // 2. Let A be ! MakeBasicObject(internalSlotsList). - let a = context.construct_object(); - - // 3. Set A.[[GetOwnProperty]] as specified in 10.4.5.1. - // 4. Set A.[[HasProperty]] as specified in 10.4.5.2. - // 5. Set A.[[DefineOwnProperty]] as specified in 10.4.5.3. - // 6. Set A.[[Get]] as specified in 10.4.5.4. - // 7. Set A.[[Set]] as specified in 10.4.5.5. - // 8. Set A.[[Delete]] as specified in 10.4.5.6. - // 9. Set A.[[OwnPropertyKeys]] as specified in 10.4.5.7. - a.borrow_mut().data = ObjectData::integer_indexed(data); - - // 10. Set A.[[Prototype]] to prototype. - a.set_prototype(prototype.into()); - - // 11. Return A. - a - } - /// Abstract operation `IsDetachedBuffer ( arrayBuffer )`. /// /// Check if `[[ArrayBufferData]]` is null. diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 1b055013c7..555089e007 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -433,7 +433,7 @@ impl TypedArray { // 6. If usingIterator is not undefined, then if let Some(using_iterator) = using_iterator { // a. Let values be ? IterableToList(source, usingIterator). - let values = iterable_to_list(context, source, Some(using_iterator.into()))?; + let values = iterable_to_list(context, source, Some(using_iterator))?; // b. Let len be the number of elements in values. // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). @@ -3188,7 +3188,7 @@ impl TypedArray { } // 2. Let obj be ! IntegerIndexedObjectCreate(proto). - let obj = IntegerIndexed::create(proto, indexed, context); + let obj = JsObject::from_proto_and_data(proto, ObjectData::integer_indexed(indexed)); // 9. Return obj. Ok(obj) diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 54398ccdc7..2db14a2492 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -42,8 +42,8 @@ pub struct StandardConstructor { impl Default for StandardConstructor { fn default() -> Self { Self { - constructor: JsObject::empty(), - prototype: JsObject::empty(), + constructor: JsObject::with_null_proto(), + prototype: JsObject::with_null_proto(), } } } @@ -52,7 +52,7 @@ impl StandardConstructor { /// Build a constructor with a defined prototype. fn with_prototype(prototype: JsObject) -> Self { Self { - constructor: JsObject::empty(), + constructor: JsObject::with_null_proto(), prototype, } } diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 6c7ea27542..f44d8f3db2 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -17,13 +17,12 @@ use crate::{ builtins::{self, function::NativeFunctionSignature}, bytecompiler::ByteCompiler, class::{Class, ClassBuilder}, - error::JsNativeError, job::JobCallback, - object::{FunctionBuilder, GlobalPropertyMap, JsObject, ObjectData}, + object::{FunctionBuilder, GlobalPropertyMap, JsObject}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, vm::{CallFrame, CodeBlock, FinallyReturn, GeneratorResumeKind, Vm}, - JsResult, JsString, JsValue, + JsResult, JsValue, }; use boa_ast::StatementList; @@ -131,6 +130,8 @@ impl Default for Context { } } +// ==== Public API ==== + impl Context { /// Create a new [`ContextBuilder`] to specify the [`Interner`] and/or /// the icu data provider. @@ -139,44 +140,34 @@ impl Context { ContextBuilder::default() } - /// Gets the string interner. - #[inline] - pub const fn interner(&self) -> &Interner { - &self.interner - } - - /// Gets a mutable reference to the string interner. - #[inline] - pub fn interner_mut(&mut self) -> &mut Interner { - &mut self.interner - } - - /// A helper function for getting an immutable reference to the `console` object. - #[cfg(feature = "console")] - pub(crate) const fn console(&self) -> &Console { - &self.console - } + /// Evaluates the given code by compiling down to bytecode, then interpreting the bytecode into a value + /// + /// # Examples + /// ``` + /// # use boa_engine::Context; + /// let mut context = Context::default(); + /// + /// let value = context.eval("1 + 3").unwrap(); + /// + /// assert!(value.is_number()); + /// assert_eq!(value.as_number().unwrap(), 4.0); + /// ``` + #[allow(clippy::unit_arg, clippy::drop_copy)] + pub fn eval(&mut self, src: S) -> JsResult + where + S: AsRef<[u8]>, + { + let main_timer = Profiler::global().start_event("Evaluation", "Main"); - /// A helper function for getting a mutable reference to the `console` object. - #[cfg(feature = "console")] - pub(crate) fn console_mut(&mut self) -> &mut Console { - &mut self.console - } + let statement_list = self.parse(src)?; + let code_block = self.compile(&statement_list)?; + let result = self.execute(code_block); - /// Sets up the default global objects within Global - fn create_intrinsics(&mut self) { - let _timer = Profiler::global().start_event("create_intrinsics", "interpreter"); - // Create intrinsics, add global objects here - builtins::init(self); - } + // The main_timer needs to be dropped before the Profiler is. + drop(main_timer); + Profiler::global().drop(); - /// Constructs an object with the `%Object.prototype%` prototype. - #[inline] - pub fn construct_object(&self) -> JsObject { - JsObject::from_proto_and_data( - self.intrinsics().constructors().object().prototype(), - ObjectData::ordinary(), - ) + result } /// Parse the given source text. @@ -184,70 +175,91 @@ impl Context { where S: AsRef<[u8]>, { + let _timer = Profiler::global().start_event("Parsing", "Main"); let mut parser = Parser::new(src.as_ref()); parser.parse_all(&mut self.interner) } - /// `Call ( F, V [ , argumentsList ] )` - /// - /// The abstract operation `Call` takes arguments `F` (an ECMAScript language value) and `V` - /// (an ECMAScript language value) and optional argument `argumentsList` (a `List` of - /// ECMAScript language values) and returns either a normal completion containing an ECMAScript - /// language value or a throw completion. It is used to call the `[[Call]]` internal method of - /// a function object. `F` is the function object, `V` is an ECMAScript language value that is - /// the `this` value of the `[[Call]]`, and `argumentsList` is the value passed to the - /// corresponding argument of the internal method. If `argumentsList` is not present, a new - /// empty `List` is used as its value. - /// - /// More information: - /// - [ECMA reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-call - pub(crate) fn call( - &mut self, - f: &JsValue, - v: &JsValue, - arguments_list: &[JsValue], - ) -> JsResult { - // 1. If argumentsList is not present, set argumentsList to a new empty List. - // 2. If IsCallable(F) is false, throw a TypeError exception. - // 3. Return ? F.[[Call]](V, argumentsList). - f.as_callable() - .ok_or_else(|| { - JsNativeError::typ() - .with_message("Value is not callable") - .into() - }) - .and_then(|f| f.call(v, arguments_list, self)) + /// Compile the AST into a `CodeBlock` ready to be executed by the VM. + pub fn compile(&mut self, statement_list: &StatementList) -> JsResult> { + let _timer = Profiler::global().start_event("Compilation", "Main"); + let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), false, self); + compiler.create_decls(statement_list, false); + compiler.compile_statement_list(statement_list, true, false)?; + Ok(Gc::new(compiler.finish())) } - /// Return the global object. - #[inline] - pub const fn global_object(&self) -> &JsObject { - self.realm.global_object() - } + /// Call the VM with a `CodeBlock` and return the result. + /// + /// Since this function receives a `Gc`, cloning the code is very cheap, since it's + /// just a pointer copy. Therefore, if you'd like to execute the same `CodeBlock` multiple + /// times, there is no need to re-compile it, and you can just call `clone()` on the + /// `Gc` returned by the [`Self::compile()`] function. + pub fn execute(&mut self, code_block: Gc) -> JsResult { + let _timer = Profiler::global().start_event("Execution", "Main"); - /// Return a mutable reference to the global object string bindings. - pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap { - self.realm.global_bindings_mut() + self.vm.push_frame(CallFrame { + code: code_block, + pc: 0, + catch: Vec::new(), + finally_return: FinallyReturn::None, + finally_jump: Vec::new(), + pop_on_return: 0, + loop_env_stack: Vec::from([0]), + try_env_stack: Vec::from([crate::vm::TryStackEntry { + num_env: 0, + num_loop_stack_entries: 0, + }]), + param_count: 0, + arg_count: 0, + generator_resume_kind: GeneratorResumeKind::Normal, + thrown: false, + async_generator: None, + }); + + self.realm.set_global_binding_number(); + let result = self.run(); + self.vm.pop_frame(); + self.clear_kept_objects(); + self.run_queued_jobs()?; + let (result, _) = result?; + Ok(result) } - /// Constructs a `Error` with the specified message. - pub fn construct_error(&mut self, message: M) -> JsValue + /// Register a global property. + /// + /// # Example + /// ``` + /// use boa_engine::{ + /// object::ObjectInitializer, + /// property::{Attribute, PropertyDescriptor}, + /// Context, + /// }; + /// + /// let mut context = Context::default(); + /// + /// context.register_global_property("myPrimitiveProperty", 10, Attribute::all()); + /// + /// let object = ObjectInitializer::new(&mut context) + /// .property("x", 0, Attribute::all()) + /// .property("y", 1, Attribute::all()) + /// .build(); + /// context.register_global_property("myObjectProperty", object, Attribute::all()); + /// ``` + pub fn register_global_property(&mut self, key: K, value: V, attribute: Attribute) where - M: Into, + K: Into, + V: Into, { - crate::builtins::error::Error::constructor( - &self - .intrinsics() - .constructors() - .error() - .constructor() - .into(), - &[message.into().into()], - self, - ) - .expect("Into used as message") + self.realm.global_property_map.insert( + &key.into(), + PropertyDescriptor::builder() + .value(value) + .writable(attribute.writable()) + .enumerable(attribute.enumerable()) + .configurable(attribute.configurable()) + .build(), + ); } /// Register a global native function. @@ -372,12 +384,6 @@ impl Context { Ok(()) } - /// - pub(crate) fn has_property(&mut self, obj: &JsValue, key: &PropertyKey) -> JsResult { - obj.as_object() - .map_or(Ok(false), |obj| obj.__has_property__(key, self)) - } - /// Register a global class of type `T`, where `T` implements `Class`. /// /// # Example @@ -410,84 +416,82 @@ impl Context { Ok(()) } - /// Register a global property. - /// - /// # Example - /// ``` - /// use boa_engine::{ - /// object::ObjectInitializer, - /// property::{Attribute, PropertyDescriptor}, - /// Context, - /// }; - /// - /// let mut context = Context::default(); - /// - /// context.register_global_property("myPrimitiveProperty", 10, Attribute::all()); - /// - /// let object = ObjectInitializer::new(&mut context) - /// .property("x", 0, Attribute::all()) - /// .property("y", 1, Attribute::all()) - /// .build(); - /// context.register_global_property("myObjectProperty", object, Attribute::all()); - /// ``` - pub fn register_global_property(&mut self, key: K, value: V, attribute: Attribute) - where - K: Into, - V: Into, - { - self.realm.global_property_map.insert( - &key.into(), - PropertyDescriptor::builder() - .value(value) - .writable(attribute.writable()) - .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()) - .build(), - ); + /// Gets the string interner. + #[inline] + pub const fn interner(&self) -> &Interner { + &self.interner } - /// Evaluates the given code by compiling down to bytecode, then interpreting the bytecode into a value + /// Gets a mutable reference to the string interner. + #[inline] + pub fn interner_mut(&mut self) -> &mut Interner { + &mut self.interner + } + + /// Return the global object. + #[inline] + pub const fn global_object(&self) -> &JsObject { + self.realm.global_object() + } + + /// Return the intrinsic constructors and objects. + #[inline] + pub const fn intrinsics(&self) -> &Intrinsics { + &self.intrinsics + } + + /// Set the value of trace on the context + pub fn set_trace(&mut self, trace: bool) { + self.vm.trace = trace; + } + + /// More information: + /// - [ECMAScript reference][spec] /// - /// # Examples - /// ``` - /// # use boa_engine::Context; - /// let mut context = Context::default(); + /// [spec]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob + pub fn host_enqueue_promise_job(&mut self, job: JobCallback /* , realm: Realm */) { + // If realm is not null ... + // TODO + // Let scriptOrModule be ... + // TODO + self.promise_job_queue.push_back(job); + } + + /// Abstract operation [`ClearKeptObjects`][clear]. /// - /// let value = context.eval("1 + 3").unwrap(); + /// Clears all objects maintained alive by calls to the [`AddToKeptObjects`][add] abstract + /// operation, used within the [`WeakRef`][weak] constructor. /// - /// assert!(value.is_number()); - /// assert_eq!(value.as_number().unwrap(), 4.0); - /// ``` - #[allow(clippy::unit_arg, clippy::drop_copy)] - pub fn eval(&mut self, src: S) -> JsResult - where - S: AsRef<[u8]>, - { - let main_timer = Profiler::global().start_event("Evaluation", "Main"); - - let statement_list = Parser::new(src.as_ref()).parse_all(&mut self.interner)?; + /// [clear]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-clear-kept-objects + /// [add]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-addtokeptobjects + /// [weak]: https://tc39.es/ecma262/multipage/managing-memory.html#sec-weak-ref-objects + pub fn clear_kept_objects(&mut self) { + self.kept_alive.clear(); + } +} - let code_block = self.compile(&statement_list)?; - let result = self.execute(code_block); +// ==== Private API ==== - // The main_timer needs to be dropped before the Profiler is. - drop(main_timer); - Profiler::global().drop(); +impl Context { + /// A helper function for getting an immutable reference to the `console` object. + #[cfg(feature = "console")] + pub(crate) const fn console(&self) -> &Console { + &self.console + } - result + /// A helper function for getting a mutable reference to the `console` object. + #[cfg(feature = "console")] + pub(crate) fn console_mut(&mut self) -> &mut Console { + &mut self.console } - /// Compile the AST into a `CodeBlock` ready to be executed by the VM. - pub fn compile(&mut self, statement_list: &StatementList) -> JsResult> { - let _timer = Profiler::global().start_event("Compilation", "Main"); - let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), false, self); - compiler.create_decls(statement_list, false); - compiler.compile_statement_list(statement_list, true, false)?; - Ok(Gc::new(compiler.finish())) + /// Return a mutable reference to the global object string bindings. + pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap { + self.realm.global_bindings_mut() } /// Compile the AST into a `CodeBlock` ready to be executed by the VM in a `JSON.parse` context. - pub fn compile_json_parse( + pub(crate) fn compile_json_parse( &mut self, statement_list: &StatementList, ) -> JsResult> { @@ -510,41 +514,17 @@ impl Context { Ok(Gc::new(compiler.finish())) } - /// Call the VM with a `CodeBlock` and return the result. - /// - /// Since this function receives a `Gc`, cloning the code is very cheap, since it's - /// just a pointer copy. Therefore, if you'd like to execute the same `CodeBlock` multiple - /// times, there is no need to re-compile it, and you can just call `clone()` on the - /// `Gc` returned by the [`Self::compile()`] function. - pub fn execute(&mut self, code_block: Gc) -> JsResult { - let _timer = Profiler::global().start_event("Execution", "Main"); - - self.vm.push_frame(CallFrame { - code: code_block, - pc: 0, - catch: Vec::new(), - finally_return: FinallyReturn::None, - finally_jump: Vec::new(), - pop_on_return: 0, - loop_env_stack: Vec::from([0]), - try_env_stack: Vec::from([crate::vm::TryStackEntry { - num_env: 0, - num_loop_stack_entries: 0, - }]), - param_count: 0, - arg_count: 0, - generator_resume_kind: GeneratorResumeKind::Normal, - thrown: false, - async_generator: None, - }); + /// Get the ICU related utilities + #[cfg(feature = "intl")] + pub(crate) const fn icu(&self) -> &icu::Icu { + &self.icu + } - self.realm.set_global_binding_number(); - let result = self.run(); - self.vm.pop_frame(); - self.clear_kept_objects(); - self.run_queued_jobs()?; - let (result, _) = result?; - Ok(result) + /// Sets up the default global objects within Global + fn create_intrinsics(&mut self) { + let _timer = Profiler::global().start_event("create_intrinsics", "interpreter"); + // Create intrinsics, add global objects here + builtins::init(self); } /// Runs all the jobs in the job queue. @@ -555,47 +535,6 @@ impl Context { } Ok(()) } - - /// Return the intrinsic constructors and objects. - #[inline] - pub const fn intrinsics(&self) -> &Intrinsics { - &self.intrinsics - } - - /// Set the value of trace on the context - pub fn set_trace(&mut self, trace: bool) { - self.vm.trace = trace; - } - - #[cfg(feature = "intl")] - /// Get the ICU related utilities - pub(crate) const fn icu(&self) -> &icu::Icu { - &self.icu - } - - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob - pub fn host_enqueue_promise_job(&mut self, job: JobCallback /* , realm: Realm */) { - // If realm is not null ... - // TODO - // Let scriptOrModule be ... - // TODO - self.promise_job_queue.push_back(job); - } - - /// Abstract operation [`ClearKeptObjects`][clear]. - /// - /// Clears all objects maintained alive by calls to the [`AddToKeptObjects`][add] abstract - /// operation, used within the [`WeakRef`][weak] constructor. - /// - /// [clear]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-clear-kept-objects - /// [add]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-addtokeptobjects - /// [weak]: https://tc39.es/ecma262/multipage/managing-memory.html#sec-weak-ref-objects - pub fn clear_kept_objects(&mut self) { - self.kept_alive.clear(); - } } /// Builder for the [`Context`] type. /// diff --git a/boa_engine/src/object/builtins/jsarraybuffer.rs b/boa_engine/src/object/builtins/jsarraybuffer.rs index ec1a577d00..00cf747552 100644 --- a/boa_engine/src/object/builtins/jsarraybuffer.rs +++ b/boa_engine/src/object/builtins/jsarraybuffer.rs @@ -91,8 +91,6 @@ impl JsArrayBuffer { StandardConstructors::array_buffer, context, )?; - let obj = context.construct_object(); - obj.set_prototype(prototype.into()); // 2. Let block be ? CreateByteDataBlock(byteLength). // @@ -102,11 +100,14 @@ impl JsArrayBuffer { // 3. Set obj.[[ArrayBufferData]] to block. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. - obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer { - array_buffer_data: Some(block), - array_buffer_byte_length: byte_length as u64, - array_buffer_detach_key: JsValue::Undefined, - }); + let obj = JsObject::from_proto_and_data( + prototype, + ObjectData::array_buffer(ArrayBuffer { + array_buffer_data: Some(block), + array_buffer_byte_length: byte_length as u64, + array_buffer_detach_key: JsValue::Undefined, + }), + ); Ok(Self { inner: obj }) } diff --git a/boa_engine/src/object/builtins/jsproxy.rs b/boa_engine/src/object/builtins/jsproxy.rs index 79f6d6d280..3d4e91b0cd 100644 --- a/boa_engine/src/object/builtins/jsproxy.rs +++ b/boa_engine/src/object/builtins/jsproxy.rs @@ -371,7 +371,7 @@ impl JsProxyBuilder { /// inside Rust code. #[must_use] pub fn build(self, context: &mut Context) -> JsProxy { - let handler = context.construct_object(); + let handler = JsObject::with_object_proto(context); if let Some(apply) = self.apply { let f = FunctionBuilder::native(context, apply).length(3).build(); diff --git a/boa_engine/src/object/internal_methods/global.rs b/boa_engine/src/object/internal_methods/global.rs index dc3325c2bd..15d7757f9e 100644 --- a/boa_engine/src/object/internal_methods/global.rs +++ b/boa_engine/src/object/internal_methods/global.rs @@ -254,7 +254,7 @@ pub(crate) fn global_get( // 6. Let getter be desc.[[Get]]. DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { // 8. Return ? Call(getter, Receiver). - context.call(get, &receiver, &[]) + get.call(&receiver, &[], context) } // 7. If getter is undefined, return undefined. _ => Ok(JsValue::undefined()), @@ -360,7 +360,7 @@ pub(crate) fn global_set_no_receiver( match own_desc.set() { Some(set) if !set.is_undefined() => { // 7. Perform ? Call(setter, Receiver, « V »). - context.call(set, &context.global_object().clone().into(), &[value])?; + set.call(&context.global_object().clone().into(), &[value], context)?; // 8. Return true. Ok(true) diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index 5694bb10f8..e2ddbc7d5b 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/boa_engine/src/object/internal_methods/mod.rs @@ -544,7 +544,7 @@ pub(crate) fn ordinary_get( // 6. Let getter be desc.[[Get]]. DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { // 8. Return ? Call(getter, Receiver). - context.call(get, &receiver, &[]) + get.call(&receiver, &[], context) } // 7. If getter is undefined, return undefined. _ => Ok(JsValue::undefined()), @@ -644,7 +644,7 @@ pub(crate) fn ordinary_set( match own_desc.set() { Some(set) if !set.is_undefined() => { // 7. Perform ? Call(setter, Receiver, « V »). - context.call(set, &receiver, &[value])?; + set.call(&receiver, &[value], context)?; // 8. Return true. Ok(true) diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 5fa4cdf2e8..9560b853f4 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -33,32 +33,50 @@ pub struct JsObject { } impl JsObject { - /// Create a new `JsObject` from an internal `Object`. - fn from_object(object: Object) -> Self { - Self { - inner: Gc::new(GcCell::new(object)), - } + /// Creates a new ordinary object with its prototype set to the `Object` prototype. + /// + /// This is equivalent to calling the specification's abstract operation + /// [`OrdinaryObjectCreate(%Object.prototype%)`][call]. + /// + /// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate + #[inline] + #[must_use] + pub fn with_object_proto(context: &mut Context) -> Self { + Self::from_proto_and_data( + context.intrinsics().constructors().object().prototype(), + ObjectData::ordinary(), + ) } - /// Create a new empty `JsObject`, with `prototype` set to `JsValue::Null` - /// and `data` set to `ObjectData::ordinary` + /// Creates a new ordinary object, with its prototype set to null. + /// + /// This is equivalent to calling the specification's abstract operation + /// [`OrdinaryObjectCreate(null)`][call]. + /// + /// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate + #[inline] #[must_use] - pub fn empty() -> Self { - Self::from_object(Object::default()) + pub fn with_null_proto() -> Self { + Self::from_proto_and_data(None, ObjectData::ordinary()) } - /// The more general form of `OrdinaryObjectCreate` and `MakeBasicObject`. + /// Creates a new object with the provided prototype and object data. + /// + /// This is equivalent to calling the specification's abstract operation [`OrdinaryObjectCreate`], + /// with the difference that the `additionalInternalSlotsList` parameter is automatically set by + /// the [`ObjectData`] provided. /// - /// Create a `JsObject` and automatically set its internal methods and - /// internal slots from the `data` provided. + /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate pub fn from_proto_and_data>>(prototype: O, data: ObjectData) -> Self { - Self::from_object(Object { - data, - prototype: prototype.into(), - extensible: true, - properties: PropertyMap::default(), - private_elements: FxHashMap::default(), - }) + Self { + inner: Gc::new(GcCell::new(Object { + data, + prototype: prototype.into(), + extensible: true, + properties: PropertyMap::default(), + private_elements: FxHashMap::default(), + })), + } } /// Immutably borrows the `Object`. @@ -80,7 +98,7 @@ impl JsObject { /// The borrow lasts until the returned `RefMut` exits scope. /// The object cannot be borrowed while this borrow is active. /// - ///# Panics + /// # Panics /// Panics if the object is currently borrowed. #[inline] #[track_caller] diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index c432eead14..0745650ea2 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -2095,7 +2095,7 @@ impl<'context> ObjectInitializer<'context> { /// Create a new `ObjectBuilder`. #[inline] pub fn new(context: &'context mut Context) -> Self { - let object = context.construct_object(); + let object = JsObject::with_object_proto(context); Self { context, object } } @@ -2186,8 +2186,8 @@ impl<'context> ConstructorBuilder<'context> { Self { context, function, - object: JsObject::empty(), - prototype: JsObject::empty(), + object: JsObject::with_null_proto(), + prototype: JsObject::with_null_proto(), length: 0, name: js_string!(), callable: true, diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index c28197f16f..92fae9d418 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -762,6 +762,35 @@ impl JsValue { Ok(list) } + /// Abstract operation [`Call ( F, V [ , argumentsList ] )`][call]. + /// + /// Calls this value if the value is a callable object. + /// + /// # Note + /// + /// It is almost always better to try to obtain a callable object first with [`JsValue::as_callable`], + /// then calling [`JsObject::call`], since that allows reusing the unwrapped function for other + /// operations. This method is only an utility method for when the spec directly uses `Call` + /// without using the value as a proper object. + /// + /// [call]: https://tc39.es/ecma262/#sec-call + #[inline] + pub(crate) fn call( + &self, + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + self.as_callable() + .ok_or_else(|| { + JsNativeError::typ().with_message(format!( + "value with type `{}` is not callable", + self.type_of() + )) + })? + .__call__(this, args, context) + } + /// Abstract operation `( V, P [ , argumentsList ] )` /// /// Calls a method property of an ECMAScript language value. @@ -779,7 +808,7 @@ impl JsValue { let func = self.get_v(key, context)?; // 3. Return ? Call(func, V, argumentsList) - context.call(&func, self, args) + func.call(self, args, context) } /// Abstract operation `OrdinaryHasInstance ( C, O )` diff --git a/boa_engine/src/value/serde_json.rs b/boa_engine/src/value/serde_json.rs index 5023f28857..e5127a4bcd 100644 --- a/boa_engine/src/value/serde_json.rs +++ b/boa_engine/src/value/serde_json.rs @@ -4,6 +4,7 @@ use super::JsValue; use crate::{ builtins::Array, error::JsNativeError, + object::JsObject, property::{PropertyDescriptor, PropertyKey}, Context, JsResult, }; @@ -63,7 +64,7 @@ impl JsValue { Ok(Array::create_array_from_list(arr, context).into()) } Value::Object(obj) => { - let js_obj = context.construct_object(); + let js_obj = JsObject::with_object_proto(context); for (key, value) in obj { let property = PropertyDescriptor::builder() .value(Self::from_json(value, context)?) diff --git a/boa_engine/src/value/tests.rs b/boa_engine/src/value/tests.rs index ab46bc3a73..96a527a0b7 100644 --- a/boa_engine/src/value/tests.rs +++ b/boa_engine/src/value/tests.rs @@ -24,7 +24,7 @@ fn undefined() { #[test] fn get_set_field() { let mut context = Context::default(); - let obj = &context.construct_object(); + let obj = &JsObject::with_object_proto(&mut context); // Create string and convert it to a Value let s = JsValue::new("bar"); obj.set("foo", s, false, &mut context).unwrap(); @@ -132,11 +132,11 @@ fn hash_rational() { #[test] #[allow(clippy::eq_op)] fn hash_object() { - let object1 = JsValue::new(JsObject::empty()); + let object1 = JsValue::new(JsObject::with_null_proto()); assert_eq!(object1, object1); assert_eq!(object1, object1.clone()); - let object2 = JsValue::new(JsObject::empty()); + let object2 = JsValue::new(JsObject::with_null_proto()); assert_ne!(object1, object2); assert_eq!(hash_value(&object1), hash_value(&object1.clone())); diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index b94c040453..043d949f19 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -506,8 +506,6 @@ pub(crate) fn create_function_object( context.intrinsics().constructors().function().prototype() }; - let prototype = context.construct_object(); - let name_property = PropertyDescriptor::builder() .value( context @@ -566,6 +564,7 @@ pub(crate) fn create_function_object( .configurable(true) .build(); + let prototype = JsObject::with_object_proto(context); prototype .define_property_or_throw(js_string!("constructor"), constructor_property, context) .expect("failed to define the constructor property of the function"); diff --git a/boa_engine/src/vm/opcode/binary_ops/mod.rs b/boa_engine/src/vm/opcode/binary_ops/mod.rs index 31d1a04b68..0c314e5965 100644 --- a/boa_engine/src/vm/opcode/binary_ops/mod.rs +++ b/boa_engine/src/vm/opcode/binary_ops/mod.rs @@ -83,16 +83,16 @@ impl Operation for In { let rhs = context.vm.pop(); let lhs = context.vm.pop(); - if !rhs.is_object() { + let Some(rhs) = rhs.as_object() else { return Err(JsNativeError::typ() .with_message(format!( "right-hand side of 'in' should be an object, got `{}`", rhs.type_of() )) .into()); - } + }; let key = lhs.to_property_key(context)?; - let value = context.has_property(&rhs, &key)?; + let value = rhs.has_property(key, context)?; context.vm.push(value); Ok(ShouldExit::False) } diff --git a/boa_engine/src/vm/opcode/generator/mod.rs b/boa_engine/src/vm/opcode/generator/mod.rs index 0ec573287c..b3aa4e59e4 100644 --- a/boa_engine/src/vm/opcode/generator/mod.rs +++ b/boa_engine/src/vm/opcode/generator/mod.rs @@ -147,18 +147,18 @@ impl Operation for GeneratorNextDelegate { match context.vm.frame().generator_resume_kind { GeneratorResumeKind::Normal => { - let result = context.call(&next_method, &iterator.clone().into(), &[received])?; - let result_object = result.as_object().ok_or_else(|| { + let result = next_method.call(&iterator.clone().into(), &[received], context)?; + let result = result.as_object().ok_or_else(|| { JsNativeError::typ().with_message("generator next method returned non-object") })?; - let done = result_object.get("done", context)?.to_boolean(); + let done = result.get("done", context)?.to_boolean(); if done { context.vm.frame_mut().pc = done_address as usize; - let value = result_object.get("value", context)?; + let value = result.get("value", context)?; context.vm.push(value); return Ok(ShouldExit::False); } - let value = result_object.get("value", context)?; + let value = result.get("value", context)?; context.vm.push(iterator.clone()); context.vm.push(next_method.clone()); context.vm.push(done); diff --git a/boa_engine/src/vm/opcode/get/name.rs b/boa_engine/src/vm/opcode/get/name.rs index 1e0ae7ee4c..6800a89f79 100644 --- a/boa_engine/src/vm/opcode/get/name.rs +++ b/boa_engine/src/vm/opcode/get/name.rs @@ -40,7 +40,7 @@ impl Operation for GetName { } => value.clone(), DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { let get = get.clone(); - context.call(&get, &context.global_object().clone().into(), &[])? + get.call(&context.global_object().clone().into(), &[], context)? } _ => { return Err(JsNativeError::reference() @@ -113,7 +113,7 @@ impl Operation for GetNameOrUndefined { } => value.clone(), DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { let get = get.clone(); - context.call(&get, &context.global_object().clone().into(), &[])? + get.call(&context.global_object().clone().into(), &[], context)? } _ => JsValue::undefined(), }, diff --git a/boa_engine/src/vm/opcode/push/object.rs b/boa_engine/src/vm/opcode/push/object.rs index f2f5eca16c..098250a76f 100644 --- a/boa_engine/src/vm/opcode/push/object.rs +++ b/boa_engine/src/vm/opcode/push/object.rs @@ -1,4 +1,5 @@ use crate::{ + object::JsObject, vm::{opcode::Operation, ShouldExit}, Context, JsResult, }; @@ -15,7 +16,8 @@ impl Operation for PushEmptyObject { const INSTRUCTION: &'static str = "INST - PushEmptyObject"; fn execute(context: &mut Context) -> JsResult { - context.vm.push(context.construct_object()); + let o = JsObject::with_object_proto(context); + context.vm.push(o); Ok(ShouldExit::False) } } diff --git a/boa_examples/src/bin/closures.rs b/boa_examples/src/bin/closures.rs index c6f059f03a..1944c20b78 100644 --- a/boa_examples/src/bin/closures.rs +++ b/boa_examples/src/bin/closures.rs @@ -42,7 +42,7 @@ fn main() -> Result<(), JsError> { } // We create a new `JsObject` with some data - let object = context.construct_object(); + let object = JsObject::with_object_proto(&mut context); object.define_property_or_throw( "name", PropertyDescriptor::builder()