From 34d6b93f36b131ef296eb789678f7bdfa329cd0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Mon, 10 Apr 2023 02:33:41 +0000 Subject: [PATCH] Fix cross-realm construction bugs (#2786) This Pull Request fixes test [`assert-throws-same-realm.js`](https://github.com/tc39/test262/blob/eb44f67274bf3896fbec8814f81dd2ae02236686/test/harness/assert-throws-same-realm.js). It changes the following: - Handles global variables through the global object, instead of the `context`. - Adds an `active_function` field to the vm, which is used as the `NewTarget` when certain builtins aren't called with `new`. - Adds a `realm_intrinsics` field to `Function`. --- .vscode/launch.json | 21 +- .vscode/tasks.json | 10 + boa_cli/src/debug/function.rs | 43 +- boa_cli/src/debug/mod.rs | 19 +- boa_cli/src/debug/realm.rs | 14 + boa_engine/src/builtins/array/mod.rs | 16 +- boa_engine/src/builtins/async_function/mod.rs | 18 +- .../src/builtins/async_generator/mod.rs | 64 ++- .../builtins/async_generator_function/mod.rs | 18 +- boa_engine/src/builtins/error/aggregate.rs | 16 + boa_engine/src/builtins/error/eval.rs | 16 + boa_engine/src/builtins/error/mod.rs | 10 + boa_engine/src/builtins/error/range.rs | 16 + boa_engine/src/builtins/error/reference.rs | 16 + boa_engine/src/builtins/error/syntax.rs | 16 + boa_engine/src/builtins/error/type.rs | 30 +- boa_engine/src/builtins/error/uri.rs | 16 + boa_engine/src/builtins/function/mod.rs | 240 +++++++--- boa_engine/src/builtins/function/tests.rs | 5 +- boa_engine/src/builtins/generator/mod.rs | 52 ++- .../src/builtins/generator_function/mod.rs | 18 +- boa_engine/src/builtins/intl/collator/mod.rs | 10 + .../src/builtins/intl/date_time_format.rs | 16 + boa_engine/src/builtins/iterable/mod.rs | 4 +- boa_engine/src/builtins/json/mod.rs | 4 +- boa_engine/src/builtins/mod.rs | 21 +- boa_engine/src/builtins/object/mod.rs | 18 +- boa_engine/src/builtins/promise/mod.rs | 45 +- boa_engine/src/builtins/proxy/mod.rs | 10 +- boa_engine/src/builtins/regexp/mod.rs | 5 +- boa_engine/src/builtins/uri/mod.rs | 4 +- boa_engine/src/class.rs | 4 +- boa_engine/src/context/hooks.rs | 7 +- boa_engine/src/context/intrinsics.rs | 41 +- boa_engine/src/context/mod.rs | 71 +-- boa_engine/src/environments/compile.rs | 15 +- boa_engine/src/environments/runtime.rs | 4 +- boa_engine/src/object/builtins/jsproxy.rs | 2 +- .../src/object/internal_methods/global.rs | 431 ------------------ boa_engine/src/object/internal_methods/mod.rs | 21 +- boa_engine/src/object/jsobject.rs | 5 +- boa_engine/src/object/mod.rs | 61 +-- boa_engine/src/object/operations.rs | 38 +- boa_engine/src/object/property_map.rs | 12 - boa_engine/src/realm.rs | 9 +- .../src/value/conversions/serde_json.rs | 2 +- boa_engine/src/value/tests.rs | 2 +- boa_engine/src/vm/code_block.rs | 152 +++--- boa_engine/src/vm/mod.rs | 5 +- boa_engine/src/vm/opcode/await_stm/mod.rs | 100 ++-- boa_engine/src/vm/opcode/call/mod.rs | 14 +- boa_engine/src/vm/opcode/define/mod.rs | 25 +- boa_engine/src/vm/opcode/delete/mod.rs | 11 +- boa_engine/src/vm/opcode/get/generator.rs | 4 +- boa_engine/src/vm/opcode/get/name.rs | 8 +- boa_engine/src/vm/opcode/push/class/mod.rs | 6 +- boa_engine/src/vm/opcode/push/object.rs | 2 +- boa_engine/src/vm/opcode/set/name.rs | 18 +- boa_examples/src/bin/closures.rs | 96 ++-- boa_examples/src/bin/futures.rs | 4 +- boa_examples/src/bin/jsarray.rs | 1 - boa_examples/src/bin/jsarraybuffer.rs | 12 +- boa_examples/src/bin/jstypedarray.rs | 12 +- boa_examples/src/bin/modulehandler.rs | 8 +- boa_gc/src/trace.rs | 21 - boa_tester/src/exec/js262.rs | 14 +- boa_tester/src/exec/mod.rs | 12 +- docs/boa_object.md | 14 + 68 files changed, 1069 insertions(+), 1006 deletions(-) create mode 100644 boa_cli/src/debug/realm.rs delete mode 100644 boa_engine/src/object/internal_methods/global.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index ce5770f294..3173af23a6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,24 +12,9 @@ "program": "${workspaceFolder}/target/debug/boa.exe" }, "program": "${workspaceFolder}/target/debug/boa", - "args": ["${workspaceFolder}/tests/js/test.js"], - "sourceLanguages": ["rust"] - }, - { - "type": "lldb", - "request": "launch", - "name": "Launch (VM)", - "cargo": { - "args": [ - "run", - "--manifest-path", - "./boa_cli/Cargo.toml", - "--features", - "vm" - ] - }, - "args": ["-t", "${workspaceFolder}/tests/js/test.js"], - "sourceLanguages": ["rust"] + "args": ["${workspaceFolder}/tests/js/test.js", "--debug-object"], + "sourceLanguages": ["rust"], + "preLaunchTask": "Cargo Build" } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 018fc7e8ae..ca67f62f35 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,6 +3,16 @@ // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ + { + "type": "process", + "label": "Cargo Build", + "command": "cargo", + "args": ["build"], + "group": "build", + "presentation": { + "clear": true + } + }, { "type": "process", "label": "Cargo Run", diff --git a/boa_cli/src/debug/function.rs b/boa_cli/src/debug/function.rs index d413c064bf..ba4425b839 100644 --- a/boa_cli/src/debug/function.rs +++ b/boa_cli/src/debug/function.rs @@ -1,5 +1,4 @@ use boa_engine::{ - builtins::function::Function, object::ObjectInitializer, vm::flowgraph::{Direction, Graph}, Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, @@ -87,17 +86,9 @@ fn flowgraph(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> Js .into()); }; - let code = match function { - Function::Ordinary { code, .. } - | Function::Async { code, .. } - | Function::Generator { code, .. } - | Function::AsyncGenerator { code, .. } => code, - Function::Native { .. } => { - return Err(JsNativeError::typ() - .with_message("native functions do not have bytecode") - .into()) - } - }; + let code = function.codeblock().ok_or_else(|| { + JsNativeError::typ().with_message("native functions do not have bytecode") + })?; let mut graph = Graph::new(direction); code.to_graph(context.interner(), graph.subgraph(String::default())); @@ -127,17 +118,9 @@ fn bytecode(_: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResul .with_message("expected function object") .into()); }; - let code = match function { - Function::Ordinary { code, .. } - | Function::Async { code, .. } - | Function::Generator { code, .. } - | Function::AsyncGenerator { code, .. } => code, - Function::Native { .. } => { - return Err(JsNativeError::typ() - .with_message("native functions do not have bytecode") - .into()) - } - }; + let code = function.codeblock().ok_or_else(|| { + JsNativeError::typ().with_message("native functions do not have bytecode") + })?; Ok(code.to_interned_string(context.interner()).into()) } @@ -149,17 +132,9 @@ fn set_trace_flag_in_function_object(object: &JsObject, value: bool) -> JsResult .with_message("expected function object") .into()); }; - let code = match function { - Function::Ordinary { code, .. } - | Function::Async { code, .. } - | Function::Generator { code, .. } - | Function::AsyncGenerator { code, .. } => code, - Function::Native { .. } => { - return Err(JsNativeError::typ() - .with_message("native functions do not have bytecode") - .into()) - } - }; + let code = function.codeblock().ok_or_else(|| { + JsNativeError::typ().with_message("native functions do not have bytecode") + })?; code.set_trace(value); Ok(()) } diff --git a/boa_cli/src/debug/mod.rs b/boa_cli/src/debug/mod.rs index 48a02a0bd2..6701a38106 100644 --- a/boa_cli/src/debug/mod.rs +++ b/boa_cli/src/debug/mod.rs @@ -7,12 +7,14 @@ mod function; mod gc; mod object; mod optimizer; +mod realm; fn create_boa_object(context: &mut Context<'_>) -> JsObject { let function_module = function::create_object(context); let object_module = object::create_object(context); let optimizer_module = optimizer::create_object(context); let gc_module = gc::create_object(context); + let realm_module = realm::create_object(context); ObjectInitializer::new(context) .property( @@ -35,14 +37,21 @@ fn create_boa_object(context: &mut Context<'_>) -> JsObject { gc_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) + .property( + "realm", + realm_module, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) .build() } pub(crate) fn init_boa_debug_object(context: &mut Context<'_>) { let boa_object = create_boa_object(context); - context.register_global_property( - "$boa", - boa_object, - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ); + context + .register_global_property( + "$boa", + boa_object, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .expect("cannot fail with the default object"); } diff --git a/boa_cli/src/debug/realm.rs b/boa_cli/src/debug/realm.rs new file mode 100644 index 0000000000..7a1e6cff9d --- /dev/null +++ b/boa_cli/src/debug/realm.rs @@ -0,0 +1,14 @@ +use boa_engine::{object::ObjectInitializer, Context, JsObject, JsResult, JsValue, NativeFunction}; + +/// Creates a new ECMAScript Realm and returns the global object of the realm. +fn create(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { + let context = &mut Context::default(); + + Ok(context.global_object().into()) +} + +pub(super) fn create_object(context: &mut Context<'_>) -> JsObject { + ObjectInitializer::new(context) + .function(NativeFunction::from_fn_ptr(create), "create", 0) + .build() +} diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 793d4d8d72..a6e01c4a13 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -147,6 +147,17 @@ impl BuiltInConstructor for Array { context: &mut Context<'_>, ) -> JsResult { // If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| context.intrinsics().constructors().array().constructor()) + .into() + } else { + new_target.clone() + }; + // 2. Let proto be ? GetPrototypeFromConstructor(newTarget, "%Array.prototype%"). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::array, context)?; @@ -358,9 +369,12 @@ impl Array { // 4. If IsConstructor(C) is true, then if let Some(c) = c.as_constructor() { // a. Let thisRealm be the current Realm Record. + let this_realm = &context.intrinsics().clone(); // b. Let realmC be ? GetFunctionRealm(C). + let realm_c = &c.get_function_realm(context)?; + // c. If thisRealm and realmC are not the same Realm Record, then - if *c == context.intrinsics().constructors().array().constructor { + if this_realm != realm_c && *c == realm_c.constructors().array().constructor() { // i. If SameValue(C, realmC.[[Intrinsics]].[[%Array%]]) is true, set C to undefined. // Note: fast path to step 6. return Self::array_create(length, None, context); diff --git a/boa_engine/src/builtins/async_function/mod.rs b/boa_engine/src/builtins/async_function/mod.rs index 53b0f0a89a..611092b87d 100644 --- a/boa_engine/src/builtins/async_function/mod.rs +++ b/boa_engine/src/builtins/async_function/mod.rs @@ -8,7 +8,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction use crate::{ - builtins::BuiltInObject, + builtins::{function::BuiltInFunctionObject, BuiltInObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, property::Attribute, symbol::JsSymbol, @@ -63,8 +63,20 @@ impl BuiltInConstructor for AsyncFunction { args: &[JsValue], context: &mut Context<'_>, ) -> JsResult { - crate::builtins::function::BuiltInFunctionObject::create_dynamic_function( - new_target, args, true, false, context, + let active_function = context.vm.active_function.clone().unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .generator_function() + .constructor() + }); + BuiltInFunctionObject::create_dynamic_function( + active_function, + new_target, + args, + true, + false, + context, ) .map(Into::into) } diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs index 0fbeb5cdba..440783146c 100644 --- a/boa_engine/src/builtins/async_generator/mod.rs +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -20,7 +20,7 @@ use crate::{ vm::GeneratorResumeKind, Context, JsArgs, JsError, JsResult, }; -use boa_gc::{Finalize, Gc, GcRefCell, Trace}; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use std::collections::VecDeque; @@ -59,7 +59,7 @@ pub struct AsyncGenerator { pub(crate) state: AsyncGeneratorState, /// The `[[AsyncGeneratorContext]]` internal slot. - pub(crate) context: Option>>, + pub(crate) context: Option, /// The `[[AsyncGeneratorQueue]]` internal slot. pub(crate) queue: VecDeque, @@ -168,7 +168,7 @@ impl AsyncGenerator { // a. Perform AsyncGeneratorResume(generator, completion). let generator_context = generator .context - .clone() + .take() .expect("generator context cannot be empty here"); drop(generator_obj_mut); @@ -176,7 +176,7 @@ impl AsyncGenerator { Self::resume( generator_object, state, - &generator_context, + generator_context, completion, context, ); @@ -252,14 +252,14 @@ impl AsyncGenerator { // a. Perform AsyncGeneratorResume(generator, completion). let generator_context = generator .context - .clone() + .take() .expect("generator context cannot be empty here"); drop(generator_obj_mut); Self::resume( generator_object, state, - &generator_context, + generator_context, completion, context, ); @@ -313,6 +313,7 @@ impl AsyncGenerator { if state == AsyncGeneratorState::SuspendedStart { // a. Set generator.[[AsyncGeneratorState]] to completed. generator.state = AsyncGeneratorState::Completed; + generator.context = None; // b. Set state to completed. state = AsyncGeneratorState::Completed; @@ -349,7 +350,7 @@ impl AsyncGenerator { if state == AsyncGeneratorState::SuspendedYield { let generator_context = generator .context - .clone() + .take() .expect("generator context cannot be empty here"); drop(generator_obj_mut); @@ -357,7 +358,7 @@ impl AsyncGenerator { Self::resume( generator_object, state, - &generator_context, + generator_context, completion, context, ); @@ -449,7 +450,7 @@ impl AsyncGenerator { pub(crate) fn resume( generator: &JsObject, state: AsyncGeneratorState, - generator_context: &Gc>, + mut generator_context: GeneratorContext, completion: (JsResult, bool), context: &mut Context<'_>, ) { @@ -460,7 +461,6 @@ impl AsyncGenerator { ); // 2. Let genContext be generator.[[AsyncGeneratorContext]]. - let mut generator_context_mut = generator_context.borrow_mut(); // 3. Let callerContext be the running execution context. // 4. Suspend callerContext. @@ -475,12 +475,19 @@ impl AsyncGenerator { // 6. Push genContext onto the execution context stack; genContext is now the running execution context. std::mem::swap( &mut context.realm.environments, - &mut generator_context_mut.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + std::mem::swap( + &mut context.vm.active_function, + &mut generator_context.active_function, + ); + std::mem::swap( + &mut context.realm.intrinsics, + &mut generator_context.realm_intrinsics, ); - std::mem::swap(&mut context.vm.stack, &mut generator_context_mut.stack); - context - .vm - .push_frame(generator_context_mut.call_frame.clone()); + + context.vm.push_frame(generator_context.call_frame.clone()); // 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation. match completion { @@ -498,18 +505,28 @@ impl AsyncGenerator { context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; } } - drop(generator_context_mut); let result = context.run(); - let mut generator_context_mut = generator_context.borrow_mut(); std::mem::swap( &mut context.realm.environments, - &mut generator_context_mut.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + generator_context.call_frame = context.vm.pop_frame().expect("generator frame must exist"); + std::mem::swap( + &mut context.vm.active_function, + &mut generator_context.active_function, ); - std::mem::swap(&mut context.vm.stack, &mut generator_context_mut.stack); - generator_context_mut.call_frame = - context.vm.pop_frame().expect("generator frame must exist"); - drop(generator_context_mut); + std::mem::swap( + &mut context.realm.intrinsics, + &mut generator_context.realm_intrinsics, + ); + + generator + .borrow_mut() + .as_async_generator_mut() + .expect("already checked before") + .context = Some(generator_context); // 8. Assert: result is never an abrupt completion. assert!(!result.is_throw_completion()); @@ -555,6 +572,7 @@ impl AsyncGenerator { .as_async_generator_mut() .expect("already checked before"); gen.state = AsyncGeneratorState::Completed; + gen.context = None; let next = gen.queue.pop_front().expect("queue must not be empty"); drop(generator_borrow_mut); Self::complete_step(&next, Err(value), true, context); @@ -577,6 +595,7 @@ impl AsyncGenerator { // a. Set generator.[[AsyncGeneratorState]] to completed. gen.state = AsyncGeneratorState::Completed; + gen.context = None; gen.queue.pop_front().expect("must have one entry") }; @@ -613,6 +632,7 @@ impl AsyncGenerator { // a. Set generator.[[AsyncGeneratorState]] to completed. gen.state = AsyncGeneratorState::Completed; + gen.context = None; // b. Let result be ThrowCompletion(reason). let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone())); diff --git a/boa_engine/src/builtins/async_generator_function/mod.rs b/boa_engine/src/builtins/async_generator_function/mod.rs index e4d50e088a..d108ac3e60 100644 --- a/boa_engine/src/builtins/async_generator_function/mod.rs +++ b/boa_engine/src/builtins/async_generator_function/mod.rs @@ -68,7 +68,21 @@ impl BuiltInConstructor for AsyncGeneratorFunction { args: &[JsValue], context: &mut Context<'_>, ) -> JsResult { - BuiltInFunctionObject::create_dynamic_function(new_target, args, true, true, context) - .map(Into::into) + let active_function = context.vm.active_function.clone().unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .generator_function() + .constructor() + }); + BuiltInFunctionObject::create_dynamic_function( + active_function, + new_target, + args, + true, + true, + context, + ) + .map(Into::into) } } diff --git a/boa_engine/src/builtins/error/aggregate.rs b/boa_engine/src/builtins/error/aggregate.rs index becc22f015..de71c6311d 100644 --- a/boa_engine/src/builtins/error/aggregate.rs +++ b/boa_engine/src/builtins/error/aggregate.rs @@ -60,6 +60,22 @@ impl BuiltInConstructor for AggregateError { context: &mut Context<'_>, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .aggregate_error() + .constructor() + }) + .into() + } else { + new_target.clone() + }; // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor( new_target, diff --git a/boa_engine/src/builtins/error/eval.rs b/boa_engine/src/builtins/error/eval.rs index cd1581609d..04b331d78f 100644 --- a/boa_engine/src/builtins/error/eval.rs +++ b/boa_engine/src/builtins/error/eval.rs @@ -62,6 +62,22 @@ impl BuiltInConstructor for EvalError { context: &mut Context<'_>, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .eval_error() + .constructor() + }) + .into() + } else { + new_target.clone() + }; // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::eval_error, context)?; diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index 6c11588e38..b8ab2ea321 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -162,6 +162,16 @@ impl BuiltInConstructor for Error { context: &mut Context<'_>, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| context.intrinsics().constructors().error().constructor()) + .into() + } else { + new_target.clone() + }; // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »). let prototype = diff --git a/boa_engine/src/builtins/error/range.rs b/boa_engine/src/builtins/error/range.rs index e6618cf37b..cddca0ac93 100644 --- a/boa_engine/src/builtins/error/range.rs +++ b/boa_engine/src/builtins/error/range.rs @@ -60,6 +60,22 @@ impl BuiltInConstructor for RangeError { context: &mut Context<'_>, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .range_error() + .constructor() + }) + .into() + } else { + new_target.clone() + }; // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::range_error, context)?; diff --git a/boa_engine/src/builtins/error/reference.rs b/boa_engine/src/builtins/error/reference.rs index 710983db04..aef5e9fe98 100644 --- a/boa_engine/src/builtins/error/reference.rs +++ b/boa_engine/src/builtins/error/reference.rs @@ -59,6 +59,22 @@ impl BuiltInConstructor for ReferenceError { context: &mut Context<'_>, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .reference_error() + .constructor() + }) + .into() + } else { + new_target.clone() + }; // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor( new_target, diff --git a/boa_engine/src/builtins/error/syntax.rs b/boa_engine/src/builtins/error/syntax.rs index 70811c4fca..f093d92553 100644 --- a/boa_engine/src/builtins/error/syntax.rs +++ b/boa_engine/src/builtins/error/syntax.rs @@ -62,6 +62,22 @@ impl BuiltInConstructor for SyntaxError { context: &mut Context<'_>, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .syntax_error() + .constructor() + }) + .into() + } else { + new_target.clone() + }; // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor( new_target, diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 6a82717bf2..a18073d8b5 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -17,7 +17,8 @@ use crate::{ builtins::{ - function::Function, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + function::{Function, FunctionKind}, + BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, @@ -70,6 +71,22 @@ impl BuiltInConstructor for TypeError { context: &mut Context<'_>, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .type_error() + .constructor() + }) + .into() + } else { + new_target.clone() + }; // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::type_error, context)?; @@ -115,10 +132,13 @@ impl IntrinsicObject for ThrowTypeError { let mut obj = obj.borrow_mut(); - obj.data = ObjectData::function(Function::Native { - function: NativeFunction::from_fn_ptr(throw_type_error), - constructor: None, - }); + obj.data = ObjectData::function(Function::new( + FunctionKind::Native { + function: NativeFunction::from_fn_ptr(throw_type_error), + constructor: None, + }, + intrinsics.clone(), + )); } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/builtins/error/uri.rs b/boa_engine/src/builtins/error/uri.rs index 180a368d35..0605e9fbc5 100644 --- a/boa_engine/src/builtins/error/uri.rs +++ b/boa_engine/src/builtins/error/uri.rs @@ -61,6 +61,22 @@ impl BuiltInConstructor for UriError { context: &mut Context<'_>, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .uri_error() + .constructor() + }) + .into() + } else { + new_target.clone() + }; // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::uri_error, context)?; diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index ca45f2206e..56e0a566ce 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -25,6 +25,7 @@ use crate::{ string::utf16, symbol::JsSymbol, value::IntegerOrInfinity, + vm::CodeBlock, Context, JsArgs, JsResult, JsString, JsValue, }; use boa_ast::{ @@ -142,14 +143,8 @@ unsafe impl Trace for ClassFieldDefinition { }} } -/// Boa representation of a Function Object. -/// -/// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code -/// (AST Node). -/// -/// #[derive(Finalize)] -pub enum Function { +pub(crate) enum FunctionKind { /// A rust function. Native { /// The rust function. @@ -161,7 +156,7 @@ pub enum Function { /// A bytecode function. Ordinary { /// The code block containing the compiled function. - code: Gc, + code: Gc, /// The `[[Environment]]` internal slot. environments: DeclarativeEnvironmentStack, @@ -185,7 +180,7 @@ pub enum Function { /// A bytecode async function. Async { /// The code block containing the compiled function. - code: Gc, + code: Gc, /// The `[[Environment]]` internal slot. environments: DeclarativeEnvironmentStack, @@ -203,7 +198,7 @@ pub enum Function { /// A bytecode generator function. Generator { /// The code block containing the compiled function. - code: Gc, + code: Gc, /// The `[[Environment]]` internal slot. environments: DeclarativeEnvironmentStack, @@ -218,7 +213,7 @@ pub enum Function { /// A bytecode async generator function. AsyncGenerator { /// The code block containing the compiled function. - code: Gc, + code: Gc, /// The `[[Environment]]` internal slot. environments: DeclarativeEnvironmentStack, @@ -231,7 +226,34 @@ pub enum Function { }, } -unsafe impl Trace for Function { +impl fmt::Debug for FunctionKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FunctionKind::Native { + function, + constructor, + } => f + .debug_struct("FunctionKind::Native") + .field("function", &function) + .field("constructor", &constructor) + .finish(), + FunctionKind::Ordinary { .. } => f + .debug_struct("FunctionKind::Ordinary") + .finish_non_exhaustive(), + FunctionKind::Async { .. } => f + .debug_struct("FunctionKind::Async") + .finish_non_exhaustive(), + FunctionKind::Generator { .. } => f + .debug_struct("FunctionKind::Generator") + .finish_non_exhaustive(), + FunctionKind::AsyncGenerator { .. } => f + .debug_struct("FunctionKind::AsyncGenerator") + .finish_non_exhaustive(), + } + } +} + +unsafe impl Trace for FunctionKind { custom_trace! {this, { match this { Self::Native { function, .. } => {mark(function)} @@ -265,27 +287,54 @@ unsafe impl Trace for Function { }} } -impl fmt::Debug for Function { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Function {{ ... }}") - } +/// Boa representation of a Function Object. +/// +/// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code +/// (AST Node). +/// +/// +#[derive(Debug, Trace, Finalize)] +pub struct Function { + kind: FunctionKind, + realm_intrinsics: Intrinsics, } impl Function { /// Returns true if the function object is a constructor. pub fn is_constructor(&self) -> bool { - match self { - Self::Native { constructor, .. } => constructor.is_some(), - Self::Generator { .. } | Self::AsyncGenerator { .. } | Self::Async { .. } => false, - Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical), + match &self.kind { + FunctionKind::Native { constructor, .. } => constructor.is_some(), + FunctionKind::Generator { .. } + | FunctionKind::AsyncGenerator { .. } + | FunctionKind::Async { .. } => false, + FunctionKind::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical), + } + } + + /// Returns the codeblock of the function, or `None` if the function is a [`NativeFunction`]. + pub fn codeblock(&self) -> Option<&CodeBlock> { + match &self.kind { + FunctionKind::Native { .. } => None, + FunctionKind::Ordinary { code, .. } + | FunctionKind::Async { code, .. } + | FunctionKind::Generator { code, .. } + | FunctionKind::AsyncGenerator { code, .. } => Some(code), + } + } + + /// Creates a new `Function`. + pub(crate) fn new(kind: FunctionKind, intrinsics: Intrinsics) -> Self { + Self { + kind, + realm_intrinsics: intrinsics, } } /// Returns true if the function object is a derived constructor. pub(crate) const fn is_derived_constructor(&self) -> bool { - if let Self::Ordinary { + if let FunctionKind::Ordinary { constructor_kind, .. - } = self + } = self.kind { constructor_kind.is_derived() } else { @@ -295,7 +344,7 @@ impl Function { /// Returns the `[[ClassFieldInitializerName]]` internal slot of the function. pub(crate) fn class_field_initializer_name(&self) -> Option { - if let Self::Ordinary { code, .. } = self { + if let FunctionKind::Ordinary { code, .. } = &self.kind { code.class_field_initializer_name } else { None @@ -304,29 +353,29 @@ impl Function { /// Returns a reference to the function `[[HomeObject]]` slot if present. pub(crate) const fn get_home_object(&self) -> Option<&JsObject> { - match self { - Self::Ordinary { home_object, .. } - | Self::Async { home_object, .. } - | Self::Generator { home_object, .. } - | Self::AsyncGenerator { home_object, .. } => home_object.as_ref(), - Self::Native { .. } => None, + match &self.kind { + FunctionKind::Ordinary { home_object, .. } + | FunctionKind::Async { home_object, .. } + | FunctionKind::Generator { home_object, .. } + | FunctionKind::AsyncGenerator { home_object, .. } => home_object.as_ref(), + FunctionKind::Native { .. } => None, } } /// Sets the `[[HomeObject]]` slot if present. pub(crate) fn set_home_object(&mut self, object: JsObject) { - match self { - Self::Ordinary { home_object, .. } - | Self::Async { home_object, .. } - | Self::Generator { home_object, .. } - | Self::AsyncGenerator { home_object, .. } => *home_object = Some(object), - Self::Native { .. } => {} + match &mut self.kind { + FunctionKind::Ordinary { home_object, .. } + | FunctionKind::Async { home_object, .. } + | FunctionKind::Generator { home_object, .. } + | FunctionKind::AsyncGenerator { home_object, .. } => *home_object = Some(object), + FunctionKind::Native { .. } => {} } } /// Returns the values of the `[[Fields]]` internal slot. pub(crate) fn get_fields(&self) -> &[ClassFieldDefinition] { - if let Self::Ordinary { fields, .. } = self { + if let FunctionKind::Ordinary { fields, .. } = &self.kind { fields } else { &[] @@ -335,23 +384,23 @@ impl Function { /// Pushes a value to the `[[Fields]]` internal slot if present. pub(crate) fn push_field(&mut self, key: PropertyKey, value: JsFunction) { - if let Self::Ordinary { fields, .. } = self { + if let FunctionKind::Ordinary { fields, .. } = &mut self.kind { fields.push(ClassFieldDefinition::Public(key, value)); } } /// Pushes a private value to the `[[Fields]]` internal slot if present. pub(crate) fn push_field_private(&mut self, key: PrivateName, value: JsFunction) { - if let Self::Ordinary { fields, .. } = self { + if let FunctionKind::Ordinary { fields, .. } = &mut self.kind { fields.push(ClassFieldDefinition::Private(key, value)); } } /// Returns the values of the `[[PrivateMethods]]` internal slot. pub(crate) fn get_private_methods(&self) -> &[(PrivateName, PrivateElement)] { - if let Self::Ordinary { + if let FunctionKind::Ordinary { private_methods, .. - } = self + } = &self.kind { private_methods } else { @@ -361,9 +410,9 @@ impl Function { /// Pushes a private method to the `[[PrivateMethods]]` internal slot if present. pub(crate) fn push_private_method(&mut self, name: PrivateName, method: PrivateElement) { - if let Self::Ordinary { + if let FunctionKind::Ordinary { private_methods, .. - } = self + } = &mut self.kind { private_methods.push((name, method)); } @@ -371,9 +420,9 @@ impl Function { /// Returns the promise capability if the function is an async function. pub(crate) const fn get_promise_capability(&self) -> Option<&PromiseCapability> { - if let Self::Async { + if let FunctionKind::Async { promise_capability, .. - } = self + } = &self.kind { Some(promise_capability) } else { @@ -383,14 +432,29 @@ impl Function { /// Sets the class object. pub(crate) fn set_class_object(&mut self, object: JsObject) { - match self { - Self::Ordinary { class_object, .. } - | Self::Async { class_object, .. } - | Self::Generator { class_object, .. } - | Self::AsyncGenerator { class_object, .. } => *class_object = Some(object), - Self::Native { .. } => {} + match &mut self.kind { + FunctionKind::Ordinary { class_object, .. } + | FunctionKind::Async { class_object, .. } + | FunctionKind::Generator { class_object, .. } + | FunctionKind::AsyncGenerator { class_object, .. } => *class_object = Some(object), + FunctionKind::Native { .. } => {} } } + + /// Gets the `Realm`'s intrinsic objects from where this function originates. + pub const fn realm_intrinsics(&self) -> &Intrinsics { + &self.realm_intrinsics + } + + /// Gets a reference to the [`FunctionKind`] of the `Function`. + pub(crate) const fn kind(&self) -> &FunctionKind { + &self.kind + } + + /// Gets a mutable reference to the [`FunctionKind`] of the `Function`. + pub(crate) fn kind_mut(&mut self) -> &mut FunctionKind { + &mut self.kind + } } /// The internal representation of a `Function` object. @@ -467,7 +531,13 @@ impl BuiltInConstructor for BuiltInFunctionObject { args: &[JsValue], context: &mut Context<'_>, ) -> JsResult { - Self::create_dynamic_function(new_target, args, false, false, context).map(Into::into) + let active_function = context + .vm + .active_function + .clone() + .unwrap_or_else(|| context.intrinsics().constructors().function().constructor()); + Self::create_dynamic_function(active_function, new_target, args, false, false, context) + .map(Into::into) } } @@ -479,23 +549,62 @@ impl BuiltInFunctionObject { /// /// [spec]: https://tc39.es/ecma262/#sec-createdynamicfunction pub(crate) fn create_dynamic_function( + constructor: JsObject, new_target: &JsValue, args: &[JsValue], r#async: bool, generator: bool, context: &mut Context<'_>, ) -> JsResult { + // 1. Let currentRealm be the current Realm Record. + // 2. Perform ? HostEnsureCanCompileStrings(currentRealm). + context.host_hooks().ensure_can_compile_strings(context)?; + + // 3. If newTarget is undefined, set newTarget to constructor. + let new_target = if new_target.is_undefined() { + constructor.into() + } else { + new_target.clone() + }; + let default = if r#async && generator { + // 7. Else, + // a. Assert: kind is asyncGenerator. + // b. Let prefix be "async function*". + // c. Let exprSym be the grammar symbol AsyncGeneratorExpression. + // d. Let bodySym be the grammar symbol AsyncGeneratorBody. + // e. Let parameterSym be the grammar symbol FormalParameters[+Yield, +Await]. + // f. Let fallbackProto be "%AsyncGeneratorFunction.prototype%". StandardConstructors::async_generator_function } else if r#async { + // 6. Else if kind is async, then + // a. Let prefix be "async function". + // b. Let exprSym be the grammar symbol AsyncFunctionExpression. + // c. Let bodySym be the grammar symbol AsyncFunctionBody. + // d. Let parameterSym be the grammar symbol FormalParameters[~Yield, +Await]. + // e. Let fallbackProto be "%AsyncFunction.prototype%". StandardConstructors::async_function } else if generator { + // 5. Else if kind is generator, then + // a. Let prefix be "function*". + // b. Let exprSym be the grammar symbol GeneratorExpression. + // c. Let bodySym be the grammar symbol GeneratorBody. + // d. Let parameterSym be the grammar symbol FormalParameters[+Yield, ~Await]. + // e. Let fallbackProto be "%GeneratorFunction.prototype%". StandardConstructors::generator_function } else { + // 4. If kind is normal, then + // a. Let prefix be "function". + // b. Let exprSym be the grammar symbol FunctionExpression. + // c. Let bodySym be the grammar symbol FunctionBody[~Yield, ~Await]. + // d. Let parameterSym be the grammar symbol FormalParameters[~Yield, ~Await]. + // e. Let fallbackProto be "%Function.prototype%". StandardConstructors::function }; - let prototype = get_prototype_from_constructor(new_target, default, context)?; + // 22. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto). + let prototype = get_prototype_from_constructor(&new_target, default, context)?; + if let Some((body_arg, args)) = args.split_last() { let parameters = if args.is_empty() { FormalParameterList::default() @@ -627,7 +736,13 @@ impl BuiltInFunctionObject { let environments = context.realm.environments.pop_to_global(); let function_object = if generator { - crate::vm::create_generator_function_object(code, r#async, false, context) + crate::vm::create_generator_function_object( + code, + r#async, + false, + Some(prototype), + context, + ) } else { crate::vm::create_function_object( code, @@ -653,8 +768,13 @@ impl BuiltInFunctionObject { ); let environments = context.realm.environments.pop_to_global(); - let function_object = - crate::vm::create_generator_function_object(code, r#async, false, context); + let function_object = crate::vm::create_generator_function_object( + code, + r#async, + false, + Some(prototype), + context, + ); context.realm.environments.extend(environments); Ok(function_object) @@ -854,17 +974,17 @@ impl BuiltInFunctionObject { .filter(|n| !n.is_empty()) .unwrap_or_else(|| "anonymous".into()); - match function { - Function::Native { .. } | Function::Ordinary { .. } => { + match function.kind { + FunctionKind::Native { .. } | FunctionKind::Ordinary { .. } => { Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into()) } - Function::Async { .. } => { + FunctionKind::Async { .. } => { Ok(js_string!(utf16!("[AsyncFunction: "), &name, utf16!("]")).into()) } - Function::Generator { .. } => { + FunctionKind::Generator { .. } => { Ok(js_string!(utf16!("[GeneratorFunction: "), &name, utf16!("]")).into()) } - Function::AsyncGenerator { .. } => { + FunctionKind::AsyncGenerator { .. } => { Ok(js_string!(utf16!("[AsyncGeneratorFunction: "), &name, utf16!("]")).into()) } } diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index f7a5cf10eb..9aece8f73f 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -133,7 +133,7 @@ fn closure_capture_clone() { run_test_actions([ TestAction::inspect_context(|ctx| { let string = js_string!("Hello"); - let object = JsObject::with_object_proto(ctx); + let object = JsObject::with_object_proto(ctx.intrinsics()); object .define_property_or_throw( "key", @@ -170,7 +170,8 @@ fn closure_capture_clone() { .name("closure") .build(); - ctx.register_global_property("closure", func, Attribute::default()); + ctx.register_global_property("closure", func, Attribute::default()) + .unwrap(); }), TestAction::assert_eq("closure()", "Hello world!"), ]); diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index 41eba9ec93..ebf5ccaea1 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -21,7 +21,7 @@ use crate::{ vm::{CallFrame, CompletionRecord, GeneratorResumeKind}, Context, JsArgs, JsError, JsResult, }; -use boa_gc::{Finalize, Gc, GcRefCell, Trace}; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use super::{BuiltInBuilder, IntrinsicObject}; @@ -45,17 +45,19 @@ pub(crate) struct GeneratorContext { pub(crate) environments: DeclarativeEnvironmentStack, pub(crate) call_frame: CallFrame, pub(crate) stack: Vec, + pub(crate) active_function: Option, + pub(crate) realm_intrinsics: Intrinsics, } /// The internal representation of a `Generator` object. -#[derive(Debug, Clone, Finalize, Trace)] +#[derive(Debug, Finalize, Trace)] pub struct Generator { /// The `[[GeneratorState]]` internal slot. #[unsafe_ignore_trace] pub(crate) state: GeneratorState, /// The `[[GeneratorContext]]` internal slot. - pub(crate) context: Option>>, + pub(crate) context: Option, } impl IntrinsicObject for Generator { @@ -208,11 +210,10 @@ impl Generator { generator.state = GeneratorState::Executing; let first_execution = matches!(state, GeneratorState::SuspendedStart); - let generator_context_cell = generator + let mut generator_context = generator .context .take() .expect("generator context cannot be empty here"); - let mut generator_context = generator_context_cell.borrow_mut(); drop(generator_obj_mut); std::mem::swap( @@ -220,6 +221,14 @@ impl Generator { &mut generator_context.environments, ); std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + std::mem::swap( + &mut context.vm.active_function, + &mut generator_context.active_function, + ); + std::mem::swap( + &mut context.realm.intrinsics, + &mut generator_context.realm_intrinsics, + ); context.vm.push_frame(generator_context.call_frame.clone()); if !first_execution { context.vm.push(value.clone()); @@ -238,6 +247,14 @@ impl Generator { &mut generator_context.environments, ); std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + std::mem::swap( + &mut context.vm.active_function, + &mut generator_context.active_function, + ); + std::mem::swap( + &mut context.realm.intrinsics, + &mut generator_context.realm_intrinsics, + ); let mut generator_obj_mut = generator_obj.borrow_mut(); let generator = generator_obj_mut @@ -247,8 +264,7 @@ impl Generator { match record { CompletionRecord::Return(value) => { generator.state = GeneratorState::SuspendedYield; - drop(generator_context); - generator.context = Some(generator_context_cell); + generator.context = Some(generator_context); Ok(create_iter_result_object(value, false, context)) } CompletionRecord::Normal(value) => { @@ -323,11 +339,10 @@ impl Generator { // 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation. // 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. // 12. Return Completion(result). - let generator_context_cell = generator + let mut generator_context = generator .context .take() .expect("generator context cannot be empty here"); - let mut generator_context = generator_context_cell.borrow_mut(); generator.state = GeneratorState::Executing; drop(generator_obj_mut); @@ -337,6 +352,14 @@ impl Generator { &mut generator_context.environments, ); std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + std::mem::swap( + &mut context.vm.active_function, + &mut generator_context.active_function, + ); + std::mem::swap( + &mut context.realm.intrinsics, + &mut generator_context.realm_intrinsics, + ); context.vm.push_frame(generator_context.call_frame.clone()); let completion_record = match abrupt_completion { @@ -361,6 +384,14 @@ impl Generator { &mut generator_context.environments, ); std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + std::mem::swap( + &mut context.vm.active_function, + &mut generator_context.active_function, + ); + std::mem::swap( + &mut context.realm.intrinsics, + &mut generator_context.realm_intrinsics, + ); let mut generator_obj_mut = generator_obj.borrow_mut(); let generator = generator_obj_mut @@ -370,8 +401,7 @@ impl Generator { match completion_record { CompletionRecord::Return(value) => { generator.state = GeneratorState::SuspendedYield; - drop(generator_context); - generator.context = Some(generator_context_cell); + generator.context = Some(generator_context); Ok(create_iter_result_object(value, false, context)) } CompletionRecord::Normal(value) => { diff --git a/boa_engine/src/builtins/generator_function/mod.rs b/boa_engine/src/builtins/generator_function/mod.rs index 15d48636b3..68d393c451 100644 --- a/boa_engine/src/builtins/generator_function/mod.rs +++ b/boa_engine/src/builtins/generator_function/mod.rs @@ -73,7 +73,21 @@ impl BuiltInConstructor for GeneratorFunction { args: &[JsValue], context: &mut Context<'_>, ) -> JsResult { - BuiltInFunctionObject::create_dynamic_function(new_target, args, false, true, context) - .map(Into::into) + let active_function = context.vm.active_function.clone().unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .generator_function() + .constructor() + }); + BuiltInFunctionObject::create_dynamic_function( + active_function, + new_target, + args, + false, + true, + context, + ) + .map(Into::into) } } diff --git a/boa_engine/src/builtins/intl/collator/mod.rs b/boa_engine/src/builtins/intl/collator/mod.rs index 5af7149d1b..64dfff821b 100644 --- a/boa_engine/src/builtins/intl/collator/mod.rs +++ b/boa_engine/src/builtins/intl/collator/mod.rs @@ -214,6 +214,16 @@ impl BuiltInConstructor for Collator { context: &mut Context<'_>, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| context.intrinsics().constructors().collator().constructor()) + .into() + } else { + new_target.clone() + }; // 2. Let internalSlotsList be « [[InitializedCollator]], [[Locale]], [[Usage]], [[Sensitivity]], [[IgnorePunctuation]], [[Collation]], [[BoundCompare]] ». // 3. If %Collator%.[[RelevantExtensionKeys]] contains "kn", then // a. Append [[Numeric]] as the last element of internalSlotsList. diff --git a/boa_engine/src/builtins/intl/date_time_format.rs b/boa_engine/src/builtins/intl/date_time_format.rs index ca38623e9a..87772b0f85 100644 --- a/boa_engine/src/builtins/intl/date_time_format.rs +++ b/boa_engine/src/builtins/intl/date_time_format.rs @@ -96,6 +96,22 @@ impl BuiltInConstructor for DateTimeFormat { context: &mut Context<'_>, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .vm + .active_function + .clone() + .unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .date_time_format() + .constructor() + }) + .into() + } else { + new_target.clone() + }; let prototype = get_prototype_from_constructor( new_target, StandardConstructors::date_time_format, diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index c7e23bc4a5..28a5e96c4c 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -39,7 +39,7 @@ macro_rules! if_abrupt_close_iterator { pub(crate) use if_abrupt_close_iterator; /// The built-in iterator prototypes. -#[derive(Debug, Default)] +#[derive(Debug, Default, Trace, Finalize)] pub struct IteratorPrototypes { /// The `IteratorPrototype` object. iterator: JsObject, @@ -186,7 +186,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 = JsObject::with_object_proto(context); + let obj = JsObject::with_object_proto(context.intrinsics()); // 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value). obj.create_data_property_or_throw(utf16!("value"), value, context) diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index d3faf2cd78..f5145d301b 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -212,7 +212,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 = JsObject::with_object_proto(context); + let root = JsObject::with_object_proto(context.intrinsics()); // b. Let rootName be the empty String. // c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered). @@ -444,7 +444,7 @@ impl Json { }; // 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%). - let wrapper = JsObject::with_object_proto(context); + let wrapper = JsObject::with_object_proto(context.intrinsics()); // 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value). wrapper diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 0d4191d6ed..0be9b38bf8 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -168,7 +168,7 @@ fn global_binding(context: &mut Context<'_>) -> JsResult<()> { let name = B::NAME; let attr = B::ATTRIBUTE; let intrinsic = B::get(context.intrinsics()); - let global_object = context.global_object().clone(); + let global_object = context.global_object(); global_object.define_property_or_throw( name, @@ -281,7 +281,7 @@ impl Intrinsics { /// /// [spec]: https://tc39.es/ecma262/#sec-setdefaultglobalbindings pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult<()> { - let global_object = context.global_object().clone(); + let global_object = context.global_object(); global_object.define_property_or_throw( utf16!("globalThis"), @@ -376,7 +376,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult #[cfg(feature = "console")] { let object = Console::init(context); - let global_object = context.global_object().clone(); + let global_object = context.global_object(); global_object.define_property_or_throw( utf16!("console"), PropertyDescriptor::builder() @@ -429,6 +429,7 @@ struct Callable { name: JsString, length: usize, kind: Kind, + intrinsics: Intrinsics, } /// Marker for an ordinary object. @@ -477,10 +478,13 @@ impl ApplyToObject for Callable { fn apply_to(self, object: &JsObject) { self.kind.apply_to(object); - let function = function::Function::Native { - function: NativeFunction::from_fn_ptr(self.function), - constructor: S::IS_CONSTRUCTOR.then_some(function::ConstructorKind::Base), - }; + let function = function::Function::new( + function::FunctionKind::Native { + function: NativeFunction::from_fn_ptr(self.function), + constructor: S::IS_CONSTRUCTOR.then_some(function::ConstructorKind::Base), + }, + self.intrinsics, + ); let length = PropertyDescriptor::builder() .value(self.length) @@ -566,6 +570,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { name: js_string!(""), length: 0, kind: OrdinaryFunction, + intrinsics: self.intrinsics.clone(), }, prototype: self.intrinsics.constructors().function().prototype(), } @@ -589,6 +594,7 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable> { inherits: Some(intrinsics.constructors().object().prototype()), attributes: Attribute::WRITABLE | Attribute::CONFIGURABLE, }, + intrinsics: intrinsics.clone(), }, prototype: intrinsics.constructors().function().prototype(), } @@ -603,6 +609,7 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable> { name: self.kind.name, length: self.kind.length, kind: ConstructorNoProto, + intrinsics: self.intrinsics.clone(), }, prototype: self.prototype, } diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index daa35e7696..151803fc95 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -130,9 +130,13 @@ impl BuiltInConstructor for Object { ) -> JsResult { // 1. If NewTarget is neither undefined nor the active function object, then if !new_target.is_undefined() - && !new_target.as_object().map_or(false, |o| { - o.eq(&context.intrinsics().constructors().object().constructor()) - }) + && new_target + != &context + .vm + .active_function + .clone() + .unwrap_or_else(|| context.intrinsics().constructors().object().constructor()) + .into() { // a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%"). let prototype = @@ -145,7 +149,7 @@ impl BuiltInConstructor for Object { // 2. If value is undefined or null, return OrdinaryObjectCreate(%Object.prototype%). if value.is_null_or_undefined() { - Ok(JsObject::with_object_proto(context).into()) + Ok(JsObject::with_object_proto(context.intrinsics()).into()) } else { // 3. Return ! ToObject(value). value.to_object(context).map(JsValue::from) @@ -486,7 +490,7 @@ impl Object { let own_keys = obj.__own_property_keys__(context)?; // 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%). - let descriptors = JsObject::with_object_proto(context); + let descriptors = JsObject::with_object_proto(context.intrinsics()); // 4. For each element key of ownKeys, do for key in own_keys { @@ -525,7 +529,7 @@ impl Object { // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 3. Assert: obj is an extensible ordinary object with no own properties. - let obj = JsObject::with_object_proto(context); + let obj = JsObject::with_object_proto(context.intrinsics()); // 4. If Desc has a [[Value]] field, then if let Some(value) = desc.value() { @@ -1270,7 +1274,7 @@ impl Object { // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 3. Assert: obj is an extensible ordinary object with no own properties. - let obj = JsObject::with_object_proto(context); + let obj = JsObject::with_object_proto(context.intrinsics()); // 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 6a1a25cde7..1785868458 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -105,25 +105,6 @@ pub struct ResolvingFunctions { pub reject: JsFunction, } -/// The internal `PromiseCapability` data type. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-promisecapability-records -#[derive(Debug, Clone, Trace, Finalize)] -// TODO: make crate-only -pub struct PromiseCapability { - /// The `[[Promise]]` field. - promise: JsObject, - - /// The `[[Resolve]]` field. - resolve: JsFunction, - - /// The `[[Reject]]` field. - reject: JsFunction, -} - // ==================== Private API ==================== /// `IfAbruptRejectPromise ( value, capability )` @@ -156,6 +137,24 @@ macro_rules! if_abrupt_reject_promise { pub(crate) use if_abrupt_reject_promise; +/// The internal `PromiseCapability` data type. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-promisecapability-records +#[derive(Debug, Clone, Trace, Finalize)] +pub(crate) struct PromiseCapability { + /// The `[[Promise]]` field. + promise: JsObject, + + /// The `[[Resolve]]` field. + resolve: JsFunction, + + /// The `[[Reject]]` field. + reject: JsFunction, +} + /// The internal `PromiseReaction` data type. /// /// More information: @@ -869,7 +868,7 @@ impl Promise { // 8. Let remainingElementsCount be F.[[RemainingElements]]. // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = JsObject::with_object_proto(context); + let obj = JsObject::with_object_proto(context.intrinsics()); // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled"). obj.create_data_property_or_throw(utf16!("status"), "fulfilled", context) @@ -955,7 +954,7 @@ impl Promise { // 8. Let remainingElementsCount be F.[[RemainingElements]]. // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = JsObject::with_object_proto(context); + let obj = JsObject::with_object_proto(context.intrinsics()); // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected"). obj.create_data_property_or_throw(utf16!("status"), "rejected", context) @@ -2302,12 +2301,14 @@ fn new_promise_reaction_job(mut reaction: ReactionRecord, argument: JsValue) -> } }; + // TODO: handle realms // 2. Let handlerRealm be null. // 3. If reaction.[[Handler]] is not empty, then // a. Let getHandlerRealmResult be Completion(GetFunctionRealm(reaction.[[Handler]].[[Callback]])). // b. If getHandlerRealmResult is a normal completion, set handlerRealm to getHandlerRealmResult.[[Value]]. // c. Else, set handlerRealm to the current Realm Record. // d. NOTE: handlerRealm is never null unless the handler is undefined. When the handler is a revoked Proxy and no ECMAScript code runs, handlerRealm is used to create error objects. + // 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }. NativeJob::new(job) } @@ -2350,10 +2351,12 @@ fn new_promise_resolve_thenable_job( then_call_result }; + // TODO: handle realms // 2. Let getThenRealmResult be Completion(GetFunctionRealm(then.[[Callback]])). // 3. If getThenRealmResult is a normal completion, let thenRealm be getThenRealmResult.[[Value]]. // 4. Else, let thenRealm be the current Realm Record. // 5. NOTE: thenRealm is never null. When then.[[Callback]] is a revoked Proxy and no code runs, thenRealm is used to create error objects. + // 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }. NativeJob::new(job) } diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 197dbb3a3f..e34d3a4f2e 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -10,8 +10,6 @@ //! [spec]: https://tc39.es/ecma262/#sec-proxy-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy -use std::cell::Cell; - use crate::{ builtins::BuiltInObject, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -21,7 +19,7 @@ use crate::{ string::utf16, Context, JsArgs, JsResult, JsValue, }; -use boa_gc::{Finalize, Trace}; +use boa_gc::{Finalize, GcRefCell, Trace}; use boa_profiler::Profiler; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; @@ -150,7 +148,7 @@ impl Proxy { // a. Let F be the active function object. // b. Let p be F.[[RevocableProxy]]. // d. Set F.[[RevocableProxy]] to null. - if let Some(p) = revocable_proxy.take() { + if let Some(p) = std::mem::take(&mut *revocable_proxy.borrow_mut()) { // e. Assert: p is a Proxy object. // f. Set p.[[ProxyTarget]] to null. // g. Set p.[[ProxyHandler]] to null. @@ -164,7 +162,7 @@ impl Proxy { // h. Return undefined. Ok(JsValue::undefined()) }, - Cell::new(Some(proxy)), + GcRefCell::new(Some(proxy)), ), ) .build() @@ -184,7 +182,7 @@ impl Proxy { let revoker = Self::revoker(p.clone(), context); // 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%). - let result = JsObject::with_object_proto(context); + let result = JsObject::with_object_proto(context.intrinsics()); // 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 8f70a93fcf..77bdbe8c95 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -327,10 +327,7 @@ impl RegExp { /// [spec]: https://tc39.es/ecma262/#sec-regexpcreate pub(crate) fn create(p: &JsValue, f: &JsValue, context: &mut Context<'_>) -> JsResult { // 1. Let obj be ? RegExpAlloc(%RegExp%). - let obj = Self::alloc( - &context.global_object().clone().get(Self::NAME, context)?, - context, - )?; + let obj = Self::alloc(&context.global_object().get(Self::NAME, context)?, context)?; // 2. Return ? RegExpInitialize(obj, P, F). Self::initialize(obj, p, f, context) diff --git a/boa_engine/src/builtins/uri/mod.rs b/boa_engine/src/builtins/uri/mod.rs index e8350e044b..1144140d6e 100644 --- a/boa_engine/src/builtins/uri/mod.rs +++ b/boa_engine/src/builtins/uri/mod.rs @@ -12,6 +12,8 @@ mod consts; +use boa_gc::{Finalize, Trace}; + use self::consts::{ is_uri_reserved_or_number_sign, is_uri_reserved_or_uri_unescaped_or_number_sign, is_uri_unescaped, @@ -29,7 +31,7 @@ use crate::{ /// Intrinsics for the [`URI Handling Functions`][spec]. /// /// [spec]: https://tc39.es/ecma262/multipage/global-object.html#sec-uri-handling-functions -#[derive(Debug)] +#[derive(Debug, Trace, Finalize)] pub struct UriFunctions { /// %decodeURI% decode_uri: JsFunction, diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index 8654190958..2d3f64d196 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -121,7 +121,7 @@ impl ClassConstructor for T { .into()); } - let class = context.global_object().clone().get(T::NAME, context)?; + let class = context.global_object().get(T::NAME, context)?; let JsValue::Object(ref class_constructor) = class else { return Err(JsNativeError::typ() .with_message(format!( @@ -175,7 +175,7 @@ impl<'ctx, 'host> ClassBuilder<'ctx, 'host> { Self { builder } } - pub(crate) fn build(mut self) -> JsFunction { + pub(crate) fn build(self) -> JsFunction { JsFunction::from_object_unchecked(self.builder.build().into()) } diff --git a/boa_engine/src/context/hooks.rs b/boa_engine/src/context/hooks.rs index 46c887f11e..ac7f912d97 100644 --- a/boa_engine/src/context/hooks.rs +++ b/boa_engine/src/context/hooks.rs @@ -1,7 +1,7 @@ use crate::{ builtins::promise::OperationType, job::JobCallback, - object::{JsFunction, JsObject, ObjectData}, + object::{JsFunction, JsObject}, Context, JsResult, JsValue, }; @@ -149,10 +149,7 @@ pub trait HostHooks { /// /// [ihdr]: https://tc39.es/ecma262/#sec-initializehostdefinedrealm fn create_global_object(&self, intrinsics: &Intrinsics) -> JsObject { - JsObject::from_proto_and_data( - intrinsics.constructors().object().prototype(), - ObjectData::global(), - ) + JsObject::with_object_proto(intrinsics) } /// Creates the global `this` of a new [`Context`] from the initial intrinsics. diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index c5a00d53fb..218b4cfe4c 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -1,13 +1,38 @@ //! Data structures that contain intrinsic objects and constructors. +use boa_gc::{Finalize, Gc, Trace}; + use crate::{ builtins::{iterable::IteratorPrototypes, uri::UriFunctions}, object::{JsFunction, JsObject, ObjectData}, }; /// The intrinsic objects and constructors. -#[derive(Debug, Default)] +/// +/// `Intrinsics` is internally stored using a `Gc`, which makes it cheapily clonable +/// for multiple references to the same set of intrinsic objects. +#[derive(Default, Clone, Trace, Finalize)] pub struct Intrinsics { + inner: Gc, +} + +impl PartialEq for Intrinsics { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(&*self.inner, &*other.inner) + } +} + +impl std::fmt::Debug for Intrinsics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Intrinsics") + .field("constructors", self.constructors()) + .field("objects", self.objects()) + .finish() + } +} + +#[derive(Default, Trace, Finalize)] +struct Inner { /// Cached standard constructors pub(super) constructors: StandardConstructors, /// Cached intrinsic objects @@ -17,19 +42,19 @@ pub struct Intrinsics { impl Intrinsics { /// Return the cached intrinsic objects. #[inline] - pub const fn objects(&self) -> &IntrinsicObjects { - &self.objects + pub fn objects(&self) -> &IntrinsicObjects { + &self.inner.objects } /// Return the cached standard constructors. #[inline] - pub const fn constructors(&self) -> &StandardConstructors { - &self.constructors + pub fn constructors(&self) -> &StandardConstructors { + &self.inner.constructors } } /// Store a builtin constructor (such as `Object`) and its corresponding prototype. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize)] pub struct StandardConstructor { pub(crate) constructor: JsObject, pub(crate) prototype: JsObject, @@ -71,7 +96,7 @@ impl StandardConstructor { } /// Cached core standard constructors. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize)] pub struct StandardConstructors { object: StandardConstructor, proxy: StandardConstructor, @@ -720,7 +745,7 @@ impl StandardConstructors { } /// Cached intrinsic objects -#[derive(Debug)] +#[derive(Debug, Trace, Finalize)] pub struct IntrinsicObjects { /// [`%Reflect%`](https://tc39.es/ecma262/#sec-reflect) reflect: JsObject, diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 2e92181dfc..04b3ec1f1a 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -22,7 +22,7 @@ use crate::{ class::{Class, ClassBuilder}, job::{IdleJobQueue, JobQueue, NativeJob}, native_function::NativeFunction, - object::{FunctionObjectBuilder, GlobalPropertyMap, JsObject}, + object::{FunctionObjectBuilder, JsObject}, optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, @@ -304,20 +304,26 @@ impl Context<'_> { /// .build(); /// context.register_global_property("myObjectProperty", object, Attribute::all()); /// ``` - pub fn register_global_property(&mut self, key: K, value: V, attribute: Attribute) + pub fn register_global_property( + &mut self, + key: K, + value: V, + attribute: Attribute, + ) -> JsResult<()> where K: Into, V: Into, { - self.realm.global_property_map.insert( - &key.into(), + self.global_object().define_property_or_throw( + key, PropertyDescriptor::builder() .value(value) .writable(attribute.writable()) .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()) - .build(), - ); + .configurable(attribute.configurable()), + self, + )?; + Ok(()) } /// Register a global native callable. @@ -332,22 +338,28 @@ impl Context<'_> { /// /// If you wish to only create the function object without binding it to the global object, you /// can use the [`FunctionObjectBuilder`] API. - pub fn register_global_callable(&mut self, name: &str, length: usize, body: NativeFunction) { + pub fn register_global_callable( + &mut self, + name: &str, + length: usize, + body: NativeFunction, + ) -> JsResult<()> { let function = FunctionObjectBuilder::new(self, body) .name(name) .length(length) .constructor(true) .build(); - self.global_bindings_mut().insert( - name.into(), + self.global_object().define_property_or_throw( + name, PropertyDescriptor::builder() .value(function) .writable(true) .enumerable(false) - .configurable(true) - .build(), - ); + .configurable(true), + self, + )?; + Ok(()) } /// Register a global native function that is not a constructor. @@ -364,22 +376,23 @@ impl Context<'_> { name: &str, length: usize, body: NativeFunction, - ) { + ) -> JsResult<()> { let function = FunctionObjectBuilder::new(self, body) .name(name) .length(length) .constructor(false) .build(); - self.global_bindings_mut().insert( - name.into(), + self.global_object().define_property_or_throw( + name, PropertyDescriptor::builder() .value(function) .writable(true) .enumerable(false) - .configurable(true) - .build(), - ); + .configurable(true), + self, + )?; + Ok(()) } /// Register a global class of type `T`, where `T` implements `Class`. @@ -407,10 +420,11 @@ impl Context<'_> { .value(class) .writable(T::ATTRIBUTES.writable()) .enumerable(T::ATTRIBUTES.enumerable()) - .configurable(T::ATTRIBUTES.configurable()) - .build(); + .configurable(T::ATTRIBUTES.configurable()); + + self.global_object() + .define_property_or_throw(T::NAME, property, self)?; - self.global_bindings_mut().insert(T::NAME.into(), property); Ok(()) } @@ -426,13 +440,13 @@ impl Context<'_> { &mut self.interner } - /// Return the global object. + /// Returns the global object. #[inline] - pub const fn global_object(&self) -> &JsObject { - self.realm.global_object() + pub fn global_object(&self) -> JsObject { + self.realm.global_object().clone() } - /// Return the intrinsic constructors and objects. + /// Returns the intrinsic constructors and objects. #[inline] pub const fn intrinsics(&self) -> &Intrinsics { &self.realm.intrinsics @@ -485,11 +499,6 @@ impl Context<'_> { // ==== Private API ==== impl Context<'_> { - /// 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(crate) fn compile_json_parse(&mut self, statement_list: &StatementList) -> Gc { let _timer = Profiler::global().start_event("Compilation", "Main"); diff --git a/boa_engine/src/environments/compile.rs b/boa_engine/src/environments/compile.rs index 5b7725295b..49b2ec9fea 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -289,13 +289,14 @@ impl Context<'_> { .interner() .resolve_expect(name.sym()) .into_common::(false); - let desc = self - .realm - .global_property_map - .string_property_map() - .get(&name_str); - if desc.is_none() { - self.global_bindings_mut().insert( + + // TODO: defer global initialization to execution time. + if !self + .global_object() + .has_own_property(name_str.clone(), self) + .unwrap_or_default() + { + self.global_object().borrow_mut().insert( name_str, PropertyDescriptor::builder() .value(JsValue::Undefined) diff --git a/boa_engine/src/environments/runtime.rs b/boa_engine/src/environments/runtime.rs index 211d9e04a2..5647a1df97 100644 --- a/boa_engine/src/environments/runtime.rs +++ b/boa_engine/src/environments/runtime.rs @@ -222,7 +222,7 @@ impl DeclarativeEnvironment { /// Environments themselves are garbage collected, /// because they must be preserved for function calls. #[derive(Clone, Debug, Trace, Finalize)] -pub struct DeclarativeEnvironmentStack { +pub(crate) struct DeclarativeEnvironmentStack { stack: Vec, } @@ -1085,7 +1085,7 @@ impl Context<'_> { /// Delete a binding form an object environment if it exists. /// /// Returns a tuple of `(found, deleted)`. - pub(crate) fn delete_binding_from_objet_environment( + pub(crate) fn delete_binding_from_object_environment( &mut self, name: Identifier, ) -> JsResult<(bool, bool)> { diff --git a/boa_engine/src/object/builtins/jsproxy.rs b/boa_engine/src/object/builtins/jsproxy.rs index ba840f20fd..266c3485da 100644 --- a/boa_engine/src/object/builtins/jsproxy.rs +++ b/boa_engine/src/object/builtins/jsproxy.rs @@ -373,7 +373,7 @@ impl JsProxyBuilder { /// inside Rust code. #[must_use] pub fn build(self, context: &mut Context<'_>) -> JsProxy { - let handler = JsObject::with_object_proto(context); + let handler = JsObject::with_object_proto(context.intrinsics()); if let Some(apply) = self.apply { let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(apply)) diff --git a/boa_engine/src/object/internal_methods/global.rs b/boa_engine/src/object/internal_methods/global.rs deleted file mode 100644 index 41be3c3d05..0000000000 --- a/boa_engine/src/object/internal_methods/global.rs +++ /dev/null @@ -1,431 +0,0 @@ -use crate::{ - object::{InternalObjectMethods, JsObject, ORDINARY_INTERNAL_METHODS}, - property::{PropertyDescriptor, PropertyKey}, - value::JsValue, - Context, JsResult, -}; -use boa_profiler::Profiler; - -/// Definitions of the internal object methods for global object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-global-object -pub(crate) static GLOBAL_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { - __get_own_property__: global_get_own_property, - __define_own_property__: global_define_own_property, - __set__: global_set, - __delete__: global_delete, - __own_property_keys__: global_own_property_keys, - ..ORDINARY_INTERNAL_METHODS -}; - -/// Abstract operation `OrdinaryGetOwnProperty`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetownproperty -#[allow(clippy::unnecessary_wraps)] -pub(crate) fn global_get_own_property( - _obj: &JsObject, - key: &PropertyKey, - context: &mut Context<'_>, -) -> JsResult> { - let _timer = Profiler::global().start_event("Object::global_get_own_property", "object"); - // 1. Assert: IsPropertyKey(P) is true. - // 2. If O does not have an own property with key P, return undefined. - // 3. Let D be a newly created Property Descriptor with no fields. - // 4. Let X be O's own property whose key is P. - // 5. If X is a data property, then - // a. Set D.[[Value]] to the value of X's [[Value]] attribute. - // b. Set D.[[Writable]] to the value of X's [[Writable]] attribute. - // 6. Else, - // a. Assert: X is an accessor property. - // b. Set D.[[Get]] to the value of X's [[Get]] attribute. - // c. Set D.[[Set]] to the value of X's [[Set]] attribute. - // 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute. - // 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute. - // 9. Return D. - Ok(context.realm.global_property_map.get(key)) -} - -/// Abstract operation `OrdinaryDefineOwnProperty`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty -#[allow(clippy::needless_pass_by_value)] -pub(crate) fn global_define_own_property( - obj: &JsObject, - key: &PropertyKey, - desc: PropertyDescriptor, - context: &mut Context<'_>, -) -> JsResult { - let _timer = Profiler::global().start_event("Object::global_define_own_property", "object"); - // 1. Let current be ? O.[[GetOwnProperty]](P). - let current = global_get_own_property(obj, key, context)?; - - // 2. Let extensible be ? IsExtensible(O). - let extensible = obj.__is_extensible__(context)?; - - // 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current). - Ok(validate_and_apply_property_descriptor( - key, extensible, desc, current, context, - )) -} - -/// Abstract operation `OrdinarySet`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ordinaryset -#[allow(clippy::needless_pass_by_value)] -pub(crate) fn global_set( - _obj: &JsObject, - key: PropertyKey, - value: JsValue, - _receiver: JsValue, - context: &mut Context<'_>, -) -> JsResult { - global_set_no_receiver(&key, value, context) -} - -pub(crate) fn global_set_no_receiver( - key: &PropertyKey, - value: JsValue, - context: &mut Context<'_>, -) -> JsResult { - let _timer = Profiler::global().start_event("Object::global_set", "object"); - - // 1. Assert: IsPropertyKey(P) is true. - // 2. Let ownDesc be ? O.[[GetOwnProperty]](P). - // 3. Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc). - - // OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc ) - // https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ordinarysetwithowndescriptor - - // 1. Assert: IsPropertyKey(P) is true. - let own_desc = if let Some(desc) = context.realm.global_property_map.get(key) { - desc - } - // c. Else, - else { - PropertyDescriptor::builder() - .value(value.clone()) - .writable(true) - .enumerable(true) - .configurable(true) - .build() - }; - - // 3. If IsDataDescriptor(ownDesc) is true, then - if own_desc.is_data_descriptor() { - // a. If ownDesc.[[Writable]] is false, return false. - if !own_desc.expect_writable() { - return Ok(false); - } - - // c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P). - // d. If existingDescriptor is not undefined, then - let desc = if let Some(existing_desc) = context.realm.global_property_map.get(key) { - // i. If IsAccessorDescriptor(existingDescriptor) is true, return false. - if existing_desc.is_accessor_descriptor() { - return Ok(false); - } - - // ii. If existingDescriptor.[[Writable]] is false, return false. - if !existing_desc.expect_writable() { - return Ok(false); - } - - // iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }. - // iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc). - PropertyDescriptor::builder().value(value).build() - } else { - // i. Assert: Receiver does not currently have a property P. - // ii. Return ? CreateDataProperty(Receiver, P, V). - PropertyDescriptor::builder() - .value(value) - .writable(true) - .enumerable(true) - .configurable(true) - .build() - }; - - // 1. Let current be ? O.[[GetOwnProperty]](P). - let current = context.realm.global_property_map.get(key); - - // 2. Let extensible be ? IsExtensible(O). - let extensible = context.global_object().clone().is_extensible(context)?; - - // 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current). - return Ok(validate_and_apply_property_descriptor( - key, extensible, desc, current, context, - )); - } - - // 4. Assert: IsAccessorDescriptor(ownDesc) is true. - debug_assert!(own_desc.is_accessor_descriptor()); - - // 5. Let setter be ownDesc.[[Set]]. - match own_desc.set() { - Some(set) if !set.is_undefined() => { - // 7. Perform ? Call(setter, Receiver, « V »). - set.call(&context.global_object().clone().into(), &[value], context)?; - - // 8. Return true. - Ok(true) - } - // 6. If setter is undefined, return false. - _ => Ok(false), - } -} - -/// Abstract operation `OrdinaryDelete`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ordinarydelete -#[allow(clippy::unnecessary_wraps, clippy::needless_pass_by_value)] -pub(crate) fn global_delete( - _obj: &JsObject, - key: &PropertyKey, - context: &mut Context<'_>, -) -> JsResult { - global_delete_no_receiver(key, context) -} - -/// Abstract operation `OrdinaryDelete`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ordinarydelete -#[allow(clippy::unnecessary_wraps)] -pub(crate) fn global_delete_no_receiver( - key: &PropertyKey, - context: &mut Context<'_>, -) -> JsResult { - let _timer = Profiler::global().start_event("Object::global_delete", "object"); - // 1. Assert: IsPropertyKey(P) is true. - // 2. Let desc be ? O.[[GetOwnProperty]](P). - match context.realm.global_property_map.get(key) { - // 4. If desc.[[Configurable]] is true, then - Some(desc) if desc.expect_configurable() => { - // a. Remove the own property with name P from O. - context.realm.global_property_map.remove(key); - // b. Return true. - Ok(true) - } - // 5. Return false. - Some(_) => Ok(false), - // 3. If desc is undefined, return true. - None => Ok(true), - } -} - -/// Abstract operation `OrdinaryOwnPropertyKeys`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ordinaryownpropertykeys -#[allow(clippy::unnecessary_wraps)] -pub(crate) fn global_own_property_keys( - _: &JsObject, - context: &mut Context<'_>, -) -> JsResult> { - // 1. Let keys be a new empty List. - let mut keys = Vec::new(); - - let ordered_indexes = { - let mut indexes: Vec<_> = context - .realm - .global_property_map - .index_property_keys() - .collect(); - indexes.sort_unstable(); - indexes - }; - - // 2. For each own property key P of O such that P is an array index, in ascending numeric index order, do - // a. Add P as the last element of keys. - keys.extend(ordered_indexes.into_iter().map(Into::into)); - - // 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend( - context - .realm - .global_property_map - .string_property_keys() - .cloned() - .map(Into::into), - ); - - // 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend( - context - .realm - .global_property_map - .symbol_property_keys() - .cloned() - .map(Into::into), - ); - - // 5. Return keys. - Ok(keys) -} - -/// Abstract operation `ValidateAndApplyPropertyDescriptor` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor -pub(crate) fn validate_and_apply_property_descriptor( - key: &PropertyKey, - extensible: bool, - desc: PropertyDescriptor, - current: Option, - context: &mut Context<'_>, -) -> bool { - let _timer = Profiler::global().start_event( - "Object::global_validate_and_apply_property_descriptor", - "object", - ); - // 1. Assert: If O is not undefined, then IsPropertyKey(P) is true. - - let Some(mut current) = current else { - // 2. If current is undefined, then - // a. If extensible is false, return false. - if !extensible { - return false; - } - - // b. Assert: extensible is true. - context.realm.global_property_map.insert( - key, - // c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then - if desc.is_generic_descriptor() || desc.is_data_descriptor() { - // i. If O is not undefined, create an own data property named P of - // object O whose [[Value]], [[Writable]], [[Enumerable]], and - // [[Configurable]] attribute values are described by Desc. - // If the value of an attribute field of Desc is absent, the attribute - // of the newly created property is set to its default value. - desc.into_data_defaulted() - } - // d. Else, - else { - // i. Assert: ! IsAccessorDescriptor(Desc) is true. - - // ii. If O is not undefined, create an own accessor property named P - // of object O whose [[Get]], [[Set]], [[Enumerable]], and [[Configurable]] - // attribute values are described by Desc. If the value of an attribute field - // of Desc is absent, the attribute of the newly created property is set to - // its default value. - desc.into_accessor_defaulted() - }, - ); - - // e. Return true. - return true; - }; - - // 3. If every field in Desc is absent, return true. - if desc.is_empty() { - return true; - } - - // 4. If current.[[Configurable]] is false, then - if !current.expect_configurable() { - // a. If Desc.[[Configurable]] is present and its value is true, return false. - if matches!(desc.configurable(), Some(true)) { - return false; - } - - // b. If Desc.[[Enumerable]] is present and ! SameValue(Desc.[[Enumerable]], current.[[Enumerable]]) - // is false, return false. - if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable()) - { - return false; - } - } - - // 5. If ! IsGenericDescriptor(Desc) is true, then - if desc.is_generic_descriptor() { - // a. NOTE: No further validation is required. - } - // 6. Else if ! SameValue(! IsDataDescriptor(current), ! IsDataDescriptor(Desc)) is false, then - else if current.is_data_descriptor() != desc.is_data_descriptor() { - // a. If current.[[Configurable]] is false, return false. - if !current.expect_configurable() { - return false; - } - - // b. If IsDataDescriptor(current) is true, then - if current.is_data_descriptor() { - // i. If O is not undefined, convert the property named P of object O from a data - // property to an accessor property. Preserve the existing values of the converted - // property's [[Configurable]] and [[Enumerable]] attributes and set the rest of - // the property's attributes to their default values. - current = current.into_accessor_defaulted(); - } - // c. Else, - else { - // i. If O is not undefined, convert the property named P of object O from an - // accessor property to a data property. Preserve the existing values of the - // converted property's [[Configurable]] and [[Enumerable]] attributes and set - // the rest of the property's attributes to their default values. - current = current.into_data_defaulted(); - } - } - // 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then - else if current.is_data_descriptor() && desc.is_data_descriptor() { - // a. If current.[[Configurable]] is false and current.[[Writable]] is false, then - if !current.expect_configurable() && !current.expect_writable() { - // i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, return false. - if matches!(desc.writable(), Some(true)) { - return false; - } - // ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false. - if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value())) - { - return false; - } - // iii. Return true. - return true; - } - } - // 8. Else, - // a. Assert: ! IsAccessorDescriptor(current) and ! IsAccessorDescriptor(Desc) are both true. - // b. If current.[[Configurable]] is false, then - else if !current.expect_configurable() { - // i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], current.[[Set]]) is false, return false. - if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) { - return false; - } - - // ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], current.[[Get]]) is false, return false. - if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) { - return false; - } - // iii. Return true. - return true; - } - - // 9. If O is not undefined, then - // a. For each field of Desc that is present, set the corresponding attribute of the - // property named P of object O to the value of the field. - current.fill_with(&desc); - context.realm.global_property_map.insert(key, current); - - // 10. Return true. - true -} diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index 00279675f6..885816abc8 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/boa_engine/src/object/internal_methods/mod.rs @@ -19,7 +19,6 @@ pub(super) mod arguments; pub(super) mod array; pub(super) mod bound_function; pub(super) mod function; -pub(crate) mod global; pub(super) mod integer_indexed; pub(super) mod proxy; pub(super) mod string; @@ -232,6 +231,7 @@ impl JsObject { context: &mut Context<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::__call__", "object"); + let func = self.borrow().data.internal_methods.__call__; func.expect("called `[[Call]]` for object without a `[[Call]]` internal method")( self, this, args, context, @@ -254,6 +254,7 @@ impl JsObject { context: &mut Context<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::__construct__", "object"); + let func = self.borrow().data.internal_methods.__construct__; func.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")( self, args, new_target, context, @@ -927,14 +928,16 @@ where // The corresponding object must be an intrinsic that is intended to be used // as the [[Prototype]] value of an object. // 2. Let proto be ? Get(constructor, "prototype"). - if let Some(object) = constructor.as_object() { - if let Some(proto) = object.get(PROTOTYPE, context)?.as_object() { + let intrinsics = if let Some(constructor) = constructor.as_object() { + if let Some(proto) = constructor.get(PROTOTYPE, context)?.as_object() { return Ok(proto.clone()); } - } - // 3. If Type(proto) is not Object, then - // TODO: handle realms - // a. Let realm be ? GetFunctionRealm(constructor). - // b. Set proto to realm's intrinsic object named intrinsicDefaultProto. - Ok(default(context.intrinsics().constructors()).prototype()) + // 3. If Type(proto) is not Object, then + // a. Let realm be ? GetFunctionRealm(constructor). + // b. Set proto to realm's intrinsic object named intrinsicDefaultProto. + constructor.get_function_realm(context)? + } else { + context.intrinsics().clone() + }; + Ok(default(intrinsics.constructors()).prototype()) } diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index ca4f497a2f..ddc67a370c 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -4,6 +4,7 @@ use super::{JsPrototype, NativeObject, Object, PropertyMap}; use crate::{ + context::intrinsics::Intrinsics, error::JsNativeError, object::{ObjectData, ObjectKind}, property::{PropertyDescriptor, PropertyKey}, @@ -42,9 +43,9 @@ impl JsObject { /// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate #[inline] #[must_use] - pub fn with_object_proto(context: &mut Context<'_>) -> Self { + pub fn with_object_proto(intrinsics: &Intrinsics) -> Self { Self::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), + intrinsics.constructors().object().prototype(), ObjectData::ordinary(), ) } diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index dd862edbdf..a624fdd90f 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -15,7 +15,6 @@ use self::internal_methods::{ BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS, BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS, }, function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS}, - global::GLOBAL_INTERNAL_METHODS, integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, proxy::{ PROXY_EXOTIC_INTERNAL_METHODS_ALL, PROXY_EXOTIC_INTERNAL_METHODS_BASIC, @@ -34,7 +33,7 @@ use crate::{ array_buffer::ArrayBuffer, async_generator::AsyncGenerator, error::ErrorKind, - function::arguments::Arguments, + function::{arguments::Arguments, FunctionKind}, function::{arguments::ParameterMap, BoundFunction, ConstructorKind, Function}, generator::Generator, iterable::AsyncFromSyncIterator, @@ -609,15 +608,6 @@ impl ObjectData { } } - /// Create the `Global` object data - #[must_use] - pub fn global() -> Self { - Self { - kind: ObjectKind::Global, - internal_methods: &GLOBAL_INTERNAL_METHODS, - } - } - /// Create the `Arguments` object data pub fn arguments(arguments: Arguments) -> Self { Self { @@ -1948,7 +1938,8 @@ where #[derive(Debug)] pub struct FunctionObjectBuilder<'ctx, 'host> { context: &'ctx mut Context<'host>, - function: Function, + function: NativeFunction, + constructor: Option, name: JsString, length: usize, } @@ -1959,10 +1950,8 @@ impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> { pub fn new(context: &'ctx mut Context<'host>, function: NativeFunction) -> Self { Self { context, - function: Function::Native { - function, - constructor: None, - }, + function, + constructor: None, name: js_string!(), length: 0, } @@ -1997,20 +1986,7 @@ impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> { /// The default is `false`. #[must_use] pub fn constructor(mut self, yes: bool) -> Self { - match self.function { - Function::Native { - ref mut constructor, - .. - } => { - *constructor = yes.then_some(ConstructorKind::Base); - } - Function::Ordinary { .. } - | Function::Generator { .. } - | Function::AsyncGenerator { .. } - | Function::Async { .. } => { - unreachable!("function must be native or closure"); - } - } + self.constructor = yes.then_some(ConstructorKind::Base); self } @@ -2022,7 +1998,13 @@ impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> { .constructors() .function() .prototype(), - ObjectData::function(self.function), + ObjectData::function(Function::new( + FunctionKind::Native { + function: self.function, + constructor: self.constructor, + }, + self.context.intrinsics().clone(), + )), ); let property = PropertyDescriptor::builder() .writable(false) @@ -2073,7 +2055,7 @@ impl<'ctx, 'host> ObjectInitializer<'ctx, 'host> { /// Create a new `ObjectBuilder`. #[inline] pub fn new(context: &'ctx mut Context<'host>) -> Self { - let object = JsObject::with_object_proto(context); + let object = JsObject::with_object_proto(context.intrinsics()); Self { context, object } } @@ -2410,12 +2392,15 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> { } /// Build the constructor function object. - pub fn build(&mut self) -> JsFunction { + pub fn build(mut self) -> JsFunction { // Create the native function - let function = Function::Native { - function: self.function.clone(), - constructor: self.constructor, - }; + let function = Function::new( + FunctionKind::Native { + function: self.function, + constructor: self.constructor, + }, + self.context.intrinsics().clone(), + ); let length = PropertyDescriptor::builder() .value(self.length) @@ -2482,6 +2467,6 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> { } } - JsFunction::from_object_unchecked(self.object.clone()) + JsFunction::from_object_unchecked(self.object) } } diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index a12cece877..8ce4e3e5e6 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -1,6 +1,6 @@ use crate::{ builtins::{function::ClassFieldDefinition, Array}, - context::intrinsics::{StandardConstructor, StandardConstructors}, + context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, object::{JsObject, PrivateElement, PROTOTYPE}, property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind}, @@ -10,7 +10,7 @@ use crate::{ }; use boa_ast::function::PrivateName; -use super::CONSTRUCTOR; +use super::{JsFunction, CONSTRUCTOR}; /// Object integrity level. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -256,7 +256,7 @@ impl JsObject { // 4. If success is false, throw a TypeError exception. if !success { return Err(JsNativeError::typ() - .with_message(format!("cannot delete property: {key}")) + .with_message(format!("cannot delete non-configurable property: {key}")) .into()); } // 5. Return success. @@ -316,11 +316,11 @@ impl JsObject { ) -> JsResult { // 1. If argumentsList is not present, set argumentsList to a new empty List. // 2. If IsCallable(F) is false, throw a TypeError exception. - if !self.is_callable() { - return Err(JsNativeError::typ().with_message("not a function").into()); - } + let function = JsFunction::from_object(self.clone()) + .ok_or_else(|| JsNativeError::typ().with_message("not a function"))?; + // 3. Return ? F.[[Call]](V, argumentsList). - self.__call__(this, args, context) + function.__call__(this, args, context) } /// `Construct ( F [ , argumentsList [ , newTarget ] ] )` @@ -655,7 +655,29 @@ impl JsObject { Ok(false) } - // todo: GetFunctionRealm + /// Abstract operation [`GetFunctionRealm`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-getfunctionrealm + pub(crate) fn get_function_realm(&self, context: &mut Context<'_>) -> JsResult { + let constructor = self.borrow(); + if let Some(fun) = constructor.as_function() { + return Ok(fun.realm_intrinsics().clone()); + } + + if let Some(bound) = constructor.as_bound_function() { + let fun = bound.target_function().clone(); + drop(constructor); + return fun.get_function_realm(context); + } + + if let Some(proxy) = constructor.as_proxy() { + let (fun, _) = proxy.try_data()?; + drop(constructor); + return fun.get_function_realm(context); + } + + Ok(context.intrinsics().clone()) + } // todo: CopyDataProperties diff --git a/boa_engine/src/object/property_map.rs b/boa_engine/src/object/property_map.rs index d9923f8ef1..228f07a3ab 100644 --- a/boa_engine/src/object/property_map.rs +++ b/boa_engine/src/object/property_map.rs @@ -6,10 +6,6 @@ use rustc_hash::{FxHashMap, FxHasher}; use std::{collections::hash_map, hash::BuildHasherDefault, iter::FusedIterator}; use thin_vec::ThinVec; -/// Type alias to make it easier to work with the string properties on the global object. -pub(crate) type GlobalPropertyMap = - IndexMap>; - /// Wrapper around `indexmap::IndexMap` for usage in `PropertyMap`. #[derive(Debug, Finalize)] struct OrderedHashMap(IndexMap>); @@ -414,14 +410,6 @@ impl PropertyMap { PropertyKey::Symbol(symbol) => self.symbol_properties.0.contains_key(symbol), } } - - pub(crate) const fn string_property_map(&self) -> &GlobalPropertyMap { - &self.string_properties.0 - } - - pub(crate) fn string_property_map_mut(&mut self) -> &mut GlobalPropertyMap { - &mut self.string_properties.0 - } } /// An iterator over the property entries of an `Object` diff --git a/boa_engine/src/realm.rs b/boa_engine/src/realm.rs index e76c323cbf..c5d5f6f4d3 100644 --- a/boa_engine/src/realm.rs +++ b/boa_engine/src/realm.rs @@ -9,7 +9,7 @@ use crate::{ context::{intrinsics::Intrinsics, HostHooks}, environments::{CompileTimeEnvironment, DeclarativeEnvironmentStack}, - object::{GlobalPropertyMap, JsObject, PropertyMap}, + object::JsObject, }; use boa_gc::{Gc, GcRefCell}; use boa_profiler::Profiler; @@ -20,7 +20,6 @@ use boa_profiler::Profiler; #[derive(Debug)] pub struct Realm { pub(crate) intrinsics: Intrinsics, - pub(crate) global_property_map: PropertyMap, pub(crate) environments: DeclarativeEnvironmentStack, pub(crate) compile_env: Gc>, global_object: JsObject, @@ -43,12 +42,10 @@ impl Realm { let global_compile_environment = Gc::new(GcRefCell::new(CompileTimeEnvironment::new_global())); - #[allow(unreachable_code)] Self { intrinsics, global_object, global_this, - global_property_map: PropertyMap::default(), environments: DeclarativeEnvironmentStack::new(global_compile_environment.clone()), compile_env: global_compile_environment, } @@ -62,10 +59,6 @@ impl Realm { &self.global_this } - pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap { - self.global_property_map.string_property_map_mut() - } - /// Set the number of bindings on the global environment. pub(crate) fn set_global_binding_number(&mut self) { let binding_number = self.compile_env.borrow().num_bindings(); diff --git a/boa_engine/src/value/conversions/serde_json.rs b/boa_engine/src/value/conversions/serde_json.rs index 153c05c268..ae5c1e75df 100644 --- a/boa_engine/src/value/conversions/serde_json.rs +++ b/boa_engine/src/value/conversions/serde_json.rs @@ -64,7 +64,7 @@ impl JsValue { Ok(Array::create_array_from_list(arr, context).into()) } Value::Object(obj) => { - let js_obj = JsObject::with_object_proto(context); + let js_obj = JsObject::with_object_proto(context.intrinsics()); 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 6e404e04d0..823edfc2ff 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() { run_test_actions([TestAction::assert_context(|ctx| { - let obj = &JsObject::with_object_proto(ctx); + let obj = &JsObject::with_object_proto(ctx.intrinsics()); // Create string and convert it to a Value let s = JsValue::new("bar"); obj.set("foo", s, false, ctx).unwrap(); diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index b99aebacb6..26c5b5f39d 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -5,7 +5,7 @@ use crate::{ builtins::{ async_generator::{AsyncGenerator, AsyncGeneratorState}, - function::{arguments::Arguments, ConstructorKind, Function, ThisMode}, + function::{arguments::Arguments, ConstructorKind, Function, FunctionKind, ThisMode}, generator::{Generator, GeneratorContext, GeneratorState}, promise::PromiseCapability, }, @@ -595,23 +595,29 @@ pub(crate) fn create_function_object( ) .expect("cannot fail per spec"); - Function::Async { - code, - environments: context.realm.environments.clone(), - home_object: None, - promise_capability, - class_object: None, - } + Function::new( + FunctionKind::Async { + code, + environments: context.realm.environments.clone(), + home_object: None, + promise_capability, + class_object: None, + }, + context.intrinsics().clone(), + ) } else { - Function::Ordinary { - code, - environments: context.realm.environments.clone(), - constructor_kind: ConstructorKind::Base, - home_object: None, - fields: ThinVec::new(), - private_methods: ThinVec::new(), - class_object: None, - } + Function::new( + FunctionKind::Ordinary { + code, + environments: context.realm.environments.clone(), + constructor_kind: ConstructorKind::Base, + home_object: None, + fields: ThinVec::new(), + private_methods: ThinVec::new(), + class_object: None, + }, + context.intrinsics().clone(), + ) }; let constructor = @@ -632,7 +638,7 @@ pub(crate) fn create_function_object( .expect("failed to define the name property of the function"); if !r#async && !arrow && !method { - let prototype = JsObject::with_object_proto(context); + let prototype = JsObject::with_object_proto(context.intrinsics()); prototype .define_property_or_throw(CONSTRUCTOR, constructor_property, context) .expect("failed to define the constructor property of the function"); @@ -656,9 +662,12 @@ pub(crate) fn create_generator_function_object( code: Gc, r#async: bool, method: bool, + prototype: Option, context: &mut Context<'_>, ) -> JsObject { - let function_prototype = if r#async { + let function_prototype = if let Some(prototype) = prototype { + prototype + } else if r#async { context .intrinsics() .constructors() @@ -701,23 +710,29 @@ pub(crate) fn create_generator_function_object( ); let constructor = if r#async { - let function = Function::AsyncGenerator { - code, - environments: context.realm.environments.clone(), - home_object: None, - class_object: None, - }; + let function = Function::new( + FunctionKind::AsyncGenerator { + code, + environments: context.realm.environments.clone(), + home_object: None, + class_object: None, + }, + context.intrinsics().clone(), + ); JsObject::from_proto_and_data( function_prototype, ObjectData::async_generator_function(function), ) } else { - let function = Function::Generator { - code, - environments: context.realm.environments.clone(), - home_object: None, - class_object: None, - }; + let function = Function::new( + FunctionKind::Generator { + code, + environments: context.realm.environments.clone(), + home_object: None, + class_object: None, + }, + context.intrinsics().clone(), + ); JsObject::from_proto_and_data(function_prototype, ObjectData::generator_function(function)) }; @@ -757,12 +772,18 @@ impl JsObject { .with_message("not a callable function") .into()); } + let old_active = context.vm.active_function.replace(self.clone()); let object = self.borrow(); let function_object = object.as_function().expect("not a function"); - match function_object { - Function::Native { + let old_intrinsics = std::mem::replace( + &mut context.realm.intrinsics, + function_object.realm_intrinsics().clone(), + ); + + let result = match function_object.kind() { + FunctionKind::Native { function, constructor, } => { @@ -776,7 +797,7 @@ impl JsObject { function.call(this, args, context) } } - Function::Ordinary { + FunctionKind::Ordinary { code, environments, class_object, @@ -803,7 +824,7 @@ impl JsObject { } else if code.strict { Some(this.clone()) } else if this.is_null_or_undefined() { - Some(context.global_object().clone().into()) + Some(context.global_object().into()) } else { Some( this.to_object(context) @@ -902,7 +923,7 @@ impl JsObject { record.consume() } - Function::Async { + FunctionKind::Async { code, environments, promise_capability, @@ -925,7 +946,7 @@ impl JsObject { } else if code.strict { Some(this.clone()) } else if this.is_null_or_undefined() { - Some(context.global_object().clone().into()) + Some(context.global_object().into()) } else { Some( this.to_object(context) @@ -1024,7 +1045,7 @@ impl JsObject { Ok(promise.into()) } - Function::Generator { + FunctionKind::Generator { code, environments, class_object, @@ -1044,7 +1065,7 @@ impl JsObject { } else if code.strict { Some(this.clone()) } else if this.is_null_or_undefined() { - Some(context.global_object().clone().into()) + Some(context.global_object().into()) } else { Some( this.to_object(context) @@ -1150,11 +1171,13 @@ impl JsObject { prototype, ObjectData::generator(Generator { state: GeneratorState::SuspendedStart, - context: Some(Gc::new(GcRefCell::new(GeneratorContext { + context: Some(GeneratorContext { environments, call_frame, stack, - }))), + active_function: context.vm.active_function.clone(), + realm_intrinsics: context.realm.intrinsics.clone(), + }), }), ); @@ -1162,7 +1185,7 @@ impl JsObject { Ok(generator.into()) } - Function::AsyncGenerator { + FunctionKind::AsyncGenerator { code, environments, class_object, @@ -1182,7 +1205,7 @@ impl JsObject { } else if code.strict { Some(this.clone()) } else if this.is_null_or_undefined() { - Some(context.global_object().clone().into()) + Some(context.global_object().into()) } else { Some( this.to_object(context) @@ -1291,29 +1314,37 @@ impl JsObject { prototype, ObjectData::async_generator(AsyncGenerator { state: AsyncGeneratorState::SuspendedStart, - context: Some(Gc::new(GcRefCell::new(GeneratorContext { + context: Some(GeneratorContext { environments, call_frame, stack, - }))), + active_function: context.vm.active_function.clone(), + realm_intrinsics: context.realm.intrinsics.clone(), + }), queue: VecDeque::new(), }), ); { + let gen_clone = generator.clone(); let mut generator_mut = generator.borrow_mut(); let gen = generator_mut .as_async_generator_mut() .expect("must be object here"); - let mut gen_context = gen.context.as_ref().expect("must exist").borrow_mut(); - gen_context.call_frame.async_generator = Some(generator.clone()); + let gen_context = gen.context.as_mut().expect("must exist"); + gen_context.call_frame.async_generator = Some(gen_clone); } init_result.consume()?; Ok(generator.into()) } - } + }; + + context.vm.active_function = old_active; + context.realm.intrinsics = old_intrinsics; + + result } pub(crate) fn construct_internal( @@ -1336,11 +1367,18 @@ impl JsObject { .into()); } + let old_active = context.vm.active_function.replace(self.clone()); + let object = self.borrow(); let function_object = object.as_function().expect("not a function"); - match function_object { - Function::Native { + let old_intrinsics = std::mem::replace( + &mut context.realm.intrinsics, + function_object.realm_intrinsics().clone(), + ); + + let result = match function_object.kind() { + FunctionKind::Native { function, constructor, .. @@ -1364,7 +1402,7 @@ impl JsObject { } } } - Function::Ordinary { + FunctionKind::Ordinary { code, environments, constructor_kind, @@ -1509,11 +1547,15 @@ impl JsObject { .clone()) } } - Function::Generator { .. } - | Function::Async { .. } - | Function::AsyncGenerator { .. } => { + FunctionKind::Generator { .. } + | FunctionKind::Async { .. } + | FunctionKind::AsyncGenerator { .. } => { unreachable!("not a constructor") } - } + }; + context.vm.active_function = old_active; + context.realm.intrinsics = old_intrinsics; + + result } } diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 2884f8e002..f147ccfd77 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -7,7 +7,7 @@ use crate::{ builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, vm::{call_frame::EarlyReturnType, code_block::Readable}, - Context, JsError, JsResult, JsValue, + Context, JsError, JsObject, JsResult, JsValue, }; #[cfg(feature = "fuzz")] use crate::{JsNativeError, JsNativeErrorKind}; @@ -47,6 +47,7 @@ pub struct Vm { #[cfg(feature = "trace")] pub(crate) trace: bool, pub(crate) stack_size_limit: usize, + pub(crate) active_function: Option, } impl Default for Vm { @@ -58,6 +59,7 @@ impl Default for Vm { #[cfg(feature = "trace")] trace: false, stack_size_limit: 1024, + active_function: None, } } } @@ -384,6 +386,7 @@ impl Context<'_> { .expect("must be async generator"); generator.state = AsyncGeneratorState::Completed; + generator.context = None; let next = generator .queue diff --git a/boa_engine/src/vm/opcode/await_stm/mod.rs b/boa_engine/src/vm/opcode/await_stm/mod.rs index 01e568a982..c96b62b1d7 100644 --- a/boa_engine/src/vm/opcode/await_stm/mod.rs +++ b/boa_engine/src/vm/opcode/await_stm/mod.rs @@ -1,7 +1,7 @@ -use boa_gc::{Gc, GcRefCell}; +use boa_gc::GcRefCell; use crate::{ - builtins::Promise, + builtins::{generator::GeneratorContext, Promise}, native_function::NativeFunction, object::FunctionObjectBuilder, vm::{ @@ -33,14 +33,22 @@ impl Operation for Await { context, )?; + let generator_context = GeneratorContext { + environments: context.realm.environments.clone(), + call_frame: context.vm.frame().clone(), + stack: context.vm.stack.clone(), + active_function: context.vm.active_function.clone(), + realm_intrinsics: context.realm.intrinsics.clone(), + }; + // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). let on_fulfilled = FunctionObjectBuilder::new( context, NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { - let mut captures = captures.borrow_mut(); - let (environment, stack, frame) = &mut *captures; + let mut generator_context = std::mem::take(&mut *captures.borrow_mut()) + .expect("function should only be called once"); // a. Let prevContext be the running execution context. // b. Suspend prevContext. // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. @@ -48,28 +56,46 @@ impl Operation for Await { // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. // f. Return undefined. - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - context.vm.push_frame(frame.clone()); + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + std::mem::swap( + &mut context.vm.active_function, + &mut generator_context.active_function, + ); + std::mem::swap( + &mut context.realm.intrinsics, + &mut generator_context.realm_intrinsics, + ); + context.vm.push_frame(generator_context.call_frame.clone()); context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; context.vm.push(args.get_or_undefined(0).clone()); context.run(); - *frame = context + context .vm .pop_frame() .expect("generator call frame must exist"); - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + std::mem::swap( + &mut context.vm.active_function, + &mut generator_context.active_function, + ); + std::mem::swap( + &mut context.realm.intrinsics, + &mut generator_context.realm_intrinsics, + ); Ok(JsValue::undefined()) }, - Gc::new(GcRefCell::new(( - context.realm.environments.clone(), - context.vm.stack.clone(), - context.vm.frame().clone(), - ))), + GcRefCell::new(Some(generator_context.clone())), ), ) .name("") @@ -82,8 +108,8 @@ impl Operation for Await { context, NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { - let mut captures = captures.borrow_mut(); - let (environment, stack, frame) = &mut *captures; + let mut generator_context = std::mem::take(&mut *captures.borrow_mut()) + .expect("function should only be called once"); // a. Let prevContext be the running execution context. // b. Suspend prevContext. // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. @@ -91,28 +117,46 @@ impl Operation for Await { // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. // f. Return undefined. - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - context.vm.push_frame(frame.clone()); + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + std::mem::swap( + &mut context.vm.active_function, + &mut generator_context.active_function, + ); + std::mem::swap( + &mut context.realm.intrinsics, + &mut generator_context.realm_intrinsics, + ); + context.vm.push_frame(generator_context.call_frame.clone()); context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; context.vm.push(args.get_or_undefined(0).clone()); context.run(); - *frame = context + context .vm .pop_frame() .expect("generator call frame must exist"); - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + std::mem::swap( + &mut context.vm.active_function, + &mut generator_context.active_function, + ); + std::mem::swap( + &mut context.realm.intrinsics, + &mut generator_context.realm_intrinsics, + ); Ok(JsValue::undefined()) }, - Gc::new(GcRefCell::new(( - context.realm.environments.clone(), - context.vm.stack.clone(), - context.vm.frame().clone(), - ))), + GcRefCell::new(Some(generator_context)), ), ) .name("") diff --git a/boa_engine/src/vm/opcode/call/mod.rs b/boa_engine/src/vm/opcode/call/mod.rs index 969f316cef..8cb582af5a 100644 --- a/boa_engine/src/vm/opcode/call/mod.rs +++ b/boa_engine/src/vm/opcode/call/mod.rs @@ -1,5 +1,5 @@ use crate::{ - builtins::function::Function, + builtins::function::FunctionKind, error::JsNativeError, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsValue, @@ -42,7 +42,11 @@ impl Operation for CallEval { }; // A native function with the name "eval" implies, that is this the built-in eval function. - let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. })); + let eval = object + .borrow() + .as_function() + .map(|f| matches!(f.kind(), FunctionKind::Native { .. })) + .unwrap_or_default(); let strict = context.vm.frame().code_block.strict; @@ -104,7 +108,11 @@ impl Operation for CallEvalSpread { }; // A native function with the name "eval" implies, that is this the built-in eval function. - let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. })); + let eval = object + .borrow() + .as_function() + .map(|f| matches!(f.kind(), FunctionKind::Native { .. })) + .unwrap_or_default(); let strict = context.vm.frame().code_block.strict; diff --git a/boa_engine/src/vm/opcode/define/mod.rs b/boa_engine/src/vm/opcode/define/mod.rs index 30adcab066..5f0bd36719 100644 --- a/boa_engine/src/vm/opcode/define/mod.rs +++ b/boa_engine/src/vm/opcode/define/mod.rs @@ -1,5 +1,4 @@ use crate::{ - property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsString, JsValue, }; @@ -22,22 +21,12 @@ impl Operation for DefVar { const INSTRUCTION: &'static str = "INST - DefVar"; fn execute(context: &mut Context<'_>) -> JsResult { + // TODO: spec specifies to return `empty` on empty vars, but we're trying to initialize. let index = context.vm.read::(); let binding_locator = context.vm.frame().code_block.bindings[index as usize]; if binding_locator.is_global() { - let key = context - .interner() - .resolve_expect(binding_locator.name().sym()) - .into_common(false); - context.global_bindings_mut().entry(key).or_insert( - PropertyDescriptor::builder() - .value(JsValue::Undefined) - .writable(true) - .enumerable(true) - .configurable(true) - .build(), - ); + // already initialized at compile time } else { context.realm.environments.put_value_if_uninitialized( binding_locator.environment_index(), @@ -74,10 +63,12 @@ impl Operation for DefInitVar { let key = context .interner() .resolve_expect(binding_locator.name().sym()) - .into_common::(false) - .into(); - crate::object::internal_methods::global::global_set_no_receiver( - &key, value, context, + .into_common::(false); + context.global_object().set( + key, + value, + context.vm.frame().code_block.strict, + context, )?; } } else { diff --git a/boa_engine/src/vm/opcode/delete/mod.rs b/boa_engine/src/vm/opcode/delete/mod.rs index 118f56157e..72820ae353 100644 --- a/boa_engine/src/vm/opcode/delete/mod.rs +++ b/boa_engine/src/vm/opcode/delete/mod.rs @@ -86,7 +86,7 @@ impl Operation for DeleteName { .binding_in_poisoned_environment(binding_locator.name()) { let (found, deleted) = - context.delete_binding_from_objet_environment(binding_locator.name())?; + context.delete_binding_from_object_environment(binding_locator.name())?; if found { context.vm.push(deleted); return Ok(CompletionType::Normal); @@ -95,11 +95,10 @@ impl Operation for DeleteName { let key: JsString = context .interner() .resolve_expect(binding_locator.name().sym()) - .into_common(false); - let deleted = crate::object::internal_methods::global::global_delete_no_receiver( - &key.clone().into(), - context, - )?; + .into_common::(false); + let deleted = context + .global_object() + .__delete__(&key.clone().into(), context)?; if !deleted && context.vm.frame().code_block.strict { return Err(JsNativeError::typ() diff --git a/boa_engine/src/vm/opcode/get/generator.rs b/boa_engine/src/vm/opcode/get/generator.rs index b1b415dc69..d14a424ca2 100644 --- a/boa_engine/src/vm/opcode/get/generator.rs +++ b/boa_engine/src/vm/opcode/get/generator.rs @@ -18,7 +18,7 @@ impl Operation for GetGenerator { let index = context.vm.read::(); let method = context.vm.read::() != 0; let code = context.vm.frame().code_block.functions[index as usize].clone(); - let function = create_generator_function_object(code, false, method, context); + let function = create_generator_function_object(code, false, method, None, context); context.vm.push(function); Ok(CompletionType::Normal) } @@ -39,7 +39,7 @@ impl Operation for GetGeneratorAsync { let index = context.vm.read::(); let method = context.vm.read::() != 0; let code = context.vm.frame().code_block.functions[index as usize].clone(); - let function = create_generator_function_object(code, true, method, context); + let function = create_generator_function_object(code, true, method, None, context); context.vm.push(function); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/get/name.rs b/boa_engine/src/vm/opcode/get/name.rs index 7a128716bb..38b4f1bbba 100644 --- a/boa_engine/src/vm/opcode/get/name.rs +++ b/boa_engine/src/vm/opcode/get/name.rs @@ -29,14 +29,14 @@ impl Operation for GetName { .interner() .resolve_expect(binding_locator.name().sym()) .into_common(false); - match context.global_bindings_mut().get(&key) { + match context.global_object().get_property(&key.clone().into()) { Some(desc) => match desc.kind() { DescriptorKind::Data { value: Some(value), .. } => value.clone(), DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { let get = get.clone(); - get.call(&context.global_object().clone().into(), &[], context)? + get.call(&context.global_object().into(), &[], context)? } _ => { return Err(JsNativeError::reference() @@ -98,14 +98,14 @@ impl Operation for GetNameOrUndefined { .interner() .resolve_expect(binding_locator.name().sym()) .into_common(false); - match context.global_bindings_mut().get(&key) { + match context.global_object().get_property(&key.into()) { Some(desc) => match desc.kind() { DescriptorKind::Data { value: Some(value), .. } => value.clone(), DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { let get = get.clone(); - get.call(&context.global_object().clone().into(), &[], context)? + get.call(&context.global_object().into(), &[], context)? } _ => JsValue::undefined(), }, diff --git a/boa_engine/src/vm/opcode/push/class/mod.rs b/boa_engine/src/vm/opcode/push/class/mod.rs index 418705a189..0c5438a1b3 100644 --- a/boa_engine/src/vm/opcode/push/class/mod.rs +++ b/boa_engine/src/vm/opcode/push/class/mod.rs @@ -1,5 +1,5 @@ use crate::{ - builtins::function::{ConstructorKind, Function}, + builtins::function::{ConstructorKind, FunctionKind}, error::JsNativeError, object::PROTOTYPE, vm::{opcode::Operation, CompletionType}, @@ -43,9 +43,9 @@ impl Operation for PushClassPrototype { let class_function = class_object_mut .as_function_mut() .expect("class must be function object"); - if let Function::Ordinary { + if let FunctionKind::Ordinary { constructor_kind, .. - } = class_function + } = class_function.kind_mut() { *constructor_kind = ConstructorKind::Derived; } diff --git a/boa_engine/src/vm/opcode/push/object.rs b/boa_engine/src/vm/opcode/push/object.rs index 9ec4b2bda0..b7681dee1d 100644 --- a/boa_engine/src/vm/opcode/push/object.rs +++ b/boa_engine/src/vm/opcode/push/object.rs @@ -16,7 +16,7 @@ impl Operation for PushEmptyObject { const INSTRUCTION: &'static str = "INST - PushEmptyObject"; fn execute(context: &mut Context<'_>) -> JsResult { - let o = JsObject::with_object_proto(context); + let o = JsObject::with_object_proto(context.intrinsics()); context.vm.push(o); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/set/name.rs b/boa_engine/src/vm/opcode/set/name.rs index e3f99f76a2..65a99f7319 100644 --- a/boa_engine/src/vm/opcode/set/name.rs +++ b/boa_engine/src/vm/opcode/set/name.rs @@ -30,7 +30,9 @@ impl Operation for SetName { .interner() .resolve_expect(binding_locator.name().sym()) .into_common(false); - let exists = context.global_bindings_mut().contains_key(&key); + let exists = context + .global_object() + .has_own_property(key.clone(), context)?; if !exists && context.vm.frame().code_block.strict { return Err(JsNativeError::reference() @@ -41,20 +43,12 @@ impl Operation for SetName { .into()); } - let success = crate::object::internal_methods::global::global_set_no_receiver( - &key.clone().into(), + context.global_object().set( + key, value, + context.vm.frame().code_block.strict, context, )?; - - if !success && context.vm.frame().code_block.strict { - return Err(JsNativeError::typ() - .with_message(format!( - "cannot set non-writable property: {}", - key.to_std_string_escaped() - )) - .into()); - } } } else if !context.put_value_if_initialized( binding_locator.environment_index(), diff --git a/boa_examples/src/bin/closures.rs b/boa_examples/src/bin/closures.rs index bcba8fe508..eb4b89705f 100644 --- a/boa_examples/src/bin/closures.rs +++ b/boa_examples/src/bin/closures.rs @@ -22,19 +22,21 @@ fn main() -> Result<(), JsError> { let variable = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1; // We register a global closure function that has the name 'closure' with length 0. - context.register_global_callable( - "closure", - 0, - NativeFunction::from_copy_closure(move |_, _, _| { - println!("Called `closure`"); - // `variable` is captured from the main function. - println!("variable = {variable}"); - println!(); - - // We return the moved variable as a `JsValue`. - Ok(JsValue::new(variable)) - }), - ); + context + .register_global_callable( + "closure", + 0, + NativeFunction::from_copy_closure(move |_, _, _| { + println!("Called `closure`"); + // `variable` is captured from the main function. + println!("variable = {variable}"); + println!(); + + // We return the moved variable as a `JsValue`. + Ok(JsValue::new(variable)) + }), + ) + .unwrap(); assert_eq!( context.eval_script(Source::from_bytes("closure()"))?, @@ -52,7 +54,7 @@ fn main() -> Result<(), JsError> { } // We create a new `JsObject` with some data - let object = JsObject::with_object_proto(&mut context); + let object = JsObject::with_object_proto(context.intrinsics()); object.define_property_or_throw( "name", PropertyDescriptor::builder() @@ -109,15 +111,17 @@ fn main() -> Result<(), JsError> { .build(); // We bind the newly constructed closure as a global property in Javascript. - context.register_global_property( - // We set the key to access the function the same as its name for - // consistency, but it may be different if needed. - "createMessage", - // We pass `js_function` as a property value. - js_function, - // We assign to the "createMessage" property the desired attributes. - Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, - ); + context + .register_global_property( + // We set the key to access the function the same as its name for + // consistency, but it may be different if needed. + "createMessage", + // We pass `js_function` as a property value. + js_function, + // We assign to the "createMessage" property the desired attributes. + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ) + .unwrap(); assert_eq!( context.eval_script(Source::from_bytes("createMessage()"))?, @@ -142,32 +146,32 @@ fn main() -> Result<(), JsError> { let numbers = RefCell::new(Vec::new()); // We register a global closure that is not `Copy`. - context.register_global_callable( - "enumerate", - 0, - // Note that it is required to use `unsafe` code, since the compiler cannot verify that the - // types captured by the closure are not traceable. - unsafe { - NativeFunction::from_closure(move |_, _, context| { - println!("Called `enumerate`"); - // `index` is captured from the main function. - println!("index = {}", index.get()); - println!(); - - numbers.borrow_mut().push(index.get()); - index.set(index.get() + 1); - - // We return the moved variable as a `JsValue`. - Ok( - JsArray::from_iter( + context + .register_global_callable( + "enumerate", + 0, + // Note that it is required to use `unsafe` code, since the compiler cannot verify that the + // types captured by the closure are not traceable. + unsafe { + NativeFunction::from_closure(move |_, _, context| { + println!("Called `enumerate`"); + // `index` is captured from the main function. + println!("index = {}", index.get()); + println!(); + + numbers.borrow_mut().push(index.get()); + index.set(index.get() + 1); + + // We return the moved variable as a `JsValue`. + Ok(JsArray::from_iter( numbers.borrow().iter().cloned().map(JsValue::from), context, ) - .into(), - ) - }) - }, - ); + .into()) + }) + }, + ) + .unwrap(); // First call should return the array `[0]`. let result = context.eval_script(Source::from_bytes("enumerate()"))?; diff --git a/boa_examples/src/bin/futures.rs b/boa_examples/src/bin/futures.rs index cbc6cf5281..bbd9b04c2b 100644 --- a/boa_examples/src/bin/futures.rs +++ b/boa_examples/src/bin/futures.rs @@ -135,7 +135,9 @@ fn main() { let context = &mut ContextBuilder::new().job_queue(&queue).build().unwrap(); // Bind the defined async function to the ECMAScript function "delay". - context.register_global_builtin_callable("delay", 1, NativeFunction::from_async_fn(delay)); + context + .register_global_builtin_callable("delay", 1, NativeFunction::from_async_fn(delay)) + .unwrap(); // Multiple calls to multiple async timers. let script = r#" diff --git a/boa_examples/src/bin/jsarray.rs b/boa_examples/src/bin/jsarray.rs index 16470c342f..7088d18d83 100644 --- a/boa_examples/src/bin/jsarray.rs +++ b/boa_examples/src/bin/jsarray.rs @@ -113,7 +113,6 @@ fn main() -> JsResult<()> { context .global_object() - .clone() .set("myArray", array, true, context)?; Ok(()) diff --git a/boa_examples/src/bin/jsarraybuffer.rs b/boa_examples/src/bin/jsarraybuffer.rs index c6987df25d..53aaf2792b 100644 --- a/boa_examples/src/bin/jsarraybuffer.rs +++ b/boa_examples/src/bin/jsarraybuffer.rs @@ -50,11 +50,13 @@ fn main() -> JsResult<()> { assert_eq!(second_byte, 2_u8); // We can also register it as a global property - context.register_global_property( - "myArrayBuffer", - array_buffer, - Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, - ); + context + .register_global_property( + "myArrayBuffer", + array_buffer, + Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, + ) + .unwrap(); // We can also take the inner data from a JsArrayBuffer let data_block: Vec = (0..5).collect(); diff --git a/boa_examples/src/bin/jstypedarray.rs b/boa_examples/src/bin/jstypedarray.rs index fe44342fc0..e5c31611c2 100644 --- a/boa_examples/src/bin/jstypedarray.rs +++ b/boa_examples/src/bin/jstypedarray.rs @@ -40,11 +40,13 @@ fn main() -> JsResult<()> { JsValue::new(sum) ); - context.register_global_property( - "myUint8Array", - array, - Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, - ); + context + .register_global_property( + "myUint8Array", + array, + Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, + ) + .unwrap(); Ok(()) } diff --git a/boa_examples/src/bin/modulehandler.rs b/boa_examples/src/bin/modulehandler.rs index 6b63b25b28..fef855a2ad 100644 --- a/boa_examples/src/bin/modulehandler.rs +++ b/boa_examples/src/bin/modulehandler.rs @@ -20,14 +20,16 @@ fn main() { let mut ctx = Context::default(); // Adding custom implementation that mimics 'require' - ctx.register_global_callable("require", 0, NativeFunction::from_fn_ptr(require)); + ctx.register_global_callable("require", 0, NativeFunction::from_fn_ptr(require)) + .unwrap(); // Adding custom object that mimics 'module.exports' let moduleobj = JsObject::default(); moduleobj .set("exports", JsValue::from(" "), false, &mut ctx) .unwrap(); - ctx.register_global_property("module", JsValue::from(moduleobj), Attribute::default()); + ctx.register_global_property("module", JsValue::from(moduleobj), Attribute::default()) + .unwrap(); // Instantiating the engine with the execution context // Loading, parsing and executing the JS code from the source file @@ -57,7 +59,7 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult Finalize for Cell> { - fn finalize(&self) { - if let Some(t) = self.take() { - t.finalize(); - self.set(Some(t)); - } - } -} - -// SAFETY: This implementation is safe, because `take` leaves `None` in the -// place of our value, making it possible to trace through it safely. -unsafe impl Trace for Cell> { - custom_trace!(this, { - if let Some(t) = this.take() { - mark(&t); - this.set(Some(t)); - } - }); -} diff --git a/boa_tester/src/exec/js262.rs b/boa_tester/src/exec/js262.rs index 781858008e..ae43ca7bba 100644 --- a/boa_tester/src/exec/js262.rs +++ b/boa_tester/src/exec/js262.rs @@ -7,7 +7,7 @@ use boa_engine::{ /// Creates the object $262 in the context. pub(super) fn register_js262(context: &mut Context<'_>) -> JsObject { - let global_obj = context.global_object().clone(); + let global_obj = context.global_object(); let js262 = ObjectInitializer::new(context) .function(NativeFunction::from_fn_ptr(create_realm), "createRealm", 0) @@ -26,11 +26,13 @@ pub(super) fn register_js262(context: &mut Context<'_>) -> JsObject { // .property("agent", agent, Attribute::default()) .build(); - context.register_global_property( - "$262", - js262.clone(), - Attribute::WRITABLE | Attribute::CONFIGURABLE, - ); + context + .register_global_property( + "$262", + js262.clone(), + Attribute::WRITABLE | Attribute::CONFIGURABLE, + ) + .expect("shouldn't fail with the default global"); js262 } diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index ab8077374d..c12c2cc3f4 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -492,11 +492,13 @@ fn register_print_fn(context: &mut Context<'_>, async_result: AsyncResult) { .length(1) .build(); - context.register_global_property( - "print", - js_function, - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ); + context + .register_global_property( + "print", + js_function, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .expect("shouldn't fail with the default global"); } /// A `Result` value that is possibly uninitialized. diff --git a/docs/boa_object.md b/docs/boa_object.md index 52c7c84392..271e9e6a64 100644 --- a/docs/boa_object.md +++ b/docs/boa_object.md @@ -148,3 +148,17 @@ Optimizer { 2 >> ``` + +## Module `$boa.realm` + +This modules contains realm utilities to test cross-realm behaviour. + +### `$boa.realm.create` + +Creates a new realm with a new set of builtins and returns its global object. + +```javascript +let global = $boa.realm.create(); + +Object != global.Object; // true +```