From 0730a11b3661407129a05b545f27e1186b4a8e38 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sat, 15 Aug 2026 03:17:26 +0000 Subject: [PATCH 01/19] feat(gc): integrate oscars GC backend into boa_engine --- .github/workflows/pull_request.yml | 1 + .github/workflows/rust.yml | 2 + .github/workflows/test262.yml | 2 + .github/workflows/webassembly.yml | 2 + Cargo.lock | 11 +- Cargo.toml | 4 + core/engine/Cargo.toml | 1 + core/engine/src/builtins/eval/mod.rs | 14 +-- .../src/builtins/finalization_registry/mod.rs | 28 ++--- .../builtins/finalization_registry/tests.rs | 1 + .../engine/src/builtins/function/arguments.rs | 4 +- core/engine/src/builtins/function/mod.rs | 10 +- core/engine/src/builtins/generator/mod.rs | 2 +- .../src/builtins/intl/list_format/mod.rs | 2 +- core/engine/src/builtins/intl/locale/mod.rs | 13 +- core/engine/src/builtins/intl/locale/utils.rs | 4 +- core/engine/src/builtins/json/mod.rs | 11 +- core/engine/src/builtins/promise/mod.rs | 27 +---- core/engine/src/builtins/set/ordered_set.rs | 6 +- core/engine/src/builtins/weak/weak_ref.rs | 6 +- core/engine/src/builtins/weak_map/mod.rs | 85 ++++++------- core/engine/src/builtins/weak_set/mod.rs | 4 +- core/engine/src/bytecompiler/class.rs | 12 +- core/engine/src/bytecompiler/function.rs | 2 +- core/engine/src/context/mod.rs | 14 +++ core/engine/src/environments/runtime/mod.rs | 14 ++- core/engine/src/error/mod.rs | 9 +- core/engine/src/host_defined.rs | 2 +- core/engine/src/lib.rs | 4 + core/engine/src/module/loader/mod.rs | 2 +- core/engine/src/module/mod.rs | 14 +-- core/engine/src/module/source.rs | 10 +- core/engine/src/module/synthetic.rs | 12 +- .../src/native_function/continuation.rs | 4 +- core/engine/src/native_function/mod.rs | 21 ++-- core/engine/src/object/builtins/jspromise.rs | 5 +- .../src/object/builtins/jstypedarray.rs | 2 +- core/engine/src/object/builtins/jsweakmap.rs | 2 +- core/engine/src/object/builtins/jsweakset.rs | 2 +- core/engine/src/object/jsobject.rs | 24 ++-- core/engine/src/object/mod.rs | 10 ++ .../shape/shared_shape/forward_transition.rs | 12 +- .../src/object/shape/shared_shape/mod.rs | 19 +-- core/engine/src/object/shape/unique_shape.rs | 16 ++- core/engine/src/realm.rs | 4 +- core/engine/src/script.rs | 8 +- core/engine/src/value/equality.rs | 2 +- core/engine/src/value/inner/legacy.rs | 8 +- core/engine/src/value/inner/nan_boxed.rs | 14 +-- core/engine/src/value/integer.rs | 8 +- core/engine/src/vm/code_block.rs | 1 + core/engine/src/vm/inline_cache/mod.rs | 1 + core/engine/src/vm/mod.rs | 2 +- core/engine/src/vm/opcode/await/mod.rs | 7 +- core/engine/src/vm/opcode/function.rs | 4 +- core/engine/src/vm/opcode/push/environment.rs | 6 +- core/engine/src/vm/tests.rs | 1 + core/gc/Cargo.toml | 13 +- core/gc/src/cell.rs | 10 +- core/gc/src/lib.rs | 114 +++++++++++++++++- core/gc/src/oscars_weak_map.rs | 109 +++++++++++++++++ core/gc/src/pointers/mutation_context.rs | 6 + core/gc/src/pointers/weak_map.rs | 13 ++ core/gc/src/test/weak.rs | 2 +- core/gc/src/trace.rs | 12 +- core/interner/src/sym.rs | 14 +-- core/macros/src/lib.rs | 38 +++--- core/runtime/src/abort/mod.rs | 3 +- core/runtime/src/console/tests.rs | 24 ++-- core/runtime/src/microtask/tests.rs | 2 +- core/runtime/src/test262.rs | 6 +- core/string/Cargo.toml | 4 + core/string/src/builder.rs | 8 +- core/string/src/lib.rs | 16 +++ core/string/src/tests.rs | 4 +- examples/src/bin/derive.rs | 1 + examples/src/bin/jstypedarray.rs | 2 +- tests/fuzz/Cargo.toml | 3 + tests/macros/tests/gcd_callback.rs | 4 +- 79 files changed, 631 insertions(+), 295 deletions(-) create mode 100644 core/gc/src/oscars_weak_map.rs diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ae17ebe355e..fa208682396 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -5,6 +5,7 @@ on: branches: - main - releases/** + - dev/oscars-gc permissions: contents: read diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0fa015031fb..1dfd917b2a6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,10 +5,12 @@ on: branches: - main - releases/** + - dev/oscars-gc push: branches: - main - releases/** + - dev/oscars-gc merge_group: types: [checks_requested] workflow_dispatch: diff --git a/.github/workflows/test262.yml b/.github/workflows/test262.yml index 6797efa033d..a8671fc37d0 100644 --- a/.github/workflows/test262.yml +++ b/.github/workflows/test262.yml @@ -5,6 +5,7 @@ on: branches: - main - releases/** + - dev/oscars-gc permissions: contents: read @@ -15,6 +16,7 @@ concurrency: jobs: run_test262: + if: ${{ github.base_ref != 'dev/oscars-gc' }} name: Run the test262 test suite runs-on: ubuntu-latest timeout-minutes: 60 diff --git a/.github/workflows/webassembly.yml b/.github/workflows/webassembly.yml index f9538775ebd..676a8602965 100644 --- a/.github/workflows/webassembly.yml +++ b/.github/workflows/webassembly.yml @@ -5,10 +5,12 @@ on: branches: - main - releases/** + - dev/oscars-gc push: branches: - main - releases/** + - dev/oscars-gc merge_group: types: [checks_requested] diff --git a/Cargo.lock b/Cargo.lock index 0585ab322c9..7e2cf9ee140 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -482,6 +482,7 @@ dependencies = [ "icu_locale_core", "oscars", "thin-vec", + "typeid", ] [[package]] @@ -593,6 +594,7 @@ version = "1.0.0-dev" dependencies = [ "fast-float2", "itoa", + "oscars", "pastey", "rustc-hash 2.1.2", "ryu-js", @@ -2846,17 +2848,22 @@ dependencies = [ [[package]] name = "oscars" version = "0.1.0" -source = "git+https://github.com/boa-dev/oscars.git?branch=main#592903ff2bec29ae3f4be7fecf4baded74674be2" +source = "git+https://github.com/boa-dev/oscars.git?branch=main#ed5f692df0356338a82c113982449a3b5f7b1927" dependencies = [ + "arrayvec", + "either", "hashbrown 0.16.1", + "icu_locale_core", "oscars_derive", "rustc-hash 2.1.2", + "thin-vec", + "typeid", ] [[package]] name = "oscars_derive" version = "0.1.0" -source = "git+https://github.com/boa-dev/oscars.git?branch=main#592903ff2bec29ae3f4be7fecf4baded74674be2" +source = "git+https://github.com/boa-dev/oscars.git?branch=main#ed5f692df0356338a82c113982449a3b5f7b1927" dependencies = [ "cfg-if", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 41ce5060891..998e9f55e02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,6 +118,7 @@ num-integer = "0.1.46" ryu-js = "1.0.2" tap = "1.0.1" thiserror = { version = "2.0.18", default-features = false } +typeid = "1.0.3" dashmap = "6.2.1" num_enum = "0.7.6" itertools = { version = "0.15.0", default-features = false } @@ -267,3 +268,6 @@ complexity = { level = "warn", priority = -1 } perf = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } +[patch."https://github.com/boa-dev/boa.git"] +boa_string = { path = "core/string" } + diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml index d31abc302c8..eafe12de72b 100644 --- a/core/engine/Cargo.toml +++ b/core/engine/Cargo.toml @@ -26,6 +26,7 @@ embedded_lz4 = ["boa_macros/embedded_lz4", "lz4_flex"] jsvalue-enum = [] deser = ["boa_interner/serde", "boa_ast/serde"] either = ["dep:either", "boa_gc/either"] +oscars_backend = ["boa_gc/oscars_backend", "boa_string/oscars_backend"] # Enables the `Intl` builtin object and bundles a default ICU4X data provider. # Prefer this over `intl` if you just want to enable `Intl` without dealing with the diff --git a/core/engine/src/builtins/eval/mod.rs b/core/engine/src/builtins/eval/mod.rs index 7b0f9246640..0fd60137801 100644 --- a/core/engine/src/builtins/eval/mod.rs +++ b/core/engine/src/builtins/eval/mod.rs @@ -320,10 +320,8 @@ impl Eval { compiler.compile_statement_list(body.statements(), true, false); - let code_block = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - compiler.finish(), - ); + let finished = compiler.finish(); + let code_block = Gc::new(&context.gc(), finished); // Strict calls don't need extensions, since all strict eval calls push a new // function environment before evaluating. @@ -350,9 +348,11 @@ impl Eval { { let frame = context.vm.frame_mut(); let global = frame.realm.environment(); - frame - .environments - .push_lexical(lexical_scope.num_bindings_non_local(), global); + frame.environments.push_lexical( + lexical_scope.num_bindings_non_local(), + global, + unsafe { boa_gc::MutationContext::global() }, + ); } context diff --git a/core/engine/src/builtins/finalization_registry/mod.rs b/core/engine/src/builtins/finalization_registry/mod.rs index 4ec7ba0c0f3..810e40e287b 100644 --- a/core/engine/src/builtins/finalization_registry/mod.rs +++ b/core/engine/src/builtins/finalization_registry/mod.rs @@ -158,10 +158,7 @@ impl BuiltInConstructor for FinalizationRegistry { }, ); - let weak_registry = WeakGc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - registry.inner(), - ); + let weak_registry = WeakGc::new(&context.gc(), registry.inner()); { async fn inner_cleanup( @@ -174,7 +171,7 @@ impl BuiltInConstructor for FinalizationRegistry { }; let Some(registry) = weak_registry - .upgrade(&unsafe { boa_gc::MutationContext::dummy() }) + .upgrade(&unsafe { boa_gc::MutationContext::global() }) .map(JsObject::from_inner) else { return Ok(JsValue::undefined()); @@ -205,7 +202,7 @@ impl FinalizationRegistry { /// [`FinalizationRegistry.prototype.register ( target, heldValue [ , unregisterToken ] )`][spec] /// /// [spec]: https://tc39.es/ecma262/sec-finalization-registry.prototype.register - fn register(this: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult { + fn register(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let finalizationRegistry be the this value. // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). let this = this.as_object(); @@ -257,10 +254,7 @@ impl FinalizationRegistry { // // TODO: support Symbols let unregister_token = match unregister_token.variant() { - JsVariant::Object(obj) => Some(WeakGc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - obj.inner(), - )), + JsVariant::Object(obj) => Some(WeakGc::new(&context.gc(), obj.inner())), // b. Set unregisterToken to empty. JsVariant::Undefined => None, // a. If unregisterToken is not undefined, throw a TypeError exception. @@ -275,7 +269,7 @@ impl FinalizationRegistry { // 6. Let cell be the Record { [[WeakRefTarget]]: target, [[HeldValue]]: heldValue, [[UnregisterToken]]: unregisterToken }. let cell = RegistryCell { target: Ephemeron::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &context.gc(), target_obj.inner(), CleanupSignaler(Cell::new(Some( registry.cleanup_notifier.clone().downgrade(), @@ -295,7 +289,7 @@ impl FinalizationRegistry { /// [`FinalizationRegistry.prototype.unregister ( unregisterToken )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-finalization-registry.prototype.unregister - fn unregister(this: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult { + fn unregister(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let finalizationRegistry be the this value. // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). let this = this.as_object(); @@ -338,20 +332,16 @@ impl FinalizationRegistry { // a. If cell.[[UnregisterToken]] is not empty and SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then if let Some(tok) = cell.unregister_token.as_ref() - && let Some(tok) = tok.upgrade(&unsafe { boa_gc::MutationContext::dummy() }) + && let Some(tok) = tok.upgrade(&context.gc()) && Gc::ptr_eq(&tok, unregister_token) { // i. Remove cell from finalizationRegistry.[[Cells]]. let cell = registry.cells.swap_remove(i); - let _key = cell - .target - .key(&unsafe { boa_gc::MutationContext::dummy() }); + let _key = cell.target.key(&context.gc()); // TODO: it might be better to add a special ref for the value that // also preserves the original key instead. - cell.target - .value(&unsafe { boa_gc::MutationContext::dummy() }) - .and_then(|v| v.0.take()); + cell.target.value(&context.gc()).and_then(|v| v.0.take()); // ii. Set removed to true. removed = true; diff --git a/core/engine/src/builtins/finalization_registry/tests.rs b/core/engine/src/builtins/finalization_registry/tests.rs index 602bcc53586..0c4802e0093 100644 --- a/core/engine/src/builtins/finalization_registry/tests.rs +++ b/core/engine/src/builtins/finalization_registry/tests.rs @@ -1,3 +1,4 @@ +#[cfg(not(feature = "oscars_backend"))] mod miri { use indoc::indoc; diff --git a/core/engine/src/builtins/function/arguments.rs b/core/engine/src/builtins/function/arguments.rs index 81fe3f10043..ab339f96d07 100644 --- a/core/engine/src/builtins/function/arguments.rs +++ b/core/engine/src/builtins/function/arguments.rs @@ -1,3 +1,5 @@ +#![allow(clippy::trivially_copy_pass_by_ref)] +#![allow(clippy::needless_pass_by_value)] use crate::{ Context, JsData, JsExpect, JsResult, JsValue, bytecompiler::ToJsString, @@ -124,7 +126,7 @@ impl MappedArguments { .get(index as usize) .copied() .flatten()?; - self.environment.get(binding_index) + (*self.environment).get(binding_index) } /// Set the value of the binding at the given index in the function environment. diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index d7585a76994..498cb555b8c 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -1073,7 +1073,9 @@ pub(crate) fn function_call( if has_binding_identifier { let frame = context.vm.frame_mut(); let global = frame.realm.environment(); - let index = frame.environments.push_lexical(1, global); + let index = frame + .environments + .push_lexical(1, global, unsafe { boa_gc::MutationContext::global() }); frame.environments.put_lexical_value( BindingLocatorScope::Stack(index), 0, @@ -1091,6 +1093,7 @@ pub(crate) fn function_call( scope, FunctionSlots::new(this, function_object.clone(), None), global, + unsafe { boa_gc::MutationContext::global() }, ); } @@ -1181,7 +1184,9 @@ fn function_construct( if has_binding_identifier { let frame = context.vm.frame_mut(); let global = frame.realm.environment(); - let index = frame.environments.push_lexical(1, global); + let index = frame + .environments + .push_lexical(1, global, unsafe { boa_gc::MutationContext::global() }); frame.environments.put_lexical_value( BindingLocatorScope::Stack(index), 0, @@ -1210,6 +1215,7 @@ fn function_construct( ), ), global, + unsafe { boa_gc::MutationContext::global() }, ); } diff --git a/core/engine/src/builtins/generator/mod.rs b/core/engine/src/builtins/generator/mod.rs index 85e086e9419..17e60e63aec 100644 --- a/core/engine/src/builtins/generator/mod.rs +++ b/core/engine/src/builtins/generator/mod.rs @@ -46,7 +46,7 @@ pub(crate) enum GeneratorState { // Need to manually implement, since `Trace` adds a `Drop` impl which disallows destructuring. unsafe impl Trace for GeneratorState { custom_trace!(this, mark, { - match &this { + match this { Self::SuspendedStart { context } | Self::SuspendedYield { context } => mark(context), Self::Executing | Self::Completed => {} } diff --git a/core/engine/src/builtins/intl/list_format/mod.rs b/core/engine/src/builtins/intl/list_format/mod.rs index 9c9bb2e0200..403fca045e6 100644 --- a/core/engine/src/builtins/intl/list_format/mod.rs +++ b/core/engine/src/builtins/intl/list_format/mod.rs @@ -329,7 +329,7 @@ impl ListFormat { part: writeable::Part, mut f: impl FnMut(&mut Self::SubPartsWrite) -> core::fmt::Result, ) -> core::fmt::Result { - assert!(part.category == "list"); + assert_eq!(part.category, "list"); let mut string = WriteString(String::new()); f(&mut string)?; if !string.0.is_empty() { diff --git a/core/engine/src/builtins/intl/locale/mod.rs b/core/engine/src/builtins/intl/locale/mod.rs index 2930d0adf7a..949f5b094ce 100644 --- a/core/engine/src/builtins/intl/locale/mod.rs +++ b/core/engine/src/builtins/intl/locale/mod.rs @@ -349,14 +349,17 @@ impl Locale { // 1. Let loc be the this value. // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]). let object = this.as_object(); + // Under `oscars_backend`, `downcast_ref` returns `GcRef<'_, Locale>`. + // Deref through the guard before cloning to get an owned `icu_locale::Locale`. + // This is required because `GcRef<'_, Locale>` doesn't implement `NativeObject`. let mut loc = object .as_ref() .and_then(|o| o.downcast_ref::()) .ok_or_else(|| { JsNativeError::typ() .with_message("`Locale.maximize` can only be called on a `Locale` object") - })? - .clone(); + }) + .map(|r| (*r).clone())?; // 3. Let maximal be the result of the Add Likely Subtags algorithm applied to loc.[[Locale]]. If an error is signaled, set maximal to loc.[[Locale]]. context @@ -387,6 +390,8 @@ impl Locale { // 1. Let loc be the this value. // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]). let object = this.as_object(); + // Under `oscars_backend`, `downcast_ref` returns `GcRef<'_, Locale>`. + // Deref through the guard before cloning to get an owned `icu_locale::Locale`. let mut loc = object .as_ref() .and_then(|o| o.downcast_ref::()) @@ -394,8 +399,8 @@ impl Locale { JsNativeError::typ().with_message( "`Locale.prototype.minimize` can only be called on a `Locale` object", ) - })? - .clone(); + }) + .map(|r| (*r).clone())?; // 3. Let minimal be the result of the Remove Likely Subtags algorithm applied to loc.[[Locale]]. If an error is signaled, set minimal to loc.[[Locale]]. context diff --git a/core/engine/src/builtins/intl/locale/utils.rs b/core/engine/src/builtins/intl/locale/utils.rs index 1193f6d53c3..7dcf9b29a31 100644 --- a/core/engine/src/builtins/intl/locale/utils.rs +++ b/core/engine/src/builtins/intl/locale/utils.rs @@ -54,7 +54,9 @@ pub(crate) fn locale_from_value(tag: &JsValue, context: &mut Context) -> JsResul if let Some(tag) = object.as_ref().and_then(|obj| obj.downcast_ref::()) { // 1. Let tag be kValue.[[Locale]]. // No need to canonicalize since all `Locale` objects should already be canonicalized. - return Ok(tag.clone()); + // Under `oscars_backend`, `downcast_ref` returns `GcRef<'_, Locale>`. + // Deref through the guard before cloning to clone the `Locale` value, not the wrapper. + return Ok((*tag).clone()); } // iv. Else, diff --git a/core/engine/src/builtins/json/mod.rs b/core/engine/src/builtins/json/mod.rs index be01ebb234b..3bc2f4f9abc 100644 --- a/core/engine/src/builtins/json/mod.rs +++ b/core/engine/src/builtins/json/mod.rs @@ -307,10 +307,8 @@ impl Json { SourcePath::Json, ); compiler.compile_statement_list(script.statements(), true, false); - Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - compiler.finish(), - ) + let finished = compiler.finish(); + Gc::new(&context.gc(), finished) }; let realm = context.realm().clone(); @@ -815,7 +813,10 @@ impl Json { // d. Else if value has a [[BigIntData]] internal slot, then else if let Some(bigint) = obj.downcast_ref::() { // i. Set value to value.[[BigIntData]]. - value = bigint.clone().into(); + // SAFETY: Under oscars_backend, `downcast_ref` returns a `GcRef<'_, JsBigInt>`. + // We must deref through the guard before calling `.clone()` so that we clone + // the inner `JsBigInt`, not the `GcRef` wrapper. + value = (*bigint).clone().into(); } // e. Else if value has a [[IsRawJSON]] internal slot, then else if obj.is::() { diff --git a/core/engine/src/builtins/promise/mod.rs b/core/engine/src/builtins/promise/mod.rs index e79b52fc256..f03ef9500da 100644 --- a/core/engine/src/builtins/promise/mod.rs +++ b/core/engine/src/builtins/promise/mod.rs @@ -244,7 +244,7 @@ impl PromiseCapability { // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1). // 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }. let promise_capability = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &context.gc(), GcRefCell::new(RejectResolve { reject: JsValue::undefined(), resolve: JsValue::undefined(), @@ -656,10 +656,7 @@ impl Promise { } // 1. Let values be a new empty List. - let values = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - GcRefCell::new(Vec::new()), - ); + let values = Gc::new(&context.gc(), GcRefCell::new(Vec::new())); // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -874,10 +871,7 @@ impl Promise { } // 1. Let values be a new empty List. - let values = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - GcRefCell::new(Vec::new()), - ); + let values = Gc::new(&context.gc(), GcRefCell::new(Vec::new())); // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -1244,10 +1238,7 @@ impl Promise { let keys = Rc::new(RefCell::new(Vec::new())); // 3. Let values be a new empty List. - let values = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - GcRefCell::new(Vec::new()), - ); + let values = Gc::new(&context.gc(), GcRefCell::new(Vec::new())); // 4. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -1557,10 +1548,7 @@ impl Promise { } // 1. Let errors be a new empty List. - let errors = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - GcRefCell::new(Vec::new()), - ); + let errors = Gc::new(&context.gc(), GcRefCell::new(Vec::new())); // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -2460,10 +2448,7 @@ impl Promise { // 1. Let alreadyResolved be the Record { [[Value]]: false }. // 5. Set resolve.[[Promise]] to promise. // 6. Set resolve.[[AlreadyResolved]] to alreadyResolved. - let promise = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - Cell::new(Some(promise.clone())), - ); + let promise = Gc::new(&context.gc(), Cell::new(Some(promise.clone()))); // 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions. // 3. Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions. diff --git a/core/engine/src/builtins/set/ordered_set.rs b/core/engine/src/builtins/set/ordered_set.rs index 6c604263662..a9888594441 100644 --- a/core/engine/src/builtins/set/ordered_set.rs +++ b/core/engine/src/builtins/set/ordered_set.rs @@ -15,9 +15,9 @@ pub struct OrderedSet { unsafe impl Trace for OrderedSet { custom_trace!(this, mark, { - for v in &this.inner { - if let MapKey::Key(v) = v { - mark(v); + for k in &this.inner { + if let MapKey::Key(key) = k { + mark(key); } } }); diff --git a/core/engine/src/builtins/weak/weak_ref.rs b/core/engine/src/builtins/weak/weak_ref.rs index 77f136812ac..0804d3d5d92 100644 --- a/core/engine/src/builtins/weak/weak_ref.rs +++ b/core/engine/src/builtins/weak/weak_ref.rs @@ -87,7 +87,7 @@ impl BuiltInConstructor for WeakRef { let weak_ref = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - WeakGc::new(&unsafe { boa_gc::MutationContext::dummy() }, target.inner()), + WeakGc::new(&context.gc(), target.inner()), ); // 4. Perform AddToKeptObjects(target). @@ -124,7 +124,7 @@ impl WeakRef { // https://tc39.es/ecma262/multipage/managing-memory.html#sec-weakrefderef // 1. Let target be weakRef.[[WeakRefTarget]]. // 2. If target is not empty, then - if let Some(object) = weak_ref.upgrade(&unsafe { boa_gc::MutationContext::dummy() }) { + if let Some(object) = weak_ref.upgrade(&context.gc()) { let object = JsObject::from(object); // a. Perform AddToKeptObjects(target). @@ -140,11 +140,13 @@ impl WeakRef { } #[cfg(test)] +#[allow(unused_imports)] mod tests { use indoc::indoc; use crate::{JsNativeErrorKind, JsValue, TestAction, run_test_actions}; + #[cfg(not(feature = "oscars_backend"))] #[test] fn weak_ref_collected() { run_test_actions([ diff --git a/core/engine/src/builtins/weak_map/mod.rs b/core/engine/src/builtins/weak_map/mod.rs index adff36ecbfc..8f0bdf8de7c 100644 --- a/core/engine/src/builtins/weak_map/mod.rs +++ b/core/engine/src/builtins/weak_map/mod.rs @@ -28,7 +28,7 @@ pub(crate) type NativeWeakMap = boa_gc::WeakMap; #[derive(Debug, Trace, Finalize)] pub(crate) struct WeakMap; -#[cfg(test)] +#[cfg(all(test, not(feature = "oscars_backend")))] mod tests; impl IntrinsicObject for WeakMap { @@ -97,7 +97,7 @@ impl BuiltInConstructor for WeakMap { let map = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - NativeWeakMap::new(&unsafe { boa_gc::MutationContext::dummy() }), + NativeWeakMap::new(&context.gc()), ) .upcast(); @@ -171,7 +171,7 @@ impl WeakMap { pub(crate) fn get( this: &JsValue, args: &[JsValue], - _context: &mut Context, + #[allow(unused_variables)] context: &mut Context, ) -> JsResult { // 1. Let M be the this value. // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). @@ -193,13 +193,8 @@ impl WeakMap { // 5. For each Record { [[Key]], [[Value]] } p of entries, do // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]]. // 6. Return undefined. - if let Some(entry) = map.get(key.inner()) - && let Some(val) = entry.value(&unsafe { boa_gc::MutationContext::dummy() }) - { - Ok(val.clone()) - } else { - Ok(JsValue::undefined()) - } + let result: Option = map.get_value(key.inner()); + Ok(result.unwrap_or_else(JsValue::undefined)) } /// `WeakMap.prototype.has ( key )` @@ -298,13 +293,14 @@ impl WeakMap { pub(crate) fn get_or_insert( this: &JsValue, args: &[JsValue], - _context: &mut Context, + #[allow(unused_variables)] context: &mut Context, ) -> JsResult { // 1. Let M be the this value. // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). let object = this.as_object(); - let map = object - .and_then(|obj| obj.clone().downcast::().ok()) + let mut map = object + .as_ref() + .and_then(JsObject::downcast_mut::) .ok_or_else(|| { js_error!(TypeError: "WeakMap.prototype.getOrInsert: expected 'this' to be a WeakMap object", @@ -324,18 +320,13 @@ impl WeakMap { }; // 4. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]] - if let Some(existing) = map.borrow().data().get(key.inner()) - && let Some(value) = existing.value(&unsafe { boa_gc::MutationContext::dummy() }) - { - // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]]. - return Ok(value.clone()); + if let Some(existing) = map.get_value(key.inner()) { + return Ok(existing); } // 5-6. Insert the new record with provided value and return it. let value = args.get_or_undefined(1).clone(); - map.borrow_mut() - .data_mut() - .insert(key.inner(), value.clone()); + map.insert(key.inner(), value.clone()); Ok(value) } @@ -353,23 +344,12 @@ impl WeakMap { pub(crate) fn get_or_insert_computed( this: &JsValue, args: &[JsValue], - context: &mut Context, + #[allow(unused_variables)] context: &mut Context, ) -> JsResult { // 1. Let M be the this value. // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). let object = this.as_object(); - let map = object - .and_then(|obj| obj.clone().downcast::().ok()) - .ok_or_else(|| { - js_error!(TypeError: - "WeakMap.prototype.getOrInsertComputed: expected 'this' to be a WeakMap object", - ) - })?; - // 3. If CanBeHeldWeakly(key) is false, throw a TypeError exception. - // TODO: Implement proper CanBeHeldWeakly once available. For now, only - // objects are accepted as keys; symbols should be allowed in the - // future according to the proposal. let key_value = args.get_or_undefined(0).clone(); let Some(key_obj) = key_value.as_object() else { return Err(js_error!(TypeError: @@ -378,6 +358,19 @@ impl WeakMap { )); }; + if let Some(map) = object + .as_ref() + .and_then(JsObject::downcast_ref::) + { + if let Some(existing) = map.get_value(key_obj.inner()) { + return Ok(existing); + } + } else { + return Err(js_error!(TypeError: + "WeakMap.prototype.getOrInsertComputed: expected 'this' to be a WeakMap object", + )); + } + // 4. If IsCallable(callback) is false, throw a TypeError exception. let Some(callback_fn) = args.get_or_undefined(1).as_callable() else { return Err(js_error!(TypeError: @@ -385,26 +378,20 @@ impl WeakMap { )); }; - // 5. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]] - if let Some(existing) = map.borrow().data().get(key_obj.inner()) - && let Some(value) = existing.value(&unsafe { boa_gc::MutationContext::dummy() }) - { - // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]]. - return Ok(value.clone()); - } - // 6. Let value be ? Call(callback, undefined, « key »). // 7. NOTE: The WeakMap may have been modified during execution of callback. - let value = callback_fn.call( - &JsValue::undefined(), - std::slice::from_ref(&key_value), - context, - )?; + let value = callback_fn.call(&JsValue::undefined(), &[key_obj.clone().into()], context)?; // 8-10. Insert or update the entry and return value. - map.borrow_mut() - .data_mut() - .insert(key_obj.inner(), value.clone()); + let mut map = object + .as_ref() + .and_then(JsObject::downcast_mut::) + .ok_or_else(|| { + js_error!(TypeError: + "WeakMap.prototype.getOrInsertComputed: expected 'this' to be a WeakMap object", + ) + })?; + map.insert(key_obj.inner(), value.clone()); Ok(value) } } diff --git a/core/engine/src/builtins/weak_set/mod.rs b/core/engine/src/builtins/weak_set/mod.rs index 50647b16881..f55b58114b6 100644 --- a/core/engine/src/builtins/weak_set/mod.rs +++ b/core/engine/src/builtins/weak_set/mod.rs @@ -86,7 +86,7 @@ impl BuiltInConstructor for WeakSet { let weak_set = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - NativeWeakSet::new(&unsafe { boa_gc::MutationContext::dummy() }), + NativeWeakSet::new(&context.gc()), ) .upcast(); @@ -255,5 +255,5 @@ impl WeakSet { } } -#[cfg(test)] +#[cfg(all(test, not(feature = "oscars_backend")))] mod tests; diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index a876cc80403..d98020efdc9 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/core/engine/src/bytecompiler/class.rs @@ -157,7 +157,7 @@ impl ByteCompiler<'_> { ); let code = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, compiler.finish(), ); let index = self.push_function_to_constants(code); @@ -444,7 +444,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; let code = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, field_compiler.finish(), ); let index = self.push_function_to_constants(code); @@ -493,7 +493,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; let code = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, field_compiler.finish(), ); let index = self.push_function_to_constants(code); @@ -551,7 +551,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; let code = field_compiler.finish(); - let code = Gc::new(&unsafe { boa_gc::MutationContext::dummy() }, code); + let code = Gc::new(&unsafe { boa_gc::MutationContext::global() }, code); static_elements.push(StaticElement::StaticField { code, @@ -595,7 +595,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; let code = field_compiler.finish(); - let code = Gc::new(&unsafe { boa_gc::MutationContext::dummy() }, code); + let code = Gc::new(&unsafe { boa_gc::MutationContext::global() }, code); static_elements.push(StaticElement::StaticField { code, @@ -639,7 +639,7 @@ impl ByteCompiler<'_> { } let code = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, compiler.finish(), ); static_elements.push(StaticElement::StaticBlock(code)); diff --git a/core/engine/src/bytecompiler/function.rs b/core/engine/src/bytecompiler/function.rs index b862326fc7b..d1633a34cd6 100644 --- a/core/engine/src/bytecompiler/function.rs +++ b/core/engine/src/bytecompiler/function.rs @@ -227,6 +227,6 @@ impl FunctionCompiler { let code = compiler.finish(); - Gc::new(&unsafe { boa_gc::MutationContext::dummy() }, code) + Gc::new(&unsafe { boa_gc::MutationContext::global() }, code) } } diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index 78453d26078..dfde062671c 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -463,6 +463,20 @@ impl Context { &self.vm.frame().realm } + /// Returns [`boa_gc::MutationContext`] to allocate on the Gc heap + /// (eg. for [`Gc::new`]) + /// + /// # Safety + /// Uses `dummy()` as a temporary bridge during the oscars GC migration. + /// Todo: replace with a real branding token in future + #[inline] + #[must_use] + pub fn gc(&self) -> boa_gc::MutationContext<'static, 'static> { + // SAFETY: `MutationContext` is a ZST phantom type, this is sound + // under boa's single-threaded GC invariant until migration is complete + unsafe { boa_gc::MutationContext::global() } + } + /// Set the value of trace on the context #[cfg(feature = "trace")] #[inline] diff --git a/core/engine/src/environments/runtime/mod.rs b/core/engine/src/environments/runtime/mod.rs index 8f7b2dd26c6..59724b87c38 100644 --- a/core/engine/src/environments/runtime/mod.rs +++ b/core/engine/src/environments/runtime/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::trivially_copy_pass_by_ref)] +#![allow(clippy::needless_pass_by_value)] use crate::{ Context, JsResult, JsString, JsSymbol, JsValue, object::{JsObject, PrivateName}, @@ -214,13 +216,14 @@ impl EnvironmentStack { &mut self, bindings_count: u32, global: &Gc<'static, DeclarativeEnvironment>, + gc: boa_gc::MutationContext<'static, '_>, ) -> u32 { let (poisoned, with) = self.compute_poisoned_with(global); let index = self.depth; self.push_env(Environment::Declarative(Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &gc, DeclarativeEnvironment::new( DeclarativeEnvironmentKind::Lexical(LexicalEnvironment::new(bindings_count)), poisoned, @@ -237,13 +240,14 @@ impl EnvironmentStack { scope: Scope, function_slots: FunctionSlots, global: &Gc<'static, DeclarativeEnvironment>, + gc: boa_gc::MutationContext<'static, '_>, ) { let num_bindings = scope.num_bindings_non_local(); let (poisoned, with) = self.compute_poisoned_with(global); self.push_env(Environment::Declarative(Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &gc, DeclarativeEnvironment::new( DeclarativeEnvironmentKind::Function(FunctionEnvironment::new( num_bindings, @@ -257,10 +261,10 @@ impl EnvironmentStack { } /// Push a module environment on the environments stack. - pub(crate) fn push_module(&mut self, scope: Scope) { + pub(crate) fn push_module(&mut self, scope: Scope, gc: boa_gc::MutationContext<'static, '_>) { let num_bindings = scope.num_bindings_non_local(); self.push_env(Environment::Declarative(Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &gc, DeclarativeEnvironment::new( DeclarativeEnvironmentKind::Module(ModuleEnvironment::new(num_bindings, scope)), false, @@ -414,7 +418,7 @@ impl EnvironmentStack { /// Push an environment onto the chain. fn push_env(&mut self, env: Environment) { self.tip = Some(Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, EnvironmentNode { env, parent: self.tip.take(), diff --git a/core/engine/src/error/mod.rs b/core/engine/src/error/mod.rs index 1fc5c934c51..2e8ec0e6949 100644 --- a/core/engine/src/error/mod.rs +++ b/core/engine/src/error/mod.rs @@ -562,10 +562,13 @@ impl JsError { let obj = val .as_object() .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; + // Under `oscars_backend`, `downcast_ref` returns a `GcRef<'_, Error>`. + // We deref through the guard before `.clone()` so we clone the `Error` value, + // not the GcRef wrapper. The `*` operator goes through `Deref`. let error_data: Error = obj .downcast_ref::() - .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))? - .clone(); + .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone())) + .map(|r| (*r).clone())?; let try_get_property = |key: JsString, name, context: &mut Context| { obj.try_get(key, context) @@ -1496,7 +1499,7 @@ unsafe impl Trace for JsNativeErrorKind { custom_trace!( this, mark, - match &this { + match this { Self::Aggregate(errors) => mark(errors), Self::Error | Self::Eval diff --git a/core/engine/src/host_defined.rs b/core/engine/src/host_defined.rs index 96ea02e1f46..8547bd59e85 100644 --- a/core/engine/src/host_defined.rs +++ b/core/engine/src/host_defined.rs @@ -34,7 +34,7 @@ unsafe impl Trace for HostDefined { }); } -impl Finalize for HostDefined {} +impl Finalize for HostDefined {} impl HostDefined { /// Insert a type into the [`HostDefined`]. diff --git a/core/engine/src/lib.rs b/core/engine/src/lib.rs index 37558607d06..62b4c21497f 100644 --- a/core/engine/src/lib.rs +++ b/core/engine/src/lib.rs @@ -71,6 +71,10 @@ // Add temporarily - Needs addressing clippy::missing_panics_doc, + + // Expected when feature "oscars_backend" is enabled, since Gc becomes a Copy type + clippy::clone_on_copy, + clippy::cloned_instead_of_copied, )] extern crate self as boa_engine; diff --git a/core/engine/src/module/loader/mod.rs b/core/engine/src/module/loader/mod.rs index 21b0ed06b2f..a9bf45844ff 100644 --- a/core/engine/src/module/loader/mod.rs +++ b/core/engine/src/module/loader/mod.rs @@ -287,7 +287,7 @@ impl ModuleLoader for MapModuleLoader { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, boa_gc::Trace, boa_gc::Finalize)] struct ModuleCacheKey { path: PathBuf, attributes: Box<[ImportAttribute]>, diff --git a/core/engine/src/module/mod.rs b/core/engine/src/module/mod.rs index e34f8a8329d..e8f260aae5b 100644 --- a/core/engine/src/module/mod.rs +++ b/core/engine/src/module/mod.rs @@ -287,7 +287,7 @@ impl Module { Ok(Self { inner: Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &context.gc(), ModuleRepr { realm, namespace: GcRefCell::default(), @@ -319,7 +319,7 @@ impl Module { Self { inner: Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &context.gc(), ModuleRepr { realm, namespace: GcRefCell::default(), @@ -826,10 +826,7 @@ fn into_js_module() { let bar_count = Rc::new(RefCell::new(0)); let dad_count = Rc::new(RefCell::new(0)); - context.insert_data(Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - GcRefCell::new(JsValue::undefined()), - )); + context.insert_data(Gc::new(&context.gc(), GcRefCell::new(JsValue::undefined()))); let module = unsafe { vec![ @@ -912,7 +909,10 @@ fn into_js_module() { promise_result.state() ); - let result = context.get_data::().unwrap().borrow().clone(); + // Under `oscars_backend`, `borrow()` returns `GcRef<'_, JsValue>`. + // Deref through the guard before cloning to clone the inner `JsValue`. If we clone + // the guard instead, the `GcRef` (and immutable borrow) stays alive, causing error. + let result = (*context.get_data::().unwrap().borrow()).clone(); assert_eq!(*foo_count.borrow(), 2); assert_eq!(*bar_count.borrow(), 15); diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index 80fe607c6d6..b84319f6893 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -1824,17 +1824,17 @@ impl SourceTextModule { compiler.compile_module_item_list(source.items()); ( - Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - compiler.finish(), - ), + { + let finished = compiler.finish(); + Gc::new(&context.gc(), finished) + }, functions, ) }; // 8. Let moduleContext be a new ECMAScript code execution context. let mut envs = EnvironmentStack::new(); - envs.push_module(source.scope().clone()); + envs.push_module(source.scope().clone(), context.gc()); drop(status); // 9. Set the Function of moduleContext to null. diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index 613561c9558..0d30f788fd6 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -120,7 +120,7 @@ impl SyntheticModuleInitializer { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the // future: https://github.com/rust-lang/rust/issues/18598 let ptr = Gc::into_raw(Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, Callback { f: closure, captures, @@ -131,7 +131,7 @@ impl SyntheticModuleInitializer { // meaning this is safe. unsafe { Self { - inner: Gc::from_raw(ptr), + inner: >::from_raw(ptr), } } } @@ -338,13 +338,11 @@ impl SyntheticModule { module_scope.escape_all_bindings(); - let cb = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - compiler.finish(), - ); + let finished = compiler.finish(); + let cb = Gc::new(&context.gc(), finished); let mut envs = EnvironmentStack::new(); - envs.push_module(module_scope); + envs.push_module(module_scope, context.gc()); for locator in exports { // b. Perform ! env.InitializeBinding(exportName, undefined). diff --git a/core/engine/src/native_function/continuation.rs b/core/engine/src/native_function/continuation.rs index c18fa9e0327..abce83e1e0f 100644 --- a/core/engine/src/native_function/continuation.rs +++ b/core/engine/src/native_function/continuation.rs @@ -108,7 +108,7 @@ impl NativeCoroutine { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the // future: https://github.com/rust-lang/rust/issues/18598 let ptr = Gc::into_raw(Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, Coroutine { f: closure, captures, @@ -118,7 +118,7 @@ impl NativeCoroutine { // meaning this is safe. unsafe { Self { - inner: Gc::from_raw(ptr), + inner: >::from_raw(ptr), } } } diff --git a/core/engine/src/native_function/mod.rs b/core/engine/src/native_function/mod.rs index 22d661a3b38..11dc49adce8 100644 --- a/core/engine/src/native_function/mod.rs +++ b/core/engine/src/native_function/mod.rs @@ -279,7 +279,7 @@ impl NativeFunction { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the // future: https://github.com/rust-lang/rust/issues/18598 let ptr = Gc::into_raw(Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, Closure { f: closure, captures, @@ -289,7 +289,7 @@ impl NativeFunction { // meaning this is safe. unsafe { Self { - inner: Inner::Closure(Gc::from_raw(ptr)), + inner: Inner::Closure(>::from_raw(ptr)), } } } @@ -340,15 +340,18 @@ pub(crate) fn native_function_call( context.check_runtime_limits()?; let this_function_object = obj.clone(); + // Under `oscars_backend`, `downcast_ref` returns a `GcRef<'_, NativeFunctionObject>`. + // We deref through the guard with `(*guard).clone()` so we clone the inner struct + // (which is `Copy` friendly via `Clone`), not the `GcRef` wrapper itself let NativeFunctionObject { f: function, name, constructor, realm, - } = obj + } = (*obj .downcast_ref::() - .expect("the object should be a native function object") - .clone(); + .expect("the object should be a native function object")) + .clone(); let pc = context.vm.frame().pc; let native_source_info = context.native_source_info(); @@ -395,15 +398,17 @@ fn native_function_construct( context.check_runtime_limits()?; let this_function_object = obj.clone(); + // Under `oscars_backend`, `downcast_ref` returns a `GcRef<'_, NativeFunctionObject>`. + // We deref through the guard with `(*guard).clone()` so we clone the inner struct. let NativeFunctionObject { f: function, name, constructor, realm, - } = obj + } = (*obj .downcast_ref::() - .expect("the object should be a native function object") - .clone(); + .expect("the object should be a native function object")) + .clone(); let pc = context.vm.frame().pc; let native_source_info = context.native_source_info(); diff --git a/core/engine/src/object/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index 85aa1b26b2b..67931d22bf9 100644 --- a/core/engine/src/object/builtins/jspromise.rs +++ b/core/engine/src/object/builtins/jspromise.rs @@ -1,3 +1,4 @@ +#![allow(clippy::redundant_locals)] //! A Rust API wrapper for Boa's promise Builtin ECMAScript Object use super::{JsArray, JsFunction}; @@ -1094,7 +1095,7 @@ impl JsPromise { } let state = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &context.gc(), GcRefCell::new(Inner { result: None, task: None, @@ -1429,6 +1430,8 @@ impl TryIntoJs for JsPromise { /// between promises and futures a bit easier. /// /// The only way to construct an instance of `JsFuture` is by calling [`JsPromise::into_js_future`]. +#[derive(Clone)] +#[allow(missing_copy_implementations)] pub struct JsFuture { inner: Gc<'static, GcRefCell>, } diff --git a/core/engine/src/object/builtins/jstypedarray.rs b/core/engine/src/object/builtins/jstypedarray.rs index 77ec287f050..6828d0f6b98 100644 --- a/core/engine/src/object/builtins/jstypedarray.rs +++ b/core/engine/src/object/builtins/jstypedarray.rs @@ -678,7 +678,7 @@ impl JsTypedArray { /// # fn main() -> JsResult<()> { /// let context = &mut Context::default(); /// let array = JsUint8Array::from_iter(vec![1, 2, 3, 4, 5], context)?; - /// let num_to_modify = Gc::new(GcRefCell::new(0u8)); + /// let num_to_modify = Gc::new(&context.gc(), GcRefCell::new(0u8)); /// /// let js_function = FunctionObjectBuilder::new( /// context.realm(), diff --git a/core/engine/src/object/builtins/jsweakmap.rs b/core/engine/src/object/builtins/jsweakmap.rs index e752f696b95..d120be65a48 100644 --- a/core/engine/src/object/builtins/jsweakmap.rs +++ b/core/engine/src/object/builtins/jsweakmap.rs @@ -30,7 +30,7 @@ impl JsWeakMap { inner: JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().constructors().weak_map().prototype(), - NativeWeakMap::new(&unsafe { boa_gc::MutationContext::dummy() }), + NativeWeakMap::new(&context.gc()), ) .upcast(), } diff --git a/core/engine/src/object/builtins/jsweakset.rs b/core/engine/src/object/builtins/jsweakset.rs index 13d14095cc8..07a53fd4264 100644 --- a/core/engine/src/object/builtins/jsweakset.rs +++ b/core/engine/src/object/builtins/jsweakset.rs @@ -30,7 +30,7 @@ impl JsWeakSet { inner: JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().constructors().weak_set().prototype(), - NativeWeakSet::new(&unsafe { boa_gc::MutationContext::dummy() }), + NativeWeakSet::new(&context.gc()), ) .upcast(), } diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs index cd30c5dceb4..711bb3cee2b 100644 --- a/core/engine/src/object/jsobject.rs +++ b/core/engine/src/object/jsobject.rs @@ -33,12 +33,6 @@ use std::{ }; use thin_vec::ThinVec; -#[cfg(not(feature = "jsvalue-enum"))] -use boa_gc::GcBox; - -#[cfg(not(feature = "jsvalue-enum"))] -use std::ptr::NonNull; - /// A wrapper type for an immutably borrowed type T. pub type Ref<'a, T> = GcRef<'a, T>; @@ -86,8 +80,8 @@ pub(crate) struct VTableObject { impl JsObject { /// Converts the `JsObject` into a raw pointer to its inner `GcBox`. #[cfg(not(feature = "jsvalue-enum"))] - pub(crate) fn into_raw(self) -> NonNull> { - Gc::into_raw(self.inner) + pub(crate) fn into_raw(self) -> *const () { + Gc::into_raw(self.inner).as_ptr() as *const () } /// Creates a new `JsObject` from a raw pointer. @@ -96,9 +90,9 @@ impl JsObject { /// The caller must ensure that the pointer is valid and points to a `GcBox`. /// The pointer must not be null. #[cfg(not(feature = "jsvalue-enum"))] - pub(crate) unsafe fn from_raw(raw: NonNull>) -> Self { + pub(crate) unsafe fn from_raw(raw: *const ()) -> Self { // SAFETY: The caller guaranteed the value to be a valid pointer to a `GcBox`. - let inner = unsafe { Gc::from_raw(raw) }; + let inner = unsafe { Gc::from_raw(core::ptr::NonNull::new_unchecked(raw as *mut _)) }; JsObject { inner } } @@ -128,7 +122,7 @@ impl JsObject { vtable: &'static InternalObjectMethods, ) -> Self { let inner = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, VTableObject { object: GcRefCell::new(object), vtable, @@ -217,7 +211,7 @@ impl JsObject { ) -> Self { let internal_methods = data.internal_methods(); let inner = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, VTableObject { object: GcRefCell::new(Object { data: ObjectData::new(data), @@ -246,7 +240,7 @@ impl JsObject { ) -> JsObject { let internal_methods = data.internal_methods(); let inner = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, VTableObject { object: GcRefCell::new(Object { data: ObjectData::new(data), @@ -1088,7 +1082,7 @@ impl JsObject { pub fn new>>(root_shape: &RootShape, prototype: O, data: T) -> Self { let internal_methods = data.internal_methods(); let inner = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, VTableObject { object: GcRefCell::new(Object { data: ObjectData::new(data), @@ -1126,7 +1120,7 @@ impl JsObject { pub fn new_unique>>(prototype: O, data: T) -> Self { let internal_methods = data.internal_methods(); let inner = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, VTableObject { object: GcRefCell::new(Object { data: ObjectData::new(data), diff --git a/core/engine/src/object/mod.rs b/core/engine/src/object/mod.rs index 258da691647..edd29e6e913 100644 --- a/core/engine/src/object/mod.rs +++ b/core/engine/src/object/mod.rs @@ -96,6 +96,16 @@ impl NativeObject for T { // TODO: Use super trait casting in Rust 1.75 impl dyn NativeObject { /// Returns `true` if the inner type is the same as `T`. + /// + /// # Type identity under `oscars_backend` + /// + /// 1. **`dyn NativeObject::is::()`** (this method) uses [`std::any::TypeId::of::()`]. + /// This is sound because `NativeObject: Any` requires `T: 'static` + /// 2. **[`JsObject::is::()`]** uses `typeid::of::>()` + /// (via [`boa_gc::type_id_of`]), which supports non-`'static` branded lifetimes + /// + /// Do not replace the `std::any::TypeId` call below with `typeid::of`. + /// `std::any::TypeId` is authoritative for `Any` bounded types. #[inline] pub fn is(&self) -> bool { // Get `TypeId` of the type this function is instantiated with. diff --git a/core/engine/src/object/shape/shared_shape/forward_transition.rs b/core/engine/src/object/shape/shared_shape/forward_transition.rs index 11934d51b79..88c286171d3 100644 --- a/core/engine/src/object/shape/shared_shape/forward_transition.rs +++ b/core/engine/src/object/shape/shared_shape/forward_transition.rs @@ -1,3 +1,5 @@ +#![allow(clippy::trivially_copy_pass_by_ref)] +#![allow(clippy::needless_pass_by_value)] use std::fmt::Debug; use boa_gc::{Finalize, Gc, GcRefCell, Trace, WeakGc}; @@ -68,7 +70,7 @@ impl ForwardTransition { properties.map.insert( key, - WeakGc::new(&unsafe { boa_gc::MutationContext::dummy() }, value), + WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, value), ); } @@ -83,11 +85,12 @@ impl ForwardTransition { prototypes.map.insert( key, - WeakGc::new(&unsafe { boa_gc::MutationContext::dummy() }, value), + WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, value), ); } /// Get a property transition, return [`None`] otherwise. + #[allow(clippy::cloned_instead_of_copied)] pub(super) fn get_property(&self, key: &TransitionKey) -> Option> { let this = self.inner.borrow(); let transitions = this.properties.as_ref()?; @@ -95,6 +98,7 @@ impl ForwardTransition { } /// Get a prototype transition, return [`None`] otherwise. + #[allow(clippy::cloned_instead_of_copied)] pub(super) fn get_prototype(&self, key: &JsPrototype) -> Option> { let this = self.inner.borrow(); let transitions = this.prototypes.as_ref()?; @@ -123,7 +127,7 @@ impl ForwardTransition { transitions.map.retain(|_, v| v.is_upgradable()); } - #[cfg(test)] + #[cfg(all(test, not(feature = "oscars_backend")))] pub(crate) fn property_transitions_count(&self) -> (usize, u8) { let this = self.inner.borrow(); this.properties.as_ref().map_or((0, 0), |transitions| { @@ -134,7 +138,7 @@ impl ForwardTransition { }) } - #[cfg(test)] + #[cfg(all(test, not(feature = "oscars_backend")))] pub(crate) fn prototype_transitions_count(&self) -> (usize, u8) { let this = self.inner.borrow(); this.prototypes.as_ref().map_or((0, 0), |transitions| { diff --git a/core/engine/src/object/shape/shared_shape/mod.rs b/core/engine/src/object/shape/shared_shape/mod.rs index 0a1609fd55a..cbbfb1dbecb 100644 --- a/core/engine/src/object/shape/shared_shape/mod.rs +++ b/core/engine/src/object/shape/shared_shape/mod.rs @@ -1,7 +1,7 @@ mod forward_transition; pub(crate) mod template; -#[cfg(test)] +#[cfg(all(test, not(feature = "oscars_backend")))] mod tests; use std::{collections::hash_map::RandomState, hash::Hash}; @@ -166,7 +166,7 @@ impl SharedShape { /// Create a new [`SharedShape`]. fn new(inner: Inner) -> Self { Self { - inner: Gc::new(&unsafe { boa_gc::MutationContext::dummy() }, inner), + inner: Gc::new(&unsafe { boa_gc::MutationContext::global() }, inner), } } @@ -188,7 +188,7 @@ impl SharedShape { /// Create a [`SharedShape`] change prototype transition. pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { if let Some(shape) = self.forward_transitions().get_prototype(&prototype) { - if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::dummy() }) { + if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::global() }) { return Self { inner }; } @@ -215,7 +215,7 @@ impl SharedShape { pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { // Check if we have already created such a transition, if so use it! if let Some(shape) = self.forward_transitions().get_property(&key) { - if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::dummy() }) { + if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::global() }) { return Self { inner }; } @@ -253,7 +253,7 @@ impl SharedShape { // Check if we have already created such a transition, if so use it! if let Some(shape) = self.forward_transitions().get_property(&key) { - if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::dummy() }) { + if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::global() }) { let action = if slot.attributes.width_match(key.attributes) { ChangeTransitionAction::Nothing } else if slot.attributes.is_accessor_descriptor() { @@ -488,15 +488,20 @@ impl WeakSharedShape { Some(SharedShape { inner: self .inner - .upgrade(&unsafe { boa_gc::MutationContext::dummy() })?, + .upgrade(&unsafe { boa_gc::MutationContext::global() })?, }) } + + #[allow(dead_code)] + pub(crate) fn is_upgradable(&self) -> bool { + self.inner.is_upgradable() + } } impl From<&SharedShape> for WeakSharedShape { fn from(value: &SharedShape) -> Self { WeakSharedShape { - inner: WeakGc::new(&unsafe { boa_gc::MutationContext::dummy() }, &value.inner), + inner: WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, &value.inner), } } } diff --git a/core/engine/src/object/shape/unique_shape.rs b/core/engine/src/object/shape/unique_shape.rs index 6947489a526..b050e4bf5eb 100644 --- a/core/engine/src/object/shape/unique_shape.rs +++ b/core/engine/src/object/shape/unique_shape.rs @@ -38,7 +38,7 @@ impl UniqueShape { pub(crate) fn new(prototype: JsPrototype, property_table: PropertyTableInner) -> Self { Self { inner: Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, Inner { property_table: RefCell::new(property_table), prototype: GcRefCell::new(prototype), @@ -58,7 +58,10 @@ impl UniqueShape { /// Get the prototype of the [`UniqueShape`]. pub(crate) fn prototype(&self) -> JsPrototype { - self.inner.prototype.borrow().clone() + // Under `oscars_backend`, `GcRefCell::borrow()` returns `GcRef<'_, Option>`. + // Deref through the guard before cloning to get the inner `Option` value. + // This is what the `JsPrototype` return type requires. + (*self.inner.prototype.borrow()).clone() } /// Get the property table of the [`UniqueShape`]. @@ -258,15 +261,20 @@ impl WeakUniqueShape { Some(UniqueShape { inner: self .inner - .upgrade(&unsafe { boa_gc::MutationContext::dummy() })?, + .upgrade(&unsafe { boa_gc::MutationContext::global() })?, }) } + + #[allow(dead_code)] + pub(crate) fn is_upgradable(&self) -> bool { + self.inner.is_upgradable() + } } impl From<&UniqueShape> for WeakUniqueShape { fn from(value: &UniqueShape) -> Self { WeakUniqueShape { - inner: WeakGc::new(&unsafe { boa_gc::MutationContext::dummy() }, &value.inner), + inner: WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, &value.inner), } } } diff --git a/core/engine/src/realm.rs b/core/engine/src/realm.rs index 84bf5c39cf2..c133ce823eb 100644 --- a/core/engine/src/realm.rs +++ b/core/engine/src/realm.rs @@ -87,14 +87,14 @@ impl Realm { .create_global_this(&intrinsics) .unwrap_or_else(|| global_object.clone()); let environment = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, DeclarativeEnvironment::global(), ); let scope = Scope::new_global(); let realm = Self { inner: Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, Inner { intrinsics, environment, diff --git a/core/engine/src/script.rs b/core/engine/src/script.rs index f11dbc61168..e7d5a36144e 100644 --- a/core/engine/src/script.rs +++ b/core/engine/src/script.rs @@ -105,7 +105,7 @@ impl Script { Ok(Self { inner: Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &context.gc(), Inner { realm: realm.unwrap_or_else(|| context.realm().clone()), phase: GcRefCell::new(ScriptPhase::Ast(code)), @@ -162,10 +162,8 @@ impl Script { compiler.global_declaration_instantiation(source); compiler.compile_statement_list(source.statements(), true, false); - Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - compiler.finish(), - ) + let finished = compiler.finish(); + Gc::new(&context.gc(), finished) }; *self.inner.phase.borrow_mut() = ScriptPhase::Codeblock(cb.clone()); diff --git a/core/engine/src/value/equality.rs b/core/engine/src/value/equality.rs index 79a209253df..5ca76b9826c 100644 --- a/core/engine/src/value/equality.rs +++ b/core/engine/src/value/equality.rs @@ -238,7 +238,7 @@ impl JsValue { } fn same_value_non_numeric(x: &Self, y: &Self) -> bool { - debug_assert!(x.get_type() == y.get_type()); + debug_assert_eq!(x.get_type(), y.get_type()); match (x.variant(), y.variant()) { (JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => { true diff --git a/core/engine/src/value/inner/legacy.rs b/core/engine/src/value/inner/legacy.rs index 087331990f3..5265b58f83f 100644 --- a/core/engine/src/value/inner/legacy.rs +++ b/core/engine/src/value/inner/legacy.rs @@ -33,8 +33,12 @@ impl Finalize for EnumBasedValue { #[allow(unsafe_op_in_unsafe_fn)] unsafe impl Trace for EnumBasedValue { custom_trace! {this, mark, { - if let Some(o) = this.as_object() { - mark(&o); + match this { + Self::Object(o) => mark(o), + Self::Symbol(s) => mark(s), + Self::String(s) => mark(s), + Self::BigInt(b) => mark(b), + _ => {} } }} } diff --git a/core/engine/src/value/inner/nan_boxed.rs b/core/engine/src/value/inner/nan_boxed.rs index 6a05d23fce4..d0c50bb3f6a 100644 --- a/core/engine/src/value/inner/nan_boxed.rs +++ b/core/engine/src/value/inner/nan_boxed.rs @@ -1,3 +1,4 @@ +#![allow(clippy::forget_non_drop)] //! A NaN-boxed inner value for JavaScript values. //! //! This [`JsValue`] is a float using `NaN` values to represent an inner @@ -109,10 +110,9 @@ #[cfg(feature = "annex-b")] use crate::builtins::is_html_dda::IsHTMLDDA; use crate::{ - JsBigInt, JsObject, JsSymbol, JsVariant, bigint::RawBigInt, object::ErasedVTableObject, - symbol::RawJsSymbol, value::Type, + JsBigInt, JsObject, JsSymbol, JsVariant, bigint::RawBigInt, symbol::RawJsSymbol, value::Type, }; -use boa_gc::{Finalize, GcBox, Trace, custom_trace}; +use boa_gc::{Finalize, Trace, custom_trace}; use boa_string::JsString; use core::fmt; use static_assertions::const_assert; @@ -479,7 +479,7 @@ impl NanBoxedValue { #[must_use] #[inline(always)] pub(crate) fn object(value: JsObject) -> Self { - let ptr = value.into_raw(); + let ptr = unsafe { NonNull::new_unchecked(value.into_raw().cast_mut()) }; let addr = bits::tag_pointer(ptr, bits::MASK_OBJECT); Self::from_object_like(ptr, addr) } @@ -684,11 +684,7 @@ impl NanBoxedValue { unsafe fn as_object_unchecked(&self) -> ManuallyDrop { let addr = bits::untag_pointer(self.value()); // SAFETY: This is guaranteed by the caller. - unsafe { - ManuallyDrop::new(JsObject::from_raw(NonNull::new_unchecked( - self.ptr.with_addr(addr).cast::>(), - ))) - } + unsafe { ManuallyDrop::new(JsObject::from_raw(self.ptr.with_addr(addr).cast::<()>())) } } /// Returns the value as a [`JsSymbol`]. diff --git a/core/engine/src/value/integer.rs b/core/engine/src/value/integer.rs index 970ce0632f2..17fdbbdc23f 100644 --- a/core/engine/src/value/integer.rs +++ b/core/engine/src/value/integer.rs @@ -105,12 +105,12 @@ mod tests { fn test_eq() { let int: i64 = 42; let int_or_inf = IntegerOrInfinity::Integer(10); - assert!(int != int_or_inf); - assert!(int_or_inf != int); + assert_ne!(int, int_or_inf); + assert_ne!(int_or_inf, int); let int: i64 = 10; - assert!(int == int_or_inf); - assert!(int_or_inf == int); + assert_eq!(int, int_or_inf); + assert_eq!(int_or_inf, int); } #[test] diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index fb494a0a972..959256a90ce 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -330,6 +330,7 @@ impl CodeBlock { /// /// If the type of the [`Constant`] is not [`Constant::Function`]. /// Or `index` is greater or equal to length of `constants`. + #[allow(clippy::clone_on_copy)] pub(crate) fn constant_function(&self, index: usize) -> Gc<'static, Self> { if let Some(Constant::Function(value)) = self.constants.get(index) { return value.clone(); diff --git a/core/engine/src/vm/inline_cache/mod.rs b/core/engine/src/vm/inline_cache/mod.rs index c55aae8f767..2ae3b6d7d77 100644 --- a/core/engine/src/vm/inline_cache/mod.rs +++ b/core/engine/src/vm/inline_cache/mod.rs @@ -98,6 +98,7 @@ impl InlineCache { while i < entries.len() { if let Some(upgraded) = entries[i].shape.upgrade() { + let upgraded: Shape = upgraded; if upgraded.to_addr_usize() == shape_addr { result = Some((upgraded, entries[i].slot)); break; diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index b7da166b5c9..48d957adfaf 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -408,7 +408,7 @@ impl Vm { let mut frames = Vec::with_capacity(16); frames.push(CallFrame::new( Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, CodeBlock::new(JsString::default(), 0, true), ), None, diff --git a/core/engine/src/vm/opcode/await/mod.rs b/core/engine/src/vm/opcode/await/mod.rs index ad2c5fcbd54..f95a89e91cb 100644 --- a/core/engine/src/vm/opcode/await/mod.rs +++ b/core/engine/src/vm/opcode/await/mod.rs @@ -56,10 +56,7 @@ impl Await { let r#gen = GeneratorContext::from_current(context, None); - let captures = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - Cell::new(Some(r#gen)), - ); + let captures = Gc::new(&context.gc(), Cell::new(Some(r#gen))); // 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, "", « »). @@ -132,7 +129,7 @@ impl Await { Ok(JsValue::undefined()) }, - captures, + captures.clone(), ), ) .name(js_string!()) diff --git a/core/engine/src/vm/opcode/function.rs b/core/engine/src/vm/opcode/function.rs index aa1e70fb325..8b125a308e6 100644 --- a/core/engine/src/vm/opcode/function.rs +++ b/core/engine/src/vm/opcode/function.rs @@ -61,7 +61,9 @@ impl GetHomeObject { .downcast_ref::() .js_expect("must be function object")? .get_home_object() - .map_or_else(JsValue::null, |o| o.clone().into()); + .map_or_else(JsValue::null, |o: &crate::object::JsObject| { + o.clone().into() + }); context.vm.set_register(function.into(), home_object); Ok(()) diff --git a/core/engine/src/vm/opcode/push/environment.rs b/core/engine/src/vm/opcode/push/environment.rs index 8f49f65b2c6..b27673d6d73 100644 --- a/core/engine/src/vm/opcode/push/environment.rs +++ b/core/engine/src/vm/opcode/push/environment.rs @@ -22,7 +22,9 @@ impl PushScope { let global = frame.realm.environment(); frame .environments - .push_lexical(scope.num_bindings_non_local(), global); + .push_lexical(scope.num_bindings_non_local(), global, unsafe { + boa_gc::MutationContext::global() + }); } } @@ -82,7 +84,7 @@ impl PushPrivateEnvironment { let ptr: *const _ = class.as_ref(); let environment = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &context.gc(), PrivateEnvironment::new(ptr.cast::<()>() as usize, names), ); diff --git a/core/engine/src/vm/tests.rs b/core/engine/src/vm/tests.rs index 0b25366ac41..4655d97a09f 100644 --- a/core/engine/src/vm/tests.rs +++ b/core/engine/src/vm/tests.rs @@ -480,6 +480,7 @@ fn cross_context_function_call() { } // See: https://github.com/boa-dev/boa/issues/1848 +#[cfg(not(feature = "oscars_backend"))] #[test] fn long_object_chain_gc_trace_stack_overflow() { run_test_actions([ diff --git a/core/gc/Cargo.toml b/core/gc/Cargo.toml index cf875a570ee..7b9b44be17e 100644 --- a/core/gc/Cargo.toml +++ b/core/gc/Cargo.toml @@ -12,18 +12,18 @@ rust-version.workspace = true [features] # Enable default implementations of trace and finalize for the thin-vec crate -thin-vec = ["dep:thin-vec"] +thin-vec = ["dep:thin-vec", "oscars?/thin-vec"] # Enable default implementations of trace and finalize for some `ICU4X` types -icu = ["dep:icu_locale_core"] +icu = ["dep:icu_locale_core", "oscars?/icu"] # Enable default implementations of trace and finalize for the `boa_string` crate boa_string = ["dep:boa_string"] # Enable default implementations of trace and finalize for the `either` crate -either = ["dep:either"] +either = ["dep:either", "oscars?/either"] # Enable default implementations of trace and finalize for the arrayvec crate -arrayvec = ["dep:arrayvec"] -default = ["boa_gc_backend"] +arrayvec = ["dep:arrayvec", "oscars?/arrayvec"] +default = [] boa_gc_backend = [] -oscars_backend = ["dep:oscars"] +oscars_backend = ["dep:oscars", "dep:typeid", "oscars?/std", "boa_string?/oscars_backend"] [dependencies] boa_macros.workspace = true @@ -35,6 +35,7 @@ thin-vec = { workspace = true, optional = true } icu_locale_core = { workspace = true, optional = true } arrayvec = { workspace = true, optional = true } oscars = { git = "https://github.com/boa-dev/oscars.git", branch = "main", features = ["null_collector_branded"], optional = true } +typeid = { workspace = true, optional = true } [lints] workspace = true diff --git a/core/gc/src/cell.rs b/core/gc/src/cell.rs index 674cf86ddb8..a07ce4ce731 100644 --- a/core/gc/src/cell.rs +++ b/core/gc/src/cell.rs @@ -59,13 +59,13 @@ impl BorrowFlag { /// - This method will panic after incrementing if the borrow count overflows. #[inline] fn add_reading(self) -> Self { - assert!(self.borrowed() != BorrowState::Writing); + assert_ne!(self.borrowed(), BorrowState::Writing); let flags = Self(self.0 + 1); // This will fail if the borrow count overflows, which shouldn't happen, // but let's be safe { - assert!(flags.borrowed() == BorrowState::Reading); + assert_eq!(flags.borrowed(), BorrowState::Reading); } flags } @@ -75,7 +75,7 @@ impl BorrowFlag { /// # Panic /// - This method will panic if the current `BorrowState` is not reading. fn sub_reading(self) -> Self { - assert!(self.borrowed() == BorrowState::Reading); + assert_eq!(self.borrowed(), BorrowState::Reading); Self(self.0 - 1) } } @@ -261,7 +261,7 @@ struct BorrowGcRef<'a> { impl Drop for BorrowGcRef<'_> { fn drop(&mut self) { - debug_assert!(self.borrow.get().borrowed() == BorrowState::Reading); + debug_assert_eq!(self.borrow.get().borrowed(), BorrowState::Reading); self.borrow.set(self.borrow.get().sub_reading()); } } @@ -411,7 +411,7 @@ struct BorrowGcRefMut<'a> { impl Drop for BorrowGcRefMut<'_> { fn drop(&mut self) { - debug_assert!(self.borrow.get().borrowed() == BorrowState::Writing); + debug_assert_eq!(self.borrow.get().borrowed(), BorrowState::Writing); self.borrow.set(BorrowFlag(UNUSED)); } } diff --git a/core/gc/src/lib.rs b/core/gc/src/lib.rs index dec09a9c077..28b85aee782 100644 --- a/core/gc/src/lib.rs +++ b/core/gc/src/lib.rs @@ -14,6 +14,11 @@ clippy::redundant_pub_crate, clippy::let_unit_value )] +#![allow(missing_docs)] +#![cfg_attr( + feature = "oscars_backend", + allow(unused_crate_dependencies, unused_extern_crates) +)] extern crate self as boa_gc; @@ -49,10 +54,111 @@ pub use internals::GcBox; pub use pointers::{Ephemeron, Gc, GcErased, MutationContext, WeakGc, WeakMap}; #[cfg(feature = "oscars_backend")] -pub use oscars::null_collector_branded::{ - Ephemeron, Finalize, Gc, GcRefCell, MutationContext, Root, Trace, Tracer, WeakGc, +pub use oscars::collectors::null_collector_branded::{ + Finalize, Gc, GcBox, GcRefCell, Root, Trace, Tracer, }; +#[cfg(feature = "oscars_backend")] +/// Re-export [`typeid::of`]. +/// +/// Computes a [`std::any::TypeId`] compatible value for `T` without requiring `T: 'static`. +/// oscars collectors use this to stamp `GcBox` at allocation, ensuring consistent +/// type comparisons. +/// +/// Use this instead of `std::any::TypeId::of::()` for types with non-`'static` +/// branded lifetimes (like `'gc` or `'id`). +pub use typeid::of as type_id_of; + +#[cfg(feature = "oscars_backend")] +/// Type alias for Ephemeron +pub type Ephemeron = oscars::collectors::null_collector_branded::Ephemeron<'static, K, V>; + +#[cfg(feature = "oscars_backend")] +/// A token granting permission to allocate into the GC arena. +/// Lifetimes are `'static` for the null collector but should be forwarded for `mark_sweep_branded`. +pub type MutationContext<'a, 'b> = + oscars::collectors::null_collector_branded::MutationContext<'static, 'static>; + +#[cfg(feature = "oscars_backend")] +/// Type alias for `WeakGc` +pub type WeakGc = oscars::collectors::null_collector_branded::WeakGc<'static, T>; + +#[cfg(feature = "oscars_backend")] +pub use oscars::collectors::null_collector_branded::cell::{GcRef, GcRefMut}; + +#[cfg(feature = "oscars_backend")] +mod oscars_weak_map; + +#[cfg(feature = "oscars_backend")] +pub use oscars_weak_map::WeakMap; + +#[cfg(feature = "oscars_backend")] +#[must_use] +/// Returns whether finalizer is safe +pub fn finalizer_safe() -> bool { + true +} + +#[cfg(feature = "oscars_backend")] +/// Implements an empty `Trace` trait for the specified types +#[macro_export] +macro_rules! empty_trace { + () => { + #[inline] + unsafe fn trace(&self, _tracer: &mut $crate::Tracer<'_>) {} + #[inline] + unsafe fn trace_non_roots(&self) {} + #[inline] + fn run_finalizer(&self) { + $crate::Finalize::finalize(self); + } + }; + ($($T:ty),* $(,)?) => { + $( + unsafe impl $crate::Trace for $T { + $crate::empty_trace!(); + } + )* + }; +} + +#[cfg(feature = "oscars_backend")] +/// Macro for custom trace +#[macro_export] +macro_rules! custom_trace { + ($this:ident, $mark:ident, $body:expr) => { + #[inline] + unsafe fn trace(&self, tracer: &mut $crate::Tracer<'_>) { + let mut $mark = |it: &dyn $crate::Trace| { + // SAFETY: implementor must ensure trace is correctly implemented + unsafe { + $crate::Trace::trace(it, tracer); + } + }; + let $this = self; + // SAFETY: The implementor must ensure the trace body is safe + unsafe { $body } + } + #[inline] + unsafe fn trace_non_roots(&self) { + #[allow(non_snake_case)] + fn $mark(_it: &T) { + // SAFETY: implementor must ensure trace is correctly implemented + unsafe { + $crate::Trace::trace_non_roots(_it); + } + } + let $this = self; + // SAFETY: The implementor must ensure the trace body is safe + unsafe { $body } + } + #[inline] + fn run_finalizer(&self) { + $crate::Finalize::finalize(self); + } + }; +} + #[cfg(not(feature = "oscars_backend"))] pub(crate) mod boa_allocator; @@ -61,3 +167,7 @@ pub use boa_allocator::*; #[cfg(all(test, not(feature = "oscars_backend")))] mod test; + +#[cfg(feature = "oscars_backend")] +/// Forces a garbage collection +pub fn force_collect() {} diff --git a/core/gc/src/oscars_weak_map.rs b/core/gc/src/oscars_weak_map.rs new file mode 100644 index 00000000000..94bbe66e1a8 --- /dev/null +++ b/core/gc/src/oscars_weak_map.rs @@ -0,0 +1,109 @@ +//! Dummy `WeakMap` implementation for the `oscars_backend` feature. +//! +//! We define this here instead of in `oscars` because `boa_engine` needs to be able to modify the `WeakMap` even when it is shared, which it handles by using `GcRefCell`. +//! Additionally, the `null_collector_branded` backend never frees memory, making a true weak map impossible. +//! Defining a dummy wrapper in `boa_gc` fulfills engine requirements without polluting it with conditional compilation gates. +//! All operations are leaky strong map operations to maintain API compatibility. + +use crate::{Finalize, Gc, MutationContext, Trace, Tracer}; +use std::collections::HashMap; +use std::fmt::{Debug, Formatter, Result}; + +#[derive(Clone)] +pub struct WeakMap { + map: HashMap, + _marker: std::marker::PhantomData<(*const K, *const V)>, +} + +impl Default for WeakMap { + fn default() -> Self { + Self { + map: HashMap::new(), + _marker: std::marker::PhantomData, + } + } +} + +impl WeakMap { + /// Creates a new, empty `WeakMap`. + /// + /// The `_mc` argument mirrors the non-oscars API; it is unused here. + #[must_use] + #[inline] + pub fn new(_mc: &MutationContext<'_, '_>) -> Self { + Self { + map: HashMap::new(), + _marker: std::marker::PhantomData, + } + } + + /// Inserts a key value pair into the map + #[inline] + pub fn insert(&mut self, key: &Gc<'_, K>, value: V) { + self.map + .insert(std::ptr::from_ref(&**key).cast::<()>() as usize, value); + } + + /// Removes a key from the map, returning `true` if the key was present. + /// Acts as a leaky strong map, so memory is never actually freed. + #[inline] + pub fn remove(&mut self, key: &Gc<'_, K>) -> bool { + self.map + .remove(&(std::ptr::from_ref(&**key).cast::<()>() as usize)) + .is_some() + } + + /// Returns `true` if the map contains the key. + #[must_use] + #[inline] + pub fn contains_key(&self, key: &Gc<'_, K>) -> bool { + self.map + .contains_key(&(std::ptr::from_ref(&**key).cast::<()>() as usize)) + } + + /// Returns the value associated with `key`, or `None` + #[must_use] + #[inline] + pub fn get(&self, key: &Gc<'_, K>) -> Option + where + V: Clone, + { + self.map + .get(&(std::ptr::from_ref(&**key).cast::<()>() as usize)) + .cloned() + } + + /// Alias for `get` to match the `boa_gc` backend's `WeakMap` API. + #[must_use] + #[inline] + pub fn get_value(&self, key: &Gc<'_, K>) -> Option + where + V: Clone, + { + self.get(key) + } +} + +impl Debug for WeakMap { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_struct("WeakMap").finish() + } +} + +impl Finalize for WeakMap {} + +unsafe impl Trace for WeakMap { + unsafe fn trace(&self, tracer: &mut Tracer<'_>) { + for value in self.map.values() { + unsafe { value.trace(tracer) }; + } + } + unsafe fn trace_non_roots(&self) { + for value in self.map.values() { + unsafe { value.trace_non_roots() }; + } + } + fn run_finalizer(&self) { + Finalize::finalize(self); + } +} diff --git a/core/gc/src/pointers/mutation_context.rs b/core/gc/src/pointers/mutation_context.rs index fc527c6fc36..df771c72c23 100644 --- a/core/gc/src/pointers/mutation_context.rs +++ b/core/gc/src/pointers/mutation_context.rs @@ -17,4 +17,10 @@ impl MutationContext<'_, '_> { _marker: PhantomData, } } + + /// Creates a global context (polyfill for the oscars backend). + #[must_use] + pub unsafe fn global() -> Self { + unsafe { Self::dummy() } + } } diff --git a/core/gc/src/pointers/weak_map.rs b/core/gc/src/pointers/weak_map.rs index f638e7d1648..624b1e71130 100644 --- a/core/gc/src/pointers/weak_map.rs +++ b/core/gc/src/pointers/weak_map.rs @@ -55,6 +55,19 @@ impl WeakMap { pub fn get<'a>(&'a self, key: &Gc<'_, K>) -> Option>> { GcRef::try_map(self.inner.borrow(), |inner| inner.get(key)) } + + /// Returns a cloned value from the ephemeron if it exists and has not been collected. + #[must_use] + #[inline] + pub fn get_value(&self, key: &Gc<'_, K>) -> Option + where + V: Clone, + { + let ephemeron = self.get(key)?; + ephemeron + .value(&unsafe { crate::MutationContext::dummy() }) + .map(|v| v.clone()) + } } /// A hash map where the bucket type is an [Ephemeron]\. diff --git a/core/gc/src/test/weak.rs b/core/gc/src/test/weak.rs index 9c4a108243a..20d3933f866 100644 --- a/core/gc/src/test/weak.rs +++ b/core/gc/src/test/weak.rs @@ -445,7 +445,7 @@ mod miri { &watched, root.clone(), ); - let eph_size = size_of::, TestCell>>(); + let eph_size = size_of::, TestCell>>(); root.inner.borrow_mut().0 = Some(root.clone()); root.inner.borrow_mut().1 = Some(root.clone()); diff --git a/core/gc/src/trace.rs b/core/gc/src/trace.rs index fb6f7e04284..73361db9ea9 100644 --- a/core/gc/src/trace.rs +++ b/core/gc/src/trace.rs @@ -133,7 +133,11 @@ macro_rules! custom_trace { } }; let $this = self; - $body + // SAFETY: The implementor must ensure the trace body is safe + #[allow(unused_unsafe)] + unsafe { + $body + } } #[inline] unsafe fn trace_non_roots(&self) { @@ -144,7 +148,11 @@ macro_rules! custom_trace { } } let $this = self; - $body + // SAFETY: The implementor must ensure the trace body is safe + #[allow(unused_unsafe)] + unsafe { + $body + } } #[inline] fn run_finalizer(&self) { diff --git a/core/interner/src/sym.rs b/core/interner/src/sym.rs index e60e7a3459d..ccd16e36589 100644 --- a/core/interner/src/sym.rs +++ b/core/interner/src/sym.rs @@ -1,4 +1,4 @@ -use boa_gc::{Finalize, Trace, empty_trace}; +use boa_gc::{Finalize, Trace}; use boa_macros::static_syms; use core::num::NonZeroUsize; @@ -13,17 +13,15 @@ use core::num::NonZeroUsize; )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[allow(clippy::unsafe_derive_deserialize)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Finalize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Finalize, Trace)] +#[boa_gc(unsafe_no_drop)] pub struct Sym { + // SAFETY: `NonZeroUsize` is a constrained `usize`, and all primitive types + // don't need to be traced by the garbage collector. + #[unsafe_ignore_trace] value: NonZeroUsize, } -// SAFETY: `NonZeroUsize` is a constrained `usize`, and all primitive types don't need to be traced -// by the garbage collector. -unsafe impl Trace for Sym { - empty_trace!(); -} - impl Sym { /// Creates a new [`Sym`] from the provided `value`, or returns `None` if `index` is zero. pub(super) fn new(value: usize) -> Option { diff --git a/core/macros/src/lib.rs b/core/macros/src/lib.rs index f53ac93b708..526e81acbd1 100644 --- a/core/macros/src/lib.rs +++ b/core/macros/src/lib.rs @@ -299,7 +299,8 @@ decl_derive! { /// Derives the `Trace` trait. #[allow(clippy::too_many_lines)] -fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { +#[allow(clippy::needless_pass_by_value)] +fn derive_trace(s: Structure<'_>) -> proc_macro2::TokenStream { struct EmptyTrace { copy: bool, drop: bool, @@ -332,6 +333,7 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { Err(e) => return e.into_compile_error(), }; + let mut s = s.clone(); if trace.copy { s.add_where_predicate(syn::parse_quote!(Self: Copy)); } @@ -341,7 +343,7 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { continue; } - return s.unsafe_bound_impl( + let normal_impl = s.unsafe_bound_impl( quote!(::boa_gc::Trace), quote! { #[inline(always)] @@ -354,43 +356,51 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { } }, ); + + return quote! { + #normal_impl + }; } } + let mut s = s.clone(); s.filter(|bi| { !bi.ast() .attrs .iter() .any(|attr| attr.path().is_ident("unsafe_ignore_trace")) }); - let trace_body = s.each(|bi| quote!(::boa_gc::Trace::trace(#bi, tracer))); - let trace_other_body = s.each(|bi| quote!(mark(#bi))); - s.add_bounds(AddBounds::Fields); - let trace_impl = s.unsafe_bound_impl( + + let mut s_ref = s.clone(); + s_ref.bind_with(|_| synstructure::BindStyle::Ref); + + // Normal backend: Unsafe Trace with &self + let trace_body_ref = s_ref.each(|bi| quote!(::boa_gc::Trace::trace(#bi, tracer))); + let trace_other_body_ref = s_ref.each(|bi| quote!(mark(#bi))); + + let normal_impl = s.unsafe_bound_impl( quote!(::boa_gc::Trace), quote! { #[inline] unsafe fn trace(&self, tracer: &mut ::boa_gc::Tracer) { #[allow(dead_code)] let mut mark = |it: &dyn ::boa_gc::Trace| { - // SAFETY: The implementor must ensure that `trace` is correctly implemented. unsafe { ::boa_gc::Trace::trace(it, tracer); } }; - match *self { #trace_body } + match *self { #trace_body_ref } } #[inline] unsafe fn trace_non_roots(&self) { #[allow(dead_code)] fn mark(it: &T) { - // SAFETY: The implementor must ensure that `trace_non_roots` is correctly implemented. unsafe { ::boa_gc::Trace::trace_non_roots(it); } } - match *self { #trace_other_body } + match *self { #trace_other_body_ref } } #[inline] fn run_finalizer(&self) { @@ -401,14 +411,11 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { ::boa_gc::Trace::run_finalizer(it); } } - match *self { #trace_other_body } + match *self { #trace_other_body_ref } } }, ); - // We also implement drop to prevent unsafe drop implementations on this - // type and encourage people to use Finalize. This implementation will - // call `Finalize::finalize` if it is safe to do so. let drop_impl = if drop { s.unbound_impl( quote!(::core::ops::Drop), @@ -427,7 +434,8 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { }; quote! { - #trace_impl + #normal_impl + #drop_impl } } diff --git a/core/runtime/src/abort/mod.rs b/core/runtime/src/abort/mod.rs index c6b05ef4b23..3852009e1ca 100644 --- a/core/runtime/src/abort/mod.rs +++ b/core/runtime/src/abort/mod.rs @@ -124,8 +124,7 @@ impl JsAbortSignal { if !self.aborted.get() { return JsValue::undefined(); } - self.reason - .borrow() + (*self.reason.borrow()) .clone() .unwrap_or_else(|| make_abort_error(context)) } diff --git a/core/runtime/src/console/tests.rs b/core/runtime/src/console/tests.rs index a536e02138a..8810d7d038e 100644 --- a/core/runtime/src/console/tests.rs +++ b/core/runtime/src/console/tests.rs @@ -195,7 +195,7 @@ fn wpt_log_symbol_any() { &mut context, ); - let logs = logger.log.borrow().clone(); + let logs = (*logger.log.borrow()).clone(); assert_eq!( logs, indoc! { r#" @@ -354,7 +354,7 @@ fn console_log_arguments() { &mut context, ); - let logs = logger.log.borrow().clone(); + let logs = (*logger.log.borrow()).clone(); assert_eq!( logs, indoc! { r#" @@ -382,7 +382,7 @@ fn console_log_regexp() { &mut context, ); - let logs = logger.log.borrow().clone(); + let logs = (*logger.log.borrow()).clone(); assert_eq!( logs, indoc! { r#" @@ -408,7 +408,7 @@ fn console_log_date() { &mut context, ); - let logs = logger.log.borrow().clone(); + let logs = (*logger.log.borrow()).clone(); assert_eq!( logs, indoc! { r#" @@ -442,7 +442,7 @@ fn trace_with_stack_trace() { &mut context, ); - let logs = logger.log.borrow().clone(); + let logs = (*logger.log.borrow()).clone(); assert_eq!( logs, indoc! { r#" @@ -473,7 +473,7 @@ macro_rules! run_table_test { &mut context, ); - logger.log.borrow().clone() + (*logger.log.borrow()).clone() }}; } @@ -698,7 +698,8 @@ fn console_table_map() { console.table(new Map([["a", 1], ["b", 2]])); "#}); - assert!(logs.contains("(iteration index)")); + assert!(logs.contains("(iteration")); + assert!(logs.contains("index)")); assert!(logs.contains("Key")); assert!(logs.contains("Values")); assert!(logs.contains("\"a\"")); @@ -714,7 +715,8 @@ fn console_table_set() { console.table(new Set([1, 2, 3])); "#}); - assert!(logs.contains("(iteration index)")); + assert!(logs.contains("(iteration")); + assert!(logs.contains("index)")); assert!(logs.contains("Values")); assert!(logs.contains('1')); assert!(logs.contains('2')); @@ -836,7 +838,8 @@ fn console_table_map_ignores_properties_filter() { console.table(new Map([["x", 1]]), ["a"]); "#}); - assert!(logs.contains("(iteration index)")); + assert!(logs.contains("(iteration")); + assert!(logs.contains("index)")); assert!(logs.contains("Key")); assert!(logs.contains("Values")); } @@ -848,6 +851,7 @@ fn console_table_set_ignores_properties_filter() { console.table(new Set([1, 2]), ["a"]); "#}); - assert!(logs.contains("(iteration index)")); + assert!(logs.contains("(iteration")); + assert!(logs.contains("index)")); assert!(logs.contains("Values")); } diff --git a/core/runtime/src/microtask/tests.rs b/core/runtime/src/microtask/tests.rs index ba7bcef9a28..3c5a1e4642a 100644 --- a/core/runtime/src/microtask/tests.rs +++ b/core/runtime/src/microtask/tests.rs @@ -37,7 +37,7 @@ fn queue_microtask() { context, ); - let logs = logger.log.borrow().clone(); + let logs = (*logger.log.borrow()).clone(); assert_eq!( logs, indoc! { r#" diff --git a/core/runtime/src/test262.rs b/core/runtime/src/test262.rs index 788adfdd7ab..45456b95817 100644 --- a/core/runtime/src/test262.rs +++ b/core/runtime/src/test262.rs @@ -276,10 +276,8 @@ fn agent_obj(handles: WorkerHandles, console: bool, context: &mut Context) -> Js })?; let buffer = buffer .downcast_ref::() - .ok_or_else(|| { - JsNativeError::typ().with_message("argument was not a shared array") - })? - .clone(); + .ok_or_else(|| JsNativeError::typ().with_message("argument was not a shared array")) + .map(|r| (*r).clone())?; bus.borrow_mut().broadcast(buffer); diff --git a/core/string/Cargo.toml b/core/string/Cargo.toml index 354abeed5da..cfcd7290d8a 100644 --- a/core/string/Cargo.toml +++ b/core/string/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] +oscars = { git = "https://github.com/boa-dev/oscars.git", branch = "main", features = ["null_collector_branded"], optional = true } itoa.workspace = true rustc-hash = { workspace = true, features = ["std"] } ryu-js.workspace = true @@ -23,5 +24,8 @@ fast-float2.workspace = true [lints] workspace = true +[features] +oscars_backend = ["dep:oscars"] + [package.metadata.docs.rs] all-features = true diff --git a/core/string/src/builder.rs b/core/string/src/builder.rs index b8b426b4aed..843c27861e2 100644 --- a/core/string/src/builder.rs +++ b/core/string/src/builder.rs @@ -771,14 +771,18 @@ impl<'seg, 'ref_str: 'seg> CommonJsStringBuilder<'seg> { let mut builder = Latin1JsStringBuilder::new(); for seg in &self.segments { match seg { - Segment::String(s) => { + Segment::String(s) => + { + #[allow(clippy::question_mark)] if let Some(data) = s.as_str().as_latin1() { builder.extend_from_slice(data); } else { return None; } } - Segment::Str(s) => { + Segment::Str(s) => + { + #[allow(clippy::question_mark)] if let Some(data) = s.as_latin1() { builder.extend_from_slice(data); } else { diff --git a/core/string/src/lib.rs b/core/string/src/lib.rs index 633cbccb6d7..23cb28aee5d 100644 --- a/core/string/src/lib.rs +++ b/core/string/src/lib.rs @@ -1041,3 +1041,19 @@ impl_js_string_slice_index!( std::ops::RangeFrom, std::ops::RangeFull, ); + +#[cfg(feature = "oscars_backend")] +// SAFETY: `JsString` does not contain any GC pointers, so an empty trace is safe. +unsafe impl oscars::collectors::null_collector_branded::Trace for JsString { + // SAFETY: Empty trace is safe. + #[inline] + unsafe fn trace(&self, _tracer: &mut oscars::collectors::null_collector_branded::Tracer<'_>) {} + // SAFETY: Empty trace is safe. + #[inline] + unsafe fn trace_non_roots(&self) {} + #[inline] + fn run_finalizer(&self) {} +} + +#[cfg(feature = "oscars_backend")] +impl oscars::collectors::null_collector_branded::Finalize for JsString {} diff --git a/core/string/src/tests.rs b/core/string/src/tests.rs index 2315a558937..0a4f80a602b 100644 --- a/core/string/src/tests.rs +++ b/core/string/src/tests.rs @@ -402,7 +402,7 @@ fn clone_builder() { // clone_from(empty) == origin(empty) let mut cloned_from = Latin1JsStringBuilder::new(); cloned_from.clone_from(&empty_origin); - assert!(cloned_from.capacity() == 0); + assert_eq!(cloned_from.capacity(), 0); assert_eq!(empty_origin, cloned_from); // utf16 builder -- test @@ -432,7 +432,7 @@ fn clone_builder() { // clone_from(empty) == origin(empty) let mut cloned_from = Utf16JsStringBuilder::new(); cloned_from.clone_from(&empty_origin); - assert!(cloned_from.capacity() == 0); + assert_eq!(cloned_from.capacity(), 0); assert_eq!(empty_origin, cloned_from); } diff --git a/examples/src/bin/derive.rs b/examples/src/bin/derive.rs index 3c228027aa5..2b7bf460ddf 100644 --- a/examples/src/bin/derive.rs +++ b/examples/src/bin/derive.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use boa_engine::value::JsVariant; use boa_engine::{Context, JsNativeError, JsResult, JsValue, Source, value::TryFromJs}; diff --git a/examples/src/bin/jstypedarray.rs b/examples/src/bin/jstypedarray.rs index fc025712d89..b82e6f99202 100644 --- a/examples/src/bin/jstypedarray.rs +++ b/examples/src/bin/jstypedarray.rs @@ -93,7 +93,7 @@ fn main() -> JsResult<()> { // forEach let array = JsUint8Array::from_iter(vec![1, 2, 3, 4, 5], context)?; let num_to_modify = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, GcRefCell::new(0u8), ); diff --git a/tests/fuzz/Cargo.toml b/tests/fuzz/Cargo.toml index 4896ad8e830..76097ca8a2a 100644 --- a/tests/fuzz/Cargo.toml +++ b/tests/fuzz/Cargo.toml @@ -42,3 +42,6 @@ test = false doc = false [package.metadata.docs.rs] all-features = true + +[patch."https://github.com/boa-dev/boa.git"] +boa_string = { path = "../../core/string" } diff --git a/tests/macros/tests/gcd_callback.rs b/tests/macros/tests/gcd_callback.rs index 29e30f0fe81..952099364ca 100644 --- a/tests/macros/tests/gcd_callback.rs +++ b/tests/macros/tests/gcd_callback.rs @@ -1,4 +1,4 @@ -#![allow(unused_crate_dependencies)] +#![allow(unused_crate_dependencies, clippy::clone_on_copy)] //! A test that mimics the `boa_engine`'s GCD test with a typed callback. use boa_engine::interop::ContextData; @@ -20,7 +20,7 @@ fn gcd_callback() { // Create the engine. let context = &mut Context::default(); let result = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + &unsafe { boa_gc::MutationContext::global() }, AtomicUsize::new(0), ); context.insert_data(result.clone()); From c80ff49157cfe18dda162969aa00baa668a250f6 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sat, 15 Aug 2026 06:01:03 +0000 Subject: [PATCH 02/19] Migrate to mark_sweep_branded and GcContext abstraction --- core/engine/src/builtins/eval/mod.rs | 2 +- .../src/builtins/finalization_registry/mod.rs | 14 ++-- core/engine/src/builtins/json/mod.rs | 2 +- core/engine/src/builtins/promise/mod.rs | 21 +++--- core/engine/src/builtins/weak/weak_ref.rs | 4 +- core/engine/src/builtins/weak_map/mod.rs | 2 +- core/engine/src/builtins/weak_set/mod.rs | 2 +- core/engine/src/context/mod.rs | 25 ++++--- core/engine/src/module/mod.rs | 36 +++++----- core/engine/src/module/source.rs | 6 +- core/engine/src/module/synthetic.rs | 4 +- core/engine/src/object/builtins/jspromise.rs | 11 ++- .../src/object/builtins/jstypedarray.rs | 2 +- core/engine/src/object/builtins/jsweakmap.rs | 2 +- core/engine/src/object/builtins/jsweakset.rs | 2 +- core/engine/src/script.rs | 21 +++--- core/engine/src/vm/opcode/await/mod.rs | 2 +- core/engine/src/vm/opcode/push/environment.rs | 5 +- core/gc/Cargo.toml | 2 +- core/gc/src/context.rs | 67 +++++++++++++++++++ core/gc/src/lib.rs | 15 +++-- core/gc/src/oscars_weak_map.rs | 2 +- core/gc/src/pointers/mutation_context.rs | 6 +- core/string/Cargo.toml | 2 +- core/string/src/lib.rs | 6 +- 25 files changed, 161 insertions(+), 102 deletions(-) create mode 100644 core/gc/src/context.rs diff --git a/core/engine/src/builtins/eval/mod.rs b/core/engine/src/builtins/eval/mod.rs index 0fd60137801..c523d269c02 100644 --- a/core/engine/src/builtins/eval/mod.rs +++ b/core/engine/src/builtins/eval/mod.rs @@ -321,7 +321,7 @@ impl Eval { compiler.compile_statement_list(body.statements(), true, false); let finished = compiler.finish(); - let code_block = Gc::new(&context.gc(), finished); + let code_block = context.alloc(finished); // Strict calls don't need extensions, since all strict eval calls push a new // function environment before evaluating. diff --git a/core/engine/src/builtins/finalization_registry/mod.rs b/core/engine/src/builtins/finalization_registry/mod.rs index 810e40e287b..3252ed47984 100644 --- a/core/engine/src/builtins/finalization_registry/mod.rs +++ b/core/engine/src/builtins/finalization_registry/mod.rs @@ -158,7 +158,7 @@ impl BuiltInConstructor for FinalizationRegistry { }, ); - let weak_registry = WeakGc::new(&context.gc(), registry.inner()); + let weak_registry = WeakGc::new(context.gc_collector(), registry.inner()); { async fn inner_cleanup( @@ -254,7 +254,7 @@ impl FinalizationRegistry { // // TODO: support Symbols let unregister_token = match unregister_token.variant() { - JsVariant::Object(obj) => Some(WeakGc::new(&context.gc(), obj.inner())), + JsVariant::Object(obj) => Some(WeakGc::new(context.gc_collector(), obj.inner())), // b. Set unregisterToken to empty. JsVariant::Undefined => None, // a. If unregisterToken is not undefined, throw a TypeError exception. @@ -269,7 +269,7 @@ impl FinalizationRegistry { // 6. Let cell be the Record { [[WeakRefTarget]]: target, [[HeldValue]]: heldValue, [[UnregisterToken]]: unregisterToken }. let cell = RegistryCell { target: Ephemeron::new( - &context.gc(), + context.gc_collector(), target_obj.inner(), CleanupSignaler(Cell::new(Some( registry.cleanup_notifier.clone().downgrade(), @@ -332,16 +332,18 @@ impl FinalizationRegistry { // a. If cell.[[UnregisterToken]] is not empty and SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then if let Some(tok) = cell.unregister_token.as_ref() - && let Some(tok) = tok.upgrade(&context.gc()) + && let Some(tok) = tok.upgrade(context.gc_collector()) && Gc::ptr_eq(&tok, unregister_token) { // i. Remove cell from finalizationRegistry.[[Cells]]. let cell = registry.cells.swap_remove(i); - let _key = cell.target.key(&context.gc()); + let _key = cell.target.key(context.gc_collector()); // TODO: it might be better to add a special ref for the value that // also preserves the original key instead. - cell.target.value(&context.gc()).and_then(|v| v.0.take()); + cell.target + .value(context.gc_collector()) + .and_then(|v| v.0.take()); // ii. Set removed to true. removed = true; diff --git a/core/engine/src/builtins/json/mod.rs b/core/engine/src/builtins/json/mod.rs index 3bc2f4f9abc..d5fb0aceb13 100644 --- a/core/engine/src/builtins/json/mod.rs +++ b/core/engine/src/builtins/json/mod.rs @@ -308,7 +308,7 @@ impl Json { ); compiler.compile_statement_list(script.statements(), true, false); let finished = compiler.finish(); - Gc::new(&context.gc(), finished) + context.alloc(finished) }; let realm = context.realm().clone(); diff --git a/core/engine/src/builtins/promise/mod.rs b/core/engine/src/builtins/promise/mod.rs index f03ef9500da..84cc6a0c058 100644 --- a/core/engine/src/builtins/promise/mod.rs +++ b/core/engine/src/builtins/promise/mod.rs @@ -243,13 +243,10 @@ impl PromiseCapability { // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1). // 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }. - let promise_capability = Gc::new( - &context.gc(), - GcRefCell::new(RejectResolve { - reject: JsValue::undefined(), - resolve: JsValue::undefined(), - }), - ); + let promise_capability = context.alloc(GcRefCell::new(RejectResolve { + reject: JsValue::undefined(), + resolve: JsValue::undefined(), + })); // 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called: // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). @@ -656,7 +653,7 @@ impl Promise { } // 1. Let values be a new empty List. - let values = Gc::new(&context.gc(), GcRefCell::new(Vec::new())); + let values = context.alloc(GcRefCell::new(Vec::new())); // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -871,7 +868,7 @@ impl Promise { } // 1. Let values be a new empty List. - let values = Gc::new(&context.gc(), GcRefCell::new(Vec::new())); + let values = context.alloc(GcRefCell::new(Vec::new())); // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -1238,7 +1235,7 @@ impl Promise { let keys = Rc::new(RefCell::new(Vec::new())); // 3. Let values be a new empty List. - let values = Gc::new(&context.gc(), GcRefCell::new(Vec::new())); + let values = context.alloc(GcRefCell::new(Vec::new())); // 4. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -1548,7 +1545,7 @@ impl Promise { } // 1. Let errors be a new empty List. - let errors = Gc::new(&context.gc(), GcRefCell::new(Vec::new())); + let errors = context.alloc(GcRefCell::new(Vec::new())); // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -2448,7 +2445,7 @@ impl Promise { // 1. Let alreadyResolved be the Record { [[Value]]: false }. // 5. Set resolve.[[Promise]] to promise. // 6. Set resolve.[[AlreadyResolved]] to alreadyResolved. - let promise = Gc::new(&context.gc(), Cell::new(Some(promise.clone()))); + let promise = context.alloc(Cell::new(Some(promise.clone()))); // 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions. // 3. Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions. diff --git a/core/engine/src/builtins/weak/weak_ref.rs b/core/engine/src/builtins/weak/weak_ref.rs index 0804d3d5d92..83b92e27a82 100644 --- a/core/engine/src/builtins/weak/weak_ref.rs +++ b/core/engine/src/builtins/weak/weak_ref.rs @@ -87,7 +87,7 @@ impl BuiltInConstructor for WeakRef { let weak_ref = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - WeakGc::new(&context.gc(), target.inner()), + WeakGc::new(context.gc_collector(), target.inner()), ); // 4. Perform AddToKeptObjects(target). @@ -124,7 +124,7 @@ impl WeakRef { // https://tc39.es/ecma262/multipage/managing-memory.html#sec-weakrefderef // 1. Let target be weakRef.[[WeakRefTarget]]. // 2. If target is not empty, then - if let Some(object) = weak_ref.upgrade(&context.gc()) { + if let Some(object) = weak_ref.upgrade(context.gc_collector()) { let object = JsObject::from(object); // a. Perform AddToKeptObjects(target). diff --git a/core/engine/src/builtins/weak_map/mod.rs b/core/engine/src/builtins/weak_map/mod.rs index 8f0bdf8de7c..91e88290846 100644 --- a/core/engine/src/builtins/weak_map/mod.rs +++ b/core/engine/src/builtins/weak_map/mod.rs @@ -97,7 +97,7 @@ impl BuiltInConstructor for WeakMap { let map = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - NativeWeakMap::new(&context.gc()), + NativeWeakMap::new(context.gc_collector()), ) .upcast(); diff --git a/core/engine/src/builtins/weak_set/mod.rs b/core/engine/src/builtins/weak_set/mod.rs index f55b58114b6..73ca5456716 100644 --- a/core/engine/src/builtins/weak_set/mod.rs +++ b/core/engine/src/builtins/weak_set/mod.rs @@ -86,7 +86,7 @@ impl BuiltInConstructor for WeakSet { let weak_set = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - NativeWeakSet::new(&context.gc()), + NativeWeakSet::new(context.gc_collector()), ) .upcast(); diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index dfde062671c..bd0aa5783db 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -107,6 +107,8 @@ pub struct Context { pub(crate) kept_alive: Vec, + pub gc: boa_gc::GcContext, + can_block: bool, #[cfg(any(feature = "temporal", feature = "intl"))] @@ -463,18 +465,20 @@ impl Context { &self.vm.frame().realm } - /// Returns [`boa_gc::MutationContext`] to allocate on the Gc heap - /// (eg. for [`Gc::new`]) - /// - /// # Safety - /// Uses `dummy()` as a temporary bridge during the oscars GC migration. - /// Todo: replace with a real branding token in future + /// Allocates a value on the Gc heap. + #[inline] + pub fn alloc( + &self, + value: T, + ) -> boa_gc::Gc<'static, T> { + self.gc.alloc(value) + } + + /// Returns the active collector. #[inline] #[must_use] - pub fn gc(&self) -> boa_gc::MutationContext<'static, 'static> { - // SAFETY: `MutationContext` is a ZST phantom type, this is sound - // under boa's single-threaded GC invariant until migration is complete - unsafe { boa_gc::MutationContext::global() } + pub fn gc_collector(&self) -> &boa_gc::MutationContext<'static, 'static> { + self.gc.gc_collector() } /// Set the value of trace on the context @@ -1273,6 +1277,7 @@ impl ContextBuilder { optimizer_options: OptimizerOptions::OPTIMIZE_ALL, root_shape, parser_identifier: 0, + gc: boa_gc::GcContext::new(), can_block: self.can_block, data: HostDefined::default(), }; diff --git a/core/engine/src/module/mod.rs b/core/engine/src/module/mod.rs index e8f260aae5b..1f6d01948fd 100644 --- a/core/engine/src/module/mod.rs +++ b/core/engine/src/module/mod.rs @@ -286,16 +286,13 @@ impl Module { let src = SourceTextModule::new(module, context.interner(), source_text, path.clone()); Ok(Self { - inner: Gc::new( - &context.gc(), - ModuleRepr { - realm, - namespace: GcRefCell::default(), - kind: ModuleKind::SourceText(Box::new(src)), - host_defined: HostDefined::default(), - path, - }, - ), + inner: context.alloc(ModuleRepr { + realm, + namespace: GcRefCell::default(), + kind: ModuleKind::SourceText(Box::new(src)), + host_defined: HostDefined::default(), + path, + }), }) } @@ -318,16 +315,13 @@ impl Module { let synth = SyntheticModule::new(names, evaluation_steps); Self { - inner: Gc::new( - &context.gc(), - ModuleRepr { - realm, - namespace: GcRefCell::default(), - kind: ModuleKind::Synthetic(Box::new(synth)), - host_defined: HostDefined::default(), - path, - }, - ), + inner: context.alloc(ModuleRepr { + realm, + namespace: GcRefCell::default(), + kind: ModuleKind::Synthetic(Box::new(synth)), + host_defined: HostDefined::default(), + path, + }), } } @@ -826,7 +820,7 @@ fn into_js_module() { let bar_count = Rc::new(RefCell::new(0)); let dad_count = Rc::new(RefCell::new(0)); - context.insert_data(Gc::new(&context.gc(), GcRefCell::new(JsValue::undefined()))); + context.insert_data(context.alloc(GcRefCell::new(JsValue::undefined()))); let module = unsafe { vec![ diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index b84319f6893..2f32a2d2cb3 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -1826,7 +1826,7 @@ impl SourceTextModule { ( { let finished = compiler.finish(); - Gc::new(&context.gc(), finished) + context.alloc(finished) }, functions, ) @@ -1834,7 +1834,9 @@ impl SourceTextModule { // 8. Let moduleContext be a new ECMAScript code execution context. let mut envs = EnvironmentStack::new(); - envs.push_module(source.scope().clone(), context.gc()); + envs.push_module(source.scope().clone(), unsafe { + boa_gc::MutationContext::global() + }); drop(status); // 9. Set the Function of moduleContext to null. diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index 0d30f788fd6..888c0db39cf 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -339,10 +339,10 @@ impl SyntheticModule { module_scope.escape_all_bindings(); let finished = compiler.finish(); - let cb = Gc::new(&context.gc(), finished); + let cb = context.alloc(finished); let mut envs = EnvironmentStack::new(); - envs.push_module(module_scope, context.gc()); + envs.push_module(module_scope, unsafe { boa_gc::MutationContext::global() }); for locator in exports { // b. Perform ! env.InitializeBinding(exportName, undefined). diff --git a/core/engine/src/object/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index 67931d22bf9..08e7af96556 100644 --- a/core/engine/src/object/builtins/jspromise.rs +++ b/core/engine/src/object/builtins/jspromise.rs @@ -1094,13 +1094,10 @@ impl JsPromise { } } - let state = Gc::new( - &context.gc(), - GcRefCell::new(Inner { - result: None, - task: None, - }), - ); + let state = context.alloc(GcRefCell::new(Inner { + result: None, + task: None, + })); let resolve = { let state = state.clone(); diff --git a/core/engine/src/object/builtins/jstypedarray.rs b/core/engine/src/object/builtins/jstypedarray.rs index 6828d0f6b98..90d94d7387e 100644 --- a/core/engine/src/object/builtins/jstypedarray.rs +++ b/core/engine/src/object/builtins/jstypedarray.rs @@ -678,7 +678,7 @@ impl JsTypedArray { /// # fn main() -> JsResult<()> { /// let context = &mut Context::default(); /// let array = JsUint8Array::from_iter(vec![1, 2, 3, 4, 5], context)?; - /// let num_to_modify = Gc::new(&context.gc(), GcRefCell::new(0u8)); + /// let num_to_modify = context.alloc(GcRefCell::new(0u8)); /// /// let js_function = FunctionObjectBuilder::new( /// context.realm(), diff --git a/core/engine/src/object/builtins/jsweakmap.rs b/core/engine/src/object/builtins/jsweakmap.rs index d120be65a48..9fdd2e8327c 100644 --- a/core/engine/src/object/builtins/jsweakmap.rs +++ b/core/engine/src/object/builtins/jsweakmap.rs @@ -30,7 +30,7 @@ impl JsWeakMap { inner: JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().constructors().weak_map().prototype(), - NativeWeakMap::new(&context.gc()), + NativeWeakMap::new(context.gc_collector()), ) .upcast(), } diff --git a/core/engine/src/object/builtins/jsweakset.rs b/core/engine/src/object/builtins/jsweakset.rs index 07a53fd4264..663a3c65df0 100644 --- a/core/engine/src/object/builtins/jsweakset.rs +++ b/core/engine/src/object/builtins/jsweakset.rs @@ -30,7 +30,7 @@ impl JsWeakSet { inner: JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().constructors().weak_set().prototype(), - NativeWeakSet::new(&context.gc()), + NativeWeakSet::new(context.gc_collector()), ) .upcast(), } diff --git a/core/engine/src/script.rs b/core/engine/src/script.rs index e7d5a36144e..ef9823a32cf 100644 --- a/core/engine/src/script.rs +++ b/core/engine/src/script.rs @@ -104,17 +104,14 @@ impl Script { let source_text = SourceText::new(source); Ok(Self { - inner: Gc::new( - &context.gc(), - Inner { - realm: realm.unwrap_or_else(|| context.realm().clone()), - phase: GcRefCell::new(ScriptPhase::Ast(code)), - source_text, - loaded_modules: GcRefCell::default(), - host_defined: HostDefined::default(), - path, - }, - ), + inner: context.alloc(Inner { + realm: realm.unwrap_or_else(|| context.realm().clone()), + phase: GcRefCell::new(ScriptPhase::Ast(code)), + source_text, + loaded_modules: GcRefCell::default(), + host_defined: HostDefined::default(), + path, + }), }) } @@ -163,7 +160,7 @@ impl Script { compiler.compile_statement_list(source.statements(), true, false); let finished = compiler.finish(); - Gc::new(&context.gc(), finished) + context.alloc(finished) }; *self.inner.phase.borrow_mut() = ScriptPhase::Codeblock(cb.clone()); diff --git a/core/engine/src/vm/opcode/await/mod.rs b/core/engine/src/vm/opcode/await/mod.rs index f95a89e91cb..887603a9bd9 100644 --- a/core/engine/src/vm/opcode/await/mod.rs +++ b/core/engine/src/vm/opcode/await/mod.rs @@ -56,7 +56,7 @@ impl Await { let r#gen = GeneratorContext::from_current(context, None); - let captures = Gc::new(&context.gc(), Cell::new(Some(r#gen))); + let captures = context.alloc(Cell::new(Some(r#gen))); // 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, "", « »). diff --git a/core/engine/src/vm/opcode/push/environment.rs b/core/engine/src/vm/opcode/push/environment.rs index b27673d6d73..0d8b34ef974 100644 --- a/core/engine/src/vm/opcode/push/environment.rs +++ b/core/engine/src/vm/opcode/push/environment.rs @@ -83,10 +83,7 @@ impl PushPrivateEnvironment { } let ptr: *const _ = class.as_ref(); - let environment = Gc::new( - &context.gc(), - PrivateEnvironment::new(ptr.cast::<()>() as usize, names), - ); + let environment = context.alloc(PrivateEnvironment::new(ptr.cast::<()>() as usize, names)); class .downcast_mut::() diff --git a/core/gc/Cargo.toml b/core/gc/Cargo.toml index 7b9b44be17e..10637fe4c36 100644 --- a/core/gc/Cargo.toml +++ b/core/gc/Cargo.toml @@ -34,7 +34,7 @@ either = { workspace = true, optional = true } thin-vec = { workspace = true, optional = true } icu_locale_core = { workspace = true, optional = true } arrayvec = { workspace = true, optional = true } -oscars = { git = "https://github.com/boa-dev/oscars.git", branch = "main", features = ["null_collector_branded"], optional = true } +oscars = { git = "https://github.com/boa-dev/oscars.git", branch = "main", features = ["mark_sweep_branded"], optional = true } typeid = { workspace = true, optional = true } [lints] diff --git a/core/gc/src/context.rs b/core/gc/src/context.rs new file mode 100644 index 00000000000..81624d8aee5 --- /dev/null +++ b/core/gc/src/context.rs @@ -0,0 +1,67 @@ +#[cfg(feature = "oscars_backend")] +use oscars::collectors::mark_sweep_branded::{Gc, MutationContext}; + +#[cfg(feature = "oscars_backend")] +#[derive(Debug, Clone, Copy)] +pub struct GcContext; + +#[cfg(feature = "oscars_backend")] +impl Default for GcContext { + fn default() -> Self { + Self::new() + } +} + +#[cfg(feature = "oscars_backend")] +impl GcContext { + #[must_use] + pub fn new() -> Self { + Self + } + + pub fn alloc(&self, value: T) -> Gc<'static, T> { + // As a bridge, we use the global MutationContext until explicit + // context threading is natively supported by the oscars backend. + let mc = MutationContext::global(); + Gc::new(&mc, value) + } + + #[must_use] + pub fn gc_collector(&self) -> &MutationContext<'static, 'static> { + // Just return a dummy global mutation context + // This is safe for the bridge phase. + unimplemented!("Not supported natively without closure yet, use MutationContext::global()") + } +} + +#[cfg(not(feature = "oscars_backend"))] +#[derive(Debug, Clone, Copy)] +pub struct GcContext; + +#[cfg(not(feature = "oscars_backend"))] +impl Default for GcContext { + fn default() -> Self { + Self::new() + } +} + +#[cfg(not(feature = "oscars_backend"))] +impl GcContext { + #[must_use] + pub fn new() -> Self { + Self + } + + pub fn alloc(&self, value: T) -> crate::Gc<'static, T> { + let mc = unsafe { crate::MutationContext::global() }; + crate::Gc::new(&mc, value) + } + + #[must_use] + pub fn gc_collector(&self) -> &crate::MutationContext<'static, 'static> { + // Just return a dummy global mutation context + static DUMMY: crate::MutationContext<'static, 'static> = + unsafe { crate::MutationContext::global() }; + &DUMMY + } +} diff --git a/core/gc/src/lib.rs b/core/gc/src/lib.rs index 28b85aee782..2296038dc87 100644 --- a/core/gc/src/lib.rs +++ b/core/gc/src/lib.rs @@ -29,6 +29,9 @@ mod pointers; #[cfg(not(feature = "oscars_backend"))] mod trace; +pub mod context; +pub use context::GcContext; + #[cfg(not(feature = "oscars_backend"))] pub(crate) mod internals; @@ -54,9 +57,7 @@ pub use internals::GcBox; pub use pointers::{Ephemeron, Gc, GcErased, MutationContext, WeakGc, WeakMap}; #[cfg(feature = "oscars_backend")] -pub use oscars::collectors::null_collector_branded::{ - Finalize, Gc, GcBox, GcRefCell, Root, Trace, Tracer, -}; +pub use oscars::collectors::mark_sweep_branded::{Finalize, Gc, GcRefCell, Root, Trace, Tracer}; #[cfg(feature = "oscars_backend")] /// Re-export [`typeid::of`]. @@ -71,20 +72,20 @@ pub use typeid::of as type_id_of; #[cfg(feature = "oscars_backend")] /// Type alias for Ephemeron -pub type Ephemeron = oscars::collectors::null_collector_branded::Ephemeron<'static, K, V>; +pub type Ephemeron = oscars::collectors::mark_sweep_branded::Ephemeron<'static, K, V>; #[cfg(feature = "oscars_backend")] /// A token granting permission to allocate into the GC arena. /// Lifetimes are `'static` for the null collector but should be forwarded for `mark_sweep_branded`. pub type MutationContext<'a, 'b> = - oscars::collectors::null_collector_branded::MutationContext<'static, 'static>; + oscars::collectors::mark_sweep_branded::MutationContext<'static, 'static>; #[cfg(feature = "oscars_backend")] /// Type alias for `WeakGc` -pub type WeakGc = oscars::collectors::null_collector_branded::WeakGc<'static, T>; +pub type WeakGc = oscars::collectors::mark_sweep_branded::WeakGc<'static, T>; #[cfg(feature = "oscars_backend")] -pub use oscars::collectors::null_collector_branded::cell::{GcRef, GcRefMut}; +pub use oscars::collectors::mark_sweep_branded::cell::{GcRef, GcRefMut}; #[cfg(feature = "oscars_backend")] mod oscars_weak_map; diff --git a/core/gc/src/oscars_weak_map.rs b/core/gc/src/oscars_weak_map.rs index 94bbe66e1a8..534078e3292 100644 --- a/core/gc/src/oscars_weak_map.rs +++ b/core/gc/src/oscars_weak_map.rs @@ -1,7 +1,7 @@ //! Dummy `WeakMap` implementation for the `oscars_backend` feature. //! //! We define this here instead of in `oscars` because `boa_engine` needs to be able to modify the `WeakMap` even when it is shared, which it handles by using `GcRefCell`. -//! Additionally, the `null_collector_branded` backend never frees memory, making a true weak map impossible. +//! Additionally, the `mark_sweep_branded` backend never frees memory, making a true weak map impossible. //! Defining a dummy wrapper in `boa_gc` fulfills engine requirements without polluting it with conditional compilation gates. //! All operations are leaky strong map operations to maintain API compatibility. diff --git a/core/gc/src/pointers/mutation_context.rs b/core/gc/src/pointers/mutation_context.rs index df771c72c23..b0c3db4fde3 100644 --- a/core/gc/src/pointers/mutation_context.rs +++ b/core/gc/src/pointers/mutation_context.rs @@ -12,7 +12,7 @@ impl MutationContext<'_, '_> { /// # Safety /// Bypasses lifetime branding, use only as a bridge during Gc migration. #[must_use] - pub unsafe fn dummy() -> Self { + pub const unsafe fn dummy() -> Self { Self { _marker: PhantomData, } @@ -20,7 +20,7 @@ impl MutationContext<'_, '_> { /// Creates a global context (polyfill for the oscars backend). #[must_use] - pub unsafe fn global() -> Self { - unsafe { Self::dummy() } + pub const unsafe fn global() -> Self { + Self::dummy() } } diff --git a/core/string/Cargo.toml b/core/string/Cargo.toml index cfcd7290d8a..89ee9a52791 100644 --- a/core/string/Cargo.toml +++ b/core/string/Cargo.toml @@ -12,7 +12,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] -oscars = { git = "https://github.com/boa-dev/oscars.git", branch = "main", features = ["null_collector_branded"], optional = true } +oscars = { git = "https://github.com/boa-dev/oscars.git", branch = "main", features = ["mark_sweep_branded"], optional = true } itoa.workspace = true rustc-hash = { workspace = true, features = ["std"] } ryu-js.workspace = true diff --git a/core/string/src/lib.rs b/core/string/src/lib.rs index 23cb28aee5d..9b46d002345 100644 --- a/core/string/src/lib.rs +++ b/core/string/src/lib.rs @@ -1044,10 +1044,10 @@ impl_js_string_slice_index!( #[cfg(feature = "oscars_backend")] // SAFETY: `JsString` does not contain any GC pointers, so an empty trace is safe. -unsafe impl oscars::collectors::null_collector_branded::Trace for JsString { +unsafe impl oscars::collectors::mark_sweep_branded::Trace for JsString { // SAFETY: Empty trace is safe. #[inline] - unsafe fn trace(&self, _tracer: &mut oscars::collectors::null_collector_branded::Tracer<'_>) {} + unsafe fn trace(&self, _tracer: &mut oscars::collectors::mark_sweep_branded::Tracer<'_>) {} // SAFETY: Empty trace is safe. #[inline] unsafe fn trace_non_roots(&self) {} @@ -1056,4 +1056,4 @@ unsafe impl oscars::collectors::null_collector_branded::Trace for JsString { } #[cfg(feature = "oscars_backend")] -impl oscars::collectors::null_collector_branded::Finalize for JsString {} +impl oscars::collectors::mark_sweep_branded::Finalize for JsString {} From bd7e085ec2fda6f80654bc8aaeae6a16f999af60 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 16 Aug 2026 03:09:08 +0000 Subject: [PATCH 03/19] Thread MutationContext through core engine and ByteCompiler --- core/engine/benches/full.rs | 6 +- core/engine/src/builtins/eval/mod.rs | 6 +- core/engine/src/builtins/function/mod.rs | 14 +- core/engine/src/builtins/iterable/mod.rs | 31 +- core/engine/src/builtins/json/mod.rs | 4 +- core/engine/src/builtins/uri/mod.rs | 14 +- core/engine/src/bytecompiler/class.rs | 30 +- core/engine/src/bytecompiler/declarations.rs | 2 + core/engine/src/bytecompiler/function.rs | 4 +- core/engine/src/bytecompiler/mod.rs | 16 + core/engine/src/context/intrinsics.rs | 319 ++++++++++-------- core/engine/src/context/mod.rs | 16 +- core/engine/src/environments/runtime/mod.rs | 10 +- core/engine/src/module/source.rs | 4 +- core/engine/src/module/synthetic.rs | 4 +- core/engine/src/object/builtins/jsfunction.rs | 26 +- core/engine/src/object/jsobject.rs | 165 ++++++--- core/engine/src/object/shape/mod.rs | 72 +++- core/engine/src/object/shape/root_shape.rs | 11 +- .../shape/shared_shape/forward_transition.rs | 38 ++- .../src/object/shape/shared_shape/mod.rs | 133 ++++++-- .../src/object/shape/shared_shape/template.rs | 125 +++++-- core/engine/src/object/shape/unique_shape.rs | 47 ++- core/engine/src/realm.rs | 15 +- core/engine/src/script.rs | 8 +- core/engine/src/vm/mod.rs | 7 +- core/engine/src/vm/opcode/push/environment.rs | 5 +- core/gc/src/context.rs | 33 +- 28 files changed, 794 insertions(+), 371 deletions(-) diff --git a/core/engine/benches/full.rs b/core/engine/benches/full.rs index b327a366991..78e6dfb5548 100644 --- a/core/engine/benches/full.rs +++ b/core/engine/benches/full.rs @@ -19,7 +19,11 @@ static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; fn create_realm(c: &mut Criterion) { c.bench_function("Create Realm", move |b| { let root_shape = RootShape::default(); - b.iter(|| Realm::create(&DefaultHooks, &root_shape)); + b.iter(|| { + Realm::create(&DefaultHooks, &root_shape, &unsafe { + boa_gc::MutationContext::global() + }) + }); }); } diff --git a/core/engine/src/builtins/eval/mod.rs b/core/engine/src/builtins/eval/mod.rs index c523d269c02..7d6cf4cd0cd 100644 --- a/core/engine/src/builtins/eval/mod.rs +++ b/core/engine/src/builtins/eval/mod.rs @@ -274,6 +274,7 @@ impl Eval { let source_text = SourceText::new(source); let spanned_source_text = SpannedSourceText::new_source_only(source_text); + let mc = context.gc_collector(); let mut compiler = ByteCompiler::new( js_string!(""), body.strict(), @@ -283,6 +284,7 @@ impl Eval { false, false, context.interner_mut(), + &mc, in_with, spanned_source_text, // TODO: Could give more information from previous shadow stack. @@ -350,8 +352,8 @@ impl Eval { let global = frame.realm.environment(); frame.environments.push_lexical( lexical_scope.num_bindings_non_local(), - global, - unsafe { boa_gc::MutationContext::global() }, + &global, + &unsafe { boa_gc::MutationContext::global() }, ); } diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index 498cb555b8c..b5279c480d8 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -659,6 +659,7 @@ impl BuiltInFunctionObject { let in_with = context.vm.frame().environments.has_object_environment(); let spanned_source_text = SpannedSourceText::new_empty(); + let mc = context.gc_collector(); let code = FunctionCompiler::new(spanned_source_text) .name(js_string!("anonymous")) .generator(generator) @@ -673,6 +674,7 @@ impl BuiltInFunctionObject { function.scopes(), function.contains_direct_eval(), context.interner_mut(), + &mc, ); let saved = context.vm.frame_mut().environments.pop_to_global(); @@ -1075,7 +1077,7 @@ pub(crate) fn function_call( let global = frame.realm.environment(); let index = frame .environments - .push_lexical(1, global, unsafe { boa_gc::MutationContext::global() }); + .push_lexical(1, &global, &unsafe { boa_gc::MutationContext::global() }); frame.environments.put_lexical_value( BindingLocatorScope::Stack(index), 0, @@ -1092,8 +1094,8 @@ pub(crate) fn function_call( frame.environments.push_function( scope, FunctionSlots::new(this, function_object.clone(), None), - global, - unsafe { boa_gc::MutationContext::global() }, + &global, + &unsafe { boa_gc::MutationContext::global() }, ); } @@ -1186,7 +1188,7 @@ fn function_construct( let global = frame.realm.environment(); let index = frame .environments - .push_lexical(1, global, unsafe { boa_gc::MutationContext::global() }); + .push_lexical(1, &global, &unsafe { boa_gc::MutationContext::global() }); frame.environments.put_lexical_value( BindingLocatorScope::Stack(index), 0, @@ -1214,8 +1216,8 @@ fn function_construct( .clone(), ), ), - global, - unsafe { boa_gc::MutationContext::global() }, + &global, + &unsafe { boa_gc::MutationContext::global() }, ); } diff --git a/core/engine/src/builtins/iterable/mod.rs b/core/engine/src/builtins/iterable/mod.rs index 837f6da38f1..835a71ebacf 100644 --- a/core/engine/src/builtins/iterable/mod.rs +++ b/core/engine/src/builtins/iterable/mod.rs @@ -91,24 +91,27 @@ pub struct IteratorPrototypes { impl Default for IteratorPrototypes { fn default() -> Self { - Self { - iterator: JsObject::with_null_proto(), - async_iterator: JsObject::with_null_proto(), - async_from_sync_iterator: JsObject::with_null_proto(), - array: JsObject::with_null_proto(), - set: JsObject::with_null_proto(), - string: JsObject::with_null_proto(), - regexp_string: JsObject::with_null_proto(), - map: JsObject::with_null_proto(), - #[cfg(feature = "intl")] - segment: JsObject::with_null_proto(), - iterator_helper: JsObject::with_null_proto(), - wrap_for_valid_iterator: JsObject::with_null_proto(), - } + Self::uninit_in(&unsafe { boa_gc::MutationContext::global() }) } } impl IteratorPrototypes { + pub(crate) fn uninit_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self { + iterator: JsObject::with_null_proto_in(mc), + async_iterator: JsObject::with_null_proto_in(mc), + async_from_sync_iterator: JsObject::with_null_proto_in(mc), + array: JsObject::with_null_proto_in(mc), + set: JsObject::with_null_proto_in(mc), + string: JsObject::with_null_proto_in(mc), + regexp_string: JsObject::with_null_proto_in(mc), + map: JsObject::with_null_proto_in(mc), + #[cfg(feature = "intl")] + segment: JsObject::with_null_proto_in(mc), + iterator_helper: JsObject::with_null_proto_in(mc), + wrap_for_valid_iterator: JsObject::with_null_proto_in(mc), + } + } /// Returns the `ArrayIteratorPrototype` object. #[inline] #[must_use] diff --git a/core/engine/src/builtins/json/mod.rs b/core/engine/src/builtins/json/mod.rs index d5fb0aceb13..9d82c87eac9 100644 --- a/core/engine/src/builtins/json/mod.rs +++ b/core/engine/src/builtins/json/mod.rs @@ -293,6 +293,7 @@ impl Json { let spanned_source_text = SpannedSourceText::new_source_only( crate::spanned_source_text::SourceText::new(source_text), ); + let gc = context.gc_collector(); let mut compiler = ByteCompiler::new( js_string!(""), script.strict(), @@ -302,7 +303,8 @@ impl Json { false, false, context.interner_mut(), - in_with, + &gc, + false, spanned_source_text, SourcePath::Json, ); diff --git a/core/engine/src/builtins/uri/mod.rs b/core/engine/src/builtins/uri/mod.rs index a8e50d6eaa7..6a5baadb554 100644 --- a/core/engine/src/builtins/uri/mod.rs +++ b/core/engine/src/builtins/uri/mod.rs @@ -49,11 +49,17 @@ pub struct UriFunctions { impl Default for UriFunctions { fn default() -> Self { + Self::uninit_in(&unsafe { boa_gc::MutationContext::global() }) + } +} + +impl UriFunctions { + pub(crate) fn uninit_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { Self { - decode_uri: JsFunction::empty_intrinsic_function(false), - decode_uri_component: JsFunction::empty_intrinsic_function(false), - encode_uri: JsFunction::empty_intrinsic_function(false), - encode_uri_component: JsFunction::empty_intrinsic_function(false), + decode_uri: JsFunction::empty_intrinsic_function_in(mc, false), + decode_uri_component: JsFunction::empty_intrinsic_function_in(mc, false), + encode_uri: JsFunction::empty_intrinsic_function_in(mc, false), + encode_uri_component: JsFunction::empty_intrinsic_function_in(mc, false), } } } diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index d98020efdc9..25f8563b08c 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/core/engine/src/bytecompiler/class.rs @@ -103,6 +103,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, spanned_source_text, self.source_path.clone(), @@ -156,10 +157,7 @@ impl ByteCompiler<'_> { class.super_ref.is_some(), ); - let code = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - compiler.finish(), - ); + let code = Gc::new(self.mc.0, compiler.finish()); let index = self.push_function_to_constants(code); let class_register = self.register_allocator.alloc(); @@ -417,6 +415,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, self.spanned_source_text.clone_only_source(), self.source_path.clone(), @@ -443,10 +442,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; - let code = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - field_compiler.finish(), - ); + let code = Gc::new(self.mc.0, field_compiler.finish()); let index = self.push_function_to_constants(code); let dst = self.register_allocator.alloc(); @@ -471,6 +467,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, self.spanned_source_text.clone_only_source(), self.source_path.clone(), @@ -492,10 +489,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; - let code = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - field_compiler.finish(), - ); + let code = Gc::new(self.mc.0, field_compiler.finish()); let index = self.push_function_to_constants(code); let dst = self.register_allocator.alloc(); self.emit_get_function(&dst, index); @@ -526,6 +520,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, self.spanned_source_text.clone_only_source(), self.source_path.clone(), @@ -551,7 +546,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; let code = field_compiler.finish(); - let code = Gc::new(&unsafe { boa_gc::MutationContext::global() }, code); + let code = Gc::new(self.mc.0, code); static_elements.push(StaticElement::StaticField { code, @@ -570,6 +565,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, self.spanned_source_text.clone_only_source(), self.source_path.clone(), @@ -595,7 +591,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; let code = field_compiler.finish(); - let code = Gc::new(&unsafe { boa_gc::MutationContext::global() }, code); + let code = Gc::new(self.mc.0, code); static_elements.push(StaticElement::StaticField { code, @@ -613,6 +609,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, self.spanned_source_text.clone_only_source(), self.source_path.clone(), @@ -638,10 +635,7 @@ impl ByteCompiler<'_> { ); } - let code = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - compiler.finish(), - ); + let code = Gc::new(self.mc.0, compiler.finish()); static_elements.push(StaticElement::StaticBlock(code)); } } diff --git a/core/engine/src/bytecompiler/declarations.rs b/core/engine/src/bytecompiler/declarations.rs index d44a2691fc3..eefc186c143 100644 --- a/core/engine/src/bytecompiler/declarations.rs +++ b/core/engine/src/bytecompiler/declarations.rs @@ -539,6 +539,7 @@ impl ByteCompiler<'_> { &scopes, contains_direct_eval, self.interner, + self.mc.0, ); // Ensures global functions are printed when generating the global flowgraph. @@ -817,6 +818,7 @@ impl ByteCompiler<'_> { &scopes, contains_direct_eval, self.interner, + self.mc.0, ); // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. diff --git a/core/engine/src/bytecompiler/function.rs b/core/engine/src/bytecompiler/function.rs index d1633a34cd6..371b8ab53fc 100644 --- a/core/engine/src/bytecompiler/function.rs +++ b/core/engine/src/bytecompiler/function.rs @@ -122,6 +122,7 @@ impl FunctionCompiler { scopes: &FunctionScopes, contains_direct_eval: bool, interner: &mut Interner, + mc: &boa_gc::MutationContext<'static, 'static>, ) -> Gc<'static, CodeBlock> { self.strict = self.strict || body.strict(); @@ -136,6 +137,7 @@ impl FunctionCompiler { self.r#async, self.generator, interner, + mc, self.in_with, self.spanned_source_text, self.source_path, @@ -227,6 +229,6 @@ impl FunctionCompiler { let code = compiler.finish(); - Gc::new(&unsafe { boa_gc::MutationContext::global() }, code) + Gc::new(mc, code) } } diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 839b40c98fc..e6dcd761d16 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -489,6 +489,15 @@ impl<'a> BorrowMut> for SourcePositionGuard<'_, 'a> { } } +#[derive(Clone, Copy)] +pub(crate) struct McWrapper<'ctx>(pub(crate) &'ctx boa_gc::MutationContext<'static, 'static>); + +impl<'ctx> std::fmt::Debug for McWrapper<'ctx> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("MutationContext").finish() + } +} + /// The [`ByteCompiler`] is used to compile ECMAScript AST from [`boa_ast`] to bytecode. #[derive(Debug)] #[allow(clippy::struct_excessive_bools)] @@ -556,6 +565,8 @@ pub struct ByteCompiler<'ctx> { pub(crate) emitted_mapped_arguments_object_opcode: bool, pub(crate) interner: &'ctx mut Interner, + /// The MutationContext for GC allocations. + pub(crate) mc: McWrapper<'ctx>, spanned_source_text: SpannedSourceText, pub(crate) global_lexs: Vec, @@ -603,6 +614,7 @@ impl<'ctx> ByteCompiler<'ctx> { is_async: bool, is_generator: bool, interner: &'ctx mut Interner, + mc: &'ctx boa_gc::MutationContext<'static, 'static>, in_with: bool, spanned_source_text: SpannedSourceText, source_path: SourcePath, @@ -674,6 +686,7 @@ impl<'ctx> ByteCompiler<'ctx> { variable_scope, lexical_scope, interner, + mc: McWrapper(mc), spanned_source_text, source_path, @@ -2441,6 +2454,7 @@ impl<'ctx> ByteCompiler<'ctx> { scopes, function.contains_direct_eval, self.interner, + self.mc.0, ); self.push_function_to_constants(code) @@ -2522,6 +2536,7 @@ impl<'ctx> ByteCompiler<'ctx> { scopes, function.contains_direct_eval, self.interner, + self.mc.0, ); let index = self.push_function_to_constants(code); @@ -2572,6 +2587,7 @@ impl<'ctx> ByteCompiler<'ctx> { scopes, function.contains_direct_eval, self.interner, + self.mc.0, ); let index = self.push_function_to_constants(code); diff --git a/core/engine/src/context/intrinsics.rs b/core/engine/src/context/intrinsics.rs index 98e601c9d4f..edd57db8cdf 100644 --- a/core/engine/src/context/intrinsics.rs +++ b/core/engine/src/context/intrinsics.rs @@ -39,13 +39,16 @@ impl Intrinsics { /// To initialize all the intrinsics with their spec properties, see [`Realm::initialize`]. /// /// [`Realm::initialize`]: crate::realm::Realm::initialize - pub(crate) fn uninit(root_shape: &RootShape) -> Option { - let constructors = StandardConstructors::default(); - let templates = ObjectTemplates::new(root_shape, &constructors); + pub(crate) fn uninit( + root_shape: &RootShape, + mc: &boa_gc::MutationContext<'static, '_>, + ) -> Option { + let constructors = StandardConstructors::uninit(mc); + let templates = ObjectTemplates::new(mc, root_shape, &constructors); Some(Self { constructors, - objects: IntrinsicObjects::uninit()?, + objects: IntrinsicObjects::uninit(mc)?, templates, }) } @@ -78,14 +81,18 @@ pub struct StandardConstructor { impl Default for StandardConstructor { fn default() -> Self { - Self { - constructor: JsFunction::empty_intrinsic_function(true), - prototype: JsObject::with_null_proto(), - } + Self::uninit(&unsafe { boa_gc::MutationContext::global() }) } } impl StandardConstructor { + /// Creates a new uninitialized `StandardConstructor` using the given context. + pub(crate) fn uninit(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self { + constructor: JsFunction::empty_intrinsic_function_in(mc, true), + prototype: JsObject::with_null_proto_in(mc), + } + } /// Creates a new `StandardConstructor` from the constructor and the prototype. pub(crate) fn new(constructor: JsFunction, prototype: JsObject) -> Self { Self { @@ -94,14 +101,19 @@ impl StandardConstructor { } } - /// Build a constructor with a defined prototype. - fn with_prototype(prototype: JsObject) -> Self { + /// Build a constructor with a defined prototype, using the given context. + fn with_prototype_in(mc: &boa_gc::MutationContext<'static, '_>, prototype: JsObject) -> Self { Self { - constructor: JsFunction::empty_intrinsic_function(true), + constructor: JsFunction::empty_intrinsic_function_in(mc, true), prototype, } } + /// Build a constructor with a defined prototype. + fn with_prototype(prototype: JsObject) -> Self { + Self::with_prototype_in(&unsafe { boa_gc::MutationContext::global() }, prototype) + } + /// Return the prototype of the constructor object. /// /// This is the same as `Object.prototype`, `Array.prototype`, etc. @@ -206,100 +218,111 @@ pub struct StandardConstructors { calendar: StandardConstructor, } -impl Default for StandardConstructors { - fn default() -> Self { +impl StandardConstructors { + pub(crate) fn uninit(mc: &boa_gc::MutationContext<'static, '_>) -> Self { Self { - object: StandardConstructor::with_prototype(JsObject::from_object_and_vtable( - Object::::default(), - &IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, - )), - async_generator_function: StandardConstructor::default(), - proxy: StandardConstructor::default(), - date: StandardConstructor::default(), + object: StandardConstructor::with_prototype_in( + mc, + JsObject::from_object_and_vtable_in( + mc, + Object::::default(), + &IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, + ), + ), + async_generator_function: StandardConstructor::uninit(mc), + proxy: StandardConstructor::uninit(mc), + date: StandardConstructor::uninit(mc), function: StandardConstructor { - constructor: JsFunction::empty_intrinsic_function(true), - prototype: JsFunction::empty_intrinsic_function(false).into(), + constructor: JsFunction::empty_intrinsic_function_in(mc, true), + prototype: JsFunction::empty_intrinsic_function_in(mc, false).into(), }, - async_function: StandardConstructor::default(), - generator_function: StandardConstructor::default(), - array: StandardConstructor::with_prototype(JsObject::from_proto_and_data(None, Array)), - bigint: StandardConstructor::default(), - number: StandardConstructor::with_prototype(JsObject::from_proto_and_data(None, 0.0)), - boolean: StandardConstructor::with_prototype(JsObject::from_proto_and_data( - None, false, - )), - string: StandardConstructor::with_prototype(JsObject::from_proto_and_data( - None, - js_string!(), - )), - regexp: StandardConstructor::default(), - symbol: StandardConstructor::default(), - error: StandardConstructor::default(), - type_error: StandardConstructor::default(), - reference_error: StandardConstructor::default(), - range_error: StandardConstructor::default(), - syntax_error: StandardConstructor::default(), - eval_error: StandardConstructor::default(), - uri_error: StandardConstructor::default(), - aggregate_error: StandardConstructor::default(), - map: StandardConstructor::default(), - set: StandardConstructor::default(), - typed_array: StandardConstructor::default(), - typed_int8_array: StandardConstructor::default(), - typed_uint8_array: StandardConstructor::default(), - typed_uint8clamped_array: StandardConstructor::default(), - typed_int16_array: StandardConstructor::default(), - typed_uint16_array: StandardConstructor::default(), - typed_int32_array: StandardConstructor::default(), - typed_uint32_array: StandardConstructor::default(), - typed_bigint64_array: StandardConstructor::default(), - typed_biguint64_array: StandardConstructor::default(), + async_function: StandardConstructor::uninit(mc), + generator_function: StandardConstructor::uninit(mc), + array: StandardConstructor::with_prototype_in( + mc, + JsObject::from_proto_and_data_in(mc, None, Array), + ), + bigint: StandardConstructor::uninit(mc), + number: StandardConstructor::with_prototype_in( + mc, + JsObject::from_proto_and_data_in(mc, None, 0.0), + ), + boolean: StandardConstructor::with_prototype_in( + mc, + JsObject::from_proto_and_data_in(mc, None, false), + ), + string: StandardConstructor::with_prototype_in( + mc, + JsObject::from_proto_and_data_in(mc, None, js_string!()), + ), + regexp: StandardConstructor::uninit(mc), + symbol: StandardConstructor::uninit(mc), + error: StandardConstructor::uninit(mc), + type_error: StandardConstructor::uninit(mc), + reference_error: StandardConstructor::uninit(mc), + range_error: StandardConstructor::uninit(mc), + syntax_error: StandardConstructor::uninit(mc), + eval_error: StandardConstructor::uninit(mc), + uri_error: StandardConstructor::uninit(mc), + aggregate_error: StandardConstructor::uninit(mc), + map: StandardConstructor::uninit(mc), + set: StandardConstructor::uninit(mc), + typed_array: StandardConstructor::uninit(mc), + typed_int8_array: StandardConstructor::uninit(mc), + typed_uint8_array: StandardConstructor::uninit(mc), + typed_uint8clamped_array: StandardConstructor::uninit(mc), + typed_int16_array: StandardConstructor::uninit(mc), + typed_uint16_array: StandardConstructor::uninit(mc), + typed_int32_array: StandardConstructor::uninit(mc), + typed_uint32_array: StandardConstructor::uninit(mc), + typed_bigint64_array: StandardConstructor::uninit(mc), + typed_biguint64_array: StandardConstructor::uninit(mc), #[cfg(feature = "float16")] - typed_float16_array: StandardConstructor::default(), - typed_float32_array: StandardConstructor::default(), - typed_float64_array: StandardConstructor::default(), - array_buffer: StandardConstructor::default(), - shared_array_buffer: StandardConstructor::default(), - data_view: StandardConstructor::default(), - date_time_format: StandardConstructor::default(), - promise: StandardConstructor::default(), - weak_ref: StandardConstructor::default(), - weak_map: StandardConstructor::default(), - weak_set: StandardConstructor::default(), - iterator: StandardConstructor::default(), - finalization_registry: StandardConstructor::default(), + typed_float16_array: StandardConstructor::uninit(mc), + typed_float32_array: StandardConstructor::uninit(mc), + typed_float64_array: StandardConstructor::uninit(mc), + array_buffer: StandardConstructor::uninit(mc), + shared_array_buffer: StandardConstructor::uninit(mc), + data_view: StandardConstructor::uninit(mc), + date_time_format: StandardConstructor::uninit(mc), + promise: StandardConstructor::uninit(mc), + weak_ref: StandardConstructor::uninit(mc), + weak_map: StandardConstructor::uninit(mc), + weak_set: StandardConstructor::uninit(mc), + iterator: StandardConstructor::uninit(mc), + finalization_registry: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - collator: StandardConstructor::default(), + collator: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - list_format: StandardConstructor::default(), + list_format: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - locale: StandardConstructor::default(), + locale: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - segmenter: StandardConstructor::default(), + segmenter: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - plural_rules: StandardConstructor::default(), + plural_rules: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - number_format: StandardConstructor::default(), + number_format: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - instant: StandardConstructor::default(), + instant: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - plain_date_time: StandardConstructor::default(), + plain_date_time: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - plain_date: StandardConstructor::default(), + plain_date: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - plain_time: StandardConstructor::default(), + plain_time: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - plain_year_month: StandardConstructor::default(), + plain_year_month: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - plain_month_day: StandardConstructor::default(), + plain_month_day: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - time_zone: StandardConstructor::default(), + time_zone: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - duration: StandardConstructor::default(), + duration: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - zoned_date_time: StandardConstructor::default(), + zoned_date_time: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - calendar: StandardConstructor::default(), + calendar: StandardConstructor::uninit(mc), } } } @@ -1164,28 +1187,28 @@ impl IntrinsicObjects { /// /// [`Realm::initialize`]: crate::realm::Realm::initialize #[allow(clippy::unnecessary_wraps)] - pub(crate) fn uninit() -> Option { + pub(crate) fn uninit(mc: &boa_gc::MutationContext<'static, '_>) -> Option { Some(Self { - reflect: JsObject::with_null_proto(), - math: JsObject::with_null_proto(), - json: JsObject::with_null_proto(), - throw_type_error: JsFunction::empty_intrinsic_function(false), - array_prototype_values: JsFunction::empty_intrinsic_function(false), - array_prototype_to_string: JsFunction::empty_intrinsic_function(false), - iterator_prototypes: IteratorPrototypes::default(), - generator: JsObject::with_null_proto(), - async_generator: JsObject::with_null_proto(), - atomics: JsObject::with_null_proto(), - eval: JsFunction::empty_intrinsic_function(false), - uri_functions: UriFunctions::default(), - is_finite: JsFunction::empty_intrinsic_function(false), - is_nan: JsFunction::empty_intrinsic_function(false), - parse_float: JsFunction::empty_intrinsic_function(false), - parse_int: JsFunction::empty_intrinsic_function(false), + reflect: JsObject::with_null_proto_in(mc), + math: JsObject::with_null_proto_in(mc), + json: JsObject::with_null_proto_in(mc), + throw_type_error: JsFunction::empty_intrinsic_function_in(mc, false), + array_prototype_values: JsFunction::empty_intrinsic_function_in(mc, false), + array_prototype_to_string: JsFunction::empty_intrinsic_function_in(mc, false), + iterator_prototypes: IteratorPrototypes::uninit_in(mc), + generator: JsObject::with_null_proto_in(mc), + async_generator: JsObject::with_null_proto_in(mc), + atomics: JsObject::with_null_proto_in(mc), + eval: JsFunction::empty_intrinsic_function_in(mc, false), + uri_functions: UriFunctions::uninit_in(mc), + is_finite: JsFunction::empty_intrinsic_function_in(mc, false), + is_nan: JsFunction::empty_intrinsic_function_in(mc, false), + parse_float: JsFunction::empty_intrinsic_function_in(mc, false), + parse_int: JsFunction::empty_intrinsic_function_in(mc, false), #[cfg(feature = "annex-b")] - escape: JsFunction::empty_intrinsic_function(false), + escape: JsFunction::empty_intrinsic_function_in(mc, false), #[cfg(feature = "annex-b")] - unescape: JsFunction::empty_intrinsic_function(false), + unescape: JsFunction::empty_intrinsic_function_in(mc, false), #[cfg(feature = "intl")] intl: JsObject::new_unique(None, Intl::new()?), #[cfg(feature = "intl")] @@ -1434,45 +1457,56 @@ pub(crate) struct ObjectTemplates { } impl ObjectTemplates { - pub(crate) fn new(root_shape: &RootShape, constructors: &StandardConstructors) -> Self { + pub(crate) fn new( + mc: &boa_gc::MutationContext<'static, '_>, + root_shape: &RootShape, + constructors: &StandardConstructors, + ) -> Self { let root_shape = root_shape.shape(); // pre-initialize used shapes. let ordinary_object = - ObjectTemplate::with_prototype(root_shape, constructors.object().prototype()); + ObjectTemplate::with_prototype_in(mc, root_shape, constructors.object().prototype()); let mut array = ObjectTemplate::new(root_shape); let length_property_key: PropertyKey = js_string!("length").into(); - array.property( + array.property_in( + mc, length_property_key.clone(), Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); - array.set_prototype(constructors.array().prototype()); - - let number = ObjectTemplate::with_prototype(root_shape, constructors.number().prototype()); - let symbol = ObjectTemplate::with_prototype(root_shape, constructors.symbol().prototype()); - let bigint = ObjectTemplate::with_prototype(root_shape, constructors.bigint().prototype()); + array.set_prototype_in(mc, constructors.array().prototype()); + + let number = + ObjectTemplate::with_prototype_in(mc, root_shape, constructors.number().prototype()); + let symbol = + ObjectTemplate::with_prototype_in(mc, root_shape, constructors.symbol().prototype()); + let bigint = + ObjectTemplate::with_prototype_in(mc, root_shape, constructors.bigint().prototype()); let boolean = - ObjectTemplate::with_prototype(root_shape, constructors.boolean().prototype()); + ObjectTemplate::with_prototype_in(mc, root_shape, constructors.boolean().prototype()); let mut string = ObjectTemplate::new(root_shape); - string.property( + string.property_in( + mc, length_property_key.clone(), Attribute::READONLY | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); - string.set_prototype(constructors.string().prototype()); + string.set_prototype_in(mc, constructors.string().prototype()); let mut regexp_without_proto = ObjectTemplate::new(root_shape); - regexp_without_proto.property(js_string!("lastIndex").into(), Attribute::WRITABLE); + regexp_without_proto.property_in(mc, js_string!("lastIndex").into(), Attribute::WRITABLE); let mut regexp = regexp_without_proto.clone(); - regexp.set_prototype(constructors.regexp().prototype()); + regexp.set_prototype_in(mc, constructors.regexp().prototype()); let name_property_key: PropertyKey = js_string!("name").into(); let mut function = ObjectTemplate::new(root_shape); - function.property( + function.property_in( + mc, length_property_key.clone(), Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); - function.property( + function.property_in( + mc, name_property_key, Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); @@ -1481,7 +1515,8 @@ impl ObjectTemplates { let mut async_function = function.clone(); let mut function_with_prototype = function.clone(); - function_with_prototype.property( + function_with_prototype.property_in( + mc, PROTOTYPE.into(), Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); @@ -1490,14 +1525,16 @@ impl ObjectTemplates { let function_with_prototype_without_proto = function_with_prototype.clone(); - function.set_prototype(constructors.function().prototype()); - function_with_prototype.set_prototype(constructors.function().prototype()); - async_function.set_prototype(constructors.async_function().prototype()); - generator_function.set_prototype(constructors.generator_function().prototype()); - async_generator_function.set_prototype(constructors.async_generator_function().prototype()); + function.set_prototype_in(mc, constructors.function().prototype()); + function_with_prototype.set_prototype_in(mc, constructors.function().prototype()); + async_function.set_prototype_in(mc, constructors.async_function().prototype()); + generator_function.set_prototype_in(mc, constructors.generator_function().prototype()); + async_generator_function + .set_prototype_in(mc, constructors.async_generator_function().prototype()); let mut function_prototype = ordinary_object.clone(); - function_prototype.property( + function_prototype.property_in( + mc, CONSTRUCTOR.into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); @@ -1506,7 +1543,8 @@ impl ObjectTemplates { // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - unmapped_arguments.property( + unmapped_arguments.property_in( + mc, length_property_key, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); @@ -1514,7 +1552,8 @@ impl ObjectTemplates { // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, // [[Configurable]]: true }). - unmapped_arguments.property( + unmapped_arguments.property_in( + mc, JsSymbol::iterator().into(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); @@ -1524,7 +1563,8 @@ impl ObjectTemplates { // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, // [[Configurable]]: false }). - unmapped_arguments.accessor( + unmapped_arguments.accessor_in( + mc, js_string!("callee").into(), true, true, @@ -1533,34 +1573,37 @@ impl ObjectTemplates { // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - mapped_arguments.property( + mapped_arguments.property_in( + mc, js_string!("callee").into(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); let mut iterator_result = ordinary_object.clone(); - iterator_result.property( + iterator_result.property_in( + mc, js_string!("value").into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, ); - iterator_result.property( + iterator_result.property_in( + mc, js_string!("done").into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, ); let mut namespace = ObjectTemplate::new(root_shape); - namespace.property(JsSymbol::to_string_tag().into(), Attribute::empty()); + namespace.property_in(mc, JsSymbol::to_string_tag().into(), Attribute::empty()); let with_resolvers = { let mut with_resolvers = ordinary_object.clone(); with_resolvers // 4. Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]). - .property(js_string!("promise").into(), Attribute::all()) + .property_in(mc, js_string!("promise").into(), Attribute::all()) // 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]). - .property(js_string!("resolve").into(), Attribute::all()) + .property_in(mc, js_string!("resolve").into(), Attribute::all()) // 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]). - .property(js_string!("reject").into(), Attribute::all()); + .property_in(mc, js_string!("reject").into(), Attribute::all()); with_resolvers }; @@ -1568,8 +1611,8 @@ impl ObjectTemplates { let wait_async = { let mut obj = ordinary_object.clone(); - obj.property(js_string!("async").into(), Attribute::all()) - .property(js_string!("value").into(), Attribute::all()); + obj.property_in(mc, js_string!("async").into(), Attribute::all()) + .property_in(mc, js_string!("value").into(), Attribute::all()); obj }; diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index bd0aa5783db..218d177ffda 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -474,10 +474,9 @@ impl Context { self.gc.alloc(value) } - /// Returns the active collector. - #[inline] + /// Gets the GC collector. #[must_use] - pub fn gc_collector(&self) -> &boa_gc::MutationContext<'static, 'static> { + pub fn gc_collector(&self) -> &'static boa_gc::MutationContext<'static, 'static> { self.gc.gc_collector() } @@ -549,7 +548,9 @@ impl Context { /// Create a new Realm with the default global bindings. pub fn create_realm(&mut self) -> JsResult { - let realm = Realm::create(self.host_hooks.as_ref(), &self.root_shape)?; + let realm = Realm::create(self.host_hooks.as_ref(), &self.root_shape, &unsafe { + boa_gc::MutationContext::global() + })?; let old_realm = self.enter_realm(realm); @@ -1223,12 +1224,13 @@ impl ContextBuilder { CANNOT_BLOCK_COUNTER.set(CANNOT_BLOCK_COUNTER.get() + 1); } - let root_shape = RootShape::default(); + let mc = unsafe { boa_gc::MutationContext::global() }; + let root_shape = RootShape::new_in(&mc); let host_hooks = self.host_hooks.unwrap_or(Rc::new(DefaultHooks)); let clock = self.clock.unwrap_or_else(|| Rc::new(StdClock::new())); - let realm = Realm::create(host_hooks.as_ref(), &root_shape)?; - let vm = Vm::new(realm); + let realm = Realm::create(host_hooks.as_ref(), &root_shape, &mc)?; + let vm = Vm::new(realm, &mc); let module_loader: Rc = if let Some(loader) = self.module_loader { loader diff --git a/core/engine/src/environments/runtime/mod.rs b/core/engine/src/environments/runtime/mod.rs index 59724b87c38..4e47a4f79ff 100644 --- a/core/engine/src/environments/runtime/mod.rs +++ b/core/engine/src/environments/runtime/mod.rs @@ -216,7 +216,7 @@ impl EnvironmentStack { &mut self, bindings_count: u32, global: &Gc<'static, DeclarativeEnvironment>, - gc: boa_gc::MutationContext<'static, '_>, + gc: &boa_gc::MutationContext<'static, '_>, ) -> u32 { let (poisoned, with) = self.compute_poisoned_with(global); @@ -240,14 +240,14 @@ impl EnvironmentStack { scope: Scope, function_slots: FunctionSlots, global: &Gc<'static, DeclarativeEnvironment>, - gc: boa_gc::MutationContext<'static, '_>, + gc: &boa_gc::MutationContext<'static, '_>, ) { let num_bindings = scope.num_bindings_non_local(); let (poisoned, with) = self.compute_poisoned_with(global); self.push_env(Environment::Declarative(Gc::new( - &gc, + gc, DeclarativeEnvironment::new( DeclarativeEnvironmentKind::Function(FunctionEnvironment::new( num_bindings, @@ -261,10 +261,10 @@ impl EnvironmentStack { } /// Push a module environment on the environments stack. - pub(crate) fn push_module(&mut self, scope: Scope, gc: boa_gc::MutationContext<'static, '_>) { + pub(crate) fn push_module(&mut self, scope: Scope, gc: &boa_gc::MutationContext<'static, '_>) { let num_bindings = scope.num_bindings_non_local(); self.push_env(Environment::Declarative(Gc::new( - &gc, + gc, DeclarativeEnvironment::new( DeclarativeEnvironmentKind::Module(ModuleEnvironment::new(num_bindings, scope)), false, diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index 2f32a2d2cb3..c7962cc5b32 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -1645,6 +1645,7 @@ impl SourceTextModule { let env = source.scope().clone(); let spanned_source_text = SpannedSourceText::new_source_only(source_text.clone()); + let mc = context.gc_collector(); let mut compiler = ByteCompiler::new( js_string!("
"), true, @@ -1654,6 +1655,7 @@ impl SourceTextModule { self.code.has_tla, false, context.interner_mut(), + &mc, false, spanned_source_text, self.code.path.clone().into(), @@ -1834,7 +1836,7 @@ impl SourceTextModule { // 8. Let moduleContext be a new ECMAScript code execution context. let mut envs = EnvironmentStack::new(); - envs.push_module(source.scope().clone(), unsafe { + envs.push_module(source.scope().clone(), &unsafe { boa_gc::MutationContext::global() }); drop(status); diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index 888c0db39cf..f0bbde545fa 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -311,6 +311,7 @@ impl SyntheticModule { // TODO: A bit of a hack to be able to pass the currently active runnable without an // available codeblock to execute. + let mc = context.gc_collector(); let compiler = ByteCompiler::new( js_string!(""), true, @@ -320,6 +321,7 @@ impl SyntheticModule { false, false, context.interner_mut(), + &mc, false, // A synthetic module does not contain `SourceText` SpannedSourceText::new_empty(), @@ -342,7 +344,7 @@ impl SyntheticModule { let cb = context.alloc(finished); let mut envs = EnvironmentStack::new(); - envs.push_module(module_scope, unsafe { boa_gc::MutationContext::global() }); + envs.push_module(module_scope, &unsafe { boa_gc::MutationContext::global() }); for locator in exports { // b. Perform ! env.InitializeBinding(exportName, undefined). diff --git a/core/engine/src/object/builtins/jsfunction.rs b/core/engine/src/object/builtins/jsfunction.rs index c1bc9d152e8..0d165e0c17c 100644 --- a/core/engine/src/object/builtins/jsfunction.rs +++ b/core/engine/src/object/builtins/jsfunction.rs @@ -122,14 +122,14 @@ impl JsFunction { Self { inner: object } } - /// Creates a new, empty intrinsic function object with only its function internal methods set. - /// - /// Mainly used to initialize objects before a [`Context`] is available to do so. - /// - /// [`Context`]: crate::Context - pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self { + /// Creates a new, empty intrinsic function object with only its function internal methods set, using the given context. + pub(crate) fn empty_intrinsic_function_in( + mc: &boa_gc::MutationContext<'static, '_>, + constructor: bool, + ) -> Self { Self { - inner: JsObject::from_proto_and_data( + inner: JsObject::from_proto_and_data_in( + mc, None, NativeFunctionObject { f: NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())), @@ -141,6 +141,18 @@ impl JsFunction { } } + /// Creates a new, empty intrinsic function object with only its function internal methods set. + /// + /// Mainly used to initialize objects before a [`Context`] is available to do so. + /// + /// [`Context`]: crate::Context + pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self { + Self::empty_intrinsic_function_in( + &unsafe { boa_gc::MutationContext::global() }, + constructor, + ) + } + /// Creates a [`JsFunction`] from a [`JsObject`], or returns `None` if the object is not a function. /// /// This does not clone the fields of the function, it only does a shallow clone of the object. diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs index 711bb3cee2b..10cfd824459 100644 --- a/core/engine/src/object/jsobject.rs +++ b/core/engine/src/object/jsobject.rs @@ -116,13 +116,14 @@ impl JsObject { Self::with_object_proto(intrinsics) } - /// Creates a new `JsObject` from its inner object and its vtable. - pub(crate) fn from_object_and_vtable( + /// Creates a new `JsObject` from its inner object and its vtable using the given context. + pub(crate) fn from_object_and_vtable_in( + mc: &boa_gc::MutationContext<'static, '_>, object: Object, vtable: &'static InternalObjectMethods, ) -> Self { let inner = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, VTableObject { object: GcRefCell::new(object), vtable, @@ -132,6 +133,18 @@ impl JsObject { JsObject { inner }.upcast() } + /// Creates a new `JsObject` from its inner object and its vtable. + pub(crate) fn from_object_and_vtable( + object: Object, + vtable: &'static InternalObjectMethods, + ) -> Self { + Self::from_object_and_vtable_in( + &unsafe { boa_gc::MutationContext::global() }, + object, + vtable, + ) + } + /// Creates a new ordinary object with its prototype set to the `Object` prototype. /// /// This is equivalent to calling the specification's abstract operation @@ -158,6 +171,13 @@ impl JsObject { ) } + /// Creates a new ordinary object, with its prototype set to null using the given context. + #[inline] + #[must_use] + pub fn with_null_proto_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self::from_proto_and_data_in(mc, None, OrdinaryObject) + } + /// Creates a new ordinary object, with its prototype set to null. /// /// This is equivalent to calling the specification's abstract operation @@ -176,7 +196,30 @@ impl JsObject { #[inline] #[must_use] pub fn with_null_proto() -> Self { - Self::from_proto_and_data(None, OrdinaryObject) + Self::with_null_proto_in(&unsafe { boa_gc::MutationContext::global() }) + } + + /// Creates a new object with the provided prototype and object data, using the given context. + pub fn from_proto_and_data_in>, T: NativeObject>( + mc: &boa_gc::MutationContext<'static, '_>, + prototype: O, + data: T, + ) -> Self { + let internal_methods = data.internal_methods(); + let inner = Gc::new( + mc, + VTableObject { + object: GcRefCell::new(Object { + data: ObjectData::new(data), + properties: PropertyMap::from_prototype_unique_shape(prototype.into()), + extensible: true, + private_elements: ThinVec::new(), + }), + vtable: internal_methods, + }, + ); + + JsObject { inner }.upcast() } /// Creates a new object with the provided prototype and object data. @@ -209,13 +252,33 @@ impl JsObject { prototype: O, data: T, ) -> Self { + Self::from_proto_and_data_in( + &unsafe { boa_gc::MutationContext::global() }, + prototype, + data, + ) + } + + /// Creates a new object with the provided prototype and object data using the given context. + pub(crate) fn from_proto_and_data_with_shared_shape_in< + O: Into>, + T: NativeObject, + >( + mc: &boa_gc::MutationContext<'static, '_>, + root_shape: &RootShape, + prototype: O, + data: T, + ) -> JsObject { let internal_methods = data.internal_methods(); let inner = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, VTableObject { object: GcRefCell::new(Object { data: ObjectData::new(data), - properties: PropertyMap::from_prototype_unique_shape(prototype.into()), + properties: PropertyMap::from_prototype_with_shared_shape( + root_shape, + prototype.into(), + ), extensible: true, private_elements: ThinVec::new(), }), @@ -223,7 +286,7 @@ impl JsObject { }, ); - JsObject { inner }.upcast() + JsObject { inner } } /// Creates a new object with the provided prototype and object data. @@ -238,24 +301,12 @@ impl JsObject { prototype: O, data: T, ) -> JsObject { - let internal_methods = data.internal_methods(); - let inner = Gc::new( + Self::from_proto_and_data_with_shared_shape_in( &unsafe { boa_gc::MutationContext::global() }, - VTableObject { - object: GcRefCell::new(Object { - data: ObjectData::new(data), - properties: PropertyMap::from_prototype_with_shared_shape( - root_shape, - prototype.into(), - ), - extensible: true, - private_elements: ThinVec::new(), - }), - vtable: internal_methods, - }, - ); - - JsObject { inner } + root_shape, + prototype, + data, + ) } /// Downcasts the object's inner data if the object is of type `T`. @@ -1057,6 +1108,33 @@ impl JsObject { } impl JsObject { + /// Creates a new `JsObject` from a `RootShape`, prototype, and data using the given context. + pub fn new_in>>( + mc: &boa_gc::MutationContext<'static, '_>, + root_shape: &RootShape, + prototype: O, + data: T, + ) -> Self { + let internal_methods = data.internal_methods(); + let inner = Gc::new( + mc, + VTableObject { + object: GcRefCell::new(Object { + data: ObjectData::new(data), + properties: PropertyMap::from_prototype_with_shared_shape( + root_shape, + prototype.into(), + ), + extensible: true, + private_elements: ThinVec::new(), + }), + vtable: internal_methods, + }, + ); + + Self { inner } + } + /// Creates a new `JsObject` from its root shape, prototype, and data. /// /// Note that the returned object will not be erased to be convertible to a @@ -1080,16 +1158,27 @@ impl JsObject { /// assert!(obj.is_ordinary()); /// ``` pub fn new>>(root_shape: &RootShape, prototype: O, data: T) -> Self { + Self::new_in( + &unsafe { boa_gc::MutationContext::global() }, + root_shape, + prototype, + data, + ) + } + + /// Creates a new `JsObject` from prototype, and data using the given context. + pub fn new_unique_in>>( + mc: &boa_gc::MutationContext<'static, '_>, + prototype: O, + data: T, + ) -> Self { let internal_methods = data.internal_methods(); let inner = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, VTableObject { object: GcRefCell::new(Object { data: ObjectData::new(data), - properties: PropertyMap::from_prototype_with_shared_shape( - root_shape, - prototype.into(), - ), + properties: PropertyMap::from_prototype_unique_shape(prototype.into()), extensible: true, private_elements: ThinVec::new(), }), @@ -1118,21 +1207,11 @@ impl JsObject { /// assert!(obj.prototype().is_none()); /// ``` pub fn new_unique>>(prototype: O, data: T) -> Self { - let internal_methods = data.internal_methods(); - let inner = Gc::new( + Self::new_unique_in( &unsafe { boa_gc::MutationContext::global() }, - VTableObject { - object: GcRefCell::new(Object { - data: ObjectData::new(data), - properties: PropertyMap::from_prototype_unique_shape(prototype.into()), - extensible: true, - private_elements: ThinVec::new(), - }), - vtable: internal_methods, - }, - ); - - Self { inner } + prototype, + data, + ) } /// Upcasts this object's inner data from a specific type `T` to an erased type diff --git a/core/engine/src/object/shape/mod.rs b/core/engine/src/object/shape/mod.rs index bfb7b512183..e4c5889093d 100644 --- a/core/engine/src/object/shape/mod.rs +++ b/core/engine/src/object/shape/mod.rs @@ -103,33 +103,45 @@ impl Shape { None } - /// Create an insert property transitions returning the new transitioned [`Shape`]. + /// Create an insert property transitions returning the new transitioned [`Shape`] using the given context. /// /// NOTE: This assumes that there is no property with the given key! - pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + pub(crate) fn insert_property_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: TransitionKey, + ) -> Self { match &self.inner { Inner::Shared(shape) => { - let shape = shape.insert_property_transition(key); + let shape = shape.insert_property_transition_in(mc, key); if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { return shape.to_unique().into(); } shape.into() } - Inner::Unique(shape) => shape.insert_property_transition(key).into(), + Inner::Unique(shape) => shape.insert_property_transition(key).into(), // UniqueShape insert doesn't allocate new GC } } + /// Create an insert property transitions returning the new transitioned [`Shape`]. + /// + /// NOTE: This assumes that there is no property with the given key! + pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + self.insert_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + /// Create a change attribute property transitions returning [`ChangeTransition`] containing the new [`Shape`] - /// and actions to be performed + /// and actions to be performed, using the given context. /// /// NOTE: This assumes that there already is a property with the given key! - pub(crate) fn change_attributes_transition( + pub(crate) fn change_attributes_transition_in( &self, + mc: &boa_gc::MutationContext<'static, '_>, key: TransitionKey, ) -> ChangeTransition { match &self.inner { Inner::Shared(shape) => { - let change_transition = shape.change_attributes_transition(key); + let change_transition = shape.change_attributes_transition_in(mc, key); let shape = if change_transition.shape.transition_count() >= Self::TRANSITION_COUNT_MAX { change_transition.shape.to_unique().into() @@ -145,13 +157,28 @@ impl Shape { } } - /// Remove a property property from the [`Shape`] returning the new transitioned [`Shape`]. + /// Create a change attribute property transitions returning [`ChangeTransition`] containing the new [`Shape`] + /// and actions to be performed /// /// NOTE: This assumes that there already is a property with the given key! - pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + pub(crate) fn change_attributes_transition( + &self, + key: TransitionKey, + ) -> ChangeTransition { + self.change_attributes_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + + /// Remove a property from the [`Shape`] returning the new transitioned [`Shape`] using the given context. + /// + /// NOTE: This assumes that there already is a property with the given key! + pub(crate) fn remove_property_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: &PropertyKey, + ) -> Self { match &self.inner { Inner::Shared(shape) => { - let shape = shape.remove_property_transition(key); + let shape = shape.remove_property_transition_in(mc, key); if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { return shape.to_unique().into(); } @@ -161,11 +188,22 @@ impl Shape { } } - /// Create a prototype transitions returning the new transitioned [`Shape`]. - pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + /// Remove a property from the [`Shape`] returning the new transitioned [`Shape`]. + /// + /// NOTE: This assumes that there already is a property with the given key! + pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + self.remove_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + + /// Create a prototype transition returning the new transitioned [`Shape`] using the given context. + pub(crate) fn change_prototype_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsPrototype, + ) -> Self { match &self.inner { Inner::Shared(shape) => { - let shape = shape.change_prototype_transition(prototype); + let shape = shape.change_prototype_transition_in(mc, prototype); if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { return shape.to_unique().into(); } @@ -175,6 +213,14 @@ impl Shape { } } + /// Create a prototype transition returning the new transitioned [`Shape`]. + pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + self.change_prototype_transition_in( + &unsafe { boa_gc::MutationContext::global() }, + prototype, + ) + } + /// Get the [`JsPrototype`] of the [`Shape`]. #[must_use] pub fn prototype(&self) -> JsPrototype { diff --git a/core/engine/src/object/shape/root_shape.rs b/core/engine/src/object/shape/root_shape.rs index 9cc3de38e59..278bddc6935 100644 --- a/core/engine/src/object/shape/root_shape.rs +++ b/core/engine/src/object/shape/root_shape.rs @@ -13,13 +13,18 @@ pub struct RootShape { impl Default for RootShape { #[inline] fn default() -> Self { - Self { - shape: SharedShape::root(), - } + Self::new_in(&unsafe { boa_gc::MutationContext::global() }) } } impl RootShape { + /// Create a new root shape using the given context. + #[inline] + pub(crate) fn new_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self { + shape: SharedShape::root_in(mc), + } + } /// Gets the inner [`SharedShape`]. #[must_use] pub const fn shape(&self) -> &SharedShape { diff --git a/core/engine/src/object/shape/shared_shape/forward_transition.rs b/core/engine/src/object/shape/shared_shape/forward_transition.rs index 88c286171d3..5846c7916b9 100644 --- a/core/engine/src/object/shape/shared_shape/forward_transition.rs +++ b/core/engine/src/object/shape/shared_shape/forward_transition.rs @@ -55,9 +55,10 @@ pub(super) struct ForwardTransition { } impl ForwardTransition { - /// Insert a property transition. - pub(super) fn insert_property( + /// Insert a property transition using the given context. + pub(super) fn insert_property_in( &self, + mc: &boa_gc::MutationContext<'static, '_>, key: TransitionKey, value: &Gc<'static, SharedShapeInner>, ) { @@ -68,14 +69,25 @@ impl ForwardTransition { properties.map.retain(|_, v| v.is_upgradable()); } - properties.map.insert( - key, - WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, value), - ); + properties.map.insert(key, WeakGc::new(mc, value)); } - /// Insert a prototype transition. - pub(super) fn insert_prototype(&self, key: JsPrototype, value: &Gc<'static, SharedShapeInner>) { + /// Insert a property transition. + pub(super) fn insert_property( + &self, + key: TransitionKey, + value: &Gc<'static, SharedShapeInner>, + ) { + self.insert_property_in(&unsafe { boa_gc::MutationContext::global() }, key, value) + } + + /// Insert a prototype transition using the given context. + pub(super) fn insert_prototype_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: JsPrototype, + value: &Gc<'static, SharedShapeInner>, + ) { let mut this = self.inner.borrow_mut(); let prototypes = this.prototypes.get_or_insert_with(Box::default); @@ -83,10 +95,12 @@ impl ForwardTransition { prototypes.map.retain(|_, v| v.is_upgradable()); } - prototypes.map.insert( - key, - WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, value), - ); + prototypes.map.insert(key, WeakGc::new(mc, value)); + } + + /// Insert a prototype transition. + pub(super) fn insert_prototype(&self, key: JsPrototype, value: &Gc<'static, SharedShapeInner>) { + self.insert_prototype_in(&unsafe { boa_gc::MutationContext::global() }, key, value) } /// Get a property transition, return [`None`] otherwise. diff --git a/core/engine/src/object/shape/shared_shape/mod.rs b/core/engine/src/object/shape/shared_shape/mod.rs index cbbfb1dbecb..3448ac5455a 100644 --- a/core/engine/src/object/shape/shared_shape/mod.rs +++ b/core/engine/src/object/shape/shared_shape/mod.rs @@ -163,32 +163,50 @@ impl SharedShape { self.inner.prototype.as_ref() == Some(prototype) } - /// Create a new [`SharedShape`]. - fn new(inner: Inner) -> Self { + /// Create a new [`SharedShape`] using the given context. + fn new_in(mc: &boa_gc::MutationContext<'static, '_>, inner: Inner) -> Self { Self { - inner: Gc::new(&unsafe { boa_gc::MutationContext::global() }, inner), + inner: Gc::new(mc, inner), } } + /// Create a new [`SharedShape`]. + fn new(inner: Inner) -> Self { + Self::new_in(&unsafe { boa_gc::MutationContext::global() }, inner) + } + + /// Create a root [`SharedShape`] using the given context. + #[must_use] + pub(crate) fn root_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self::new_in( + mc, + Inner { + forward_transitions: ForwardTransition::default(), + prototype: None, + property_count: 0, + // Most of the time the root shape initiates with between 1-4 properties. + property_table: PropertyTable::with_capacity(4), + previous: None, + flags: ShapeFlags::default(), + transition_count: 0, + }, + ) + } + /// Create a root [`SharedShape`]. #[must_use] pub(crate) fn root() -> Self { - Self::new(Inner { - forward_transitions: ForwardTransition::default(), - prototype: None, - property_count: 0, - // Most of the time the root shape initiates with between 1-4 properties. - property_table: PropertyTable::with_capacity(4), - previous: None, - flags: ShapeFlags::default(), - transition_count: 0, - }) + Self::root_in(&unsafe { boa_gc::MutationContext::global() }) } - /// Create a [`SharedShape`] change prototype transition. - pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + /// Create a [`SharedShape`] change prototype transition using the given context. + pub(crate) fn change_prototype_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsPrototype, + ) -> Self { if let Some(shape) = self.forward_transitions().get_prototype(&prototype) { - if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::global() }) { + if let Some(inner) = shape.upgrade(mc) { return Self { inner }; } @@ -203,7 +221,7 @@ impl SharedShape { transition_count: self.transition_count() + 1, flags: ShapeFlags::prototype_transition_from(self.flags()), }; - let new_shape = Self::new(new_inner_shape); + let new_shape = Self::new_in(mc, new_inner_shape); self.forward_transitions() .insert_prototype(prototype, &new_shape.inner); @@ -211,11 +229,23 @@ impl SharedShape { new_shape } - /// Create a [`SharedShape`] insert property transition. - pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + /// Create a [`SharedShape`] change prototype transition. + pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + self.change_prototype_transition_in( + &unsafe { boa_gc::MutationContext::global() }, + prototype, + ) + } + + /// Create a [`SharedShape`] insert property transition using the given context. + pub(crate) fn insert_property_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: TransitionKey, + ) -> Self { // Check if we have already created such a transition, if so use it! if let Some(shape) = self.forward_transitions().get_property(&key) { - if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::global() }) { + if let Some(inner) = shape.upgrade(mc) { return Self { inner }; } @@ -236,7 +266,7 @@ impl SharedShape { transition_count: self.transition_count() + 1, flags: ShapeFlags::insert_property_transition_from(self.flags()), }; - let new_shape = Self::new(new_inner_shape); + let new_shape = Self::new_in(mc, new_inner_shape); self.forward_transitions() .insert_property(key, &new_shape.inner); @@ -244,16 +274,30 @@ impl SharedShape { new_shape } + /// Create a [`SharedShape`] insert property transition. + pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + self.insert_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + /// Create a [`SharedShape`] change prototype transition, returning [`ChangeTransition`]. pub(crate) fn change_attributes_transition( &self, key: TransitionKey, + ) -> ChangeTransition { + self.change_attributes_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + + /// Create a [`SharedShape`] change prototype transition using the given context, returning [`ChangeTransition`]. + pub(crate) fn change_attributes_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: TransitionKey, ) -> ChangeTransition { let slot = self.property_table().get_expect(&key.property_key); // Check if we have already created such a transition, if so use it! if let Some(shape) = self.forward_transitions().get_property(&key) { - if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::global() }) { + if let Some(inner) = shape.upgrade(mc) { let action = if slot.attributes.width_match(key.attributes) { ChangeTransitionAction::Nothing } else if slot.attributes.is_accessor_descriptor() { @@ -412,13 +456,17 @@ impl SharedShape { (base, prototype, transitions) } - /// Remove a property from [`SharedShape`], returning the new [`SharedShape`]. - pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + /// Remove a property from [`SharedShape`], returning the new [`SharedShape`] using the given context. + pub(crate) fn remove_property_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: &PropertyKey, + ) -> Self { let (mut base, prototype, transitions) = self.rollback_before(key); // Apply prototype transition, if it was found. if let Some(prototype) = prototype { - base = base.change_prototype_transition(prototype); + base = base.change_prototype_transition_in(mc, prototype); } for (property_key, attributes) in transitions.into_iter().rev() { @@ -426,12 +474,17 @@ impl SharedShape { property_key, attributes, }; - base = base.insert_property_transition(transition); + base = base.insert_property_transition_in(mc, transition); } base } + /// Remove a property from [`SharedShape`], returning the new [`SharedShape`]. + pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + self.remove_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + /// Do a property lookup, returns [`None`] if property not found. pub(crate) fn lookup(&self, key: &PropertyKey) -> Option { let property_count = self.property_count(); @@ -481,27 +534,39 @@ pub(crate) struct WeakSharedShape { impl WeakSharedShape { /// Upgrade returns a [`SharedShape`] pointer for the internal value if the pointer is still live, - /// or [`None`] if the value was already garbage collected. + /// or [`None`] if the value was already garbage collected, using the given context. #[inline] #[must_use] - pub(crate) fn upgrade(&self) -> Option { + pub(crate) fn upgrade_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + ) -> Option { Some(SharedShape { - inner: self - .inner - .upgrade(&unsafe { boa_gc::MutationContext::global() })?, + inner: self.inner.upgrade(mc)?, }) } + /// Upgrade returns a [`SharedShape`] pointer for the internal value if the pointer is still live, + /// or [`None`] if the value was already garbage collected. + #[inline] + #[must_use] + pub(crate) fn upgrade(&self) -> Option { + self.upgrade_in(&unsafe { boa_gc::MutationContext::global() }) + } + #[allow(dead_code)] pub(crate) fn is_upgradable(&self) -> bool { self.inner.is_upgradable() } + pub(crate) fn new_in(mc: &boa_gc::MutationContext<'static, '_>, value: &SharedShape) -> Self { + WeakSharedShape { + inner: WeakGc::new(mc, &value.inner), + } + } } impl From<&SharedShape> for WeakSharedShape { fn from(value: &SharedShape) -> Self { - WeakSharedShape { - inner: WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, &value.inner), - } + Self::new_in(&unsafe { boa_gc::MutationContext::global() }, value) } } diff --git a/core/engine/src/object/shape/shared_shape/template.rs b/core/engine/src/object/shape/shared_shape/template.rs index 2e6c9fb90cb..0359b79153a 100644 --- a/core/engine/src/object/shape/shared_shape/template.rs +++ b/core/engine/src/object/shape/shared_shape/template.rs @@ -27,10 +27,23 @@ impl ObjectTemplate { } } + /// Create and [`ObjectTemplate`] with a prototype using the given context. + pub(crate) fn with_prototype_in( + mc: &boa_gc::MutationContext<'static, '_>, + shape: &SharedShape, + prototype: JsObject, + ) -> Self { + let shape = shape.change_prototype_transition_in(mc, Some(prototype)); + Self { shape } + } + /// Create and [`ObjectTemplate`] with a prototype. pub(crate) fn with_prototype(shape: &SharedShape, prototype: JsObject) -> Self { - let shape = shape.change_prototype_transition(Some(prototype)); - Self { shape } + Self::with_prototype_in( + &unsafe { boa_gc::MutationContext::global() }, + shape, + prototype, + ) } /// Check if the shape has a specific, prototype. @@ -38,12 +51,25 @@ impl ObjectTemplate { self.shape.has_prototype(prototype) } + /// Set the prototype of the [`ObjectTemplate`] using the given context. + /// + /// This assumes that the prototype has not been set yet. + pub(crate) fn set_prototype_in( + &mut self, + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsObject, + ) -> &mut Self { + self.shape = self + .shape + .change_prototype_transition_in(mc, Some(prototype)); + self + } + /// Set the prototype of the [`ObjectTemplate`]. /// /// This assumes that the prototype has not been set yet. pub(crate) fn set_prototype(&mut self, prototype: JsObject) -> &mut Self { - self.shape = self.shape.change_prototype_transition(Some(prototype)); - self + self.set_prototype_in(&unsafe { boa_gc::MutationContext::global() }, prototype) } /// Returns the inner shape of the [`ObjectTemplate`]. @@ -51,33 +77,51 @@ impl ObjectTemplate { &self.shape } - /// Add a data property to the [`ObjectTemplate`]. + /// Add a data property to the [`ObjectTemplate`] using the given context. /// /// This assumes that the property with the given key was not previously set /// and that it's a string or symbol. - pub(crate) fn property(&mut self, key: PropertyKey, attributes: Attribute) -> &mut Self { + pub(crate) fn property_in( + &mut self, + mc: &boa_gc::MutationContext<'static, '_>, + key: PropertyKey, + attributes: Attribute, + ) -> &mut Self { debug_assert!(!matches!(&key, PropertyKey::Index(_))); - let attributes = SlotAttributes::from_bits_truncate(attributes.bits()); - self.shape = self.shape.insert_property_transition(TransitionKey { + let transition = TransitionKey { property_key: key, - attributes, - }); + attributes: SlotAttributes::from_bits_truncate(attributes.bits()), + }; + self.shape = self.shape.insert_property_transition_in(mc, transition); self } + /// Add a data property to the [`ObjectTemplate`]. + /// + /// This assumes that the property with the given key was not previously set + /// and that it's a string or symbol. + pub(crate) fn property(&mut self, key: PropertyKey, attributes: Attribute) -> &mut Self { + self.property_in( + &unsafe { boa_gc::MutationContext::global() }, + key, + attributes, + ) + } + /// Add a accessor property to the [`ObjectTemplate`]. /// /// This assumes that the property with the given key was not previously set /// and that it's a string or symbol. - pub(crate) fn accessor( + /// Add a accessor property to the [`ObjectTemplate`] using the given context. + pub(crate) fn accessor_in( &mut self, + mc: &boa_gc::MutationContext<'static, '_>, key: PropertyKey, get: bool, set: bool, attributes: Attribute, ) -> &mut Self { - // TODO: We don't support indexed keys. debug_assert!(!matches!(&key, PropertyKey::Index(_))); let attributes = { @@ -97,29 +141,66 @@ impl ObjectTemplate { result }; - self.shape = self.shape.insert_property_transition(TransitionKey { - property_key: key, - attributes, - }); + self.shape = self.shape.insert_property_transition_in( + mc, + TransitionKey { + property_key: key, + attributes, + }, + ); self } - /// Create an object from the [`ObjectTemplate`] + /// Add a accessor property to the [`ObjectTemplate`]. /// - /// The storage must match the properties provided. - pub(crate) fn create(&self, data: T, storage: Vec) -> JsObject { + /// This assumes that the property with the given key was not previously set + /// and that it's a string or symbol. + pub(crate) fn accessor( + &mut self, + key: PropertyKey, + get: bool, + set: bool, + attributes: Attribute, + ) -> &mut Self { + self.accessor_in( + &unsafe { boa_gc::MutationContext::global() }, + key, + get, + set, + attributes, + ) + } + + /// Create an object from the [`ObjectTemplate`] using the given context. + pub(crate) fn create_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + data: T, + storage: Vec, + ) -> JsObject { let internal_methods = data.internal_methods(); + let mut properties = PropertyMap::new( + self.shape.clone().into(), + crate::object::IndexedProperties::default(), + ); + properties.storage = storage; + let mut object = Object { data: ObjectData::new(data), extensible: true, - properties: PropertyMap::new(self.shape.clone().into(), IndexedProperties::default()), + properties, private_elements: ThinVec::new(), }; - object.properties.storage = storage; + JsObject::from_object_and_vtable_in(mc, object, internal_methods) + } - JsObject::from_object_and_vtable(object, internal_methods) + /// Create an object from the [`ObjectTemplate`] + /// + /// The storage must match the properties provided. + pub(crate) fn create(&self, data: T, storage: Vec) -> JsObject { + self.create_in(&unsafe { boa_gc::MutationContext::global() }, data, storage) } /// Create an object from the [`ObjectTemplate`] diff --git a/core/engine/src/object/shape/unique_shape.rs b/core/engine/src/object/shape/unique_shape.rs index b050e4bf5eb..2b19a497af4 100644 --- a/core/engine/src/object/shape/unique_shape.rs +++ b/core/engine/src/object/shape/unique_shape.rs @@ -34,11 +34,15 @@ pub(crate) struct UniqueShape { } impl UniqueShape { - /// Create a new [`UniqueShape`]. - pub(crate) fn new(prototype: JsPrototype, property_table: PropertyTableInner) -> Self { + /// Create a new [`UniqueShape`] using the given context. + pub(crate) fn new_in( + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsPrototype, + property_table: PropertyTableInner, + ) -> Self { Self { inner: Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, Inner { property_table: RefCell::new(property_table), prototype: GcRefCell::new(prototype), @@ -47,6 +51,15 @@ impl UniqueShape { } } + /// Create a new [`UniqueShape`]. + pub(crate) fn new(prototype: JsPrototype, property_table: PropertyTableInner) -> Self { + Self::new_in( + &unsafe { boa_gc::MutationContext::global() }, + prototype, + property_table, + ) + } + pub(crate) fn override_internal( &self, property_table: PropertyTableInner, @@ -254,27 +267,39 @@ pub(crate) struct WeakUniqueShape { impl WeakUniqueShape { /// Upgrade returns a [`UniqueShape`] pointer for the internal value if the pointer is still live, - /// or [`None`] if the value was already garbage collected. + /// or [`None`] if the value was already garbage collected, using the given context. #[inline] #[must_use] - pub(crate) fn upgrade(&self) -> Option { + pub(crate) fn upgrade_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + ) -> Option { Some(UniqueShape { - inner: self - .inner - .upgrade(&unsafe { boa_gc::MutationContext::global() })?, + inner: self.inner.upgrade(mc)?, }) } + /// Upgrade returns a [`UniqueShape`] pointer for the internal value if the pointer is still live, + /// or [`None`] if the value was already garbage collected. + #[inline] + #[must_use] + pub(crate) fn upgrade(&self) -> Option { + self.upgrade_in(&unsafe { boa_gc::MutationContext::global() }) + } + #[allow(dead_code)] pub(crate) fn is_upgradable(&self) -> bool { self.inner.is_upgradable() } + pub(crate) fn new_in(mc: &boa_gc::MutationContext<'static, '_>, value: &UniqueShape) -> Self { + WeakUniqueShape { + inner: WeakGc::new(mc, &value.inner), + } + } } impl From<&UniqueShape> for WeakUniqueShape { fn from(value: &UniqueShape) -> Self { - WeakUniqueShape { - inner: WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, &value.inner), - } + Self::new_in(&unsafe { boa_gc::MutationContext::global() }, value) } } diff --git a/core/engine/src/realm.rs b/core/engine/src/realm.rs index c133ce823eb..a1d0ec1c8b1 100644 --- a/core/engine/src/realm.rs +++ b/core/engine/src/realm.rs @@ -77,8 +77,12 @@ struct Inner { impl Realm { /// Create a new [`Realm`]. #[inline] - pub fn create(hooks: &dyn HostHooks, root_shape: &RootShape) -> JsResult { - let intrinsics = Intrinsics::uninit(root_shape).ok_or_else(|| { + pub fn create( + hooks: &dyn HostHooks, + root_shape: &RootShape, + mc: &boa_gc::MutationContext<'static, '_>, + ) -> JsResult { + let intrinsics = Intrinsics::uninit(root_shape, mc).ok_or_else(|| { JsNativeError::typ().with_message("failed to create the realm intrinsics") })?; @@ -86,15 +90,12 @@ impl Realm { let global_this = hooks .create_global_this(&intrinsics) .unwrap_or_else(|| global_object.clone()); - let environment = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - DeclarativeEnvironment::global(), - ); + let environment = Gc::new(mc, DeclarativeEnvironment::global()); let scope = Scope::new_global(); let realm = Self { inner: Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, Inner { intrinsics, environment, diff --git a/core/engine/src/script.rs b/core/engine/src/script.rs index ef9823a32cf..373129572b9 100644 --- a/core/engine/src/script.rs +++ b/core/engine/src/script.rs @@ -137,6 +137,7 @@ impl Script { let spanned_source_text = SpannedSourceText::new_source_only(self.get_source()); + let mc = context.gc_collector(); let mut compiler = ByteCompiler::new( js_string!("
"), source.strict(), @@ -146,9 +147,14 @@ impl Script { false, false, context.interner_mut(), + &mc, false, spanned_source_text, - self.path().map(Path::to_owned).into(), + self.inner + .path + .as_deref() + .map(std::path::Path::to_path_buf) + .into(), ); #[cfg(feature = "annex-b")] diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 48d957adfaf..cc4ee8247b7 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -404,13 +404,10 @@ impl ActiveRunnable { impl Vm { /// Creates a new virtual machine. - pub(crate) fn new(realm: Realm) -> Self { + pub(crate) fn new(realm: Realm, mc: &boa_gc::MutationContext<'static, '_>) -> Self { let mut frames = Vec::with_capacity(16); frames.push(CallFrame::new( - Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - CodeBlock::new(JsString::default(), 0, true), - ), + Gc::new(mc, CodeBlock::new(JsString::default(), 0, true)), None, EnvironmentStack::new(), realm, diff --git a/core/engine/src/vm/opcode/push/environment.rs b/core/engine/src/vm/opcode/push/environment.rs index 0d8b34ef974..a1eb7a76926 100644 --- a/core/engine/src/vm/opcode/push/environment.rs +++ b/core/engine/src/vm/opcode/push/environment.rs @@ -18,13 +18,12 @@ impl PushScope { #[inline(always)] pub(crate) fn operation(index: IndexOperand, context: &mut Context) { let scope = context.vm.frame().code_block().constant_scope(index.into()); + let mc = context.gc_collector(); let frame = context.vm.frame_mut(); let global = frame.realm.environment(); frame .environments - .push_lexical(scope.num_bindings_non_local(), global, unsafe { - boa_gc::MutationContext::global() - }); + .push_lexical(scope.num_bindings_non_local(), global, mc); } } diff --git a/core/gc/src/context.rs b/core/gc/src/context.rs index 81624d8aee5..b3857952074 100644 --- a/core/gc/src/context.rs +++ b/core/gc/src/context.rs @@ -12,6 +12,13 @@ impl Default for GcContext { } } +#[cfg(feature = "oscars_backend")] +struct SyncWrapper(MutationContext<'static, 'static>); +#[cfg(feature = "oscars_backend")] +unsafe impl Sync for SyncWrapper {} +#[cfg(feature = "oscars_backend")] +unsafe impl Send for SyncWrapper {} + #[cfg(feature = "oscars_backend")] impl GcContext { #[must_use] @@ -20,17 +27,15 @@ impl GcContext { } pub fn alloc(&self, value: T) -> Gc<'static, T> { - // As a bridge, we use the global MutationContext until explicit - // context threading is natively supported by the oscars backend. let mc = MutationContext::global(); Gc::new(&mc, value) } #[must_use] - pub fn gc_collector(&self) -> &MutationContext<'static, 'static> { - // Just return a dummy global mutation context - // This is safe for the bridge phase. - unimplemented!("Not supported natively without closure yet, use MutationContext::global()") + pub fn gc_collector(&self) -> &'static MutationContext<'static, 'static> { + static DUMMY: std::sync::LazyLock = + std::sync::LazyLock::new(|| SyncWrapper(MutationContext::global())); + &DUMMY.0 } } @@ -45,6 +50,13 @@ impl Default for GcContext { } } +#[cfg(not(feature = "oscars_backend"))] +struct SyncWrapperDefault(crate::MutationContext<'static, 'static>); +#[cfg(not(feature = "oscars_backend"))] +unsafe impl Sync for SyncWrapperDefault {} +#[cfg(not(feature = "oscars_backend"))] +unsafe impl Send for SyncWrapperDefault {} + #[cfg(not(feature = "oscars_backend"))] impl GcContext { #[must_use] @@ -58,10 +70,9 @@ impl GcContext { } #[must_use] - pub fn gc_collector(&self) -> &crate::MutationContext<'static, 'static> { - // Just return a dummy global mutation context - static DUMMY: crate::MutationContext<'static, 'static> = - unsafe { crate::MutationContext::global() }; - &DUMMY + pub fn gc_collector(&self) -> &'static crate::MutationContext<'static, 'static> { + static DUMMY: SyncWrapperDefault = + SyncWrapperDefault(unsafe { crate::MutationContext::global() }); + &DUMMY.0 } } From 282344b4bc7eeef0cbd0db2083b2f8897f64c595 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Mon, 17 Aug 2026 15:35:24 +0000 Subject: [PATCH 04/19] Thread MutationContext through buitlin standard library objects --- cli/src/debug/limits.rs | 104 +++++---- cli/src/debug/optimizer.rs | 28 ++- core/engine/benches/full.rs | 2 +- .../src/builtins/array/array_iterator.rs | 5 +- core/engine/src/builtins/array/mod.rs | 77 ++++--- core/engine/src/builtins/array_buffer/mod.rs | 16 +- .../src/builtins/array_buffer/shared.rs | 13 +- .../engine/src/builtins/async_function/mod.rs | 4 +- .../src/builtins/async_generator/mod.rs | 6 +- .../builtins/async_generator_function/mod.rs | 4 +- core/engine/src/builtins/atomics/mod.rs | 10 +- core/engine/src/builtins/bigint/mod.rs | 4 +- core/engine/src/builtins/boolean/mod.rs | 12 +- core/engine/src/builtins/builder.rs | 54 +++-- core/engine/src/builtins/dataview/mod.rs | 11 +- core/engine/src/builtins/date/mod.rs | 16 +- core/engine/src/builtins/error/aggregate.rs | 5 +- core/engine/src/builtins/error/eval.rs | 4 +- core/engine/src/builtins/error/mod.rs | 10 +- core/engine/src/builtins/error/range.rs | 4 +- core/engine/src/builtins/error/reference.rs | 4 +- core/engine/src/builtins/error/syntax.rs | 4 +- core/engine/src/builtins/error/type.rs | 8 +- core/engine/src/builtins/error/uri.rs | 4 +- core/engine/src/builtins/escape/mod.rs | 8 +- core/engine/src/builtins/eval/mod.rs | 4 +- .../src/builtins/finalization_registry/mod.rs | 5 +- .../engine/src/builtins/function/arguments.rs | 2 + core/engine/src/builtins/function/bound.rs | 1 + core/engine/src/builtins/function/mod.rs | 14 +- core/engine/src/builtins/function/tests.rs | 3 +- core/engine/src/builtins/generator/mod.rs | 4 +- .../src/builtins/generator_function/mod.rs | 4 +- core/engine/src/builtins/intl/collator/mod.rs | 18 +- .../src/builtins/intl/date_time_format/mod.rs | 10 +- .../src/builtins/intl/list_format/mod.rs | 27 +-- core/engine/src/builtins/intl/locale/mod.rs | 52 +++-- core/engine/src/builtins/intl/mod.rs | 4 +- .../src/builtins/intl/number_format/mod.rs | 8 +- core/engine/src/builtins/intl/options.rs | 1 + .../src/builtins/intl/plural_rules/mod.rs | 5 +- .../src/builtins/intl/segmenter/iterator.rs | 5 +- .../engine/src/builtins/intl/segmenter/mod.rs | 14 +- .../src/builtins/intl/segmenter/segments.rs | 5 +- .../iterable/async_from_sync_iterator.rs | 9 +- .../builtins/iterable/iterator_constructor.rs | 6 +- .../builtins/iterable/iterator_helper/mod.rs | 5 +- .../builtins/iterable/iterator_prototype.rs | 35 +-- core/engine/src/builtins/iterable/mod.rs | 56 +++-- .../iterable/wrap_for_valid_iterator.rs | 4 +- core/engine/src/builtins/json/mod.rs | 12 +- core/engine/src/builtins/map/map_iterator.rs | 5 +- core/engine/src/builtins/map/mod.rs | 20 +- core/engine/src/builtins/math/mod.rs | 4 +- core/engine/src/builtins/mod.rs | 192 ++++++++-------- core/engine/src/builtins/number/globals.rs | 16 +- core/engine/src/builtins/number/mod.rs | 12 +- .../src/builtins/object/for_in_iterator.rs | 14 +- core/engine/src/builtins/object/mod.rs | 23 +- core/engine/src/builtins/options.rs | 7 +- core/engine/src/builtins/promise/mod.rs | 43 +++- core/engine/src/builtins/proxy/mod.rs | 9 +- core/engine/src/builtins/reflect/mod.rs | 4 +- core/engine/src/builtins/regexp/mod.rs | 44 ++-- .../builtins/regexp/regexp_string_iterator.rs | 5 +- core/engine/src/builtins/set/mod.rs | 15 +- core/engine/src/builtins/set/set_iterator.rs | 5 +- core/engine/src/builtins/string/mod.rs | 18 +- .../src/builtins/string/string_iterator.rs | 5 +- core/engine/src/builtins/symbol/mod.rs | 8 +- .../src/builtins/temporal/duration/mod.rs | 43 ++-- .../src/builtins/temporal/instant/mod.rs | 28 ++- core/engine/src/builtins/temporal/mod.rs | 4 +- core/engine/src/builtins/temporal/now.rs | 4 +- .../src/builtins/temporal/plain_date/mod.rs | 66 +++--- .../builtins/temporal/plain_date_time/mod.rs | 81 +++---- .../builtins/temporal/plain_month_day/mod.rs | 24 +- .../src/builtins/temporal/plain_time/mod.rs | 48 ++-- .../builtins/temporal/plain_year_month/mod.rs | 47 ++-- .../builtins/temporal/zoneddatetime/mod.rs | 109 +++++---- .../src/builtins/typed_array/builtin.rs | 48 ++-- core/engine/src/builtins/typed_array/mod.rs | 6 +- core/engine/src/builtins/uri/mod.rs | 16 +- core/engine/src/builtins/weak/weak_ref.rs | 5 +- core/engine/src/builtins/weak_map/mod.rs | 5 +- core/engine/src/builtins/weak_set/mod.rs | 5 +- core/engine/src/class.rs | 16 +- core/engine/src/context/hooks.rs | 8 +- core/engine/src/context/intrinsics.rs | 117 +++++----- core/engine/src/context/mod.rs | 6 +- core/engine/src/error/mod.rs | 1 + core/engine/src/module/mod.rs | 12 +- core/engine/src/module/namespace.rs | 1 + core/engine/src/module/source.rs | 2 + core/engine/src/native_function/mod.rs | 9 +- .../src/object/builtins/jsarraybuffer.rs | 1 + core/engine/src/object/builtins/jsdataview.rs | 1 + core/engine/src/object/builtins/jsdate.rs | 11 +- core/engine/src/object/builtins/jsfunction.rs | 2 +- core/engine/src/object/builtins/jsmap.rs | 2 + core/engine/src/object/builtins/jspromise.rs | 26 ++- core/engine/src/object/builtins/jsproxy.rs | 72 ++++-- core/engine/src/object/builtins/jsset.rs | 2 + .../object/builtins/jssharedarraybuffer.rs | 2 +- .../src/object/builtins/jstypedarray.rs | 12 +- core/engine/src/object/builtins/jsweakmap.rs | 1 + core/engine/src/object/builtins/jsweakset.rs | 1 + core/engine/src/object/datatypes.rs | 2 +- .../engine/src/object/internal_methods/mod.rs | 31 ++- core/engine/src/object/jsobject.rs | 210 +++++------------- core/engine/src/object/mod.rs | 177 ++++++++++----- core/engine/src/object/property_map.rs | 31 ++- core/engine/src/object/shape/mod.rs | 64 ++---- core/engine/src/object/shape/root_shape.rs | 11 +- .../shape/shared_shape/forward_transition.rs | 14 +- .../src/object/shape/shared_shape/mod.rs | 82 ++----- .../src/object/shape/shared_shape/template.rs | 60 +---- .../src/object/shape/shared_shape/tests.rs | 58 +++-- core/engine/src/object/shape/unique_shape.rs | 46 ++-- core/engine/src/realm.rs | 4 +- core/engine/src/symbol.rs | 11 +- .../src/value/conversions/serde_json.rs | 17 +- core/engine/src/value/inner/nan_boxed.rs | 2 +- core/engine/src/value/mod.rs | 60 ++--- core/engine/src/value/tests.rs | 10 +- core/engine/src/vm/code_block.rs | 52 +++-- core/engine/src/vm/inline_cache/mod.rs | 16 +- core/engine/src/vm/inline_cache/tests.rs | 64 +++--- core/engine/src/vm/opcode/await/mod.rs | 2 + core/engine/src/vm/opcode/call/mod.rs | 3 + core/engine/src/vm/opcode/generator/mod.rs | 2 + core/engine/src/vm/opcode/get/name.rs | 4 +- core/engine/src/vm/opcode/get/property.rs | 4 +- core/engine/src/vm/opcode/iteration/for_in.rs | 3 +- .../src/vm/opcode/iteration/iterator.rs | 3 +- core/engine/src/vm/opcode/meta/mod.rs | 2 +- core/engine/src/vm/opcode/push/array.rs | 10 +- core/engine/src/vm/opcode/push/class/mod.rs | 2 +- core/engine/src/vm/opcode/push/object.rs | 10 +- .../src/vm/opcode/set/class_prototype.rs | 1 + core/engine/src/vm/opcode/set/property.rs | 4 +- core/macros/src/class.rs | 14 +- core/macros/src/lib.rs | 2 +- core/macros/src/module.rs | 6 +- core/runtime/src/console/mod.rs | 2 +- core/runtime/src/fetch/headers_iterator.rs | 6 +- core/runtime/src/fetch/mod.rs | 1 + core/runtime/src/process/mod.rs | 2 +- core/runtime/src/store/to.rs | 2 +- core/runtime/src/test262.rs | 6 +- examples/src/bin/closures.rs | 3 +- examples/src/bin/jsarray.rs | 3 + examples/src/bin/jspromise.rs | 6 +- examples/src/bin/jstypedarray.rs | 4 + examples/src/bin/modulehandler.rs | 2 +- examples/src/bin/modules.rs | 4 +- examples/src/bin/synthetic.rs | 5 + tests/macros/tests/class.rs | 2 +- tests/macros/tests/fibonacci.rs | 4 +- tests/macros/tests/gcd_callback.rs | 2 +- tests/tester/src/exec/mod.rs | 1 + tests/wpt/src/lib.rs | 4 +- 162 files changed, 1726 insertions(+), 1450 deletions(-) diff --git a/cli/src/debug/limits.rs b/cli/src/debug/limits.rs index 51ec1a900f8..b8370d7cb89 100644 --- a/cli/src/debug/limits.rs +++ b/cli/src/debug/limits.rs @@ -64,48 +64,72 @@ fn set_backtrace(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResu } pub(super) fn create_object(context: &mut Context) -> JsObject { - let get_loop = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_loop)) - .name(js_string!("get loop")) - .length(0) - .build(); - let set_loop = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_loop)) - .name(js_string!("set loop")) - .length(1) - .build(); + let get_loop = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(get_loop), + ) + .name(js_string!("get loop")) + .length(0) + .build(); + let set_loop = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(set_loop), + ) + .name(js_string!("set loop")) + .length(1) + .build(); - let get_stack = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_stack)) - .name(js_string!("get stack")) - .length(0) - .build(); - let set_stack = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_stack)) - .name(js_string!("set stack")) - .length(1) - .build(); + let get_stack = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(get_stack), + ) + .name(js_string!("get stack")) + .length(0) + .build(); + let set_stack = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(set_stack), + ) + .name(js_string!("set stack")) + .length(1) + .build(); - let get_recursion = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_recursion)) - .name(js_string!("get recursion")) - .length(0) - .build(); - let set_recursion = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_recursion)) - .name(js_string!("set recursion")) - .length(1) - .build(); - let get_backtrace = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_backtrace)) - .name(js_string!("get backtrace")) - .length(0) - .build(); - let set_backtrace = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_backtrace)) - .name(js_string!("set backtrace")) - .length(1) - .build(); + let get_recursion = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(get_recursion), + ) + .name(js_string!("get recursion")) + .length(0) + .build(); + let set_recursion = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(set_recursion), + ) + .name(js_string!("set recursion")) + .length(1) + .build(); + let get_backtrace = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(get_backtrace), + ) + .name(js_string!("get backtrace")) + .length(0) + .build(); + let set_backtrace = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(set_backtrace), + ) + .name(js_string!("set backtrace")) + .length(1) + .build(); ObjectInitializer::new(context) .accessor( diff --git a/cli/src/debug/optimizer.rs b/cli/src/debug/optimizer.rs index 20b33d85ba8..c5f868d9fb1 100644 --- a/cli/src/debug/optimizer.rs +++ b/cli/src/debug/optimizer.rs @@ -38,6 +38,7 @@ fn set_statistics(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsRes pub(super) fn create_object(context: &mut Context) -> JsObject { let get_constant_folding = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(get_constant_folding), ) .name("get constantFolding") @@ -45,22 +46,29 @@ pub(super) fn create_object(context: &mut Context) -> JsObject { .build(); let set_constant_folding = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(set_constant_folding), ) .name("set constantFolding") .length(1) .build(); - let get_statistics = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_statistics)) - .name("get statistics") - .length(0) - .build(); - let set_statistics = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_statistics)) - .name("set statistics") - .length(1) - .build(); + let get_statistics = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(get_statistics), + ) + .name("get statistics") + .length(0) + .build(); + let set_statistics = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(set_statistics), + ) + .name("set statistics") + .length(1) + .build(); ObjectInitializer::new(context) .accessor( js_string!("constantFolding"), diff --git a/core/engine/benches/full.rs b/core/engine/benches/full.rs index 78e6dfb5548..78cf1f27d7a 100644 --- a/core/engine/benches/full.rs +++ b/core/engine/benches/full.rs @@ -18,7 +18,7 @@ static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; fn create_realm(c: &mut Criterion) { c.bench_function("Create Realm", move |b| { - let root_shape = RootShape::default(); + let root_shape = RootShape::new(&unsafe { boa_gc::MutationContext::global() }); b.iter(|| { Realm::create(&DefaultHooks, &root_shape, &unsafe { boa_gc::MutationContext::global() diff --git a/core/engine/src/builtins/array/array_iterator.rs b/core/engine/src/builtins/array/array_iterator.rs index 1c05b997c3b..2490214b261 100644 --- a/core/engine/src/builtins/array/array_iterator.rs +++ b/core/engine/src/builtins/array/array_iterator.rs @@ -37,8 +37,8 @@ pub(crate) struct ArrayIterator { } impl IntrinsicObject for ArrayIterator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .prototype(realm.intrinsics().constructors().iterator().prototype()) .static_method(Self::next, js_string!("next"), 0) .static_property( @@ -78,6 +78,7 @@ impl ArrayIterator { context: &Context, ) -> JsValue { let array_iterator = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().objects().iterator_prototypes().array(), Self::new(array, kind), diff --git a/core/engine/src/builtins/array/mod.rs b/core/engine/src/builtins/array/mod.rs index acd695878ea..e07046b7978 100644 --- a/core/engine/src/builtins/array/mod.rs +++ b/core/engine/src/builtins/array/mod.rs @@ -76,11 +76,11 @@ impl JsData for Array { } impl IntrinsicObject for Array { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let symbol_iterator = JsSymbol::iterator(); let symbol_unscopables = JsSymbol::unscopables(); - let get_species = BuiltInBuilder::callable(realm, Self::get_species) + let get_species = BuiltInBuilder::callable(realm, Self::get_species, mc) .name(js_string!("get [Symbol.species]")) .build(); @@ -88,6 +88,7 @@ impl IntrinsicObject for Array { realm, realm.intrinsics().objects().array_prototype_values().into(), Self::values, + mc, ) .name(js_string!("values")) .build(); @@ -100,13 +101,14 @@ impl IntrinsicObject for Array { .array_prototype_to_string() .into(), Self::to_string, + mc, ) .name(js_string!("toString")) .build(); - let unscopables_object = Self::unscopables_object(); + let unscopables_object = Self::unscopables_object(mc); - let builder = BuiltInBuilder::from_standard_constructor::(realm) + let builder = BuiltInBuilder::from_standard_constructor::(realm, mc) // Static Methods .static_method(Self::from, js_string!("from"), 1) .static_method(Self::is_array, js_string!("isArray"), 1) @@ -333,11 +335,11 @@ impl Array { // Fast path: if prototype.is_none() { - return Ok(context - .intrinsics() - .templates() - .array() - .create(Array, vec![JsValue::new(length)])); + return Ok(context.intrinsics().templates().array().create( + context.gc_collector(), + Array, + vec![JsValue::new(length)], + )); } // 7. Return A. @@ -355,16 +357,20 @@ impl Array { .array() .has_prototype(&prototype) { - return Ok(context - .intrinsics() - .templates() - .array() - .create(Array, vec![JsValue::new(length)])); + return Ok(context.intrinsics().templates().array().create( + context.gc_collector(), + Array, + vec![JsValue::new(length)], + )); } - let array = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, Array) - .upcast(); + let array = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + Array, + ) + .upcast(); // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). ordinary_define_own_property( @@ -408,6 +414,7 @@ impl Array { .templates() .array() .create_with_indexed_properties( + context.gc_collector(), Array, vec![JsValue::new(length)], IndexedProperties::from_dense_js_value(elements), @@ -3283,9 +3290,9 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype-@@unscopables /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@unscopables - pub(crate) fn unscopables_object() -> JsObject { + pub(crate) fn unscopables_object(mc: &boa_gc::MutationContext<'static, '_>) -> JsObject { // 1. Let unscopableList be OrdinaryObjectCreate(null). - let unscopable_list = JsObject::with_null_proto(); + let unscopable_list = JsObject::with_null_proto(mc); let true_prop = PropertyDescriptor::builder() .value(true) .writable(true) @@ -3294,37 +3301,37 @@ impl Array { { let mut obj = unscopable_list.borrow_mut(); // 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true). - obj.insert(js_string!("at"), true_prop.clone()); + obj.insert(mc, js_string!("at"), true_prop.clone()); // 3. Perform ! CreateDataPropertyOrThrow(unscopableList, "copyWithin", true). - obj.insert(js_string!("copyWithin"), true_prop.clone()); + obj.insert(mc, js_string!("copyWithin"), true_prop.clone()); // 4. Perform ! CreateDataPropertyOrThrow(unscopableList, "entries", true). - obj.insert(js_string!("entries"), true_prop.clone()); + obj.insert(mc, js_string!("entries"), true_prop.clone()); // 5. Perform ! CreateDataPropertyOrThrow(unscopableList, "fill", true). - obj.insert(js_string!("fill"), true_prop.clone()); + obj.insert(mc, js_string!("fill"), true_prop.clone()); // 6. Perform ! CreateDataPropertyOrThrow(unscopableList, "find", true). - obj.insert(js_string!("find"), true_prop.clone()); + obj.insert(mc, js_string!("find"), true_prop.clone()); // 7. Perform ! CreateDataPropertyOrThrow(unscopableList, "findIndex", true). - obj.insert(js_string!("findIndex"), true_prop.clone()); + obj.insert(mc, js_string!("findIndex"), true_prop.clone()); // 8. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLast", true). - obj.insert(js_string!("findLast"), true_prop.clone()); + obj.insert(mc, js_string!("findLast"), true_prop.clone()); // 9. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLastIndex", true). - obj.insert(js_string!("findLastIndex"), true_prop.clone()); + obj.insert(mc, js_string!("findLastIndex"), true_prop.clone()); // 10. Perform ! CreateDataPropertyOrThrow(unscopableList, "flat", true). - obj.insert(js_string!("flat"), true_prop.clone()); + obj.insert(mc, js_string!("flat"), true_prop.clone()); // 11. Perform ! CreateDataPropertyOrThrow(unscopableList, "flatMap", true). - obj.insert(js_string!("flatMap"), true_prop.clone()); + obj.insert(mc, js_string!("flatMap"), true_prop.clone()); // 12. Perform ! CreateDataPropertyOrThrow(unscopableList, "includes", true). - obj.insert(js_string!("includes"), true_prop.clone()); + obj.insert(mc, js_string!("includes"), true_prop.clone()); // 13. Perform ! CreateDataPropertyOrThrow(unscopableList, "keys", true). - obj.insert(js_string!("keys"), true_prop.clone()); + obj.insert(mc, js_string!("keys"), true_prop.clone()); // 14. Perform ! CreateDataPropertyOrThrow(unscopableList, "toReversed", true). - obj.insert(js_string!("toReversed"), true_prop.clone()); + obj.insert(mc, js_string!("toReversed"), true_prop.clone()); // 15. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSorted", true). - obj.insert(js_string!("toSorted"), true_prop.clone()); + obj.insert(mc, js_string!("toSorted"), true_prop.clone()); // 16. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSpliced", true). - obj.insert(js_string!("toSpliced"), true_prop.clone()); + obj.insert(mc, js_string!("toSpliced"), true_prop.clone()); // 17. Perform ! CreateDataPropertyOrThrow(unscopableList, "values", true). - obj.insert(js_string!("values"), true_prop); + obj.insert(mc, js_string!("values"), true_prop); } // 13. Return unscopableList. diff --git a/core/engine/src/builtins/array_buffer/mod.rs b/core/engine/src/builtins/array_buffer/mod.rs index 5b8c5a1bfa5..3e937776a42 100644 --- a/core/engine/src/builtins/array_buffer/mod.rs +++ b/core/engine/src/builtins/array_buffer/mod.rs @@ -327,31 +327,31 @@ impl ArrayBuffer { } impl IntrinsicObject for ArrayBuffer { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_species = BuiltInBuilder::callable(realm, Self::get_species) + let get_species = BuiltInBuilder::callable(realm, Self::get_species, mc) .name(js_string!("get [Symbol.species]")) .build(); - let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length) + let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length, mc) .name(js_string!("get byteLength")) .build(); - let get_resizable = BuiltInBuilder::callable(realm, Self::get_resizable) + let get_resizable = BuiltInBuilder::callable(realm, Self::get_resizable, mc) .name(js_string!("get resizable")) .build(); - let get_max_byte_length = BuiltInBuilder::callable(realm, Self::get_max_byte_length) + let get_max_byte_length = BuiltInBuilder::callable(realm, Self::get_max_byte_length, mc) .name(js_string!("get maxByteLength")) .build(); #[cfg(feature = "experimental")] - let get_detached = BuiltInBuilder::callable(realm, Self::get_detached) + let get_detached = BuiltInBuilder::callable(realm, Self::get_detached, mc) .name(js_string!("get detached")) .build(); - let builder = BuiltInBuilder::from_standard_constructor::(realm) + let builder = BuiltInBuilder::from_standard_constructor::(realm, mc) .static_accessor( JsSymbol::species(), Some(get_species), @@ -848,6 +848,7 @@ impl ArrayBuffer { .prototype(); Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, ArrayBuffer { @@ -899,6 +900,7 @@ impl ArrayBuffer { let block = create_byte_data_block(byte_len, max_byte_len, context)?; let obj = JsObject::new( + context.gc_collector(), context.root_shape(), prototype, Self { diff --git a/core/engine/src/builtins/array_buffer/shared.rs b/core/engine/src/builtins/array_buffer/shared.rs index 5c4b30b8482..31f80885850 100644 --- a/core/engine/src/builtins/array_buffer/shared.rs +++ b/core/engine/src/builtins/array_buffer/shared.rs @@ -96,26 +96,26 @@ impl SharedArrayBuffer { } impl IntrinsicObject for SharedArrayBuffer { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_species = BuiltInBuilder::callable(realm, Self::get_species) + let get_species = BuiltInBuilder::callable(realm, Self::get_species, mc) .name(js_string!("get [Symbol.species]")) .build(); - let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length) + let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length, mc) .name(js_string!("get byteLength")) .build(); - let get_growable = BuiltInBuilder::callable(realm, Self::get_growable) + let get_growable = BuiltInBuilder::callable(realm, Self::get_growable, mc) .name(js_string!("get growable")) .build(); - let get_max_byte_length = BuiltInBuilder::callable(realm, Self::get_max_byte_length) + let get_max_byte_length = BuiltInBuilder::callable(realm, Self::get_max_byte_length, mc) .name(js_string!("get maxByteLength")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_accessor( JsSymbol::species(), Some(get_species), @@ -537,6 +537,7 @@ impl SharedArrayBuffer { // 10. Else, // a. Set obj.[[ArrayBufferByteLength]] to byteLength. let obj = JsObject::new( + context.gc_collector(), context.root_shape(), prototype, Self { diff --git a/core/engine/src/builtins/async_function/mod.rs b/core/engine/src/builtins/async_function/mod.rs index 783f97c7e77..da47f3e351b 100644 --- a/core/engine/src/builtins/async_function/mod.rs +++ b/core/engine/src/builtins/async_function/mod.rs @@ -24,8 +24,8 @@ use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; pub struct AsyncFunction; impl IntrinsicObject for AsyncFunction { - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .prototype(realm.intrinsics().constructors().function().constructor()) .inherits(Some( realm.intrinsics().constructors().function().prototype(), diff --git a/core/engine/src/builtins/async_generator/mod.rs b/core/engine/src/builtins/async_generator/mod.rs index c3d99c3187b..298fc9f29c1 100644 --- a/core/engine/src/builtins/async_generator/mod.rs +++ b/core/engine/src/builtins/async_generator/mod.rs @@ -70,8 +70,8 @@ pub struct AsyncGenerator { } impl IntrinsicObject for AsyncGenerator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .prototype( realm .intrinsics() @@ -580,6 +580,7 @@ impl AsyncGenerator { // 12. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). let on_fulfilled = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, generator, context| { // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue. @@ -611,6 +612,7 @@ impl AsyncGenerator { // 14. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). let on_rejected = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, generator, context| { // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue. diff --git a/core/engine/src/builtins/async_generator_function/mod.rs b/core/engine/src/builtins/async_generator_function/mod.rs index 5e9eded12fb..97be453d7cd 100644 --- a/core/engine/src/builtins/async_generator_function/mod.rs +++ b/core/engine/src/builtins/async_generator_function/mod.rs @@ -24,8 +24,8 @@ use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; pub struct AsyncGeneratorFunction; impl IntrinsicObject for AsyncGeneratorFunction { - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .inherits(Some( realm.intrinsics().constructors().function().prototype(), )) diff --git a/core/engine/src/builtins/atomics/mod.rs b/core/engine/src/builtins/atomics/mod.rs index 43c0c1e7d0a..f19ae8ba98c 100644 --- a/core/engine/src/builtins/atomics/mod.rs +++ b/core/engine/src/builtins/atomics/mod.rs @@ -39,8 +39,8 @@ use super::{ pub(crate) struct Atomics; impl IntrinsicObject for Atomics { - fn init(realm: &Realm) { - let builder = BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let builder = BuiltInBuilder::with_intrinsic::(realm, mc) .static_property( JsSymbol::to_string_tag(), Self::NAME, @@ -494,7 +494,11 @@ impl Atomics { .intrinsics() .templates() .wait_async() - .create(OrdinaryObject, vec![is_async.into(), value]) + .create( + context.gc_collector(), + OrdinaryObject, + vec![is_async.into(), value], + ) .into()) } else { let result = unsafe { diff --git a/core/engine/src/builtins/bigint/mod.rs b/core/engine/src/builtins/bigint/mod.rs index f22f3c8da08..b24d56e1ba5 100644 --- a/core/engine/src/builtins/bigint/mod.rs +++ b/core/engine/src/builtins/bigint/mod.rs @@ -37,8 +37,8 @@ mod tests; pub struct BigInt; impl IntrinsicObject for BigInt { - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .method(Self::to_string, js_string!("toString"), 0) .method(Self::to_locale_string, js_string!("toLocaleString"), 0) .method(Self::value_of, js_string!("valueOf"), 0) diff --git a/core/engine/src/builtins/boolean/mod.rs b/core/engine/src/builtins/boolean/mod.rs index af79e50b5e7..982646bafb3 100644 --- a/core/engine/src/builtins/boolean/mod.rs +++ b/core/engine/src/builtins/boolean/mod.rs @@ -30,8 +30,8 @@ use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; pub(crate) struct Boolean; impl IntrinsicObject for Boolean { - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .method(Self::to_string, js_string!("toString"), 0) .method(Self::value_of, js_string!("valueOf"), 0) .build(); @@ -69,8 +69,12 @@ impl BuiltInConstructor for Boolean { } let prototype = get_prototype_from_constructor(new_target, StandardConstructors::boolean, context)?; - let boolean = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, data); + let boolean = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + data, + ); Ok(boolean.into()) } diff --git a/core/engine/src/builtins/builder.rs b/core/engine/src/builtins/builder.rs index ded9726ac82..20a4aef5808 100644 --- a/core/engine/src/builtins/builder.rs +++ b/core/engine/src/builtins/builder.rs @@ -60,12 +60,13 @@ pub(crate) struct OrdinaryObject; /// Applies the pending builder data to the object. pub(crate) trait ApplyToObject { - fn apply_to(self, object: &JsObject); + fn apply_to(self, mc: &boa_gc::MutationContext<'static, '_>, object: &JsObject); } impl ApplyToObject for Constructor { - fn apply_to(self, object: &JsObject) { + fn apply_to(self, mc: &boa_gc::MutationContext<'static, '_>, object: &JsObject) { object.insert( + mc, PROTOTYPE, PropertyDescriptor::builder() .value(self.prototype.clone()) @@ -76,8 +77,9 @@ impl ApplyToObject for Constructor { { let mut prototype = self.prototype.borrow_mut(); - prototype.set_prototype(self.inherits); + prototype.set_prototype(mc, self.inherits); prototype.insert( + mc, CONSTRUCTOR, PropertyDescriptor::builder() .value(object.clone()) @@ -90,15 +92,15 @@ impl ApplyToObject for Constructor { } impl ApplyToObject for ConstructorNoProto { - fn apply_to(self, _: &JsObject) {} + fn apply_to(self, _: &boa_gc::MutationContext<'static, '_>, _: &JsObject) {} } impl ApplyToObject for OrdinaryFunction { - fn apply_to(self, _: &JsObject) {} + fn apply_to(self, _: &boa_gc::MutationContext<'static, '_>, _: &JsObject) {} } impl ApplyToObject for Callable { - fn apply_to(self, object: &JsObject) { + fn apply_to(self, mc: &boa_gc::MutationContext<'static, '_>, object: &JsObject) { { let mut function = object .downcast_mut::() @@ -108,6 +110,7 @@ impl ApplyToObject for Callable { function.realm = Some(self.realm); } object.insert( + mc, StaticJsStrings::LENGTH, PropertyDescriptor::builder() .value(self.length) @@ -116,6 +119,7 @@ impl ApplyToObject for Callable { .configurable(true), ); object.insert( + mc, js_string!("name"), PropertyDescriptor::builder() .value(self.name) @@ -124,22 +128,22 @@ impl ApplyToObject for Callable { .configurable(true), ); - self.kind.apply_to(object); + self.kind.apply_to(mc, object); } } impl ApplyToObject for OrdinaryObject { - fn apply_to(self, _: &JsObject) {} + fn apply_to(self, _: &boa_gc::MutationContext<'static, '_>, _: &JsObject) {} } /// Builder for creating built-in objects, like `Array`. /// /// The marker `ObjectType` restricts the methods that can be called depending on the /// type of object that is being constructed. -#[derive(Debug)] #[must_use = "You need to call the `build` method in order for this to correctly assign the inner data"] pub(crate) struct BuiltInBuilder<'ctx, Kind> { realm: &'ctx Realm, + mc: &'ctx boa_gc::MutationContext<'static, 'ctx>, object: JsObject, kind: Kind, prototype: JsObject, @@ -148,9 +152,11 @@ pub(crate) struct BuiltInBuilder<'ctx, Kind> { impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { pub(crate) fn with_intrinsic( realm: &'ctx Realm, + mc: &'ctx boa_gc::MutationContext<'static, 'ctx>, ) -> BuiltInBuilder<'ctx, OrdinaryObject> { BuiltInBuilder { realm, + mc, object: I::get(realm.intrinsics()), kind: OrdinaryObject, prototype: realm.intrinsics().constructors().object().prototype(), @@ -160,6 +166,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { pub(crate) struct BuiltInConstructorWithPrototype<'ctx> { realm: &'ctx Realm, + mc: &'ctx boa_gc::MutationContext<'static, 'ctx>, function: NativeFunctionPointer, name: JsString, length: usize, @@ -222,7 +229,7 @@ impl BuiltInConstructorWithPrototype<'_> { B: Into, { let binding = binding.into(); - let function = BuiltInBuilder::callable(self.realm, function) + let function = BuiltInBuilder::callable(self.realm, function, self.mc) .name(binding.name) .length(length) .build(); @@ -302,7 +309,7 @@ impl BuiltInConstructorWithPrototype<'_> { B: Into, { let binding = binding.into(); - let function = BuiltInBuilder::callable(self.realm, function) + let function = BuiltInBuilder::callable(self.realm, function, self.mc) .name(binding.name) .length(length) .build(); @@ -490,6 +497,7 @@ impl BuiltInConstructorWithPrototype<'_> { pub(crate) struct BuiltInCallable<'ctx> { realm: &'ctx Realm, + mc: &'ctx boa_gc::MutationContext<'static, 'ctx>, function: NativeFunctionPointer, name: JsString, length: usize, @@ -515,6 +523,7 @@ impl BuiltInCallable<'_> { pub(crate) fn build(self) -> JsFunction { let object = self.realm.intrinsics().templates().function().create( + self.mc, NativeFunctionObject { f: NativeFunction::from_fn_ptr(self.function), name: self.name.clone(), @@ -532,9 +541,11 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { pub(crate) fn callable( realm: &'ctx Realm, function: NativeFunctionPointer, + mc: &'ctx boa_gc::MutationContext<'static, 'ctx>, ) -> BuiltInCallable<'ctx> { BuiltInCallable { realm, + mc, function, length: 0, name: js_string!(), @@ -544,9 +555,11 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { pub(crate) fn callable_with_intrinsic( realm: &'ctx Realm, function: NativeFunctionPointer, + mc: &'ctx boa_gc::MutationContext<'static, 'ctx>, ) -> BuiltInBuilder<'ctx, Callable> { BuiltInBuilder { realm, + mc, object: I::get(realm.intrinsics()), kind: Callable { function, @@ -563,9 +576,11 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { realm: &'ctx Realm, object: JsObject, function: NativeFunctionPointer, + mc: &'ctx boa_gc::MutationContext<'static, 'ctx>, ) -> BuiltInBuilder<'ctx, Callable> { BuiltInBuilder { realm, + mc, object, kind: Callable { function, @@ -586,10 +601,12 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable> { /// (less reallocations). pub(crate) fn from_standard_constructor( realm: &'ctx Realm, + mc: &'ctx boa_gc::MutationContext<'static, 'ctx>, ) -> BuiltInConstructorWithPrototype<'ctx> { let constructor = SC::STANDARD_CONSTRUCTOR(realm.intrinsics().constructors()); BuiltInConstructorWithPrototype { realm, + mc, function: SC::constructor, name: js_string!(SC::NAME), length: SC::CONSTRUCTOR_ARGUMENTS, @@ -634,12 +651,13 @@ impl BuiltInBuilder<'_, T> { B: Into, { let binding = binding.into(); - let function = BuiltInBuilder::callable(self.realm, function) + let function = BuiltInBuilder::callable(self.realm, function, self.mc) .name(binding.name) .length(length) .build(); self.object.insert( + self.mc, binding.binding, PropertyDescriptor::builder() .value(function) @@ -661,7 +679,7 @@ impl BuiltInBuilder<'_, T> { .writable(attribute.writable()) .enumerable(attribute.enumerable()) .configurable(attribute.configurable()); - self.object.insert(key, property); + self.object.insert(self.mc, key, property); self } @@ -690,7 +708,7 @@ impl BuiltInBuilder<'_, T> { let key = key.into(); - self.object.insert(key, property); + self.object.insert(self.mc, key, property); self } @@ -726,9 +744,9 @@ impl BuiltInBuilder<'_, Callable> { impl BuiltInBuilder<'_, OrdinaryObject> { /// Build the builtin object. pub(crate) fn build(self) -> JsObject { - self.kind.apply_to(&self.object); + self.kind.apply_to(self.mc, &self.object); - self.object.set_prototype(Some(self.prototype)); + self.object.set_prototype(self.mc, Some(self.prototype)); self.object } @@ -737,9 +755,9 @@ impl BuiltInBuilder<'_, OrdinaryObject> { impl BuiltInBuilder<'_, Callable> { /// Build the builtin callable. pub(crate) fn build(self) -> JsFunction { - self.kind.apply_to(&self.object); + self.kind.apply_to(self.mc, &self.object); - self.object.set_prototype(Some(self.prototype)); + self.object.set_prototype(self.mc, Some(self.prototype)); JsFunction::from_object_unchecked(self.object) } diff --git a/core/engine/src/builtins/dataview/mod.rs b/core/engine/src/builtins/dataview/mod.rs index 36b2de954ab..e699062ddc6 100644 --- a/core/engine/src/builtins/dataview/mod.rs +++ b/core/engine/src/builtins/dataview/mod.rs @@ -94,22 +94,22 @@ impl DataView { } impl IntrinsicObject for DataView { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_buffer = BuiltInBuilder::callable(realm, Self::get_buffer) + let get_buffer = BuiltInBuilder::callable(realm, Self::get_buffer, mc) .name(js_string!("get buffer")) .build(); - let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length) + let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length, mc) .name(js_string!("get byteLength")) .build(); - let get_byte_offset = BuiltInBuilder::callable(realm, Self::get_byte_offset) + let get_byte_offset = BuiltInBuilder::callable(realm, Self::get_byte_offset, mc) .name(js_string!("get byteOffset")) .build(); - let builder = BuiltInBuilder::from_standard_constructor::(realm) + let builder = BuiltInBuilder::from_standard_constructor::(realm, mc) .accessor( js_string!("buffer"), Some(get_buffer), @@ -295,6 +295,7 @@ impl BuiltInConstructor for DataView { } let obj = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, Self { diff --git a/core/engine/src/builtins/date/mod.rs b/core/engine/src/builtins/date/mod.rs index 0c82b5b570f..9b94d563960 100644 --- a/core/engine/src/builtins/date/mod.rs +++ b/core/engine/src/builtins/date/mod.rs @@ -83,18 +83,18 @@ impl Date { } impl IntrinsicObject for Date { - fn init(realm: &Realm) { - let to_utc_string = BuiltInBuilder::callable(realm, Self::to_utc_string) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let to_utc_string = BuiltInBuilder::callable(realm, Self::to_utc_string, mc) .name(js_string!("toUTCString")) .length(0) .build(); - let to_primitive = BuiltInBuilder::callable(realm, Self::to_primitive) + let to_primitive = BuiltInBuilder::callable(realm, Self::to_primitive, mc) .name(js_string!("[Symbol.toPrimitive]")) .length(1) .build(); - let builder = BuiltInBuilder::from_standard_constructor::(realm) + let builder = BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method(Self::now, js_string!("now"), 0) .static_method(Self::parse, js_string!("parse"), 1) .static_method(Self::utc, js_string!("UTC"), 7) @@ -335,8 +335,12 @@ impl BuiltInConstructor for Date { get_prototype_from_constructor(new_target, StandardConstructors::date, context)?; // 7. Set O.[[DateValue]] to dv. - let obj = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, dv); + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + dv, + ); // 8. Return O. Ok(obj.into()) diff --git a/core/engine/src/builtins/error/aggregate.rs b/core/engine/src/builtins/error/aggregate.rs index 186f6aede5a..ac0d3651731 100644 --- a/core/engine/src/builtins/error/aggregate.rs +++ b/core/engine/src/builtins/error/aggregate.rs @@ -27,9 +27,9 @@ use super::{Error, ErrorKind}; pub(crate) struct AggregateError; impl IntrinsicObject for AggregateError { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) @@ -86,6 +86,7 @@ impl BuiltInConstructor for AggregateError { context, )?; let o = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, Error::with_caller_position(ErrorKind::Aggregate, context), diff --git a/core/engine/src/builtins/error/eval.rs b/core/engine/src/builtins/error/eval.rs index aa762a4b139..2de873ce3be 100644 --- a/core/engine/src/builtins/error/eval.rs +++ b/core/engine/src/builtins/error/eval.rs @@ -29,9 +29,9 @@ use super::{Error, ErrorKind}; pub(crate) struct EvalError; impl IntrinsicObject for EvalError { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) diff --git a/core/engine/src/builtins/error/mod.rs b/core/engine/src/builtins/error/mod.rs index 44c8f561b9b..ada42af50cd 100644 --- a/core/engine/src/builtins/error/mod.rs +++ b/core/engine/src/builtins/error/mod.rs @@ -179,20 +179,20 @@ impl Error { } impl IntrinsicObject for Error { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let property_attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; let accessor_attribute = Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - let get_stack = BuiltInBuilder::callable(realm, Self::get_stack) + let get_stack = BuiltInBuilder::callable(realm, Self::get_stack, mc) .name(js_string!("get stack")) .build(); - let set_stack = BuiltInBuilder::callable(realm, Self::set_stack) + let set_stack = BuiltInBuilder::callable(realm, Self::set_stack, mc) .name(js_string!("set stack")) .build(); - let builder = BuiltInBuilder::from_standard_constructor::(realm) + let builder = BuiltInBuilder::from_standard_constructor::(realm, mc) .property(js_string!("name"), Self::NAME, property_attribute) .property(js_string!("message"), js_string!(), property_attribute) .method(Self::to_string, js_string!("toString"), 0) @@ -248,6 +248,7 @@ impl BuiltInConstructor for Error { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::error, context)?; let o = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, Error::with_caller_position(ErrorKind::Error, context), @@ -468,6 +469,7 @@ impl Error { let prototype = get_prototype_from_constructor(new_target, constructor_fn, context)?; let o = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, Error::with_caller_position(error_kind, context), diff --git a/core/engine/src/builtins/error/range.rs b/core/engine/src/builtins/error/range.rs index c31abc2727c..0ff83aed0ad 100644 --- a/core/engine/src/builtins/error/range.rs +++ b/core/engine/src/builtins/error/range.rs @@ -27,9 +27,9 @@ use super::{Error, ErrorKind}; pub(crate) struct RangeError; impl IntrinsicObject for RangeError { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) diff --git a/core/engine/src/builtins/error/reference.rs b/core/engine/src/builtins/error/reference.rs index 310346e2ce5..be892436ab1 100644 --- a/core/engine/src/builtins/error/reference.rs +++ b/core/engine/src/builtins/error/reference.rs @@ -26,9 +26,9 @@ use super::{Error, ErrorKind}; pub(crate) struct ReferenceError; impl IntrinsicObject for ReferenceError { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) diff --git a/core/engine/src/builtins/error/syntax.rs b/core/engine/src/builtins/error/syntax.rs index 1954b65a21c..2ede57d5e1c 100644 --- a/core/engine/src/builtins/error/syntax.rs +++ b/core/engine/src/builtins/error/syntax.rs @@ -29,9 +29,9 @@ use super::{Error, ErrorKind}; pub(crate) struct SyntaxError; impl IntrinsicObject for SyntaxError { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) diff --git a/core/engine/src/builtins/error/type.rs b/core/engine/src/builtins/error/type.rs index e783ecdc94f..a3f06151cbb 100644 --- a/core/engine/src/builtins/error/type.rs +++ b/core/engine/src/builtins/error/type.rs @@ -35,9 +35,9 @@ use super::{Error, ErrorKind}; pub(crate) struct TypeError; impl IntrinsicObject for TypeError { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) @@ -82,8 +82,8 @@ impl BuiltInConstructor for TypeError { pub(crate) struct ThrowTypeError; impl IntrinsicObject for ThrowTypeError { - fn init(realm: &Realm) { - let obj = BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let obj = BuiltInBuilder::with_intrinsic::(realm, mc) .prototype(realm.intrinsics().constructors().function().prototype()) .static_property(StaticJsStrings::LENGTH, 0, Attribute::empty()) .static_property(js_string!("name"), js_string!(), Attribute::empty()) diff --git a/core/engine/src/builtins/error/uri.rs b/core/engine/src/builtins/error/uri.rs index 0da05778709..08f970c9be7 100644 --- a/core/engine/src/builtins/error/uri.rs +++ b/core/engine/src/builtins/error/uri.rs @@ -28,9 +28,9 @@ use super::{Error, ErrorKind}; pub(crate) struct UriError; impl IntrinsicObject for UriError { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) .property(js_string!("name"), Self::NAME, attribute) diff --git a/core/engine/src/builtins/escape/mod.rs b/core/engine/src/builtins/escape/mod.rs index cc6bd6aa67d..937867673ab 100644 --- a/core/engine/src/builtins/escape/mod.rs +++ b/core/engine/src/builtins/escape/mod.rs @@ -22,8 +22,8 @@ use super::{BuiltInBuilder, BuiltInObject, IntrinsicObject}; pub(crate) struct Escape; impl IntrinsicObject for Escape { - fn init(realm: &Realm) { - BuiltInBuilder::callable_with_intrinsic::(realm, escape) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, escape, mc) .name(Self::NAME) .length(1) .build(); @@ -94,8 +94,8 @@ fn escape(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult(realm, unescape) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, unescape, mc) .name(Self::NAME) .length(1) .build(); diff --git a/core/engine/src/builtins/eval/mod.rs b/core/engine/src/builtins/eval/mod.rs index 7d6cf4cd0cd..764c73cbf6e 100644 --- a/core/engine/src/builtins/eval/mod.rs +++ b/core/engine/src/builtins/eval/mod.rs @@ -36,8 +36,8 @@ use super::{BuiltInBuilder, IntrinsicObject}; pub(crate) struct Eval; impl IntrinsicObject for Eval { - fn init(realm: &Realm) { - BuiltInBuilder::callable_with_intrinsic::(realm, Self::eval) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, Self::eval, mc) .name(Self::NAME) .length(1) .build(); diff --git a/core/engine/src/builtins/finalization_registry/mod.rs b/core/engine/src/builtins/finalization_registry/mod.rs index 3252ed47984..76899e0256a 100644 --- a/core/engine/src/builtins/finalization_registry/mod.rs +++ b/core/engine/src/builtins/finalization_registry/mod.rs @@ -74,8 +74,8 @@ impl IntrinsicObject for FinalizationRegistry { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), js_string!("FinalizationRegistry"), @@ -149,6 +149,7 @@ impl BuiltInConstructor for FinalizationRegistry { let (sender, receiver) = async_channel::bounded(1); let registry = JsObject::new_unique( + context.gc_collector(), prototype, FinalizationRegistry { realm, diff --git a/core/engine/src/builtins/function/arguments.rs b/core/engine/src/builtins/function/arguments.rs index ab339f96d07..36670bae84d 100644 --- a/core/engine/src/builtins/function/arguments.rs +++ b/core/engine/src/builtins/function/arguments.rs @@ -47,6 +47,7 @@ impl UnmappedArguments { .templates() .unmapped_arguments() .create( + context.gc_collector(), Self, vec![ // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), @@ -244,6 +245,7 @@ impl MappedArguments { // 11. Set obj.[[ParameterMap]] to map. let obj = context.intrinsics().templates().mapped_arguments().create( + context.gc_collector(), map, vec![ // 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), diff --git a/core/engine/src/builtins/function/bound.rs b/core/engine/src/builtins/function/bound.rs index f0331bb209b..c9ea75acb95 100644 --- a/core/engine/src/builtins/function/bound.rs +++ b/core/engine/src/builtins/function/bound.rs @@ -66,6 +66,7 @@ impl BoundFunction { // 9. Set obj.[[BoundArguments]] to boundArgs. // 10. Return obj. Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), proto, Self { diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index b5279c480d8..800de70a02a 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -308,15 +308,15 @@ impl OrdinaryFunction { pub struct BuiltInFunctionObject; impl IntrinsicObject for BuiltInFunctionObject { - fn init(realm: &Realm) { - let has_instance = BuiltInBuilder::callable(realm, Self::has_instance) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let has_instance = BuiltInBuilder::callable(realm, Self::has_instance, mc) .name(js_string!("[Symbol.hasInstance]")) .length(1) .build(); let throw_type_error = realm.intrinsics().objects().throw_type_error(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .method(Self::apply, js_string!("apply"), 2) .method(Self::bind, js_string!("bind"), 1) .method(Self::call, js_string!("call"), 1) @@ -338,12 +338,15 @@ impl IntrinsicObject for BuiltInFunctionObject { let prototype = realm.intrinsics().constructors().function().prototype(); - BuiltInBuilder::callable_with_object(realm, prototype.clone(), Self::prototype) + BuiltInBuilder::callable_with_object(realm, prototype.clone(), Self::prototype, mc) .name(js_string!()) .length(0) .build(); - prototype.set_prototype(Some(realm.intrinsics().constructors().object().prototype())); + prototype.set_prototype( + mc, + Some(realm.intrinsics().constructors().object().prototype()), + ); } fn get(intrinsics: &Intrinsics) -> JsObject { @@ -1144,6 +1147,7 @@ fn function_construct( let prototype = get_prototype_from_constructor(&new_target, StandardConstructors::object, context)?; let this = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, OrdinaryObject, diff --git a/core/engine/src/builtins/function/tests.rs b/core/engine/src/builtins/function/tests.rs index 7200a610e3c..bb63f86efe7 100644 --- a/core/engine/src/builtins/function/tests.rs +++ b/core/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.intrinsics()); + let object = JsObject::with_object_proto(ctx.gc_collector(), ctx.intrinsics()); object .define_property_or_throw( js_string!("key"), @@ -148,6 +148,7 @@ fn closure_capture_clone() { let func = FunctionObjectBuilder::new( ctx.realm(), + ctx.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, _, captures, context| { let (string, object) = &captures; diff --git a/core/engine/src/builtins/generator/mod.rs b/core/engine/src/builtins/generator/mod.rs index 17e60e63aec..9acf67076b0 100644 --- a/core/engine/src/builtins/generator/mod.rs +++ b/core/engine/src/builtins/generator/mod.rs @@ -154,8 +154,8 @@ pub struct Generator { } impl IntrinsicObject for Generator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .prototype(realm.intrinsics().constructors().iterator().prototype()) .static_method(Self::next, js_string!("next"), 1) .static_method(Self::r#return, js_string!("return"), 1) diff --git a/core/engine/src/builtins/generator_function/mod.rs b/core/engine/src/builtins/generator_function/mod.rs index 1e328772a09..04f8632e4d8 100644 --- a/core/engine/src/builtins/generator_function/mod.rs +++ b/core/engine/src/builtins/generator_function/mod.rs @@ -29,8 +29,8 @@ use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; pub struct GeneratorFunction; impl IntrinsicObject for GeneratorFunction { - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .inherits(Some( realm.intrinsics().constructors().function().prototype(), )) diff --git a/core/engine/src/builtins/intl/collator/mod.rs b/core/engine/src/builtins/intl/collator/mod.rs index 00eb1eb84ea..bdf8ceea4be 100644 --- a/core/engine/src/builtins/intl/collator/mod.rs +++ b/core/engine/src/builtins/intl/collator/mod.rs @@ -69,12 +69,12 @@ impl Service for Collator { } impl IntrinsicObject for Collator { - fn init(realm: &Realm) { - let compare = BuiltInBuilder::callable(realm, Self::compare) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let compare = BuiltInBuilder::callable(realm, Self::compare, mc) .name(js_string!("get compare")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method( Self::supported_locales_of, js_string!("supportedLocalesOf"), @@ -274,6 +274,7 @@ impl BuiltInConstructor for Collator { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::collator, context)?; let collator = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, Self { @@ -354,6 +355,7 @@ impl Collator { } else { let bound_compare = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), // 10.3.3.1. Collator Compare Functions // https://tc39.es/ecma402/#sec-collator-compare-functions NativeFunction::from_copy_closure_with_captures( @@ -423,11 +425,11 @@ impl Collator { })?; // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). - let options = context - .intrinsics() - .templates() - .ordinary_object() - .create(OrdinaryObject, vec![]); + let options = context.intrinsics().templates().ordinary_object().create( + context.gc_collector(), + OrdinaryObject, + vec![], + ); // 4. For each row of Table 4, except the header row, in table order, do // a. Let p be the Property value of the current row. diff --git a/core/engine/src/builtins/intl/date_time_format/mod.rs b/core/engine/src/builtins/intl/date_time_format/mod.rs index b888eebbe21..0100ad0ab41 100644 --- a/core/engine/src/builtins/intl/date_time_format/mod.rs +++ b/core/engine/src/builtins/intl/date_time_format/mod.rs @@ -101,13 +101,13 @@ impl Service for DateTimeFormat { } impl IntrinsicObject for DateTimeFormat { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { use crate::JsSymbol; - let get_format = BuiltInBuilder::callable(realm, Self::get_format) + let get_format = BuiltInBuilder::callable(realm, Self::get_format, mc) .name(js_string!("get format")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method( Self::supported_locales_of, js_string!("supportedLocalesOf"), @@ -189,7 +189,8 @@ impl BuiltInConstructor for DateTimeFormat { StandardConstructors::date_time_format, context, )?; - let date_time_format = JsObject::from_proto_and_data(prototype, dtf); + let date_time_format = + JsObject::from_proto_and_data(context.gc_collector(), prototype, dtf); // 3. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then // a. Let this be the this value. @@ -259,6 +260,7 @@ impl DateTimeFormat { // a. Let F be a new built-in function object as defined in DateTime Format Functions (11.5.4). let bound_format = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, dtf, context| { // 1. Let dtf be F.[[DateTimeFormat]]. diff --git a/core/engine/src/builtins/intl/list_format/mod.rs b/core/engine/src/builtins/intl/list_format/mod.rs index 403fca045e6..4c9f79b0d9e 100644 --- a/core/engine/src/builtins/intl/list_format/mod.rs +++ b/core/engine/src/builtins/intl/list_format/mod.rs @@ -53,8 +53,8 @@ impl Service for ListFormat { } impl IntrinsicObject for ListFormat { - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method( Self::supported_locales_of, js_string!("supportedLocalesOf"), @@ -116,7 +116,7 @@ impl BuiltInConstructor for ListFormat { let requested_locales = canonicalize_locale_list(locales, context)?; // 4. Set options to ? GetOptionsObject(options). - let options = get_options_object(options)?; + let options = get_options_object(options, context.gc_collector())?; // 5. Let opt be a new Record. // 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). @@ -173,6 +173,7 @@ impl BuiltInConstructor for ListFormat { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::list_format, context)?; let list_format = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, Self { @@ -380,11 +381,11 @@ impl ListFormat { // 4. For each Record { [[Type]], [[Value]] } part in parts, do for (n, part) in parts.0.into_iter().enumerate() { // a. Let O be OrdinaryObjectCreate(%Object.prototype%). - let o = context - .intrinsics() - .templates() - .ordinary_object() - .create(OrdinaryObject, vec![]); + let o = context.intrinsics().templates().ordinary_object().create( + context.gc_collector(), + OrdinaryObject, + vec![], + ); // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). o.create_data_property_or_throw(js_string!("type"), js_string!(part.typ()), context) @@ -429,11 +430,11 @@ impl ListFormat { })?; // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). - let options = context - .intrinsics() - .templates() - .ordinary_object() - .create(OrdinaryObject, vec![]); + let options = context.intrinsics().templates().ordinary_object().create( + context.gc_collector(), + OrdinaryObject, + vec![], + ); // 4. For each row of Table 11, except the header row, in table order, do // a. Let p be the Property value of the current row. diff --git a/core/engine/src/builtins/intl/locale/mod.rs b/core/engine/src/builtins/intl/locale/mod.rs index 949f5b094ce..1f1d540e6ff 100644 --- a/core/engine/src/builtins/intl/locale/mod.rs +++ b/core/engine/src/builtins/intl/locale/mod.rs @@ -29,52 +29,52 @@ use super::options::coerce_options_to_object; pub(crate) struct Locale; impl IntrinsicObject for Locale { - fn init(realm: &Realm) { - let base_name = BuiltInBuilder::callable(realm, Self::base_name) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let base_name = BuiltInBuilder::callable(realm, Self::base_name, mc) .name(js_string!("get baseName")) .build(); - let calendar = BuiltInBuilder::callable(realm, Self::calendar) + let calendar = BuiltInBuilder::callable(realm, Self::calendar, mc) .name(js_string!("get calendar")) .build(); - let case_first = BuiltInBuilder::callable(realm, Self::case_first) + let case_first = BuiltInBuilder::callable(realm, Self::case_first, mc) .name(js_string!("get caseFirst")) .build(); - let collation = BuiltInBuilder::callable(realm, Self::collation) + let collation = BuiltInBuilder::callable(realm, Self::collation, mc) .name(js_string!("get collation")) .build(); - let hour_cycle = BuiltInBuilder::callable(realm, Self::hour_cycle) + let hour_cycle = BuiltInBuilder::callable(realm, Self::hour_cycle, mc) .name(js_string!("get hourCycle")) .build(); - let numeric = BuiltInBuilder::callable(realm, Self::numeric) + let numeric = BuiltInBuilder::callable(realm, Self::numeric, mc) .name(js_string!("get numeric")) .build(); - let numbering_system = BuiltInBuilder::callable(realm, Self::numbering_system) + let numbering_system = BuiltInBuilder::callable(realm, Self::numbering_system, mc) .name(js_string!("get numberingSystem")) .build(); - let language = BuiltInBuilder::callable(realm, Self::language) + let language = BuiltInBuilder::callable(realm, Self::language, mc) .name(js_string!("get language")) .build(); - let script = BuiltInBuilder::callable(realm, Self::script) + let script = BuiltInBuilder::callable(realm, Self::script, mc) .name(js_string!("get script")) .build(); - let region = BuiltInBuilder::callable(realm, Self::region) + let region = BuiltInBuilder::callable(realm, Self::region, mc) .name(js_string!("get region")) .build(); - let variants = BuiltInBuilder::callable(realm, Self::variants) + let variants = BuiltInBuilder::callable(realm, Self::variants, mc) .name(js_string!("get variants")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), js_string!("Intl.Locale"), @@ -325,8 +325,12 @@ impl BuiltInConstructor for Locale { .locale_canonicalizer()? .canonicalize(&mut tag); - let locale = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, tag); + let locale = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + tag, + ); // 39. Return locale. Ok(locale.into()) @@ -369,10 +373,13 @@ impl Locale { // 4. Return ! Construct(%Locale%, maximal). let prototype = context.intrinsics().constructors().locale().prototype(); - Ok( - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, loc) - .into(), + Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + loc, ) + .into()) } /// [`Intl.Locale.prototype.minimize ( )`][spec] @@ -410,10 +417,13 @@ impl Locale { // 4. Return ! Construct(%Locale%, minimal). let prototype = context.intrinsics().constructors().locale().prototype(); - Ok( - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, loc) - .into(), + Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + loc, ) + .into()) } /// [`Intl.Locale.prototype.toString ( )`][spec]. diff --git a/core/engine/src/builtins/intl/mod.rs b/core/engine/src/builtins/intl/mod.rs index 401e5524e9f..afd24d3c4b2 100644 --- a/core/engine/src/builtins/intl/mod.rs +++ b/core/engine/src/builtins/intl/mod.rs @@ -119,8 +119,8 @@ impl Intl { } impl IntrinsicObject for Intl { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .static_property( JsSymbol::to_string_tag(), Self::NAME, diff --git a/core/engine/src/builtins/intl/number_format/mod.rs b/core/engine/src/builtins/intl/number_format/mod.rs index 5cbb36e60c2..d8253b0b581 100644 --- a/core/engine/src/builtins/intl/number_format/mod.rs +++ b/core/engine/src/builtins/intl/number_format/mod.rs @@ -162,12 +162,12 @@ impl Service for NumberFormat { } impl IntrinsicObject for NumberFormat { - fn init(realm: &Realm) { - let get_format = BuiltInBuilder::callable(realm, Self::get_format) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_format = BuiltInBuilder::callable(realm, Self::get_format, mc) .name(js_string!("get format")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method( Self::supported_locales_of, js_string!("supportedLocalesOf"), @@ -242,6 +242,7 @@ impl BuiltInConstructor for NumberFormat { let number_format = Self::new(locales, options, context)?; let number_format = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, number_format, @@ -605,6 +606,7 @@ impl NumberFormat { // c. Set nf.[[BoundFormat]] to F. let bound_format = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), // Number Format Functions // NativeFunction::from_copy_closure_with_captures( diff --git a/core/engine/src/builtins/intl/options.rs b/core/engine/src/builtins/intl/options.rs index 52543429017..93d08e24ff7 100644 --- a/core/engine/src/builtins/intl/options.rs +++ b/core/engine/src/builtins/intl/options.rs @@ -153,6 +153,7 @@ pub(super) fn coerce_options_to_object( if options.is_undefined() { // a. Return OrdinaryObjectCreate(null). return Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), None, OrdinaryObject, diff --git a/core/engine/src/builtins/intl/plural_rules/mod.rs b/core/engine/src/builtins/intl/plural_rules/mod.rs index 36787b4edda..d1a7bde8b1a 100644 --- a/core/engine/src/builtins/intl/plural_rules/mod.rs +++ b/core/engine/src/builtins/intl/plural_rules/mod.rs @@ -47,8 +47,8 @@ impl Service for PluralRules { } impl IntrinsicObject for PluralRules { - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method( Self::supported_locales_of, js_string!("supportedLocalesOf"), @@ -151,6 +151,7 @@ impl BuiltInConstructor for PluralRules { // 12. Return pluralRules. Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), proto, Self { diff --git a/core/engine/src/builtins/intl/segmenter/iterator.rs b/core/engine/src/builtins/intl/segmenter/iterator.rs index ceb7c1222f2..fb361570826 100644 --- a/core/engine/src/builtins/intl/segmenter/iterator.rs +++ b/core/engine/src/builtins/intl/segmenter/iterator.rs @@ -59,8 +59,8 @@ pub(crate) struct SegmentIterator { } impl IntrinsicObject for SegmentIterator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .static_property( JsSymbol::to_string_tag(), js_string!("Segmenter String Iterator"), @@ -87,6 +87,7 @@ impl SegmentIterator { // 5. Set iterator.[[IteratedStringNextSegmentCodeUnitIndex]] to 0. // 6. Return iterator. JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context .intrinsics() diff --git a/core/engine/src/builtins/intl/segmenter/mod.rs b/core/engine/src/builtins/intl/segmenter/mod.rs index 459a385f6ed..120f17d0d26 100644 --- a/core/engine/src/builtins/intl/segmenter/mod.rs +++ b/core/engine/src/builtins/intl/segmenter/mod.rs @@ -103,8 +103,8 @@ impl Service for Segmenter { } impl IntrinsicObject for Segmenter { - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method( Self::supported_locales_of, js_string!("supportedLocalesOf"), @@ -155,7 +155,7 @@ impl BuiltInConstructor for Segmenter { let requested_locales = canonicalize_locale_list(locales, context)?; // 5. Set options to ? GetOptionsObject(options). - let options = get_options_object(options)?; + let options = get_options_object(options, context.gc_collector())?; // 6. Let opt be a new Record. // 7. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). @@ -214,8 +214,12 @@ impl BuiltInConstructor for Segmenter { let proto = get_prototype_from_constructor(new_target, StandardConstructors::segmenter, context)?; - let segmenter = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, segmenter); + let segmenter = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + proto, + segmenter, + ); // 14. Return segmenter. Ok(segmenter.into()) diff --git a/core/engine/src/builtins/intl/segmenter/segments.rs b/core/engine/src/builtins/intl/segmenter/segments.rs index f489dce1247..248acc71318 100644 --- a/core/engine/src/builtins/intl/segmenter/segments.rs +++ b/core/engine/src/builtins/intl/segmenter/segments.rs @@ -19,8 +19,8 @@ pub(crate) struct Segments { } impl IntrinsicObject for Segments { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .static_method(Self::containing, js_string!("containing"), 1) .static_method(Self::iterator, JsSymbol::iterator(), 0) .build(); @@ -42,6 +42,7 @@ impl Segments { // 4. Set segments.[[SegmentsString]] to string. // 5. Return segments. JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().objects().segments_prototype(), Self { segmenter, string }, diff --git a/core/engine/src/builtins/iterable/async_from_sync_iterator.rs b/core/engine/src/builtins/iterable/async_from_sync_iterator.rs index 56b198e80e3..d1c2bbd4e6d 100644 --- a/core/engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/core/engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -26,8 +26,8 @@ pub(crate) struct AsyncFromSyncIterator { } impl IntrinsicObject for AsyncFromSyncIterator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .prototype( realm .intrinsics() @@ -63,6 +63,7 @@ impl AsyncFromSyncIterator { // 1. Let asyncIterator be OrdinaryObjectCreate(%AsyncFromSyncIteratorPrototype%, « [[SyncIteratorRecord]] »). // 2. Set asyncIterator.[[SyncIteratorRecord]] to syncIteratorRecord. let async_iterator = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context .intrinsics() @@ -82,7 +83,7 @@ impl AsyncFromSyncIterator { // 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: asyncIterator, [[NextMethod]]: nextMethod, [[Done]]: false }. // 5. Return iteratorRecord. - IteratorRecord::new(async_iterator, next_method) + IteratorRecord::new(async_iterator, next_method, context.gc_collector()) } /// `%AsyncFromSyncIteratorPrototype%.next ( [ value ] )` @@ -362,6 +363,7 @@ impl AsyncFromSyncIterator { // 10. Let onFulfilled be CreateBuiltinFunction(unwrap, 1, "", « »). let on_fulfilled = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure(move |_this, args, context| { // a. Return CreateIterResultObject(value, done). Ok(create_iter_result_object( @@ -393,6 +395,7 @@ impl AsyncFromSyncIterator { Some( FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, iter, context| { // i. Return ? IteratorClose(syncIteratorRecord, ThrowCompletion(error)). diff --git a/core/engine/src/builtins/iterable/iterator_constructor.rs b/core/engine/src/builtins/iterable/iterator_constructor.rs index 3f9b2c26f5a..0d29a8e20f7 100644 --- a/core/engine/src/builtins/iterable/iterator_constructor.rs +++ b/core/engine/src/builtins/iterable/iterator_constructor.rs @@ -40,9 +40,9 @@ use super::{iterator_helper::IteratorHelper, wrap_for_valid_iterator::WrapForVal pub(crate) struct IteratorConstructor; impl IntrinsicObject for IteratorConstructor { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let iterator_prototype = realm.intrinsics().constructors().iterator().prototype(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .inherits(Some(iterator_prototype.clone())) // Static methods .static_method(Self::from, js_string!("from"), 1) @@ -101,6 +101,7 @@ impl BuiltInConstructor for IteratorConstructor { // Create an ordinary object (Iterator instances have no internal data slots). Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, OrdinaryObject, @@ -141,6 +142,7 @@ impl IteratorConstructor { // 5. Set wrapper.[[Iterated]] to iteratorRecord. // 6. Return wrapper. let wrapper = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context .intrinsics() diff --git a/core/engine/src/builtins/iterable/iterator_helper/mod.rs b/core/engine/src/builtins/iterable/iterator_helper/mod.rs index 862046a2a58..89c3df3f7ee 100644 --- a/core/engine/src/builtins/iterable/iterator_helper/mod.rs +++ b/core/engine/src/builtins/iterable/iterator_helper/mod.rs @@ -75,8 +75,8 @@ pub(crate) struct IteratorHelper { } impl IntrinsicObject for IteratorHelper { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .prototype(realm.intrinsics().constructors().iterator().prototype()) .static_method(Self::next, js_string!("next"), 0) .static_method(Self::r#return, js_string!("return"), 0) @@ -296,6 +296,7 @@ impl IteratorHelper { // ). // ii. Set result.[[UnderlyingIterators]] to « iterated ». JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context .intrinsics() diff --git a/core/engine/src/builtins/iterable/iterator_prototype.rs b/core/engine/src/builtins/iterable/iterator_prototype.rs index 189f7369804..f244ae1c6f3 100644 --- a/core/engine/src/builtins/iterable/iterator_prototype.rs +++ b/core/engine/src/builtins/iterable/iterator_prototype.rs @@ -26,20 +26,20 @@ use crate::{ pub(crate) struct Iterator; impl IntrinsicObject for Iterator { - fn init(realm: &Realm) { - let get_constructor = BuiltInBuilder::callable(realm, Self::get_constructor) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_constructor = BuiltInBuilder::callable(realm, Self::get_constructor, mc) .name(js_string!("get constructor")) .build(); - let set_constructor = BuiltInBuilder::callable(realm, Self::set_constructor) + let set_constructor = BuiltInBuilder::callable(realm, Self::set_constructor, mc) .name(js_string!("set constructor")) .build(); - let get_to_string_tag = BuiltInBuilder::callable(realm, Self::get_to_string_tag) + let get_to_string_tag = BuiltInBuilder::callable(realm, Self::get_to_string_tag, mc) .name(js_string!("get [Symbol.toStringTag]")) .build(); - let set_to_string_tag = BuiltInBuilder::callable(realm, Self::set_to_string_tag) + let set_to_string_tag = BuiltInBuilder::callable(realm, Self::set_to_string_tag, mc) .name(js_string!("set [Symbol.toStringTag]")) .build(); - let builder = BuiltInBuilder::with_intrinsic::(realm) + let builder = BuiltInBuilder::with_intrinsic::(realm, mc) .static_method(|v, _, _| Ok(v.clone()), JsSymbol::iterator(), 0) .static_method(Self::map, js_string!("map"), 1) .static_method(Self::filter, js_string!("filter"), 1) @@ -217,7 +217,7 @@ impl Iterator { .ok_or_else(|| js_error!(TypeError: "Iterator.prototype.map called on non-object"))?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(o, JsValue::undefined()); + let iterated = IteratorRecord::new(o, JsValue::undefined(), context.gc_collector()); // 4. If IsCallable(mapper) is false, then // a. Let error be ThrowCompletion(a newly created TypeError object). @@ -255,7 +255,7 @@ impl Iterator { )?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(o, JsValue::undefined()); + let iterated = IteratorRecord::new(o, JsValue::undefined(), context.gc_collector()); // 4. If IsCallable(predicate) is false, then // a. Let error be ThrowCompletion(a newly created TypeError object). @@ -295,7 +295,7 @@ impl Iterator { .ok_or_else(|| js_error!(TypeError: "Iterator.prototype.take called on non-object"))?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(o, JsValue::undefined()); + let iterated = IteratorRecord::new(o, JsValue::undefined(), context.gc_collector()); // 4. Let numLimit be Completion(ToNumber(limit)). // 5. IfAbruptCloseIterator(numLimit, iterated). @@ -356,7 +356,7 @@ impl Iterator { .ok_or_else(|| js_error!(TypeError: "Iterator.prototype.drop called on non-object"))?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(o.clone(), JsValue::undefined()); + let iterated = IteratorRecord::new(o.clone(), JsValue::undefined(), context.gc_collector()); // 4. Let numLimit be Completion(ToNumber(limit)). // 5. IfAbruptCloseIterator(numLimit, iterated). @@ -416,7 +416,7 @@ impl Iterator { )?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(o, JsValue::undefined()); + let iterated = IteratorRecord::new(o, JsValue::undefined(), context.gc_collector()); // 4. If IsCallable(mapper) is false, then // a. Let error be ThrowCompletion(a newly created TypeError object). @@ -454,7 +454,8 @@ impl Iterator { )?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(obj.clone(), JsValue::undefined()); + let iterated = + IteratorRecord::new(obj.clone(), JsValue::undefined(), context.gc_collector()); let search_element = args.get_or_undefined(0); @@ -525,7 +526,7 @@ impl Iterator { )?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(o, JsValue::undefined()); + let iterated = IteratorRecord::new(o, JsValue::undefined(), context.gc_collector()); // 4. If IsCallable(reducer) is false, then let Some(reducer) = args.get_or_undefined(0).as_callable() else { @@ -620,7 +621,7 @@ impl Iterator { )?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(o, JsValue::undefined()); + let iterated = IteratorRecord::new(o, JsValue::undefined(), context.gc_collector()); // 4. If IsCallable(fn) is false, then let Some(func) = args.get_or_undefined(0).as_callable() else { @@ -676,7 +677,7 @@ impl Iterator { .ok_or_else(|| js_error!(TypeError: "Iterator.prototype.some called on non-object"))?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(o, JsValue::undefined()); + let iterated = IteratorRecord::new(o, JsValue::undefined(), context.gc_collector()); // 4. If IsCallable(predicate) is false, then let Some(predicate) = args.get_or_undefined(0).as_callable() else { @@ -735,7 +736,7 @@ impl Iterator { .ok_or_else(|| js_error!(TypeError: "Iterator.prototype.every called on non-object"))?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(o, JsValue::undefined()); + let iterated = IteratorRecord::new(o, JsValue::undefined(), context.gc_collector()); // 4. If IsCallable(predicate) is false, then let Some(predicate) = args.get_or_undefined(0).as_callable() else { @@ -795,7 +796,7 @@ impl Iterator { .ok_or_else(|| js_error!(TypeError: "Iterator.prototype.find called on non-object"))?; // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. - let iterated = IteratorRecord::new(o, JsValue::undefined()); + let iterated = IteratorRecord::new(o, JsValue::undefined(), context.gc_collector()); // 4. If IsCallable(predicate) is false, then let Some(predicate) = args.get_or_undefined(0).as_callable() else { diff --git a/core/engine/src/builtins/iterable/mod.rs b/core/engine/src/builtins/iterable/mod.rs index 835a71ebacf..032bae56384 100644 --- a/core/engine/src/builtins/iterable/mod.rs +++ b/core/engine/src/builtins/iterable/mod.rs @@ -98,18 +98,18 @@ impl Default for IteratorPrototypes { impl IteratorPrototypes { pub(crate) fn uninit_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { Self { - iterator: JsObject::with_null_proto_in(mc), - async_iterator: JsObject::with_null_proto_in(mc), - async_from_sync_iterator: JsObject::with_null_proto_in(mc), - array: JsObject::with_null_proto_in(mc), - set: JsObject::with_null_proto_in(mc), - string: JsObject::with_null_proto_in(mc), - regexp_string: JsObject::with_null_proto_in(mc), - map: JsObject::with_null_proto_in(mc), + iterator: JsObject::with_null_proto(mc), + async_iterator: JsObject::with_null_proto(mc), + async_from_sync_iterator: JsObject::with_null_proto(mc), + array: JsObject::with_null_proto(mc), + set: JsObject::with_null_proto(mc), + string: JsObject::with_null_proto(mc), + regexp_string: JsObject::with_null_proto(mc), + map: JsObject::with_null_proto(mc), #[cfg(feature = "intl")] - segment: JsObject::with_null_proto_in(mc), - iterator_helper: JsObject::with_null_proto_in(mc), - wrap_for_valid_iterator: JsObject::with_null_proto_in(mc), + segment: JsObject::with_null_proto(mc), + iterator_helper: JsObject::with_null_proto(mc), + wrap_for_valid_iterator: JsObject::with_null_proto(mc), } } /// Returns the `ArrayIteratorPrototype` object. @@ -193,8 +193,8 @@ impl IteratorPrototypes { pub(crate) struct AsyncIterator; impl IntrinsicObject for AsyncIterator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .static_method(|v, _, _| Ok(v.clone()), JsSymbol::async_iterator(), 0) .build(); } @@ -212,11 +212,11 @@ pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Conte // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value). // 4. Perform ! CreateDataPropertyOrThrow(obj, "done", done). - let obj = context - .intrinsics() - .templates() - .iterator_result() - .create(OrdinaryObject, vec![value, done.into()]); + let obj = context.intrinsics().templates().iterator_result().create( + context.gc_collector(), + OrdinaryObject, + vec![value, done.into()], + ); // 5. Return obj. obj.into() @@ -254,7 +254,11 @@ impl JsValue { let next_method = iterator_obj.get(js_string!("next"), context)?; // 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }. // 5. Return iteratorRecord. - Ok(IteratorRecord::new(iterator_obj.clone(), next_method)) + Ok(IteratorRecord::new( + iterator_obj.clone(), + next_method, + context.gc_collector(), + )) } /// `GetIterator ( obj, kind )` @@ -403,13 +407,17 @@ impl IteratorRecord { /// Creates a new `IteratorRecord` with the given iterator object, next method and `done` flag. #[inline] #[must_use] - pub fn new(iterator: JsObject, next_method: JsValue) -> Self { + pub fn new( + iterator: JsObject, + next_method: JsValue, + mc: &boa_gc::MutationContext<'static, '_>, + ) -> Self { Self { iterator, next_method, done: false, last_result: IteratorResult { - object: JsObject::with_null_proto(), + object: JsObject::with_null_proto(mc), }, } } @@ -697,7 +705,11 @@ pub(crate) fn get_iterator_direct( let next_method = obj.get(js_string!("next"), context)?; // 2. Let iteratorRecord be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }. // 3. Return iteratorRecord. - Ok(IteratorRecord::new(obj.clone(), next_method)) + Ok(IteratorRecord::new( + obj.clone(), + next_method, + context.gc_collector(), + )) } /// `GetIteratorFlattenable ( obj, stringHandling )` diff --git a/core/engine/src/builtins/iterable/wrap_for_valid_iterator.rs b/core/engine/src/builtins/iterable/wrap_for_valid_iterator.rs index a15270e66d6..8500e690921 100644 --- a/core/engine/src/builtins/iterable/wrap_for_valid_iterator.rs +++ b/core/engine/src/builtins/iterable/wrap_for_valid_iterator.rs @@ -34,8 +34,8 @@ pub(crate) struct WrapForValidIterator { } impl IntrinsicObject for WrapForValidIterator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .prototype(realm.intrinsics().constructors().iterator().prototype()) .static_method(Self::next, js_string!("next"), 0) .static_method(Self::r#return, js_string!("return"), 0) diff --git a/core/engine/src/builtins/json/mod.rs b/core/engine/src/builtins/json/mod.rs index 9d82c87eac9..52553eb6a83 100644 --- a/core/engine/src/builtins/json/mod.rs +++ b/core/engine/src/builtins/json/mod.rs @@ -200,11 +200,11 @@ impl<'ast> boa_ast::visitor::Visitor<'ast> for JsonSourceVisitor<'_> { pub(crate) struct Json; impl IntrinsicObject for Json { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let to_string_tag = JsSymbol::to_string_tag(); let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::with_intrinsic::(realm) + BuiltInBuilder::with_intrinsic::(realm, mc) .static_method(Self::parse, js_string!("parse"), 2) .static_method(Self::stringify, js_string!("stringify"), 3) .static_method(Self::raw_json, js_string!("rawJSON"), 1) @@ -338,7 +338,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.intrinsics()); + let root = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); // b. Let rootName be the empty String. // c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered). @@ -467,7 +467,7 @@ impl Json { // For objects/arrays or modified values: context = {} (no source property) // Per spec, source is only provided when the value is still the same // primitive that was produced by parsing the original JSON text. - let ctx_obj = JsObject::with_object_proto(context.intrinsics()); + let ctx_obj = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); if let Some(JsonNode::Primitive(source_text)) = source_node { // Check if the current value matches what the source text produces. // If the reviver modified the value, it won't match and we skip source. @@ -571,7 +571,7 @@ impl Json { // 3. Let internalSlotsList be « [[IsRawJSON]] ». // 4. Let obj be OrdinaryObjectCreate(null, internalSlotsList). - let obj = JsObject::from_proto_and_data(None::, RawJson); + let obj = JsObject::from_proto_and_data(context.gc_collector(), None::, RawJson); // 5. Perform ! CreateDataPropertyOrThrow(obj, "rawJSON", jsonString). obj.create_data_property_or_throw(js_string!("rawJSON"), json_string, context) @@ -738,7 +738,7 @@ impl Json { }; // 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%). - let wrapper = JsObject::with_object_proto(context.intrinsics()); + let wrapper = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); // 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value). wrapper diff --git a/core/engine/src/builtins/map/map_iterator.rs b/core/engine/src/builtins/map/map_iterator.rs index 67233688762..326f28a8567 100644 --- a/core/engine/src/builtins/map/map_iterator.rs +++ b/core/engine/src/builtins/map/map_iterator.rs @@ -52,8 +52,8 @@ pub(crate) struct MapIterator { } impl IntrinsicObject for MapIterator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .prototype(realm.intrinsics().constructors().iterator().prototype()) .static_method(Self::next, js_string!("next"), 0) .static_property( @@ -89,6 +89,7 @@ impl MapIterator { iteration_kind: kind, }; let map_iterator = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().objects().iterator_prototypes().map(), iter, diff --git a/core/engine/src/builtins/map/mod.rs b/core/engine/src/builtins/map/mod.rs index 9d5900961b0..81fa5862f4c 100644 --- a/core/engine/src/builtins/map/mod.rs +++ b/core/engine/src/builtins/map/mod.rs @@ -41,20 +41,20 @@ mod tests; pub(crate) struct Map; impl IntrinsicObject for Map { - fn init(realm: &Realm) { - let get_species = BuiltInBuilder::callable(realm, Self::get_species) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_species = BuiltInBuilder::callable(realm, Self::get_species, mc) .name(js_string!("get [Symbol.species]")) .build(); - let get_size = BuiltInBuilder::callable(realm, Self::get_size) + let get_size = BuiltInBuilder::callable(realm, Self::get_size, mc) .name(js_string!("get size")) .build(); - let entries_function = BuiltInBuilder::callable(realm, Self::entries) + let entries_function = BuiltInBuilder::callable(realm, Self::entries, mc) .name(js_string!("entries")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method(Self::group_by, js_string!("groupBy"), 2) .static_accessor( JsSymbol::species(), @@ -144,6 +144,7 @@ impl BuiltInConstructor for Map { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::map, context)?; let map = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, >::new(), @@ -797,10 +798,13 @@ impl Map { let proto = context.intrinsics().constructors().map().prototype(); // 4. Return map. - Ok( - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, map) - .into(), + Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + proto, + map, ) + .into()) } } diff --git a/core/engine/src/builtins/math/mod.rs b/core/engine/src/builtins/math/mod.rs index 9d842188c1d..f0abaf7d019 100644 --- a/core/engine/src/builtins/math/mod.rs +++ b/core/engine/src/builtins/math/mod.rs @@ -27,9 +27,9 @@ mod tests; pub(crate) struct Math; impl IntrinsicObject for Math { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - let builder = BuiltInBuilder::with_intrinsic::(realm) + let builder = BuiltInBuilder::with_intrinsic::(realm, mc) .static_property(js_string!("E"), std::f64::consts::E, attribute) .static_property(js_string!("LN10"), std::f64::consts::LN_10, attribute) .static_property(js_string!("LN2"), std::f64::consts::LN_2, attribute) diff --git a/core/engine/src/builtins/mod.rs b/core/engine/src/builtins/mod.rs index 6b6e8470ec3..4857922eed0 100644 --- a/core/engine/src/builtins/mod.rs +++ b/core/engine/src/builtins/mod.rs @@ -130,7 +130,7 @@ pub(crate) trait IntrinsicObject { /// /// This is where the methods, properties, static methods and the constructor of a built-in must /// be initialized to be accessible from ECMAScript. - fn init(realm: &Realm); + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>); /// Gets the intrinsic object. fn get(intrinsics: &Intrinsics) -> JsObject; @@ -242,113 +242,113 @@ impl Realm { /// Abstract operation [`CreateIntrinsics ( realmRec )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-createintrinsics - pub(crate) fn initialize(&self) { - BuiltInFunctionObject::init(self); - OrdinaryObject::init(self); - Iterator::init(self); - AsyncIterator::init(self); - AsyncFromSyncIterator::init(self); - IteratorConstructor::init(self); - WrapForValidIterator::init(self); - IteratorHelper::init(self); - Math::init(self); - Json::init(self); - Array::init(self); - ArrayIterator::init(self); - Proxy::init(self); - ArrayBuffer::init(self); - SharedArrayBuffer::init(self); - BigInt::init(self); - Boolean::init(self); - Date::init(self); - DataView::init(self); - Map::init(self); - MapIterator::init(self); - IsFinite::init(self); - IsNaN::init(self); - ParseInt::init(self); - ParseFloat::init(self); - Number::init(self); - Eval::init(self); - Set::init(self); - SetIterator::init(self); - String::init(self); - StringIterator::init(self); - RegExp::init(self); - RegExpStringIterator::init(self); - BuiltinTypedArray::init(self); - Int8Array::init(self); - Uint8Array::init(self); - Uint8ClampedArray::init(self); - Int16Array::init(self); - Uint16Array::init(self); - Int32Array::init(self); - Uint32Array::init(self); - BigInt64Array::init(self); - BigUint64Array::init(self); + pub(crate) fn initialize(&self, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInFunctionObject::init(self, mc); + OrdinaryObject::init(self, mc); + Iterator::init(self, mc); + AsyncIterator::init(self, mc); + AsyncFromSyncIterator::init(self, mc); + IteratorConstructor::init(self, mc); + WrapForValidIterator::init(self, mc); + IteratorHelper::init(self, mc); + Math::init(self, mc); + Json::init(self, mc); + Array::init(self, mc); + ArrayIterator::init(self, mc); + Proxy::init(self, mc); + ArrayBuffer::init(self, mc); + SharedArrayBuffer::init(self, mc); + BigInt::init(self, mc); + Boolean::init(self, mc); + Date::init(self, mc); + DataView::init(self, mc); + Map::init(self, mc); + MapIterator::init(self, mc); + IsFinite::init(self, mc); + IsNaN::init(self, mc); + ParseInt::init(self, mc); + ParseFloat::init(self, mc); + Number::init(self, mc); + Eval::init(self, mc); + Set::init(self, mc); + SetIterator::init(self, mc); + String::init(self, mc); + StringIterator::init(self, mc); + RegExp::init(self, mc); + RegExpStringIterator::init(self, mc); + BuiltinTypedArray::init(self, mc); + Int8Array::init(self, mc); + Uint8Array::init(self, mc); + Uint8ClampedArray::init(self, mc); + Int16Array::init(self, mc); + Uint16Array::init(self, mc); + Int32Array::init(self, mc); + Uint32Array::init(self, mc); + BigInt64Array::init(self, mc); + BigUint64Array::init(self, mc); #[cfg(feature = "float16")] - typed_array::Float16Array::init(self); - Float32Array::init(self); - Float64Array::init(self); - Symbol::init(self); - Error::init(self); - RangeError::init(self); - ReferenceError::init(self); - TypeError::init(self); - ThrowTypeError::init(self); - SyntaxError::init(self); - EvalError::init(self); - UriError::init(self); - AggregateError::init(self); - Reflect::init(self); - Generator::init(self); - GeneratorFunction::init(self); - Promise::init(self); - AsyncFunction::init(self); - AsyncGenerator::init(self); - AsyncGeneratorFunction::init(self); - EncodeUri::init(self); - EncodeUriComponent::init(self); - DecodeUri::init(self); - DecodeUriComponent::init(self); - WeakRef::init(self); - WeakMap::init(self); - WeakSet::init(self); - Atomics::init(self); - FinalizationRegistry::init(self); + typed_array::Float16Array::init(self, mc); + Float32Array::init(self, mc); + Float64Array::init(self, mc); + Symbol::init(self, mc); + Error::init(self, mc); + RangeError::init(self, mc); + ReferenceError::init(self, mc); + TypeError::init(self, mc); + ThrowTypeError::init(self, mc); + SyntaxError::init(self, mc); + EvalError::init(self, mc); + UriError::init(self, mc); + AggregateError::init(self, mc); + Reflect::init(self, mc); + Generator::init(self, mc); + GeneratorFunction::init(self, mc); + Promise::init(self, mc); + AsyncFunction::init(self, mc); + AsyncGenerator::init(self, mc); + AsyncGeneratorFunction::init(self, mc); + EncodeUri::init(self, mc); + EncodeUriComponent::init(self, mc); + DecodeUri::init(self, mc); + DecodeUriComponent::init(self, mc); + WeakRef::init(self, mc); + WeakMap::init(self, mc); + WeakSet::init(self, mc); + Atomics::init(self, mc); + FinalizationRegistry::init(self, mc); #[cfg(feature = "annex-b")] { - escape::Escape::init(self); - escape::Unescape::init(self); + escape::Escape::init(self, mc); + escape::Unescape::init(self, mc); } #[cfg(feature = "intl")] { - intl::Intl::init(self); - intl::Collator::init(self); - intl::ListFormat::init(self); - intl::Locale::init(self); - intl::DateTimeFormat::init(self); - intl::Segmenter::init(self); - intl::segmenter::Segments::init(self); - intl::segmenter::SegmentIterator::init(self); - intl::PluralRules::init(self); - intl::NumberFormat::init(self); + intl::Intl::init(self, mc); + intl::Collator::init(self, mc); + intl::ListFormat::init(self, mc); + intl::Locale::init(self, mc); + intl::DateTimeFormat::init(self, mc); + intl::Segmenter::init(self, mc); + intl::segmenter::Segments::init(self, mc); + intl::segmenter::SegmentIterator::init(self, mc); + intl::PluralRules::init(self, mc); + intl::NumberFormat::init(self, mc); } #[cfg(feature = "temporal")] { - temporal::Temporal::init(self); - temporal::Now::init(self); - temporal::Instant::init(self); - temporal::Duration::init(self); - temporal::PlainDate::init(self); - temporal::PlainTime::init(self); - temporal::PlainDateTime::init(self); - temporal::PlainMonthDay::init(self); - temporal::PlainYearMonth::init(self); - temporal::ZonedDateTime::init(self); + temporal::Temporal::init(self, mc); + temporal::Now::init(self, mc); + temporal::Instant::init(self, mc); + temporal::Duration::init(self, mc); + temporal::PlainDate::init(self, mc); + temporal::PlainTime::init(self, mc); + temporal::PlainDateTime::init(self, mc); + temporal::PlainMonthDay::init(self, mc); + temporal::PlainYearMonth::init(self, mc); + temporal::ZonedDateTime::init(self, mc); } } } diff --git a/core/engine/src/builtins/number/globals.rs b/core/engine/src/builtins/number/globals.rs index 39c8c92b611..a40be6e03f2 100644 --- a/core/engine/src/builtins/number/globals.rs +++ b/core/engine/src/builtins/number/globals.rs @@ -36,8 +36,8 @@ fn is_finite(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult(realm, is_finite) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, is_finite, mc) .name(Self::NAME) .length(1) .build(); @@ -78,8 +78,8 @@ pub(crate) fn is_nan(_: &JsValue, args: &[JsValue], context: &mut Context) -> Js pub(crate) struct IsNaN; impl IntrinsicObject for IsNaN { - fn init(realm: &Realm) { - BuiltInBuilder::callable_with_intrinsic::(realm, is_nan) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, is_nan, mc) .name(Self::NAME) .length(1) .build(); @@ -267,8 +267,8 @@ pub(crate) fn parse_int(_: &JsValue, args: &[JsValue], context: &mut Context) -> pub(crate) struct ParseInt; impl IntrinsicObject for ParseInt { - fn init(realm: &Realm) { - BuiltInBuilder::callable_with_intrinsic::(realm, parse_int) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, parse_int, mc) .name(Self::NAME) .length(2) .build(); @@ -383,8 +383,8 @@ pub(crate) fn parse_float( pub(crate) struct ParseFloat; impl IntrinsicObject for ParseFloat { - fn init(realm: &Realm) { - BuiltInBuilder::callable_with_intrinsic::(realm, parse_float) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, parse_float, mc) .name(Self::NAME) .length(1) .build(); diff --git a/core/engine/src/builtins/number/mod.rs b/core/engine/src/builtins/number/mod.rs index b4a0ad6a9b8..e6414927829 100644 --- a/core/engine/src/builtins/number/mod.rs +++ b/core/engine/src/builtins/number/mod.rs @@ -47,10 +47,10 @@ const BUF_SIZE: usize = 2200; pub(crate) struct Number; impl IntrinsicObject for Number { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_property(js_string!("EPSILON"), f64::EPSILON, attribute) .static_property( js_string!("MAX_SAFE_INTEGER"), @@ -126,8 +126,12 @@ impl BuiltInConstructor for Number { } let prototype = get_prototype_from_constructor(new_target, StandardConstructors::number, context)?; - let this = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, data); + let this = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + data, + ); Ok(this.into()) } } diff --git a/core/engine/src/builtins/object/for_in_iterator.rs b/core/engine/src/builtins/object/for_in_iterator.rs index 980d772c0c4..dfeac3601e1 100644 --- a/core/engine/src/builtins/object/for_in_iterator.rs +++ b/core/engine/src/builtins/object/for_in_iterator.rs @@ -58,17 +58,21 @@ impl ForInIterator { context: &Context, ) -> (JsObject, JsValue) { let iterator = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().iterator().prototype(), Self::new(object), ) .upcast(); - let next_method = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(Self::next)) - .name(js_string!("next")) - .length(0) - .build(); + let next_method = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(Self::next), + ) + .name(js_string!("next")) + .length(0) + .build(); (iterator, next_method.into()) } diff --git a/core/engine/src/builtins/object/mod.rs b/core/engine/src/builtins/object/mod.rs index 1947101550a..8872a4c2815 100644 --- a/core/engine/src/builtins/object/mod.rs +++ b/core/engine/src/builtins/object/mod.rs @@ -48,17 +48,17 @@ mod tests; pub struct OrdinaryObject; impl IntrinsicObject for OrdinaryObject { - fn init(realm: &Realm) { - let legacy_proto_getter = BuiltInBuilder::callable(realm, Self::legacy_proto_getter) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let legacy_proto_getter = BuiltInBuilder::callable(realm, Self::legacy_proto_getter, mc) .name(js_string!("get __proto__")) .build(); - let legacy_setter_proto = BuiltInBuilder::callable(realm, Self::legacy_proto_setter) + let legacy_setter_proto = BuiltInBuilder::callable(realm, Self::legacy_proto_setter, mc) .name(js_string!("set __proto__")) .length(1) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .inherits(None) .accessor( js_string!("__proto__"), @@ -172,6 +172,7 @@ impl BuiltInConstructor for OrdinaryObject { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::object, context)?; let object = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, OrdinaryObject, @@ -183,7 +184,7 @@ impl BuiltInConstructor for OrdinaryObject { // 2. If value is undefined or null, return OrdinaryObjectCreate(%Object.prototype%). if value.is_null_or_undefined() { - Ok(JsObject::with_object_proto(context.intrinsics()).into()) + Ok(JsObject::with_object_proto(context.gc_collector(), context.intrinsics()).into()) } else { // 3. Return ! ToObject(value). value.to_object(context).map(JsValue::from) @@ -440,7 +441,7 @@ impl OrdinaryObject { } } - /// `Object.create( proto, [propertiesObject] )` + /// `Object.create( context.gc_collector(), proto, [propertiesObject] )` /// /// Creates a new object from the provided prototype. /// @@ -457,6 +458,7 @@ impl OrdinaryObject { let obj = match prototype.variant() { JsVariant::Object(_) | JsVariant::Null => { JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype.as_object(), OrdinaryObject, @@ -532,7 +534,7 @@ impl OrdinaryObject { obj.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?; // 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%). - let descriptors = JsObject::with_object_proto(context.intrinsics()); + let descriptors = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); // 4. For each element key of ownKeys, do for key in own_keys { @@ -573,7 +575,7 @@ impl OrdinaryObject { // 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.intrinsics()); + let obj = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); // 4. If Desc has a [[Value]] field, then if let Some(value) = desc.value() { @@ -1299,12 +1301,13 @@ impl OrdinaryObject { // 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.intrinsics()); + let obj = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); // 4. Let closure be a new Abstract Closure with parameters (key, value) that captures // obj and performs the following steps when called: let closure = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, obj, context| { let key = args.get_or_undefined(0); @@ -1413,7 +1416,7 @@ impl OrdinaryObject { } // 2. Let obj be OrdinaryObjectCreate(null). - let obj = JsObject::with_null_proto(); + let obj = JsObject::with_null_proto(context.gc_collector()); // 3. For each Record { [[Key]], [[Elements]] } g of groups, do for (key, elements) in groups { diff --git a/core/engine/src/builtins/options.rs b/core/engine/src/builtins/options.rs index ba5d805874f..912a5c33e12 100644 --- a/core/engine/src/builtins/options.rs +++ b/core/engine/src/builtins/options.rs @@ -74,12 +74,15 @@ pub(crate) fn get_option( /// default empty `JsObject`. It throws a `TypeError` if `options` is not undefined and not a `JsObject`. /// /// [spec]: https://tc39.es/ecma402/#sec-getoptionsobject -pub(crate) fn get_options_object(options: &JsValue) -> JsResult { +pub(crate) fn get_options_object( + options: &JsValue, + mc: &boa_gc::MutationContext<'static, '_>, +) -> JsResult { match options.variant() { // If options is undefined, then JsVariant::Undefined => { // a. Return OrdinaryObjectCreate(null). - Ok(JsObject::with_null_proto()) + Ok(JsObject::with_null_proto(mc)) } // 2. If Type(options) is Object, then JsVariant::Object(obj) => { diff --git a/core/engine/src/builtins/promise/mod.rs b/core/engine/src/builtins/promise/mod.rs index 84cc6a0c058..6d7d2c1c76c 100644 --- a/core/engine/src/builtins/promise/mod.rs +++ b/core/engine/src/builtins/promise/mod.rs @@ -252,6 +252,7 @@ impl PromiseCapability { // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). let executor = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args: &[JsValue], captures, _| { let mut promise_capability = captures.borrow_mut(); @@ -338,12 +339,12 @@ impl PromiseCapability { } impl IntrinsicObject for Promise { - fn init(realm: &Realm) { - let get_species = BuiltInBuilder::callable(realm, Self::get_species) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_species = BuiltInBuilder::callable(realm, Self::get_species, mc) .name(js_string!("get [Symbol.species]")) .build(); - let builder = BuiltInBuilder::from_standard_constructor::(realm) + let builder = BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method(Self::all, js_string!("all"), 1) .static_method(Self::all_settled, js_string!("allSettled"), 1) .static_method(Self::any, js_string!("any"), 1) @@ -422,6 +423,7 @@ impl BuiltInConstructor for Promise { get_prototype_from_constructor(new_target, StandardConstructors::promise, context)?; let promise = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), promise, // 4. Set promise.[[PromiseState]] to pending. @@ -556,6 +558,7 @@ impl Promise { // 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]). // 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]). let obj = context.intrinsics().templates().with_resolvers().create( + context.gc_collector(), OrdinaryObject, vec![promise.into(), resolve.into(), reject.into()], ); @@ -680,6 +683,7 @@ impl Promise { // l. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. let on_fulfilled = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, captures, context| { // https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions @@ -898,6 +902,7 @@ impl Promise { // m. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. let on_fulfilled = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, captures, context| { // https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions @@ -919,7 +924,10 @@ impl Promise { // 8. Let remainingElementsCount be F.[[RemainingElements]]. // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = JsObject::with_object_proto(context.intrinsics()); + let obj = JsObject::with_object_proto( + context.gc_collector(), + context.intrinsics(), + ); // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled"). obj.create_data_property_or_throw( @@ -988,6 +996,7 @@ impl Promise { // u. Set onRejected.[[RemainingElements]] to remainingElementsCount. let on_rejected = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, captures, context| { // https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions @@ -1009,7 +1018,10 @@ impl Promise { // 8. Let remainingElementsCount be F.[[RemainingElements]]. // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = JsObject::with_object_proto(context.intrinsics()); + let obj = JsObject::with_object_proto( + context.gc_collector(), + context.intrinsics(), + ); // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected"). obj.create_data_property_or_throw( @@ -1272,6 +1284,7 @@ impl Promise { // vi. Let onFulfilled be a new Abstract Closure... let on_fulfilled = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, captures, context| { // 1. If alreadyCalled.[[Value]] is true, return undefined. @@ -1291,7 +1304,10 @@ impl Promise { } else { // 4. Else (variant is all-settled) // a. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = JsObject::with_object_proto(context.intrinsics()); + let obj = JsObject::with_object_proto( + context.gc_collector(), + context.intrinsics(), + ); // b. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled"). obj.create_data_property_or_throw( js_string!("status"), @@ -1354,6 +1370,7 @@ impl Promise { // Else (variant is all-settled), let onRejected be a new Abstract Closure... let on_rejected_fn = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, captures, context| { // 1. If alreadyCalled.[[Value]] is true, return undefined. @@ -1367,7 +1384,10 @@ impl Promise { let x = args.get_or_undefined(0).clone(); // 3. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = JsObject::with_object_proto(context.intrinsics()); + let obj = JsObject::with_object_proto( + context.gc_collector(), + context.intrinsics(), + ); // 4. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected"). obj.create_data_property_or_throw( js_string!("status"), @@ -1573,6 +1593,7 @@ impl Promise { // l. Set onRejected.[[RemainingElements]] to remainingElementsCount. let on_rejected = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, captures, context| { // https://tc39.es/ecma262/#sec-promise.any-reject-element-functions @@ -2006,6 +2027,7 @@ impl Promise { // a. Let thenFinallyClosure be a new Abstract Closure with parameters (value) that captures onFinally and C and performs the following steps when called: let then_finally_closure = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { /// Capture object for the abstract `returnValue` closure. @@ -2027,6 +2049,7 @@ impl Promise { // iii. Let returnValue be a new Abstract Closure with no parameters that captures value and performs the following steps when called: let return_value = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, _args, captures, _context| { // 1. Return value. @@ -2057,6 +2080,7 @@ impl Promise { // c. Let catchFinallyClosure be a new Abstract Closure with parameters (reason) that captures onFinally and C and performs the following steps when called: let catch_finally_closure = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { /// Capture object for the abstract `throwReason` closure. @@ -2078,6 +2102,7 @@ impl Promise { // iii. Let throwReason be a new Abstract Closure with no parameters that captures reason and performs the following steps when called: let throw_reason = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, _args, captures, _context| { // 1. Return ThrowCompletion(reason). @@ -2452,6 +2477,7 @@ impl Promise { // 4. Let resolve be CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »). let resolve = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { // https://tc39.es/ecma262/#sec-promise-resolve-functions @@ -2549,6 +2575,7 @@ impl Promise { // 9. Let reject be CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »). let reject = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { // https://tc39.es/ecma262/#sec-promise-reject-functions @@ -2761,7 +2788,7 @@ fn create_keyed_result_object( debug_assert_eq!(keys.len(), values.len()); // 2. Let obj be OrdinaryObjectCreate(null). - let obj = JsObject::with_null_proto(); + let obj = JsObject::with_null_proto(context.gc_collector()); // 3. For each integer i such that 0 ≤ i < the number of elements in keys, in ascending order, do for (key, value) in keys.iter().zip(values.iter()) { diff --git a/core/engine/src/builtins/proxy/mod.rs b/core/engine/src/builtins/proxy/mod.rs index a86155e2421..4b7c4d5e27c 100644 --- a/core/engine/src/builtins/proxy/mod.rs +++ b/core/engine/src/builtins/proxy/mod.rs @@ -87,8 +87,8 @@ impl JsData for Proxy { } impl IntrinsicObject for Proxy { - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method(Self::revocable, js_string!("revocable"), 2) .build_without_prototype(); } @@ -181,6 +181,7 @@ impl Proxy { // 6. Set P.[[ProxyTarget]] to target. // 7. Set P.[[ProxyHandler]] to handler. let p = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().object().prototype(), Self::new(target.clone(), handler.clone()), @@ -215,7 +216,7 @@ impl Proxy { }, GcRefCell::new(Some(proxy)), ) - .to_js_function(context.realm()) + .to_js_function(context.realm(), context.gc_collector()) } /// `28.2.2.1 Proxy.revocable ( target, handler )` @@ -232,7 +233,7 @@ impl Proxy { let revoker = Self::revoker(p.clone(), context); // 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%). - let result = JsObject::with_object_proto(context.intrinsics()); + let result = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); // 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p). result diff --git a/core/engine/src/builtins/reflect/mod.rs b/core/engine/src/builtins/reflect/mod.rs index be57983f920..2c1e21e5491 100644 --- a/core/engine/src/builtins/reflect/mod.rs +++ b/core/engine/src/builtins/reflect/mod.rs @@ -33,10 +33,10 @@ mod tests; pub(crate) struct Reflect; impl IntrinsicObject for Reflect { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let to_string_tag = JsSymbol::to_string_tag(); - BuiltInBuilder::with_intrinsic::(realm) + BuiltInBuilder::with_intrinsic::(realm, mc) .static_method(Self::apply, js_string!("apply"), 3) .static_method(Self::construct, js_string!("construct"), 2) .static_method(Self::define_property, js_string!("defineProperty"), 3) diff --git a/core/engine/src/builtins/regexp/mod.rs b/core/engine/src/builtins/regexp/mod.rs index 037eed6eb19..5b6b97f8787 100644 --- a/core/engine/src/builtins/regexp/mod.rs +++ b/core/engine/src/builtins/regexp/mod.rs @@ -60,44 +60,44 @@ impl RegExp { } impl IntrinsicObject for RegExp { - fn init(realm: &Realm) { - let get_species = BuiltInBuilder::callable(realm, Self::get_species) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_species = BuiltInBuilder::callable(realm, Self::get_species, mc) .name(js_string!("get [Symbol.species]")) .build(); let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_has_indices = BuiltInBuilder::callable(realm, Self::get_has_indices) + let get_has_indices = BuiltInBuilder::callable(realm, Self::get_has_indices, mc) .name(js_string!("get hasIndices")) .build(); - let get_global = BuiltInBuilder::callable(realm, Self::get_global) + let get_global = BuiltInBuilder::callable(realm, Self::get_global, mc) .name(js_string!("get global")) .build(); - let get_ignore_case = BuiltInBuilder::callable(realm, Self::get_ignore_case) + let get_ignore_case = BuiltInBuilder::callable(realm, Self::get_ignore_case, mc) .name(js_string!("get ignoreCase")) .build(); - let get_multiline = BuiltInBuilder::callable(realm, Self::get_multiline) + let get_multiline = BuiltInBuilder::callable(realm, Self::get_multiline, mc) .name(js_string!("get multiline")) .build(); - let get_dot_all = BuiltInBuilder::callable(realm, Self::get_dot_all) + let get_dot_all = BuiltInBuilder::callable(realm, Self::get_dot_all, mc) .name(js_string!("get dotAll")) .build(); - let get_unicode = BuiltInBuilder::callable(realm, Self::get_unicode) + let get_unicode = BuiltInBuilder::callable(realm, Self::get_unicode, mc) .name(js_string!("get unicode")) .build(); - let get_unicode_sets = BuiltInBuilder::callable(realm, Self::get_unicode_sets) + let get_unicode_sets = BuiltInBuilder::callable(realm, Self::get_unicode_sets, mc) .name(js_string!("get unicodeSets")) .build(); - let get_sticky = BuiltInBuilder::callable(realm, Self::get_sticky) + let get_sticky = BuiltInBuilder::callable(realm, Self::get_sticky, mc) .name(js_string!("get sticky")) .build(); - let get_flags = BuiltInBuilder::callable(realm, Self::get_flags) + let get_flags = BuiltInBuilder::callable(realm, Self::get_flags, mc) .name(js_string!("get flags")) .build(); - let get_source = BuiltInBuilder::callable(realm, Self::get_source) + let get_source = BuiltInBuilder::callable(realm, Self::get_source, mc) .name(js_string!("get source")) .build(); - let regexp = BuiltInBuilder::from_standard_constructor::(realm) + let regexp = BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method(Self::escape, js_string!("escape"), 1) .static_accessor( JsSymbol::species(), @@ -425,14 +425,14 @@ impl RegExp { .templates() .regexp_without_proto() .clone(); - template.set_prototype(prototype); - template.create(regexp, vec![0.into()]) + template.set_prototype(context.gc_collector(), prototype); + template.create(context.gc_collector(), regexp, vec![0.into()]) } else { - context - .intrinsics() - .templates() - .regexp() - .create(regexp, vec![0.into()]) + context.intrinsics().templates().regexp().create( + context.gc_collector(), + regexp, + vec![0.into()], + ) }; // 23. Return obj. @@ -1270,8 +1270,8 @@ impl RegExp { #[allow(clippy::if_not_else)] let (groups, group_names) = if !named_groups.clone().is_empty() { // a. Let groups be OrdinaryObjectCreate(null). - let groups = JsObject::with_null_proto(); - let group_names = JsObject::with_null_proto(); + let groups = JsObject::with_null_proto(context.gc_collector()); + let group_names = JsObject::with_null_proto(context.gc_collector()); // e. If the ith capture of R was defined with a GroupName, then // i. Let s be the CapturingGroupName of that GroupName. diff --git a/core/engine/src/builtins/regexp/regexp_string_iterator.rs b/core/engine/src/builtins/regexp/regexp_string_iterator.rs index 8d20e1366ac..0e72a55a759 100644 --- a/core/engine/src/builtins/regexp/regexp_string_iterator.rs +++ b/core/engine/src/builtins/regexp/regexp_string_iterator.rs @@ -40,8 +40,8 @@ pub(crate) struct RegExpStringIterator { } impl IntrinsicObject for RegExpStringIterator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .prototype(realm.intrinsics().constructors().iterator().prototype()) .static_method(Self::next, js_string!("next"), 0) .static_property( @@ -95,6 +95,7 @@ impl RegExpStringIterator { // 5. Return ! CreateIteratorFromClosure(closure, "%RegExpStringIteratorPrototype%", %RegExpStringIteratorPrototype%). let regexp_string_iterator = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context .intrinsics() diff --git a/core/engine/src/builtins/set/mod.rs b/core/engine/src/builtins/set/mod.rs index 9052886b924..869829fffbb 100644 --- a/core/engine/src/builtins/set/mod.rs +++ b/core/engine/src/builtins/set/mod.rs @@ -123,20 +123,20 @@ impl IntrinsicObject for Set { fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } - fn init(realm: &Realm) { - let get_species = BuiltInBuilder::callable(realm, Self::get_species) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_species = BuiltInBuilder::callable(realm, Self::get_species, mc) .name(js_string!("get [Symbol.species]")) .build(); - let size_getter = BuiltInBuilder::callable(realm, Self::size_getter) + let size_getter = BuiltInBuilder::callable(realm, Self::size_getter, mc) .name(js_string!("get size")) .build(); - let values_function = BuiltInBuilder::callable(realm, Self::values) + let values_function = BuiltInBuilder::callable(realm, Self::values, mc) .name(js_string!("values")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_accessor( JsSymbol::species(), Some(get_species), @@ -221,6 +221,7 @@ impl BuiltInConstructor for Set { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::set, context)?; let set = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, OrderedSet::default(), @@ -841,6 +842,7 @@ impl Set { // 9. Set result.[[SetData]] to resultSetData. // 10. Return result. Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().set().prototype(), result_set, @@ -897,6 +899,7 @@ impl Set { // 9. Set result.[[SetData]] to resultSetData. // 10. Return result. Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().set().prototype(), result_set, @@ -996,6 +999,7 @@ impl Set { // 8. Set result.[[SetData]] to resultSetData. // 9. Return result. Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().set().prototype(), result_set, @@ -1093,6 +1097,7 @@ impl Set { // 8. Set result.[[SetData]] to resultSetData. // 9. Return result. Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().set().prototype(), result_set, diff --git a/core/engine/src/builtins/set/set_iterator.rs b/core/engine/src/builtins/set/set_iterator.rs index 03583591761..5872b9f0b94 100644 --- a/core/engine/src/builtins/set/set_iterator.rs +++ b/core/engine/src/builtins/set/set_iterator.rs @@ -52,8 +52,8 @@ pub(crate) struct SetIterator { } impl IntrinsicObject for SetIterator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .prototype(realm.intrinsics().constructors().iterator().prototype()) .static_method(Self::next, js_string!("next"), 0) .static_property( @@ -84,6 +84,7 @@ impl SetIterator { context: &Context, ) -> JsValue { let set_iterator = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().objects().iterator_prototypes().set(), Self { diff --git a/core/engine/src/builtins/string/mod.rs b/core/engine/src/builtins/string/mod.rs index 9421b4654d7..ff26b4d10d8 100644 --- a/core/engine/src/builtins/string/mod.rs +++ b/core/engine/src/builtins/string/mod.rs @@ -62,13 +62,13 @@ pub(crate) enum Placement { pub(crate) struct String; impl IntrinsicObject for String { - fn init(realm: &Realm) { - let trim_start = BuiltInBuilder::callable(realm, Self::trim_start) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let trim_start = BuiltInBuilder::callable(realm, Self::trim_start, mc) .length(0) .name(js_string!("trimStart")) .build(); - let trim_end = BuiltInBuilder::callable(realm, Self::trim_end) + let trim_end = BuiltInBuilder::callable(realm, Self::trim_end, mc) .length(0) .name(js_string!("trimEnd")) .build(); @@ -80,7 +80,7 @@ impl IntrinsicObject for String { let trim_right = trim_end.clone(); let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - let builder = BuiltInBuilder::from_standard_constructor::(realm) + let builder = BuiltInBuilder::from_standard_constructor::(realm, mc) .property(js_string!("length"), 0, attribute) .property( js_string!("trimStart"), @@ -250,9 +250,13 @@ impl String { // 4. Set S.[[GetOwnProperty]] as specified in 10.4.3.1. // 5. Set S.[[DefineOwnProperty]] as specified in 10.4.3.2. // 6. Set S.[[OwnPropertyKeys]] as specified in 10.4.3.3. - let s = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, value) - .upcast(); + let s = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + value, + ) + .upcast(); // 8. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor { [[Value]]: 𝔽(length), // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }). diff --git a/core/engine/src/builtins/string/string_iterator.rs b/core/engine/src/builtins/string/string_iterator.rs index 426ea9418fa..bf15695adfa 100644 --- a/core/engine/src/builtins/string/string_iterator.rs +++ b/core/engine/src/builtins/string/string_iterator.rs @@ -31,8 +31,8 @@ pub(crate) struct StringIterator { } impl IntrinsicObject for StringIterator { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .prototype(realm.intrinsics().constructors().iterator().prototype()) .static_method(Self::next, js_string!("next"), 0) .static_property( @@ -52,6 +52,7 @@ impl StringIterator { /// Create a new `StringIterator`. pub(crate) fn create_string_iterator(string: JsString, context: &mut Context) -> JsObject { JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context .intrinsics() diff --git a/core/engine/src/builtins/symbol/mod.rs b/core/engine/src/builtins/symbol/mod.rs index 91b87175b6d..232de88d863 100644 --- a/core/engine/src/builtins/symbol/mod.rs +++ b/core/engine/src/builtins/symbol/mod.rs @@ -93,7 +93,7 @@ impl GlobalSymbolRegistry { pub struct Symbol; impl IntrinsicObject for Symbol { - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { let symbol_async_iterator = JsSymbol::async_iterator(); let symbol_has_instance = JsSymbol::has_instance(); let symbol_is_concat_spreadable = JsSymbol::is_concat_spreadable(); @@ -112,16 +112,16 @@ impl IntrinsicObject for Symbol { let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - let to_primitive = BuiltInBuilder::callable(realm, Self::to_primitive) + let to_primitive = BuiltInBuilder::callable(realm, Self::to_primitive, mc) .name(js_string!("[Symbol.toPrimitive]")) .length(1) .build(); - let get_description = BuiltInBuilder::callable(realm, Self::get_description) + let get_description = BuiltInBuilder::callable(realm, Self::get_description, mc) .name(js_string!("get description")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_method(Self::for_, js_string!("for"), 1) .static_method(Self::key_for, js_string!("keyFor"), 1) .static_property( diff --git a/core/engine/src/builtins/temporal/duration/mod.rs b/core/engine/src/builtins/temporal/duration/mod.rs index 7e0d8026ae7..a65c1e26d74 100644 --- a/core/engine/src/builtins/temporal/duration/mod.rs +++ b/core/engine/src/builtins/temporal/duration/mod.rs @@ -59,56 +59,56 @@ impl BuiltInObject for Duration { } impl IntrinsicObject for Duration { - fn init(realm: &Realm) { - let get_years = BuiltInBuilder::callable(realm, Self::get_years) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_years = BuiltInBuilder::callable(realm, Self::get_years, mc) .name(js_string!("get Years")) .build(); - let get_months = BuiltInBuilder::callable(realm, Self::get_months) + let get_months = BuiltInBuilder::callable(realm, Self::get_months, mc) .name(js_string!("get Months")) .build(); - let get_weeks = BuiltInBuilder::callable(realm, Self::get_weeks) + let get_weeks = BuiltInBuilder::callable(realm, Self::get_weeks, mc) .name(js_string!("get Weeks")) .build(); - let get_days = BuiltInBuilder::callable(realm, Self::get_days) + let get_days = BuiltInBuilder::callable(realm, Self::get_days, mc) .name(js_string!("get Days")) .build(); - let get_hours = BuiltInBuilder::callable(realm, Self::get_hours) + let get_hours = BuiltInBuilder::callable(realm, Self::get_hours, mc) .name(js_string!("get Hours")) .build(); - let get_minutes = BuiltInBuilder::callable(realm, Self::get_minutes) + let get_minutes = BuiltInBuilder::callable(realm, Self::get_minutes, mc) .name(js_string!("get Minutes")) .build(); - let get_seconds = BuiltInBuilder::callable(realm, Self::get_seconds) + let get_seconds = BuiltInBuilder::callable(realm, Self::get_seconds, mc) .name(js_string!("get Seconds")) .build(); - let get_milliseconds = BuiltInBuilder::callable(realm, Self::get_milliseconds) + let get_milliseconds = BuiltInBuilder::callable(realm, Self::get_milliseconds, mc) .name(js_string!("get Milliseconds")) .build(); - let get_microseconds = BuiltInBuilder::callable(realm, Self::get_microseconds) + let get_microseconds = BuiltInBuilder::callable(realm, Self::get_microseconds, mc) .name(js_string!("get Microseconds")) .build(); - let get_nanoseconds = BuiltInBuilder::callable(realm, Self::get_nanoseconds) + let get_nanoseconds = BuiltInBuilder::callable(realm, Self::get_nanoseconds, mc) .name(js_string!("get Nanoseconds")) .build(); - let get_sign = BuiltInBuilder::callable(realm, Self::get_sign) + let get_sign = BuiltInBuilder::callable(realm, Self::get_sign, mc) .name(js_string!("get Sign")) .build(); - let is_blank = BuiltInBuilder::callable(realm, Self::get_blank) + let is_blank = BuiltInBuilder::callable(realm, Self::get_blank, mc) .name(js_string!("get blank")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), StaticJsStrings::DURATION_TAG, @@ -612,7 +612,7 @@ impl Duration { // 2. Set two to ? ToTemporalDuration(two). let two = to_temporal_duration(args.get_or_undefined(1), context)?; // 3. Let resolvedOptions be ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(2))?; + let options = get_options_object(args.get_or_undefined(2), context.gc_collector())?; // 4. Let relativeToRecord be ? GetTemporalRelativeToOption(resolvedOptions). let relative_to = get_relative_to_option(&options, context)?; @@ -908,7 +908,7 @@ impl Duration { // a. Let paramString be roundTo. let param_string = param_string.clone(); // b. Set roundTo to OrdinaryObjectCreate(null). - let new_round_to = JsObject::with_null_proto(); + let new_round_to = JsObject::with_null_proto(context.gc_collector()); // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). new_round_to.create_data_property_or_throw( js_string!("smallestUnit"), @@ -919,7 +919,7 @@ impl Duration { } else { // 5. Else, // a. Set roundTo to ? GetOptionsObject(roundTo). - get_options_object(round_to_arg)? + get_options_object(round_to_arg, context.gc_collector())? }; // NOTE: 6 & 7 unused in favor of `is_none()`. @@ -1009,7 +1009,7 @@ impl Duration { JsVariant::String(param_string) => { // a. Let paramString be totalOf. // b. Set totalOf to OrdinaryObjectCreate(null). - let total_of = JsObject::with_null_proto(); + let total_of = JsObject::with_null_proto(context.gc_collector()); // c. Perform ! CreateDataPropertyOrThrow(totalOf, "unit", paramString). total_of.create_data_property_or_throw( js_string!("unit"), @@ -1021,7 +1021,7 @@ impl Duration { // 5. Else, _ => { // a. Set totalOf to ? GetOptionsObject(totalOf). - get_options_object(total_of)? + get_options_object(total_of, context.gc_collector())? } }; @@ -1072,7 +1072,7 @@ impl Duration { JsNativeError::typ().with_message("this value must be a Duration object.") })?; - let options = get_options_object(args.get_or_undefined(0))?; + let options = get_options_object(args.get_or_undefined(0), context.gc_collector())?; let precision = get_digits_option(&options, context)?; let rounding_mode = get_option::(&options, js_string!("roundingMode"), context)?; @@ -1262,7 +1262,8 @@ pub(crate) fn create_temporal_duration( // 12. Set object.[[Microseconds]] to ℝ(𝔽(microseconds)). // 13. Set object.[[Nanoseconds]] to ℝ(𝔽(nanoseconds)). - let obj = JsObject::from_proto_and_data(prototype, Duration::new(inner)); + let obj = + JsObject::from_proto_and_data(context.gc_collector(), prototype, Duration::new(inner)); // 14. Return object. Ok(obj) } diff --git a/core/engine/src/builtins/temporal/instant/mod.rs b/core/engine/src/builtins/temporal/instant/mod.rs index 1a26d930893..9f254d545b2 100644 --- a/core/engine/src/builtins/temporal/instant/mod.rs +++ b/core/engine/src/builtins/temporal/instant/mod.rs @@ -61,16 +61,16 @@ impl BuiltInObject for Instant { } impl IntrinsicObject for Instant { - fn init(realm: &Realm) { - let get_millis = BuiltInBuilder::callable(realm, Self::get_epoch_milliseconds) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_millis = BuiltInBuilder::callable(realm, Self::get_epoch_milliseconds, mc) .name(js_string!("get epochMilliseconds")) .build(); - let get_nanos = BuiltInBuilder::callable(realm, Self::get_epoch_nanoseconds) + let get_nanos = BuiltInBuilder::callable(realm, Self::get_epoch_nanoseconds, mc) .name(js_string!("get epochNanoseconds")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), StaticJsStrings::INSTANT_TAG, @@ -428,8 +428,10 @@ impl Instant { let other = to_temporal_instant(args.get_or_undefined(0), context)?; // Fetch the necessary options. - let settings = - get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?; + let settings = get_difference_settings( + &get_options_object(args.get_or_undefined(1), context.gc_collector())?, + context, + )?; let result = instant.inner.until(&other, settings)?; create_temporal_duration(result, None, context).map(Into::into) } @@ -462,8 +464,10 @@ impl Instant { // 3. Return ? DifferenceTemporalInstant(since, instant, other, options). let other = to_temporal_instant(args.get_or_undefined(0), context)?; - let settings = - get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?; + let settings = get_difference_settings( + &get_options_object(args.get_or_undefined(1), context.gc_collector())?, + context, + )?; let result = instant.inner.since(&other, settings)?; create_temporal_duration(result, None, context).map(Into::into) } @@ -506,7 +510,7 @@ impl Instant { // a. Let paramString be roundTo. let param_string = param_string.clone(); // b. Set roundTo to OrdinaryObjectCreate(null). - let new_round_to = JsObject::with_null_proto(); + let new_round_to = JsObject::with_null_proto(context.gc_collector()); // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). new_round_to.create_data_property_or_throw( js_string!("smallestUnit"), @@ -517,7 +521,7 @@ impl Instant { } else { // 5. Else, // a. Set roundTo to ? GetOptionsObject(roundTo). - get_options_object(round_to_arg)? + get_options_object(round_to_arg, context.gc_collector())? }; // 6. NOTE: The following steps read options and perform independent validation in @@ -624,7 +628,7 @@ impl Instant { .with_message("the this object must be a Temporal.Instant object.") })?; - let options = get_options_object(args.get_or_undefined(0))?; + let options = get_options_object(args.get_or_undefined(0), context.gc_collector())?; let precision = get_digits_option(&options, context)?; let rounding_mode = @@ -784,7 +788,7 @@ pub(crate) fn create_temporal_instant( get_prototype_from_constructor(&new_target, StandardConstructors::instant, context)?; // 4. Set object.[[Nanoseconds]] to epochNanoseconds. - let obj = JsObject::from_proto_and_data(proto, Instant::new(instant)); + let obj = JsObject::from_proto_and_data(context.gc_collector(), proto, Instant::new(instant)); // 5. Return object. Ok(obj.into()) diff --git a/core/engine/src/builtins/temporal/mod.rs b/core/engine/src/builtins/temporal/mod.rs index 7ae6451d484..f0941b8e274 100644 --- a/core/engine/src/builtins/temporal/mod.rs +++ b/core/engine/src/builtins/temporal/mod.rs @@ -82,8 +82,8 @@ impl BuiltInObject for Temporal { } impl IntrinsicObject for Temporal { - fn init(realm: &Realm) { - BuiltInBuilder::with_intrinsic::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::with_intrinsic::(realm, mc) .static_property( JsSymbol::to_string_tag(), Self::NAME, diff --git a/core/engine/src/builtins/temporal/now.rs b/core/engine/src/builtins/temporal/now.rs index 9737fd9e056..c5e09d76db6 100644 --- a/core/engine/src/builtins/temporal/now.rs +++ b/core/engine/src/builtins/temporal/now.rs @@ -39,13 +39,13 @@ pub struct Now; impl IntrinsicObject for Now { /// Initializes the `Temporal.Now` object. - fn init(realm: &Realm) { + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { // is an ordinary object. // has a [[Prototype]] internal slot whose value is %Object.prototype%. // is not a function object. // does not have a [[Construct]] internal method; it cannot be used as a constructor with the new operator. // does not have a [[Call]] internal method; it cannot be invoked as a function. - BuiltInBuilder::with_intrinsic::(realm) + BuiltInBuilder::with_intrinsic::(realm, mc) .static_property( JsSymbol::to_string_tag(), StaticJsStrings::NOW_TAG, diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index e3a220ca78e..cebba525bfb 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -72,72 +72,72 @@ impl BuiltInObject for PlainDate { } impl IntrinsicObject for PlainDate { - fn init(realm: &Realm) { - let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id, mc) .name(js_string!("get calendarId")) .build(); - let get_era = BuiltInBuilder::callable(realm, Self::get_era) + let get_era = BuiltInBuilder::callable(realm, Self::get_era, mc) .name(js_string!("get era")) .build(); - let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year) + let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year, mc) .name(js_string!("get eraYear")) .build(); - let get_year = BuiltInBuilder::callable(realm, Self::get_year) + let get_year = BuiltInBuilder::callable(realm, Self::get_year, mc) .name(js_string!("get year")) .build(); - let get_month = BuiltInBuilder::callable(realm, Self::get_month) + let get_month = BuiltInBuilder::callable(realm, Self::get_month, mc) .name(js_string!("get month")) .build(); - let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code) + let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code, mc) .name(js_string!("get monthCode")) .build(); - let get_day = BuiltInBuilder::callable(realm, Self::get_day) + let get_day = BuiltInBuilder::callable(realm, Self::get_day, mc) .name(js_string!("get day")) .build(); - let get_day_of_week = BuiltInBuilder::callable(realm, Self::get_day_of_week) + let get_day_of_week = BuiltInBuilder::callable(realm, Self::get_day_of_week, mc) .name(js_string!("get dayOfWeek")) .build(); - let get_day_of_year = BuiltInBuilder::callable(realm, Self::get_day_of_year) + let get_day_of_year = BuiltInBuilder::callable(realm, Self::get_day_of_year, mc) .name(js_string!("get dayOfYear")) .build(); - let get_week_of_year = BuiltInBuilder::callable(realm, Self::get_week_of_year) + let get_week_of_year = BuiltInBuilder::callable(realm, Self::get_week_of_year, mc) .name(js_string!("get weekOfYear")) .build(); - let get_year_of_week = BuiltInBuilder::callable(realm, Self::get_year_of_week) + let get_year_of_week = BuiltInBuilder::callable(realm, Self::get_year_of_week, mc) .name(js_string!("get yearOfWeek")) .build(); - let get_days_in_week = BuiltInBuilder::callable(realm, Self::get_days_in_week) + let get_days_in_week = BuiltInBuilder::callable(realm, Self::get_days_in_week, mc) .name(js_string!("get daysInWeek")) .build(); - let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month) + let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month, mc) .name(js_string!("get daysInMonth")) .build(); - let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year) + let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year, mc) .name(js_string!("get daysInYear")) .build(); - let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year) + let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year, mc) .name(js_string!("get monthsInYear")) .build(); - let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year) + let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year, mc) .name(js_string!("get inLeapYear")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), StaticJsStrings::PLAIN_DATE_TAG, @@ -752,7 +752,10 @@ impl PlainDate { let object = item.as_object(); if let Some(date) = object.as_ref().and_then(JsObject::downcast_ref::) { - let options = get_options_object(options.unwrap_or(&JsValue::undefined()))?; + let options = get_options_object( + options.unwrap_or(&JsValue::undefined()), + context.gc_collector(), + )?; let _ = get_option::(&options, js_string!("overflow"), context)?; return create_temporal_date(date.inner.clone(), None, context).map(Into::into); } @@ -869,7 +872,7 @@ impl PlainDate { let duration = to_temporal_duration_record(args.get_or_undefined(0), context)?; // 4. Set options to ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let overflow = get_option::(&options, js_string!("overflow"), context)?; @@ -905,7 +908,7 @@ impl PlainDate { let duration = to_temporal_duration_record(args.get_or_undefined(0), context)?; // 4. Set options to ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let overflow = get_option::(&options, js_string!("overflow"), context)?; // 5. Let negatedDuration be CreateNegatedTemporalDuration(duration). @@ -954,7 +957,7 @@ impl PlainDate { let fields = to_calendar_fields(&partial_object, date.inner.calendar(), context)?; // 8. Let resolvedOptions be ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; // 9. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let overflow = get_option::(&options, js_string!("overflow"), context)?; @@ -1018,7 +1021,7 @@ impl PlainDate { let other = to_temporal_date(args.get_or_undefined(0), None, context)?; // 3. Return ? DifferenceTemporalPlainDate(until, temporalDate, other, options). - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let settings = get_difference_settings(&options, context)?; create_temporal_duration(date.inner.until(&other, settings)?, None, context).map(Into::into) @@ -1049,7 +1052,7 @@ impl PlainDate { // 3. Return ? DifferenceTemporalPlainDate(since, temporalDate, other, options). let other = to_temporal_date(args.get_or_undefined(0), None, context)?; - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let settings = get_difference_settings(&options, context)?; create_temporal_duration(date.inner.since(&other, settings)?, None, context).map(Into::into) @@ -1204,7 +1207,7 @@ impl PlainDate { JsNativeError::typ().with_message("the this object must be a PlainDate object.") })?; - let options = get_options_object(args.get_or_undefined(0))?; + let options = get_options_object(args.get_or_undefined(0), context.gc_collector())?; let display_calendar = get_option::(&options, js_string!("calendarName"), context)? .unwrap_or(DisplayCalendar::Auto); @@ -1302,7 +1305,8 @@ pub(crate) fn create_temporal_date( // 6. Set object.[[ISOMonth]] to isoMonth. // 7. Set object.[[ISODay]] to isoDay. // 8. Set object.[[Calendar]] to calendar. - let obj = JsObject::from_proto_and_data(prototype, PlainDate::new(inner)); + let obj = + JsObject::from_proto_and_data(context.gc_collector(), prototype, PlainDate::new(inner)); // 9. Return object. Ok(obj) @@ -1326,11 +1330,11 @@ pub(crate) fn to_temporal_date( if let Some(object) = item.as_object() { // a. If item has an [[InitializedTemporalDate]] internal slot, then if let Some(date) = object.downcast_ref::() { - let _options_obj = get_options_object(&options)?; + let _options_obj = get_options_object(&options, context.gc_collector())?; return Ok(date.inner.clone()); // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then } else if let Some(zdt) = object.downcast_ref::() { - let options_obj = get_options_object(&options)?; + let options_obj = get_options_object(&options, context.gc_collector())?; // i. Perform ? ToTemporalOverflow(options). let _overflow = get_option(&options_obj, js_string!("overflow"), context)? .unwrap_or(Overflow::Constrain); @@ -1341,7 +1345,7 @@ pub(crate) fn to_temporal_date( return Ok(zdt.inner.to_plain_date()); // c. If item has an [[InitializedTemporalDateTime]] internal slot, then } else if let Some(dt) = object.downcast_ref::() { - let options_obj = get_options_object(&options)?; + let options_obj = get_options_object(&options, context.gc_collector())?; // i. Perform ? ToTemporalOverflow(options). let _overflow = get_option(&options_obj, js_string!("overflow"), context)? .unwrap_or(Overflow::Constrain); @@ -1356,7 +1360,7 @@ pub(crate) fn to_temporal_date( // e. Let fields be ? PrepareCalendarFields(calendar, item, « year, month, month-code, day », «», «»). let partial = to_partial_date_record(&object, context)?; // f. Let resolvedOptions be ? GetOptionsObject(options). - let resolved_options = get_options_object(&options)?; + let resolved_options = get_options_object(&options, context.gc_collector())?; // g. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let overflow = get_option::(&resolved_options, js_string!("overflow"), context)?; // h. Let isoDate be ? CalendarDateFromFields(calendar, fields, overflow). @@ -1381,7 +1385,7 @@ pub(crate) fn to_temporal_date( // 6. If calendar is empty, set calendar to "iso8601". // 7. Set calendar to ? CanonicalizeCalendar(calendar). // 8. Let resolvedOptions be ? GetOptionsObject(options). - let resolved_options = get_options_object(&options)?; + let resolved_options = get_options_object(&options, context.gc_collector())?; // 9. Perform ? GetTemporalOverflowOption(resolvedOptions). let _overflow = get_option::(&resolved_options, js_string!("overflow"), context)? .unwrap_or(Overflow::Constrain); diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index 4270df4f84e..bb8c8e6c8a6 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -75,96 +75,96 @@ impl BuiltInObject for PlainDateTime { } impl IntrinsicObject for PlainDateTime { - fn init(realm: &Realm) { - let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id, mc) .name(js_string!("get calendarId")) .build(); - let get_era = BuiltInBuilder::callable(realm, Self::get_era) + let get_era = BuiltInBuilder::callable(realm, Self::get_era, mc) .name(js_string!("get era")) .build(); - let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year) + let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year, mc) .name(js_string!("get eraYear")) .build(); - let get_year = BuiltInBuilder::callable(realm, Self::get_year) + let get_year = BuiltInBuilder::callable(realm, Self::get_year, mc) .name(js_string!("get year")) .build(); - let get_month = BuiltInBuilder::callable(realm, Self::get_month) + let get_month = BuiltInBuilder::callable(realm, Self::get_month, mc) .name(js_string!("get month")) .build(); - let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code) + let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code, mc) .name(js_string!("get monthCode")) .build(); - let get_day = BuiltInBuilder::callable(realm, Self::get_day) + let get_day = BuiltInBuilder::callable(realm, Self::get_day, mc) .name(js_string!("get day")) .build(); - let get_hour = BuiltInBuilder::callable(realm, Self::get_hour) + let get_hour = BuiltInBuilder::callable(realm, Self::get_hour, mc) .name(js_string!("get hour")) .build(); - let get_minute = BuiltInBuilder::callable(realm, Self::get_minute) + let get_minute = BuiltInBuilder::callable(realm, Self::get_minute, mc) .name(js_string!("get minute")) .build(); - let get_second = BuiltInBuilder::callable(realm, Self::get_second) + let get_second = BuiltInBuilder::callable(realm, Self::get_second, mc) .name(js_string!("get second")) .build(); - let get_millisecond = BuiltInBuilder::callable(realm, Self::get_millisecond) + let get_millisecond = BuiltInBuilder::callable(realm, Self::get_millisecond, mc) .name(js_string!("get millisecond")) .build(); - let get_microsecond = BuiltInBuilder::callable(realm, Self::get_microsecond) + let get_microsecond = BuiltInBuilder::callable(realm, Self::get_microsecond, mc) .name(js_string!("get microsecond")) .build(); - let get_nanosecond = BuiltInBuilder::callable(realm, Self::get_nanosecond) + let get_nanosecond = BuiltInBuilder::callable(realm, Self::get_nanosecond, mc) .name(js_string!("get nanosecond")) .build(); - let get_day_of_week = BuiltInBuilder::callable(realm, Self::get_day_of_week) + let get_day_of_week = BuiltInBuilder::callable(realm, Self::get_day_of_week, mc) .name(js_string!("get dayOfWeek")) .build(); - let get_day_of_year = BuiltInBuilder::callable(realm, Self::get_day_of_year) + let get_day_of_year = BuiltInBuilder::callable(realm, Self::get_day_of_year, mc) .name(js_string!("get dayOfYear")) .build(); - let get_week_of_year = BuiltInBuilder::callable(realm, Self::get_week_of_year) + let get_week_of_year = BuiltInBuilder::callable(realm, Self::get_week_of_year, mc) .name(js_string!("get weekOfYear")) .build(); - let get_year_of_week = BuiltInBuilder::callable(realm, Self::get_year_of_week) + let get_year_of_week = BuiltInBuilder::callable(realm, Self::get_year_of_week, mc) .name(js_string!("get yearOfWeek")) .build(); - let get_days_in_week = BuiltInBuilder::callable(realm, Self::get_days_in_week) + let get_days_in_week = BuiltInBuilder::callable(realm, Self::get_days_in_week, mc) .name(js_string!("get daysInWeek")) .build(); - let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month) + let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month, mc) .name(js_string!("get daysInMonth")) .build(); - let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year) + let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year, mc) .name(js_string!("get daysInYear")) .build(); - let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year) + let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year, mc) .name(js_string!("get monthsInYear")) .build(); - let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year) + let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year, mc) .name(js_string!("get inLeapYear")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), StaticJsStrings::PLAIN_DATETIME_TAG, @@ -1024,7 +1024,7 @@ impl PlainDateTime { let object = item.as_object(); let dt = if let Some(pdt) = object.as_ref().and_then(JsObject::downcast_ref::) { // a. Perform ? GetTemporalOverflowOption(options). - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let _ = get_option::(&options, js_string!("overflow"), context)?; // b. Return ! CreateTemporalDateTime(item.[[ISOYear]], item.[[ISOMonth]], // item.[[ISODay]], item.[[ISOHour]], item.[[ISOMinute]], item.[[ISOSecond]], @@ -1110,7 +1110,7 @@ impl PlainDateTime { // 13. Set fields to CalendarMergeFields(calendar, fields, partialDateTime). let fields = to_date_time_fields(&partial_object, dt.inner.calendar(), context)?; // 14. Let resolvedOptions be ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; // 15. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let overflow = get_option::(&options, js_string!("overflow"), context)?; @@ -1202,7 +1202,7 @@ impl PlainDateTime { let duration = to_temporal_duration_record(args.get_or_undefined(0), context)?; // 4. Set options to ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let overflow = get_option::(&options, js_string!("overflow"), context)?; // 5. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « date-add »). @@ -1236,7 +1236,7 @@ impl PlainDateTime { let duration = to_temporal_duration_record(args.get_or_undefined(0), context)?; // 4. Set options to ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let overflow = get_option::(&options, js_string!("overflow"), context)?; // 5. Let negatedDuration be CreateNegatedTemporalDuration(duration). @@ -1268,7 +1268,7 @@ impl PlainDateTime { let other = to_temporal_datetime(args.get_or_undefined(0), None, context)?; - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let settings = get_difference_settings(&options, context)?; create_temporal_duration(dt.inner.until(&other, settings)?, None, context).map(Into::into) @@ -1296,7 +1296,7 @@ impl PlainDateTime { let other = to_temporal_datetime(args.get_or_undefined(0), None, context)?; - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let settings = get_difference_settings(&options, context)?; create_temporal_duration(dt.inner.since(&other, settings)?, None, context).map(Into::into) @@ -1334,7 +1334,7 @@ impl PlainDateTime { // a. Let paramString be roundTo. let param_string = param_string.clone(); // b. Set roundTo to OrdinaryObjectCreate(null). - let new_round_to = JsObject::with_null_proto(); + let new_round_to = JsObject::with_null_proto(context.gc_collector()); // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). new_round_to.create_data_property_or_throw( js_string!("smallestUnit"), @@ -1345,7 +1345,7 @@ impl PlainDateTime { } else { // 5. Else, // a. Set roundTo to ? GetOptionsObject(roundTo). - get_options_object(round_to_arg)? + get_options_object(round_to_arg, context.gc_collector())? }; let mut options = RoundingOptions::default(); @@ -1425,7 +1425,7 @@ impl PlainDateTime { JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") })?; - let options = get_options_object(args.get_or_undefined(0))?; + let options = get_options_object(args.get_or_undefined(0), context.gc_collector())?; let show_calendar = get_option::(&options, js_string!("calendarName"), context)? @@ -1538,7 +1538,7 @@ impl PlainDateTime { // 3. Let timeZone be ? ToTemporalTimeZoneIdentifier(temporalTimeZoneLike). let timezone = to_temporal_timezone_identifier(args.get_or_undefined(0), context)?; // 4. Let resolvedOptions be ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; // 5. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions). let disambiguation = get_option::(&options, js_string!("disambiguation"), context)? @@ -1647,7 +1647,8 @@ pub(crate) fn create_temporal_datetime( // 13. Set object.[[ISOMicrosecond]] to microsecond. // 14. Set object.[[ISONanosecond]] to nanosecond. // 15. Set object.[[Calendar]] to calendar. - let obj = JsObject::from_proto_and_data(prototype, PlainDateTime::new(inner)); + let obj = + JsObject::from_proto_and_data(context.gc_collector(), prototype, PlainDateTime::new(inner)); // 16. Return object. Ok(obj) @@ -1669,7 +1670,7 @@ pub(crate) fn to_temporal_datetime( // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then } else if let Some(zdt) = object.downcast_ref::() { // i. Perform ? GetTemporalOverflowOption(resolvedOptions). - let options = get_options_object(&options.unwrap_or_default())?; + let options = get_options_object(&options.unwrap_or_default(), context.gc_collector())?; let _ = get_option::(&options, js_string!("overflow"), context)?; // ii. Let instant be ! CreateTemporalInstant(item.[[Nanoseconds]]). // iii. Let timeZoneRec be ? CreateTimeZoneMethodsRecord(item.[[TimeZone]], « get-offset-nanoseconds-for »). @@ -1678,7 +1679,7 @@ pub(crate) fn to_temporal_datetime( // c. If item has an [[InitializedTemporalDate]] internal slot, then } else if let Some(date) = object.downcast_ref::() { // i. Perform ? GetTemporalOverflowOption(resolvedOptions). - let options = get_options_object(&options.unwrap_or_default())?; + let options = get_options_object(&options.unwrap_or_default(), context.gc_collector())?; let _ = get_option::(&options, js_string!("overflow"), context)?; // ii. Return ? CreateTemporalDateTime(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], 0, 0, 0, 0, 0, 0, item.[[Calendar]]). return Ok(date.inner.to_plain_date_time(None)?); @@ -1691,7 +1692,8 @@ pub(crate) fn to_temporal_datetime( // "nanosecond", "second" », «») // TODO: Move validation to `temporal_rs`. let partial_dt = to_partial_datetime(&object, context)?; - let resolved_options = get_options_object(&options.unwrap_or_default())?; + let resolved_options = + get_options_object(&options.unwrap_or_default(), context.gc_collector())?; // g. Let result be ? InterpretTemporalDateTimeFields(calendarRec, fields, resolvedOptions). let overflow = get_option::(&resolved_options, js_string!("overflow"), context)?; return InnerDateTime::from_partial(partial_dt, overflow).map_err(Into::into); @@ -1713,7 +1715,8 @@ pub(crate) fn to_temporal_datetime( // h. Set calendar to CanonicalizeUValue("ca", calendar). let date = string.to_std_string_escaped().parse::()?; // i. Perform ? GetTemporalOverflowOption(resolvedOptions). - let resolved_options = get_options_object(&options.unwrap_or_default())?; + let resolved_options = + get_options_object(&options.unwrap_or_default(), context.gc_collector())?; let _ = get_option::(&resolved_options, js_string!("overflow"), context)?; // 5. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], // result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], diff --git a/core/engine/src/builtins/temporal/plain_month_day/mod.rs b/core/engine/src/builtins/temporal/plain_month_day/mod.rs index dde472d2a2b..b67156053d0 100644 --- a/core/engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/core/engine/src/builtins/temporal/plain_month_day/mod.rs @@ -56,20 +56,20 @@ impl BuiltInObject for PlainMonthDay { } impl IntrinsicObject for PlainMonthDay { - fn init(realm: &Realm) { - let get_day = BuiltInBuilder::callable(realm, Self::get_day) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_day = BuiltInBuilder::callable(realm, Self::get_day, mc) .name(js_string!("get day")) .build(); - let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code) + let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code, mc) .name(js_string!("get monthCode")) .build(); - let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id) + let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id, mc) .name(js_string!("get calendarId")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), StaticJsStrings::PLAIN_MD_TAG, @@ -297,7 +297,8 @@ impl PlainMonthDay { let fields = to_calendar_fields(&object, month_day.inner.calendar(), context)?; // 7. Set fields to CalendarMergeFields(calendar, fields, partialMonthDay). // 8. Let resolvedOptions be ? GetOptionsObject(options). - let resolved_options = get_options_object(args.get_or_undefined(1))?; + let resolved_options = + get_options_object(args.get_or_undefined(1), context.gc_collector())?; // 9. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let overflow = get_option::(&resolved_options, js_string!("overflow"), context)?; // 10. Let isoDate be ? CalendarMonthDayFromFields(calendar, fields, overflow). @@ -354,7 +355,7 @@ impl PlainMonthDay { })?; // 3. Set options to ? NormalizeOptionsObject(options). - let options = get_options_object(args.get_or_undefined(0))?; + let options = get_options_object(args.get_or_undefined(0), context.gc_collector())?; // 4. Let showCalendar be ? ToShowCalendarOption(options). // Get calendarName from the options object let show_calendar = @@ -513,7 +514,8 @@ pub(crate) fn create_temporal_month_day( // 6. Set object.[[ISODay]] to isoDay. // 7. Set object.[[Calendar]] to calendar. // 8. Set object.[[ISOYear]] to referenceISOYear. - let obj = JsObject::from_proto_and_data(proto, PlainMonthDay::new(inner)); + let obj = + JsObject::from_proto_and_data(context.gc_collector(), proto, PlainMonthDay::new(inner)); // 9. Return object. Ok(obj.into()) @@ -531,7 +533,7 @@ fn to_temporal_month_day( // a. If item has an [[InitializedTemporalMonthDay]] internal slot, then if let Some(md) = obj.downcast_ref::() { // i. Let resolvedOptions be ? GetOptionsObject(options). - let options = get_options_object(options)?; + let options = get_options_object(options, context.gc_collector())?; // ii. Perform ? GetTemporalOverflowOption(resolvedOptions). let _ = get_option::(&options, js_string!("overflow"), context)?; // iii. Return ! CreateTemporalMonthDay(item.[[ISODate]], item.[[Calendar]]). @@ -591,7 +593,7 @@ fn to_temporal_month_day( .with_calendar(calendar); // d. Let resolvedOptions be ? GetOptionsObject(options). - let options = get_options_object(options)?; + let options = get_options_object(options, context.gc_collector())?; // e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let overflow = get_option::(&options, js_string!("overflow"), context)?; // f. Let isoDate be ? CalendarMonthDayFromFields(calendar, fields, overflow). @@ -612,7 +614,7 @@ fn to_temporal_month_day( let parse_record = ParsedDate::month_day_from_utf8(md_string.to_std_string_escaped().as_bytes())?; // 8. Let resolvedOptions be ? GetOptionsObject(options). - let options = get_options_object(options)?; + let options = get_options_object(options, context.gc_collector())?; // 9. Perform ? GetTemporalOverflowOption(resolvedOptions). let _ = get_option::(&options, js_string!("overflow"), context)?; // 10. If calendar is "iso8601", then diff --git a/core/engine/src/builtins/temporal/plain_time/mod.rs b/core/engine/src/builtins/temporal/plain_time/mod.rs index f9113dd5100..a061bbfa7a1 100644 --- a/core/engine/src/builtins/temporal/plain_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_time/mod.rs @@ -56,32 +56,32 @@ impl BuiltInObject for PlainTime { } impl IntrinsicObject for PlainTime { - fn init(realm: &Realm) { - let get_hour = BuiltInBuilder::callable(realm, Self::get_hour) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_hour = BuiltInBuilder::callable(realm, Self::get_hour, mc) .name(js_string!("get hour")) .build(); - let get_minute = BuiltInBuilder::callable(realm, Self::get_minute) + let get_minute = BuiltInBuilder::callable(realm, Self::get_minute, mc) .name(js_string!("get minute")) .build(); - let get_second = BuiltInBuilder::callable(realm, Self::get_second) + let get_second = BuiltInBuilder::callable(realm, Self::get_second, mc) .name(js_string!("get second")) .build(); - let get_millisecond = BuiltInBuilder::callable(realm, Self::get_millisecond) + let get_millisecond = BuiltInBuilder::callable(realm, Self::get_millisecond, mc) .name(js_string!("get millisecond")) .build(); - let get_microsecond = BuiltInBuilder::callable(realm, Self::get_microsecond) + let get_microsecond = BuiltInBuilder::callable(realm, Self::get_microsecond, mc) .name(js_string!("get microsecond")) .build(); - let get_nanosecond = BuiltInBuilder::callable(realm, Self::get_nanosecond) + let get_nanosecond = BuiltInBuilder::callable(realm, Self::get_nanosecond, mc) .name(js_string!("get nanosecond")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), StaticJsStrings::PLAIN_TIME_TAG, @@ -548,7 +548,7 @@ impl PlainTime { let partial = to_js_partial_time_record(&partial_object, context)?; // 17. Let resolvedOptions be ? GetOptionsObject(options). // 18. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let overflow = get_option::(&options, js_string!("overflow"), context)?; create_temporal_time( @@ -582,8 +582,10 @@ impl PlainTime { let other = to_temporal_time(args.get_or_undefined(0), None, context)?; - let settings = - get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?; + let settings = get_difference_settings( + &get_options_object(args.get_or_undefined(1), context.gc_collector())?, + context, + )?; let result = time.inner.until(&other, settings)?; @@ -612,8 +614,10 @@ impl PlainTime { let other = to_temporal_time(args.get_or_undefined(0), None, context)?; - let settings = - get_difference_settings(&get_options_object(args.get_or_undefined(1))?, context)?; + let settings = get_difference_settings( + &get_options_object(args.get_or_undefined(1), context.gc_collector())?, + context, + )?; let result = time.inner.since(&other, settings)?; @@ -654,7 +658,7 @@ impl PlainTime { // a. Let paramString be roundTo. let param_string = param_string.clone(); // b. Set roundTo to OrdinaryObjectCreate(null). - let new_round_to = JsObject::with_null_proto(); + let new_round_to = JsObject::with_null_proto(context.gc_collector()); // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). new_round_to.create_data_property_or_throw( js_string!("smallestUnit"), @@ -665,7 +669,7 @@ impl PlainTime { } else { // 5. Else, // a. Set roundTo to ? GetOptionsObject(roundTo). - get_options_object(round_to_arg)? + get_options_object(round_to_arg, context.gc_collector())? }; let mut options = RoundingOptions::default(); @@ -751,7 +755,7 @@ impl PlainTime { JsNativeError::typ().with_message("the this object must be a PlainTime object.") })?; - let options = get_options_object(args.get_or_undefined(0))?; + let options = get_options_object(args.get_or_undefined(0), context.gc_collector())?; let precision = get_digits_option(&options, context)?; let rounding_mode = @@ -868,7 +872,7 @@ pub(crate) fn create_temporal_time( // 7. Set object.[[ISOMillisecond]] to millisecond. // 8. Set object.[[ISOMicrosecond]] to microsecond. // 9. Set object.[[ISONanosecond]] to nanosecond. - let obj = JsObject::from_proto_and_data(prototype, PlainTime { inner }); + let obj = JsObject::from_proto_and_data(context.gc_collector(), prototype, PlainTime { inner }); // 10. Return object. Ok(obj) @@ -889,7 +893,7 @@ pub(crate) fn to_temporal_time( // a. If item has an [[InitializedTemporalTime]] internal slot, then if let Some(time) = object.downcast_ref::() { // i. Return item. - let options = get_options_object(options)?; + let options = get_options_object(options, context.gc_collector())?; let _overflow = get_option::(&options, js_string!("overflow"), context)?; return Ok(time.inner); // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then @@ -900,7 +904,7 @@ pub(crate) fn to_temporal_time( // iv. Return ! CreateTemporalTime(plainDateTime.[[ISOHour]], plainDateTime.[[ISOMinute]], // plainDateTime.[[ISOSecond]], plainDateTime.[[ISOMillisecond]], plainDateTime.[[ISOMicrosecond]], // plainDateTime.[[ISONanosecond]]). - let options = get_options_object(options)?; + let options = get_options_object(options, context.gc_collector())?; let _overflow = get_option::(&options, js_string!("overflow"), context)?; return Ok(zdt.inner.to_plain_time()); // c. If item has an [[InitializedTemporalDateTime]] internal slot, then @@ -908,7 +912,7 @@ pub(crate) fn to_temporal_time( // i. Return ! CreateTemporalTime(item.[[ISOHour]], item.[[ISOMinute]], // item.[[ISOSecond]], item.[[ISOMillisecond]], item.[[ISOMicrosecond]], // item.[[ISONanosecond]]). - let options = get_options_object(options)?; + let options = get_options_object(options, context.gc_collector())?; let _overflow = get_option::(&options, js_string!("overflow"), context)?; return Ok(PlainTimeInner::from(dt.inner.clone())); } @@ -918,7 +922,7 @@ pub(crate) fn to_temporal_time( // result.[[Nanosecond]], overflow). let partial = to_js_partial_time_record(&object, context)?; - let options = get_options_object(options)?; + let options = get_options_object(options, context.gc_collector())?; let overflow = get_option::(&options, js_string!("overflow"), context)?; PlainTimeInner::from_partial(partial.as_temporal_partial_time(overflow)?, overflow) @@ -930,7 +934,7 @@ pub(crate) fn to_temporal_time( // c. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. let result = str.to_std_string_escaped().parse::()?; - let options = get_options_object(options)?; + let options = get_options_object(options, context.gc_collector())?; let _overflow = get_option::(&options, js_string!("overflow"), context)?; Ok(result) diff --git a/core/engine/src/builtins/temporal/plain_year_month/mod.rs b/core/engine/src/builtins/temporal/plain_year_month/mod.rs index 1c328da5997..7072b4cd3e6 100644 --- a/core/engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/core/engine/src/builtins/temporal/plain_year_month/mod.rs @@ -61,48 +61,48 @@ impl BuiltInObject for PlainYearMonth { } impl IntrinsicObject for PlainYearMonth { - fn init(realm: &Realm) { - let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id, mc) .name(js_string!("get calendarId")) .build(); - let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year) + let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year, mc) .name(js_string!("get eraYear")) .build(); - let get_era = BuiltInBuilder::callable(realm, Self::get_era) + let get_era = BuiltInBuilder::callable(realm, Self::get_era, mc) .name(js_string!("get era")) .build(); - let get_year = BuiltInBuilder::callable(realm, Self::get_year) + let get_year = BuiltInBuilder::callable(realm, Self::get_year, mc) .name(js_string!("get year")) .build(); - let get_month = BuiltInBuilder::callable(realm, Self::get_month) + let get_month = BuiltInBuilder::callable(realm, Self::get_month, mc) .name(js_string!("get month")) .build(); - let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code) + let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code, mc) .name(js_string!("get monthCode")) .build(); - let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month) + let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month, mc) .name(js_string!("get daysInMonth")) .build(); - let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year) + let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year, mc) .name(js_string!("get daysInYear")) .build(); - let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year) + let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year, mc) .name(js_string!("get monthsInYear")) .build(); - let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year) + let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year, mc) .name(js_string!("get inLeapYear")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), StaticJsStrings::PLAIN_YM_TAG, @@ -571,7 +571,8 @@ impl PlainYearMonth { .into()); } // 8. Let resolvedOptions be ? GetOptionsObject(options). - let resolved_options = get_options_object(args.get_or_undefined(1))?; + let resolved_options = + get_options_object(args.get_or_undefined(1), context.gc_collector())?; // 9. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let overflow = get_option::(&resolved_options, js_string!("overflow"), context)? .unwrap_or_default(); @@ -594,7 +595,7 @@ impl PlainYearMonth { /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.add fn add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let duration_like = args.get_or_undefined(0); - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; add_or_subtract_duration(true, this, duration_like, &options, context) } @@ -612,7 +613,7 @@ impl PlainYearMonth { /// [temporal_rs-docs]: https://docs.rs/temporal_rs/latest/temporal_rs/struct.PlainYearMonth.html#method.subtract fn subtract(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let duration_like = args.get_or_undefined(0); - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; add_or_subtract_duration(false, this, duration_like, &options, context) } @@ -645,7 +646,8 @@ impl PlainYearMonth { .into()); } - let resolved_options = get_options_object(args.get_or_undefined(1))?; + let resolved_options = + get_options_object(args.get_or_undefined(1), context.gc_collector())?; // TODO: Disallowed units must be rejected in `temporal_rs`. let settings = get_difference_settings(&resolved_options, context)?; let result = year_month.inner.until(&other, settings)?; @@ -680,7 +682,8 @@ impl PlainYearMonth { .into()); } - let resolved_options = get_options_object(args.get_or_undefined(1))?; + let resolved_options = + get_options_object(args.get_or_undefined(1), context.gc_collector())?; // TODO: Disallowed units must be rejected in `temporal_rs`. let settings = get_difference_settings(&resolved_options, context)?; let result = year_month.inner.since(&other, settings)?; @@ -735,7 +738,7 @@ impl PlainYearMonth { })?; // 3. Set options to ? NormalizeOptionsObject(options). - let options = get_options_object(args.get_or_undefined(0))?; + let options = get_options_object(args.get_or_undefined(0), context.gc_collector())?; // 4. Let showCalendar be ? ToShowCalendarOption(options). // Get calendarName from the options object let show_calendar = @@ -875,7 +878,7 @@ fn to_temporal_year_month( // a. If item has an [[InitializedTemporalYearMonth]] internal slot, then if let Some(ym) = obj.downcast_ref::() { // i. Let resolvedOptions be ? GetOptionsObject(options). - let resolved_options = get_options_object(&options)?; + let resolved_options = get_options_object(&options, context.gc_collector())?; // ii. Perform ? GetTemporalOverflowOption(resolvedOptions). let _overflow = get_option::(&resolved_options, js_string!("overflow"), context)? @@ -887,7 +890,7 @@ fn to_temporal_year_month( // c. Let fields be ? PrepareCalendarFields(calendar, item, « year, month, month-code », «», «»). let partial = to_partial_year_month(&obj, context)?; // d. Let resolvedOptions be ? GetOptionsObject(options). - let resolved_options = get_options_object(&options)?; + let resolved_options = get_options_object(&options, context.gc_collector())?; // e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). let overflow = get_option::(&resolved_options, js_string!("overflow"), context)?; // f. Let isoDate be ? CalendarYearMonthFromFields(calendar, fields, overflow). @@ -908,7 +911,7 @@ fn to_temporal_year_month( // 6. If calendar is empty, set calendar to "iso8601". // 7. Set calendar to ? CanonicalizeCalendar(calendar). // 8. Let resolvedOptions be ? GetOptionsObject(options). - let resolved_options = get_options_object(&options)?; + let resolved_options = get_options_object(&options, context.gc_collector())?; // 9. Perform ? GetTemporalOverflowOption(resolvedOptions). let _overflow = get_option::(&resolved_options, js_string!("overflow"), context)? .unwrap_or(Overflow::Constrain); @@ -956,7 +959,7 @@ pub(crate) fn create_temporal_year_month( // 7. Set object.[[Calendar]] to calendar. // 8. Set object.[[ISODay]] to referenceISODay. - let obj = JsObject::from_proto_and_data(proto, PlainYearMonth::new(ym)); + let obj = JsObject::from_proto_and_data(context.gc_collector(), proto, PlainYearMonth::new(ym)); // 9. Return object. Ok(obj.into()) diff --git a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs index 7f186c87878..f3f40a632ef 100644 --- a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs +++ b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs @@ -71,120 +71,122 @@ impl BuiltInObject for ZonedDateTime { } impl IntrinsicObject for ZonedDateTime { - fn init(realm: &Realm) { - let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_calendar_id = BuiltInBuilder::callable(realm, Self::get_calendar_id, mc) .name(js_string!("get calendarId")) .build(); - let get_timezone_id = BuiltInBuilder::callable(realm, Self::get_timezone_id) + let get_timezone_id = BuiltInBuilder::callable(realm, Self::get_timezone_id, mc) .name(js_string!("get timeZoneId")) .build(); - let get_era = BuiltInBuilder::callable(realm, Self::get_era) + let get_era = BuiltInBuilder::callable(realm, Self::get_era, mc) .name(js_string!("get era")) .build(); - let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year) + let get_era_year = BuiltInBuilder::callable(realm, Self::get_era_year, mc) .name(js_string!("get eraYear")) .build(); - let get_year = BuiltInBuilder::callable(realm, Self::get_year) + let get_year = BuiltInBuilder::callable(realm, Self::get_year, mc) .name(js_string!("get year")) .build(); - let get_month = BuiltInBuilder::callable(realm, Self::get_month) + let get_month = BuiltInBuilder::callable(realm, Self::get_month, mc) .name(js_string!("get month")) .build(); - let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code) + let get_month_code = BuiltInBuilder::callable(realm, Self::get_month_code, mc) .name(js_string!("get monthCode")) .build(); - let get_day = BuiltInBuilder::callable(realm, Self::get_day) + let get_day = BuiltInBuilder::callable(realm, Self::get_day, mc) .name(js_string!("get day")) .build(); - let get_hour = BuiltInBuilder::callable(realm, Self::get_hour) + let get_hour = BuiltInBuilder::callable(realm, Self::get_hour, mc) .name(js_string!("get hour")) .build(); - let get_minute = BuiltInBuilder::callable(realm, Self::get_minute) + let get_minute = BuiltInBuilder::callable(realm, Self::get_minute, mc) .name(js_string!("get minute")) .build(); - let get_second = BuiltInBuilder::callable(realm, Self::get_second) + let get_second = BuiltInBuilder::callable(realm, Self::get_second, mc) .name(js_string!("get second")) .build(); - let get_millisecond = BuiltInBuilder::callable(realm, Self::get_millisecond) + let get_millisecond = BuiltInBuilder::callable(realm, Self::get_millisecond, mc) .name(js_string!("get millisecond")) .build(); - let get_microsecond = BuiltInBuilder::callable(realm, Self::get_microsecond) + let get_microsecond = BuiltInBuilder::callable(realm, Self::get_microsecond, mc) .name(js_string!("get microsecond")) .build(); - let get_nanosecond = BuiltInBuilder::callable(realm, Self::get_nanosecond) + let get_nanosecond = BuiltInBuilder::callable(realm, Self::get_nanosecond, mc) .name(js_string!("get nanosecond")) .build(); - let get_epoch_milliseconds = BuiltInBuilder::callable(realm, Self::get_epoch_milliseconds) - .name(js_string!("get epochMilliseconds")) - .build(); + let get_epoch_milliseconds = + BuiltInBuilder::callable(realm, Self::get_epoch_milliseconds, mc) + .name(js_string!("get epochMilliseconds")) + .build(); - let get_epoch_nanoseconds = BuiltInBuilder::callable(realm, Self::get_epoch_nanoseconds) - .name(js_string!("get epochNanoseconds")) - .build(); + let get_epoch_nanoseconds = + BuiltInBuilder::callable(realm, Self::get_epoch_nanoseconds, mc) + .name(js_string!("get epochNanoseconds")) + .build(); - let get_day_of_week = BuiltInBuilder::callable(realm, Self::get_day_of_week) + let get_day_of_week = BuiltInBuilder::callable(realm, Self::get_day_of_week, mc) .name(js_string!("get dayOfWeek")) .build(); - let get_day_of_year = BuiltInBuilder::callable(realm, Self::get_day_of_year) + let get_day_of_year = BuiltInBuilder::callable(realm, Self::get_day_of_year, mc) .name(js_string!("get dayOfYear")) .build(); - let get_week_of_year = BuiltInBuilder::callable(realm, Self::get_week_of_year) + let get_week_of_year = BuiltInBuilder::callable(realm, Self::get_week_of_year, mc) .name(js_string!("get weekOfYear")) .build(); - let get_hours_in_day = BuiltInBuilder::callable(realm, Self::get_hours_in_day) + let get_hours_in_day = BuiltInBuilder::callable(realm, Self::get_hours_in_day, mc) .name(js_string!("get daysInWeek")) .build(); - let get_year_of_week = BuiltInBuilder::callable(realm, Self::get_year_of_week) + let get_year_of_week = BuiltInBuilder::callable(realm, Self::get_year_of_week, mc) .name(js_string!("get yearOfWeek")) .build(); - let get_days_in_week = BuiltInBuilder::callable(realm, Self::get_days_in_week) + let get_days_in_week = BuiltInBuilder::callable(realm, Self::get_days_in_week, mc) .name(js_string!("get daysInWeek")) .build(); - let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month) + let get_days_in_month = BuiltInBuilder::callable(realm, Self::get_days_in_month, mc) .name(js_string!("get daysInMonth")) .build(); - let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year) + let get_days_in_year = BuiltInBuilder::callable(realm, Self::get_days_in_year, mc) .name(js_string!("get daysInYear")) .build(); - let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year) + let get_months_in_year = BuiltInBuilder::callable(realm, Self::get_months_in_year, mc) .name(js_string!("get monthsInYear")) .build(); - let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year) + let get_in_leap_year = BuiltInBuilder::callable(realm, Self::get_in_leap_year, mc) .name(js_string!("get inLeapYear")) .build(); - let get_offset_nanos = BuiltInBuilder::callable(realm, Self::get_offset_nanoseconds) + let get_offset_nanos = BuiltInBuilder::callable(realm, Self::get_offset_nanoseconds, mc) .name(js_string!("get offsetNanoseconds")) .build(); - let get_offset = BuiltInBuilder::callable(realm, Self::get_offset) + let get_offset = BuiltInBuilder::callable(realm, Self::get_offset, mc) .name(js_string!("get offset")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), StaticJsStrings::ZONED_DT_TAG, @@ -1214,7 +1216,8 @@ impl ZonedDateTime { )?; // 19. Let resolvedOptions be ? GetOptionsObject(options). - let resolved_options = get_options_object(args.get_or_undefined(1))?; + let resolved_options = + get_options_object(args.get_or_undefined(1), context.gc_collector())?; // 20. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions). let disambiguation = get_option::(&resolved_options, js_string!("disambiguation"), context)?; @@ -1345,7 +1348,7 @@ impl ZonedDateTime { let duration = to_temporal_duration(args.get_or_undefined(0), context)?; - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let overflow = get_option::(&options, js_string!("overflow"), context)?; let result = @@ -1376,7 +1379,7 @@ impl ZonedDateTime { let duration = to_temporal_duration(args.get_or_undefined(0), context)?; - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let overflow = get_option::(&options, js_string!("overflow"), context)?; let result = @@ -1407,7 +1410,7 @@ impl ZonedDateTime { let other = to_temporal_zoneddatetime(args.get_or_undefined(0), None, context)?; - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let settings = get_difference_settings(&options, context)?; let result = @@ -1438,7 +1441,7 @@ impl ZonedDateTime { let other = to_temporal_zoneddatetime(args.get_or_undefined(0), None, context)?; - let options = get_options_object(args.get_or_undefined(1))?; + let options = get_options_object(args.get_or_undefined(1), context.gc_collector())?; let settings = get_difference_settings(&options, context)?; let result = @@ -1482,7 +1485,7 @@ impl ZonedDateTime { // a. Let paramString be roundTo. let param_string = param_string.clone(); // b. Set roundTo to OrdinaryObjectCreate(null). - let new_round_to = JsObject::with_null_proto(); + let new_round_to = JsObject::with_null_proto(context.gc_collector()); // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). new_round_to.create_data_property_or_throw( js_string!("smallestUnit"), @@ -1493,7 +1496,7 @@ impl ZonedDateTime { } else { // 5. Else, // a. Set roundTo to ? GetOptionsObject(roundTo). - get_options_object(round_to_arg)? + get_options_object(round_to_arg, context.gc_collector())? }; // 6. NOTE: The following steps read options and perform independent validation @@ -1571,7 +1574,7 @@ impl ZonedDateTime { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - let options = get_options_object(args.get_or_undefined(0))?; + let options = get_options_object(args.get_or_undefined(0), context.gc_collector())?; let show_calendar = get_option::(&options, js_string!("calendarName"), context)? @@ -1741,7 +1744,7 @@ impl ZonedDateTime { let options_obj = if let Some(param_str) = direction_param.as_string() { // a. Let paramString be directionParam. // b. Set directionParam to OrdinaryObjectCreate(null). - let obj = JsObject::with_null_proto(); + let obj = JsObject::with_null_proto(context.gc_collector()); // c. Perform ! CreateDataPropertyOrThrow(directionParam, "direction", paramString). obj.create_data_property_or_throw( js_string!("direction"), @@ -1752,7 +1755,7 @@ impl ZonedDateTime { // 6. Else, } else { // a. Set directionParam to ? GetOptionsObject(directionParam). - get_options_object(direction_param)? + get_options_object(direction_param, context.gc_collector())? }; // TODO: step 7 @@ -1902,7 +1905,8 @@ pub(crate) fn create_temporal_zoneddatetime( // 4. Set object.[[EpochNanoseconds]] to epochNanoseconds. // 5. Set object.[[TimeZone]] to timeZone. // 6. Set object.[[Calendar]] to calendar. - let obj = JsObject::from_proto_and_data(prototype, ZonedDateTime::new(inner)); + let obj = + JsObject::from_proto_and_data(context.gc_collector(), prototype, ZonedDateTime::new(inner)); // 7. Return object. Ok(obj) @@ -1927,7 +1931,10 @@ pub(crate) fn to_temporal_zoneddatetime( // (GetTemporalDisambiguationOption reads "disambiguation", GetTemporalOffsetOption // reads "offset", and GetTemporalOverflowOption reads "overflow"). // ii. Let resolvedOptions be ? GetOptionsObject(options). - let options = get_options_object(options.unwrap_or(&JsValue::undefined()))?; + let options = get_options_object( + options.unwrap_or(&JsValue::undefined()), + context.gc_collector(), + )?; // iii. Perform ? GetTemporalDisambiguationOption(resolvedOptions). let _disambiguation = get_option::(&options, js_string!("disambiguation"), context)? @@ -1946,7 +1953,10 @@ pub(crate) fn to_temporal_zoneddatetime( // f. If offsetString is unset, the // i. Set offsetBehaviour to wall. // g. Let resolvedOptions be ? GetOptionsObject(options). - let options = get_options_object(options.unwrap_or(&JsValue::undefined()))?; + let options = get_options_object( + options.unwrap_or(&JsValue::undefined()), + context.gc_collector(), + )?; // h. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions). let disambiguation = get_option::(&options, js_string!("disambiguation"), context)?; @@ -1985,7 +1995,10 @@ pub(crate) fn to_temporal_zoneddatetime( // k. Set calendar to ? CanonicalizeCalendar(calendar). // l. Set matchBehaviour to match-minutes. // m. Let resolvedOptions be ? GetOptionsObject(options). - let options = get_options_object(options.unwrap_or(&JsValue::undefined()))?; + let options = get_options_object( + options.unwrap_or(&JsValue::undefined()), + context.gc_collector(), + )?; // n. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions). let disambiguation = get_option::(&options, js_string!("disambiguation"), context)? diff --git a/core/engine/src/builtins/typed_array/builtin.rs b/core/engine/src/builtins/typed_array/builtin.rs index 5dae075cb19..c22d21a9c20 100644 --- a/core/engine/src/builtins/typed_array/builtin.rs +++ b/core/engine/src/builtins/typed_array/builtin.rs @@ -35,37 +35,37 @@ use crate::{builtins::array_buffer::utils::memmove_naive, value::JsVariant}; pub(crate) struct BuiltinTypedArray; impl IntrinsicObject for BuiltinTypedArray { - fn init(realm: &Realm) { - let get_species = BuiltInBuilder::callable(realm, Self::get_species) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_species = BuiltInBuilder::callable(realm, Self::get_species, mc) .name(js_string!("get [Symbol.species]")) .build(); - let get_buffer = BuiltInBuilder::callable(realm, Self::buffer) + let get_buffer = BuiltInBuilder::callable(realm, Self::buffer, mc) .name(js_string!("get buffer")) .build(); - let get_byte_length = BuiltInBuilder::callable(realm, Self::byte_length) + let get_byte_length = BuiltInBuilder::callable(realm, Self::byte_length, mc) .name(js_string!("get byteLength")) .build(); - let get_byte_offset = BuiltInBuilder::callable(realm, Self::byte_offset) + let get_byte_offset = BuiltInBuilder::callable(realm, Self::byte_offset, mc) .name(js_string!("get byteOffset")) .build(); - let get_length = BuiltInBuilder::callable(realm, Self::length) + let get_length = BuiltInBuilder::callable(realm, Self::length, mc) .name(js_string!("get length")) .build(); - let get_to_string_tag = BuiltInBuilder::callable(realm, Self::to_string_tag) + let get_to_string_tag = BuiltInBuilder::callable(realm, Self::to_string_tag, mc) .name(js_string!("get [Symbol.toStringTag]")) .build(); - let values_function = BuiltInBuilder::callable(realm, Self::values) + let values_function = BuiltInBuilder::callable(realm, Self::values, mc) .name(js_string!("values")) .length(0) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .static_accessor( JsSymbol::species(), Some(get_species), @@ -2815,8 +2815,13 @@ impl BuiltinTypedArray { let len = values.len() as u64; // 2. Perform ? AllocateTypedArrayBuffer(O, len). let buf = Self::allocate_buffer::(len, context)?; - let obj = JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, buf) - .upcast(); + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + proto, + buf, + ) + .upcast(); // 3. Let k be 0. // 4. Repeat, while k < len, @@ -2865,9 +2870,13 @@ impl BuiltinTypedArray { let indexed = Self::allocate_buffer::(length, context)?; // 2. Let obj be ! IntegerIndexedObjectCreate(proto). - let obj = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, indexed) - .upcast(); + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + proto, + indexed, + ) + .upcast(); // 9. Return obj. Ok(obj) @@ -3008,6 +3017,7 @@ impl BuiltinTypedArray { // 15. Set O.[[ByteOffset]] to 0. // 16. Set O.[[ArrayLength]] to elementLength. let obj = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), proto, TypedArray::new( @@ -3128,6 +3138,7 @@ impl BuiltinTypedArray { // 11. Set O.[[ByteOffset]] to offset. // 12. Return unused. Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), proto, TypedArray::new(buffer, T::ERASED, offset, byte_length, array_length), @@ -3151,8 +3162,13 @@ impl BuiltinTypedArray { // 2. Perform ? AllocateTypedArrayBuffer(O, len). let buf = Self::allocate_buffer::(len, context)?; - let obj = JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, buf) - .upcast(); + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + proto, + buf, + ) + .upcast(); // 3. Let k be 0. // 4. Repeat, while k < len, diff --git a/core/engine/src/builtins/typed_array/mod.rs b/core/engine/src/builtins/typed_array/mod.rs index d79c8d188c5..2d6546df197 100644 --- a/core/engine/src/builtins/typed_array/mod.rs +++ b/core/engine/src/builtins/typed_array/mod.rs @@ -47,12 +47,12 @@ impl IntrinsicObject for T { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } - fn init(realm: &Realm) { - let get_species = BuiltInBuilder::callable(realm, BuiltinTypedArray::get_species) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + let get_species = BuiltInBuilder::callable(realm, BuiltinTypedArray::get_species, mc) .name(js_string!("get [Symbol.species]")) .build(); - BuiltInBuilder::from_standard_constructor::(realm) + BuiltInBuilder::from_standard_constructor::(realm, mc) .prototype( realm .intrinsics() diff --git a/core/engine/src/builtins/uri/mod.rs b/core/engine/src/builtins/uri/mod.rs index 6a5baadb554..58d8ff73be2 100644 --- a/core/engine/src/builtins/uri/mod.rs +++ b/core/engine/src/builtins/uri/mod.rs @@ -87,8 +87,8 @@ impl UriFunctions { pub(crate) struct DecodeUri; impl IntrinsicObject for DecodeUri { - fn init(realm: &Realm) { - BuiltInBuilder::callable_with_intrinsic::(realm, decode_uri) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, decode_uri, mc) .name(Self::NAME) .length(1) .build(); @@ -105,8 +105,8 @@ impl BuiltInObject for DecodeUri { pub(crate) struct DecodeUriComponent; impl IntrinsicObject for DecodeUriComponent { - fn init(realm: &Realm) { - BuiltInBuilder::callable_with_intrinsic::(realm, decode_uri_component) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, decode_uri_component, mc) .name(Self::NAME) .length(1) .build(); @@ -127,8 +127,8 @@ impl BuiltInObject for DecodeUriComponent { pub(crate) struct EncodeUri; impl IntrinsicObject for EncodeUri { - fn init(realm: &Realm) { - BuiltInBuilder::callable_with_intrinsic::(realm, encode_uri) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, encode_uri, mc) .name(Self::NAME) .length(1) .build(); @@ -144,8 +144,8 @@ impl BuiltInObject for EncodeUri { pub(crate) struct EncodeUriComponent; impl IntrinsicObject for EncodeUriComponent { - fn init(realm: &Realm) { - BuiltInBuilder::callable_with_intrinsic::(realm, encode_uri_component) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::callable_with_intrinsic::(realm, encode_uri_component, mc) .name(Self::NAME) .length(1) .build(); diff --git a/core/engine/src/builtins/weak/weak_ref.rs b/core/engine/src/builtins/weak/weak_ref.rs index 83b92e27a82..308f273e8db 100644 --- a/core/engine/src/builtins/weak/weak_ref.rs +++ b/core/engine/src/builtins/weak/weak_ref.rs @@ -30,8 +30,8 @@ impl IntrinsicObject for WeakRef { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), js_string!("WeakRef"), @@ -85,6 +85,7 @@ impl BuiltInConstructor for WeakRef { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::weak_ref, context)?; let weak_ref = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, WeakGc::new(context.gc_collector(), target.inner()), diff --git a/core/engine/src/builtins/weak_map/mod.rs b/core/engine/src/builtins/weak_map/mod.rs index 91e88290846..b286a6359d2 100644 --- a/core/engine/src/builtins/weak_map/mod.rs +++ b/core/engine/src/builtins/weak_map/mod.rs @@ -36,8 +36,8 @@ impl IntrinsicObject for WeakMap { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), Self::NAME, @@ -95,6 +95,7 @@ impl BuiltInConstructor for WeakMap { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::weak_map, context)?; let map = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, NativeWeakMap::new(context.gc_collector()), diff --git a/core/engine/src/builtins/weak_set/mod.rs b/core/engine/src/builtins/weak_set/mod.rs index 73ca5456716..79855e5886f 100644 --- a/core/engine/src/builtins/weak_set/mod.rs +++ b/core/engine/src/builtins/weak_set/mod.rs @@ -32,8 +32,8 @@ impl IntrinsicObject for WeakSet { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } - fn init(realm: &Realm) { - BuiltInBuilder::from_standard_constructor::(realm) + fn init(realm: &Realm, mc: &boa_gc::MutationContext<'static, '_>) { + BuiltInBuilder::from_standard_constructor::(realm, mc) .property( JsSymbol::to_string_tag(), Self::NAME, @@ -84,6 +84,7 @@ impl BuiltInConstructor for WeakSet { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::weak_set, context)?; let weak_set = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, NativeWeakSet::new(context.gc_collector()), diff --git a/core/engine/src/class.rs b/core/engine/src/class.rs index 91ac60edb3e..ffa3b875531 100644 --- a/core/engine/src/class.rs +++ b/core/engine/src/class.rs @@ -196,8 +196,12 @@ pub trait Class: NativeObject + Sized { let data = Self::data_constructor(new_target, args, context)?; - let object = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, data); + let object = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + data, + ); Self::object_constructor(&object, args, context)?; @@ -228,8 +232,12 @@ pub trait Class: NativeObject + Sized { })? .prototype(); - let object = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, data); + let object = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + data, + ); Self::object_constructor(&object, &[], context)?; diff --git a/core/engine/src/context/hooks.rs b/core/engine/src/context/hooks.rs index c1f2906563c..2560c21eb71 100644 --- a/core/engine/src/context/hooks.rs +++ b/core/engine/src/context/hooks.rs @@ -169,8 +169,12 @@ pub trait HostHooks { /// Equivalent to the step 7 of [`InitializeHostDefinedRealm ( )`][ihdr]. /// /// [ihdr]: https://tc39.es/ecma262/#sec-initializehostdefinedrealm - fn create_global_object(&self, intrinsics: &Intrinsics) -> JsObject { - JsObject::with_object_proto(intrinsics) + fn create_global_object( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + intrinsics: &Intrinsics, + ) -> JsObject { + JsObject::with_object_proto(mc, intrinsics) } /// Creates the global `this` of a new [`Context`] from the initial intrinsics. diff --git a/core/engine/src/context/intrinsics.rs b/core/engine/src/context/intrinsics.rs index edd57db8cdf..7a182e34317 100644 --- a/core/engine/src/context/intrinsics.rs +++ b/core/engine/src/context/intrinsics.rs @@ -79,18 +79,12 @@ pub struct StandardConstructor { prototype: JsObject, } -impl Default for StandardConstructor { - fn default() -> Self { - Self::uninit(&unsafe { boa_gc::MutationContext::global() }) - } -} - impl StandardConstructor { /// Creates a new uninitialized `StandardConstructor` using the given context. pub(crate) fn uninit(mc: &boa_gc::MutationContext<'static, '_>) -> Self { Self { constructor: JsFunction::empty_intrinsic_function_in(mc, true), - prototype: JsObject::with_null_proto_in(mc), + prototype: JsObject::with_null_proto(mc), } } /// Creates a new `StandardConstructor` from the constructor and the prototype. @@ -102,7 +96,7 @@ impl StandardConstructor { } /// Build a constructor with a defined prototype, using the given context. - fn with_prototype_in(mc: &boa_gc::MutationContext<'static, '_>, prototype: JsObject) -> Self { + fn with_prototype(mc: &boa_gc::MutationContext<'static, '_>, prototype: JsObject) -> Self { Self { constructor: JsFunction::empty_intrinsic_function_in(mc, true), prototype, @@ -110,9 +104,6 @@ impl StandardConstructor { } /// Build a constructor with a defined prototype. - fn with_prototype(prototype: JsObject) -> Self { - Self::with_prototype_in(&unsafe { boa_gc::MutationContext::global() }, prototype) - } /// Return the prototype of the constructor object. /// @@ -221,9 +212,9 @@ pub struct StandardConstructors { impl StandardConstructors { pub(crate) fn uninit(mc: &boa_gc::MutationContext<'static, '_>) -> Self { Self { - object: StandardConstructor::with_prototype_in( + object: StandardConstructor::with_prototype( mc, - JsObject::from_object_and_vtable_in( + JsObject::from_object_and_vtable( mc, Object::::default(), &IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, @@ -238,22 +229,22 @@ impl StandardConstructors { }, async_function: StandardConstructor::uninit(mc), generator_function: StandardConstructor::uninit(mc), - array: StandardConstructor::with_prototype_in( + array: StandardConstructor::with_prototype( mc, - JsObject::from_proto_and_data_in(mc, None, Array), + JsObject::from_proto_and_data(mc, None, Array), ), bigint: StandardConstructor::uninit(mc), - number: StandardConstructor::with_prototype_in( + number: StandardConstructor::with_prototype( mc, - JsObject::from_proto_and_data_in(mc, None, 0.0), + JsObject::from_proto_and_data(mc, None, 0.0), ), - boolean: StandardConstructor::with_prototype_in( + boolean: StandardConstructor::with_prototype( mc, - JsObject::from_proto_and_data_in(mc, None, false), + JsObject::from_proto_and_data(mc, None, false), ), - string: StandardConstructor::with_prototype_in( + string: StandardConstructor::with_prototype( mc, - JsObject::from_proto_and_data_in(mc, None, js_string!()), + JsObject::from_proto_and_data(mc, None, js_string!()), ), regexp: StandardConstructor::uninit(mc), symbol: StandardConstructor::uninit(mc), @@ -1189,16 +1180,16 @@ impl IntrinsicObjects { #[allow(clippy::unnecessary_wraps)] pub(crate) fn uninit(mc: &boa_gc::MutationContext<'static, '_>) -> Option { Some(Self { - reflect: JsObject::with_null_proto_in(mc), - math: JsObject::with_null_proto_in(mc), - json: JsObject::with_null_proto_in(mc), + reflect: JsObject::with_null_proto(mc), + math: JsObject::with_null_proto(mc), + json: JsObject::with_null_proto(mc), throw_type_error: JsFunction::empty_intrinsic_function_in(mc, false), array_prototype_values: JsFunction::empty_intrinsic_function_in(mc, false), array_prototype_to_string: JsFunction::empty_intrinsic_function_in(mc, false), iterator_prototypes: IteratorPrototypes::uninit_in(mc), - generator: JsObject::with_null_proto_in(mc), - async_generator: JsObject::with_null_proto_in(mc), - atomics: JsObject::with_null_proto_in(mc), + generator: JsObject::with_null_proto(mc), + async_generator: JsObject::with_null_proto(mc), + atomics: JsObject::with_null_proto(mc), eval: JsFunction::empty_intrinsic_function_in(mc, false), uri_functions: UriFunctions::uninit_in(mc), is_finite: JsFunction::empty_intrinsic_function_in(mc, false), @@ -1210,13 +1201,13 @@ impl IntrinsicObjects { #[cfg(feature = "annex-b")] unescape: JsFunction::empty_intrinsic_function_in(mc, false), #[cfg(feature = "intl")] - intl: JsObject::new_unique(None, Intl::new()?), + intl: JsObject::new_unique(mc, None, Intl::new()?), #[cfg(feature = "intl")] - segments_prototype: JsObject::with_null_proto(), + segments_prototype: JsObject::with_null_proto(mc), #[cfg(feature = "temporal")] - temporal: JsObject::with_null_proto(), + temporal: JsObject::with_null_proto(mc), #[cfg(feature = "temporal")] - now: JsObject::with_null_proto(), + now: JsObject::with_null_proto(mc), }) } @@ -1466,46 +1457,46 @@ impl ObjectTemplates { // pre-initialize used shapes. let ordinary_object = - ObjectTemplate::with_prototype_in(mc, root_shape, constructors.object().prototype()); + ObjectTemplate::with_prototype(mc, root_shape, constructors.object().prototype()); let mut array = ObjectTemplate::new(root_shape); let length_property_key: PropertyKey = js_string!("length").into(); - array.property_in( + array.property( mc, length_property_key.clone(), Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); - array.set_prototype_in(mc, constructors.array().prototype()); + array.set_prototype(mc, constructors.array().prototype()); let number = - ObjectTemplate::with_prototype_in(mc, root_shape, constructors.number().prototype()); + ObjectTemplate::with_prototype(mc, root_shape, constructors.number().prototype()); let symbol = - ObjectTemplate::with_prototype_in(mc, root_shape, constructors.symbol().prototype()); + ObjectTemplate::with_prototype(mc, root_shape, constructors.symbol().prototype()); let bigint = - ObjectTemplate::with_prototype_in(mc, root_shape, constructors.bigint().prototype()); + ObjectTemplate::with_prototype(mc, root_shape, constructors.bigint().prototype()); let boolean = - ObjectTemplate::with_prototype_in(mc, root_shape, constructors.boolean().prototype()); + ObjectTemplate::with_prototype(mc, root_shape, constructors.boolean().prototype()); let mut string = ObjectTemplate::new(root_shape); - string.property_in( + string.property( mc, length_property_key.clone(), Attribute::READONLY | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); - string.set_prototype_in(mc, constructors.string().prototype()); + string.set_prototype(mc, constructors.string().prototype()); let mut regexp_without_proto = ObjectTemplate::new(root_shape); - regexp_without_proto.property_in(mc, js_string!("lastIndex").into(), Attribute::WRITABLE); + regexp_without_proto.property(mc, js_string!("lastIndex").into(), Attribute::WRITABLE); let mut regexp = regexp_without_proto.clone(); - regexp.set_prototype_in(mc, constructors.regexp().prototype()); + regexp.set_prototype(mc, constructors.regexp().prototype()); let name_property_key: PropertyKey = js_string!("name").into(); let mut function = ObjectTemplate::new(root_shape); - function.property_in( + function.property( mc, length_property_key.clone(), Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); - function.property_in( + function.property( mc, name_property_key, Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, @@ -1515,7 +1506,7 @@ impl ObjectTemplates { let mut async_function = function.clone(); let mut function_with_prototype = function.clone(); - function_with_prototype.property_in( + function_with_prototype.property( mc, PROTOTYPE.into(), Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, @@ -1525,15 +1516,15 @@ impl ObjectTemplates { let function_with_prototype_without_proto = function_with_prototype.clone(); - function.set_prototype_in(mc, constructors.function().prototype()); - function_with_prototype.set_prototype_in(mc, constructors.function().prototype()); - async_function.set_prototype_in(mc, constructors.async_function().prototype()); - generator_function.set_prototype_in(mc, constructors.generator_function().prototype()); + function.set_prototype(mc, constructors.function().prototype()); + function_with_prototype.set_prototype(mc, constructors.function().prototype()); + async_function.set_prototype(mc, constructors.async_function().prototype()); + generator_function.set_prototype(mc, constructors.generator_function().prototype()); async_generator_function - .set_prototype_in(mc, constructors.async_generator_function().prototype()); + .set_prototype(mc, constructors.async_generator_function().prototype()); let mut function_prototype = ordinary_object.clone(); - function_prototype.property_in( + function_prototype.property( mc, CONSTRUCTOR.into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, @@ -1543,7 +1534,7 @@ impl ObjectTemplates { // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - unmapped_arguments.property_in( + unmapped_arguments.property( mc, length_property_key, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, @@ -1552,7 +1543,7 @@ impl ObjectTemplates { // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, // [[Configurable]]: true }). - unmapped_arguments.property_in( + unmapped_arguments.property( mc, JsSymbol::iterator().into(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, @@ -1563,7 +1554,7 @@ impl ObjectTemplates { // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, // [[Configurable]]: false }). - unmapped_arguments.accessor_in( + unmapped_arguments.accessor( mc, js_string!("callee").into(), true, @@ -1573,37 +1564,37 @@ impl ObjectTemplates { // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - mapped_arguments.property_in( + mapped_arguments.property( mc, js_string!("callee").into(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); let mut iterator_result = ordinary_object.clone(); - iterator_result.property_in( + iterator_result.property( mc, js_string!("value").into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, ); - iterator_result.property_in( + iterator_result.property( mc, js_string!("done").into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, ); let mut namespace = ObjectTemplate::new(root_shape); - namespace.property_in(mc, JsSymbol::to_string_tag().into(), Attribute::empty()); + namespace.property(mc, JsSymbol::to_string_tag().into(), Attribute::empty()); let with_resolvers = { let mut with_resolvers = ordinary_object.clone(); with_resolvers // 4. Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]). - .property_in(mc, js_string!("promise").into(), Attribute::all()) + .property(mc, js_string!("promise").into(), Attribute::all()) // 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]). - .property_in(mc, js_string!("resolve").into(), Attribute::all()) + .property(mc, js_string!("resolve").into(), Attribute::all()) // 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]). - .property_in(mc, js_string!("reject").into(), Attribute::all()); + .property(mc, js_string!("reject").into(), Attribute::all()); with_resolvers }; @@ -1611,8 +1602,8 @@ impl ObjectTemplates { let wait_async = { let mut obj = ordinary_object.clone(); - obj.property_in(mc, js_string!("async").into(), Attribute::all()) - .property_in(mc, js_string!("value").into(), Attribute::all()); + obj.property(mc, js_string!("async").into(), Attribute::all()) + .property(mc, js_string!("value").into(), Attribute::all()); obj }; diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index 218d177ffda..a84ebd8fa41 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -291,7 +291,7 @@ impl Context { length: usize, body: NativeFunction, ) -> JsResult<()> { - let function = FunctionObjectBuilder::new(self.realm(), body) + let function = FunctionObjectBuilder::new(self.realm(), self.gc_collector(), body) .name(name.clone()) .length(length) .constructor(true) @@ -324,7 +324,7 @@ impl Context { length: usize, body: NativeFunction, ) -> JsResult<()> { - let function = FunctionObjectBuilder::new(self.realm(), body) + let function = FunctionObjectBuilder::new(self.realm(), self.gc_collector(), body) .name(name.clone()) .length(length) .constructor(false) @@ -1225,7 +1225,7 @@ impl ContextBuilder { } let mc = unsafe { boa_gc::MutationContext::global() }; - let root_shape = RootShape::new_in(&mc); + let root_shape = RootShape::new(&mc); let host_hooks = self.host_hooks.unwrap_or(Rc::new(DefaultHooks)); let clock = self.clock.unwrap_or_else(|| Rc::new(StdClock::new())); diff --git a/core/engine/src/error/mod.rs b/core/engine/src/error/mod.rs index 2e8ec0e6949..00e1a3a03d7 100644 --- a/core/engine/src/error/mod.rs +++ b/core/engine/src/error/mod.rs @@ -1346,6 +1346,7 @@ impl JsNativeError { }; let o = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, Error::with_stack(tag, stack.0.clone()), diff --git a/core/engine/src/module/mod.rs b/core/engine/src/module/mod.rs index 1f6d01948fd..01c9d8f7a45 100644 --- a/core/engine/src/module/mod.rs +++ b/core/engine/src/module/mod.rs @@ -658,7 +658,7 @@ impl Module { }, self.clone(), ) - .to_js_function(context.realm()), + .to_js_function(context.realm(), context.gc_collector()), ), None, context, @@ -670,7 +670,7 @@ impl Module { |_, _, module, context| Ok(module.evaluate(context)?.into()), self.clone(), ) - .to_js_function(context.realm()), + .to_js_function(context.realm(), context.gc_collector()), ), None, context, @@ -784,8 +784,12 @@ impl + Clone> IntoJsModule fo unsafe { SyntheticModuleInitializer::from_closure(move |module, context| { for (name, f) in names.iter().zip(fns.iter()) { - module - .set_export(name, f.clone().to_js_function(context.realm()).into())?; + module.set_export( + name, + f.clone() + .to_js_function(context.realm(), context.gc_collector()) + .into(), + )?; } Ok(()) }) diff --git a/core/engine/src/module/namespace.rs b/core/engine/src/module/namespace.rs index f42692c9f1d..ea860363ce5 100644 --- a/core/engine/src/module/namespace.rs +++ b/core/engine/src/module/namespace.rs @@ -100,6 +100,7 @@ impl ModuleNamespace { // 10. Return M. context.intrinsics().templates().namespace().create( + context.gc_collector(), Self { module, exports, diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index c7962cc5b32..26adadc14d7 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -1477,6 +1477,7 @@ impl SourceTextModule { // 5. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »). let on_fulfilled = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, _, module, context| { // a. Perform AsyncModuleExecutionFulfilled(module). @@ -1493,6 +1494,7 @@ impl SourceTextModule { // 7. Let onRejected be CreateBuiltinFunction(rejectedClosure, 0, "", « »). let on_rejected = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, module, context| { let error = JsError::from_opaque(args.get_or_undefined(0).clone()); diff --git a/core/engine/src/native_function/mod.rs b/core/engine/src/native_function/mod.rs index 11dc49adce8..86e9d141160 100644 --- a/core/engine/src/native_function/mod.rs +++ b/core/engine/src/native_function/mod.rs @@ -312,8 +312,12 @@ impl NativeFunction { /// /// Useful to create functions that will only be used once, such as callbacks. #[must_use] - pub fn to_js_function(self, realm: &Realm) -> JsFunction { - FunctionObjectBuilder::new(realm, self).build() + pub fn to_js_function( + self, + realm: &Realm, + mc: &boa_gc::MutationContext<'static, '_>, + ) -> JsFunction { + FunctionObjectBuilder::new(realm, mc, self).build() } } @@ -443,6 +447,7 @@ fn native_function_construct( context, )?; Ok(JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, OrdinaryObject, diff --git a/core/engine/src/object/builtins/jsarraybuffer.rs b/core/engine/src/object/builtins/jsarraybuffer.rs index b377f684dee..3ea14c087dc 100644 --- a/core/engine/src/object/builtins/jsarraybuffer.rs +++ b/core/engine/src/object/builtins/jsarraybuffer.rs @@ -124,6 +124,7 @@ impl JsArrayBuffer { // 3. Set obj.[[ArrayBufferData]] to block. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. let obj = JsObject::new( + context.gc_collector(), context.root_shape(), prototype, ArrayBuffer::from_data(block, JsValue::undefined()), diff --git a/core/engine/src/object/builtins/jsdataview.rs b/core/engine/src/object/builtins/jsdataview.rs index 7bf07ade92d..c059cacebcf 100644 --- a/core/engine/src/object/builtins/jsdataview.rs +++ b/core/engine/src/object/builtins/jsdataview.rs @@ -135,6 +135,7 @@ impl JsDataView { } let obj = JsObject::new( + context.gc_collector(), context.root_shape(), prototype, DataView { diff --git a/core/engine/src/object/builtins/jsdate.rs b/core/engine/src/object/builtins/jsdate.rs index 3a056242f9b..59e9830795a 100644 --- a/core/engine/src/object/builtins/jsdate.rs +++ b/core/engine/src/object/builtins/jsdate.rs @@ -45,9 +45,13 @@ impl JsDate { pub fn new(context: &mut Context) -> Self { let prototype = context.intrinsics().constructors().date().prototype(); let now = Date::utc_now(context); - let inner = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, now) - .upcast(); + let inner = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + prototype, + now, + ) + .upcast(); Self { inner } } @@ -552,6 +556,7 @@ impl JsDate { Ok(Self { inner: JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, date_time, diff --git a/core/engine/src/object/builtins/jsfunction.rs b/core/engine/src/object/builtins/jsfunction.rs index 0d165e0c17c..8e76b155ad3 100644 --- a/core/engine/src/object/builtins/jsfunction.rs +++ b/core/engine/src/object/builtins/jsfunction.rs @@ -128,7 +128,7 @@ impl JsFunction { constructor: bool, ) -> Self { Self { - inner: JsObject::from_proto_and_data_in( + inner: JsObject::from_proto_and_data( mc, None, NativeFunctionObject { diff --git a/core/engine/src/object/builtins/jsmap.rs b/core/engine/src/object/builtins/jsmap.rs index c35a014cdbf..e90ef3db208 100644 --- a/core/engine/src/object/builtins/jsmap.rs +++ b/core/engine/src/object/builtins/jsmap.rs @@ -155,6 +155,7 @@ impl JsMap { /// # let context = &mut Context::default(); /// // `some_object` can be any JavaScript `Map` object. /// let some_object = JsObject::from_proto_and_data( + /// context.gc_collector(), /// context.intrinsics().constructors().map().prototype(), /// OrderedMap::::new(), /// ); @@ -198,6 +199,7 @@ impl JsMap { // Create a default map object with [[MapData]] as a new empty list JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, >::new(), diff --git a/core/engine/src/object/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index 08e7af96556..fef2ce9811f 100644 --- a/core/engine/src/object/builtins/jspromise.rs +++ b/core/engine/src/object/builtins/jspromise.rs @@ -62,7 +62,7 @@ use std::{future::Future, pin::Pin, task}; /// Err(JsError::from_opaque(args.get_or_undefined(0).clone()) /// .into()) /// }) -/// .to_js_function(context.realm()), +/// .to_js_function(context.realm(), context.gc_collector()), /// ), /// None, /// context, @@ -71,7 +71,7 @@ use std::{future::Future, pin::Pin, task}; /// NativeFunction::from_fn_ptr(|_, args, _| { /// Ok(args.get_or_undefined(0).clone()) /// }) -/// .to_js_function(context.realm()), +/// .to_js_function(context.realm(), context.gc_collector()), /// context, /// )? /// .finally( @@ -84,7 +84,7 @@ use std::{future::Future, pin::Pin, task}; /// )?; /// Ok(JsValue::undefined()) /// }) -/// .to_js_function(context.realm()), +/// .to_js_function(context.realm(), context.gc_collector()), /// context, /// )?; /// @@ -168,6 +168,7 @@ impl JsPromise { F: FnOnce(&ResolvingFunctions, &mut Context) -> JsResult, { let promise = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().promise().prototype(), Promise::new(), @@ -219,6 +220,7 @@ impl JsPromise { #[inline] pub fn new_pending(context: &mut Context) -> (Self, ResolvingFunctions) { let promise = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().promise().prototype(), Promise::new(), @@ -255,7 +257,7 @@ impl JsPromise { /// PromiseState::Fulfilled(JsValue::undefined()) /// ); /// - /// assert!(JsPromise::from_object(JsObject::with_null_proto()).is_err()); + /// assert!(JsPromise::from_object(JsObject::with_null_proto(context.gc_collector())).is_err()); /// /// # Ok(()) /// # } @@ -541,7 +543,7 @@ impl JsPromise { /// .to_string(context) /// .map(JsValue::from) /// }) - /// .to_js_function(context.realm()), + /// .to_js_function(context.realm(), context.gc_collector()), /// ), /// None, /// context, @@ -611,7 +613,7 @@ impl JsPromise { /// .to_string(context) /// .map(JsValue::from) /// }) - /// .to_js_function(context.realm()), + /// .to_js_function(context.realm(), context.gc_collector()), /// context, /// )?; /// @@ -685,7 +687,7 @@ impl JsPromise { /// )?; /// Ok(JsValue::undefined()) /// }) - /// .to_js_function(context.realm()), + /// .to_js_function(context.realm(), context.gc_collector()), /// context, /// )?; /// @@ -1125,8 +1127,8 @@ impl JsPromise { }; drop(self.then( - Some(resolve.to_js_function(context.realm())), - Some(reject.to_js_function(context.realm())), + Some(resolve.to_js_function(context.realm(), context.gc_collector())), + Some(reject.to_js_function(context.realm(), context.gc_collector())), context, )?); @@ -1165,7 +1167,7 @@ impl JsPromise { /// assert_eq!(*args.get_or_undefined(0), JsValue::new(1)); /// Ok(JsValue::new(2)) /// }) - /// .to_js_function(context.realm()), + /// .to_js_function(context.realm(), context.gc_collector()), /// ), /// None, /// context, @@ -1192,7 +1194,7 @@ impl JsPromise { /// NativeFunction::from_fn_ptr(|_, _, _| { /// panic!("This will not happen."); /// }) - /// .to_js_function(context.realm()), + /// .to_js_function(context.realm(), context.gc_collector()), /// ), /// None, /// context, @@ -1241,6 +1243,7 @@ impl JsPromise { // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). let on_fulfilled = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { // a. Let prevContext be the running execution context. @@ -1305,6 +1308,7 @@ impl JsPromise { // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). let on_rejected = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { // a. Let prevContext be the running execution context. diff --git a/core/engine/src/object/builtins/jsproxy.rs b/core/engine/src/object/builtins/jsproxy.rs index 145d6196f21..f8b17c8c294 100644 --- a/core/engine/src/object/builtins/jsproxy.rs +++ b/core/engine/src/object/builtins/jsproxy.rs @@ -394,21 +394,28 @@ impl JsProxyBuilder { /// [`JsObject`] in case there's a need to manipulate the returned object /// inside Rust code. pub fn build(self, context: &mut Context) -> JsResult { - let handler = JsObject::with_object_proto(context.intrinsics()); + let handler = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); if let Some(apply) = self.apply { - let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(apply)) - .length(3) - .build(); + let f = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(apply), + ) + .length(3) + .build(); handler .create_data_property_or_throw(js_string!("apply"), f, context) .js_expect("new object should be writable")?; } if let Some(construct) = self.construct { - let f = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(construct)) - .length(3) - .build(); + let f = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(construct), + ) + .length(3) + .build(); handler .create_data_property_or_throw(js_string!("construct"), f, context) .js_expect("new object should be writable")?; @@ -416,6 +423,7 @@ impl JsProxyBuilder { if let Some(define_property) = self.define_property { let f = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(define_property), ) .length(3) @@ -427,6 +435,7 @@ impl JsProxyBuilder { if let Some(delete_property) = self.delete_property { let f = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(delete_property), ) .length(2) @@ -436,9 +445,13 @@ impl JsProxyBuilder { .js_expect("new object should be writable")?; } if let Some(get) = self.get { - let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get)) - .length(3) - .build(); + let f = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(get), + ) + .length(3) + .build(); handler .create_data_property_or_throw(js_string!("get"), f, context) .js_expect("new object should be writable")?; @@ -446,6 +459,7 @@ impl JsProxyBuilder { if let Some(get_own_property_descriptor) = self.get_own_property_descriptor { let f = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(get_own_property_descriptor), ) .length(2) @@ -457,6 +471,7 @@ impl JsProxyBuilder { if let Some(get_prototype_of) = self.get_prototype_of { let f = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(get_prototype_of), ) .length(1) @@ -466,9 +481,13 @@ impl JsProxyBuilder { .js_expect("new object should be writable")?; } if let Some(has) = self.has { - let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(has)) - .length(2) - .build(); + let f = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(has), + ) + .length(2) + .build(); handler .create_data_property_or_throw(js_string!("has"), f, context) .js_expect("new object should be writable")?; @@ -476,6 +495,7 @@ impl JsProxyBuilder { if let Some(is_extensible) = self.is_extensible { let f = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(is_extensible), ) .length(1) @@ -485,10 +505,13 @@ impl JsProxyBuilder { .js_expect("new object should be writable")?; } if let Some(own_keys) = self.own_keys { - let f = - FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(own_keys)) - .length(1) - .build(); + let f = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(own_keys), + ) + .length(1) + .build(); handler .create_data_property_or_throw(js_string!("ownKeys"), f, context) .js_expect("new object should be writable")?; @@ -496,6 +519,7 @@ impl JsProxyBuilder { if let Some(prevent_extensions) = self.prevent_extensions { let f = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(prevent_extensions), ) .length(1) @@ -505,9 +529,13 @@ impl JsProxyBuilder { .js_expect("new object should be writable")?; } if let Some(set) = self.set { - let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set)) - .length(4) - .build(); + let f = FunctionObjectBuilder::new( + context.realm(), + context.gc_collector(), + NativeFunction::from_fn_ptr(set), + ) + .length(4) + .build(); handler .create_data_property_or_throw(js_string!("set"), f, context) .js_expect("new object should be writable")?; @@ -515,6 +543,7 @@ impl JsProxyBuilder { if let Some(set_prototype_of) = self.set_prototype_of { let f = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(set_prototype_of), ) .length(2) @@ -525,6 +554,7 @@ impl JsProxyBuilder { } let proxy = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().object().prototype(), Proxy::new(self.target, handler), diff --git a/core/engine/src/object/builtins/jsset.rs b/core/engine/src/object/builtins/jsset.rs index 01efe9407d6..09f9554c036 100644 --- a/core/engine/src/object/builtins/jsset.rs +++ b/core/engine/src/object/builtins/jsset.rs @@ -29,6 +29,7 @@ impl JsSet { pub fn new(context: &mut Context) -> Self { Self { inner: JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().set().prototype(), OrderedSet::new(), @@ -179,6 +180,7 @@ impl JsSet { Self { inner: JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().set().prototype(), set, diff --git a/core/engine/src/object/builtins/jssharedarraybuffer.rs b/core/engine/src/object/builtins/jssharedarraybuffer.rs index 36b2453211b..76481653359 100644 --- a/core/engine/src/object/builtins/jssharedarraybuffer.rs +++ b/core/engine/src/object/builtins/jssharedarraybuffer.rs @@ -58,7 +58,7 @@ impl JsSharedArrayBuffer { .shared_array_buffer() .prototype(); - let inner = JsObject::new(context.root_shape(), proto, buffer); + let inner = JsObject::new(context.gc_collector(), context.root_shape(), proto, buffer); Self { inner } } diff --git a/core/engine/src/object/builtins/jstypedarray.rs b/core/engine/src/object/builtins/jstypedarray.rs index 90d94d7387e..eaa250230a4 100644 --- a/core/engine/src/object/builtins/jstypedarray.rs +++ b/core/engine/src/object/builtins/jstypedarray.rs @@ -516,8 +516,7 @@ impl JsTypedArray { /// let array = JsUint8Array::from_iter(data, context)?; /// /// let greater_than_10_predicate = FunctionObjectBuilder::new( - /// context.realm(), - /// NativeFunction::from_fn_ptr(|_this, args, _context| { + /// context.realm(), /// NativeFunction::from_fn_ptr(|_this, args, _context| { /// let element = args /// .first() /// .cloned() @@ -574,8 +573,7 @@ impl JsTypedArray { /// let array = JsUint8Array::from_iter(data, context)?; /// /// let lower_than_200_predicate = FunctionObjectBuilder::new( - /// context.realm(), - /// NativeFunction::from_fn_ptr(|_this, args, _context| { + /// context.realm(), /// NativeFunction::from_fn_ptr(|_this, args, _context| { /// let element = args /// .first() /// .cloned() @@ -624,8 +622,7 @@ impl JsTypedArray { /// let array = JsUint8Array::from_iter(data, context)?; /// /// let lower_than_200_predicate = FunctionObjectBuilder::new( - /// context.realm(), - /// NativeFunction::from_fn_ptr(|_this, args, _context| { + /// context.realm(), /// NativeFunction::from_fn_ptr(|_this, args, _context| { /// let element = args /// .first() /// .cloned() @@ -681,8 +678,7 @@ impl JsTypedArray { /// let num_to_modify = context.alloc(GcRefCell::new(0u8)); /// /// let js_function = FunctionObjectBuilder::new( - /// context.realm(), - /// NativeFunction::from_copy_closure_with_captures( + /// context.realm(), /// NativeFunction::from_copy_closure_with_captures( /// |_, args, captures, inner_context| { /// let element = args /// .first() diff --git a/core/engine/src/object/builtins/jsweakmap.rs b/core/engine/src/object/builtins/jsweakmap.rs index 9fdd2e8327c..bad6925a7f1 100644 --- a/core/engine/src/object/builtins/jsweakmap.rs +++ b/core/engine/src/object/builtins/jsweakmap.rs @@ -28,6 +28,7 @@ impl JsWeakMap { pub fn new(context: &mut Context) -> Self { Self { inner: JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().weak_map().prototype(), NativeWeakMap::new(context.gc_collector()), diff --git a/core/engine/src/object/builtins/jsweakset.rs b/core/engine/src/object/builtins/jsweakset.rs index 663a3c65df0..d226066c290 100644 --- a/core/engine/src/object/builtins/jsweakset.rs +++ b/core/engine/src/object/builtins/jsweakset.rs @@ -28,6 +28,7 @@ impl JsWeakSet { pub fn new(context: &mut Context) -> Self { Self { inner: JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().weak_set().prototype(), NativeWeakSet::new(context.gc_collector()), diff --git a/core/engine/src/object/datatypes.rs b/core/engine/src/object/datatypes.rs index 9d62f28b99a..c8c3c9acb75 100644 --- a/core/engine/src/object/datatypes.rs +++ b/core/engine/src/object/datatypes.rs @@ -34,7 +34,7 @@ use super::internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; /// } /// /// let object = -/// JsObject::from_proto_and_data(None, CustomStruct { counter: 5 }); +/// JsObject::from_proto_and_data(context.gc_collector(), None, CustomStruct { counter: 5 }); /// /// assert_eq!(object.downcast_ref::().unwrap().counter, 5); /// ``` diff --git a/core/engine/src/object/internal_methods/mod.rs b/core/engine/src/object/internal_methods/mod.rs index 8e474b5dde5..e2c11a4f662 100644 --- a/core/engine/src/object/internal_methods/mod.rs +++ b/core/engine/src/object/internal_methods/mod.rs @@ -44,6 +44,11 @@ impl<'ctx> InternalMethodPropertyContext<'ctx> { pub(crate) fn slot(&mut self) -> &mut Slot { &mut self.slot } + + #[inline] + pub(crate) fn slot_and_mc(&mut self) -> (&mut Slot, &boa_gc::MutationContext<'static, '_>) { + (&mut self.slot, self.context.gc_collector()) + } } impl Deref for InternalMethodPropertyContext<'_> { @@ -532,8 +537,9 @@ pub(crate) fn ordinary_get_prototype_of( pub(crate) fn ordinary_set_prototype_of( obj: &JsObject, val: JsPrototype, - _: &mut Context, + context: &mut Context, ) -> JsResult { + let mc = context.gc_collector(); // 1. Assert: Either Type(V) is Object or Type(V) is Null. // 2. Let current be O.[[Prototype]]. let current = obj.prototype(); @@ -573,7 +579,7 @@ pub(crate) fn ordinary_set_prototype_of( } // 9. Set O.[[Prototype]] to V. - obj.set_prototype(val); + obj.set_prototype(mc, val); // 10. Return true. Ok(true) @@ -657,12 +663,14 @@ pub(crate) fn ordinary_define_own_property( let extensible = obj.__is_extensible__(context)?; // 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current). + let (slot, mc) = context.slot_and_mc(); Ok(validate_and_apply_property_descriptor( Some((obj, key)), extensible, desc, current, - context.slot(), + slot, + mc, )) } @@ -945,7 +953,7 @@ pub(crate) fn ordinary_delete( // 4. If desc.[[Configurable]] is true, then Some(desc) if desc.expect_configurable() => { // a. Remove the own property with name P from O. - obj.borrow_mut().remove(key); + obj.borrow_mut().remove(context.gc_collector(), key); // b. Return true. true } @@ -1004,7 +1012,16 @@ pub(crate) fn is_compatible_property_descriptor( current: Option, ) -> bool { // 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current). - validate_and_apply_property_descriptor(None, extensible, desc, current, &mut Slot::new()) + let mut dummy_slot = Slot::new(); + let dummy_mc = unsafe { boa_gc::MutationContext::global() }; + validate_and_apply_property_descriptor( + None, + extensible, + desc, + current, + &mut dummy_slot, + &dummy_mc, + ) } /// Abstract operation `ValidateAndApplyPropertyDescriptor` @@ -1019,6 +1036,7 @@ pub(crate) fn validate_and_apply_property_descriptor( desc: PropertyDescriptor, current: Option, slot: &mut Slot, + mc: &boa_gc::MutationContext<'static, '_>, ) -> bool { // 1. Assert: If O is not undefined, then IsPropertyKey(P) is true. @@ -1033,6 +1051,7 @@ pub(crate) fn validate_and_apply_property_descriptor( if let Some((obj, key)) = obj_and_key { obj.borrow_mut().properties.insert_with_slot( + mc, key, // c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then if desc.is_generic_descriptor() || desc.is_data_descriptor() { @@ -1153,7 +1172,7 @@ pub(crate) fn validate_and_apply_property_descriptor( current.fill_with(desc); obj.borrow_mut() .properties - .insert_with_slot(key, current, slot); + .insert_with_slot(mc, key, current, slot); slot.attributes |= SlotAttributes::FOUND; } diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs index 10cfd824459..5827278464d 100644 --- a/core/engine/src/object/jsobject.rs +++ b/core/engine/src/object/jsobject.rs @@ -112,12 +112,12 @@ impl JsObject { /// ``` #[inline] #[must_use] - pub fn default(intrinsics: &Intrinsics) -> Self { - Self::with_object_proto(intrinsics) + pub fn default(mc: &boa_gc::MutationContext<'static, '_>, intrinsics: &Intrinsics) -> Self { + Self::with_object_proto(mc, intrinsics) } /// Creates a new `JsObject` from its inner object and its vtable using the given context. - pub(crate) fn from_object_and_vtable_in( + pub(crate) fn from_object_and_vtable( mc: &boa_gc::MutationContext<'static, '_>, object: Object, vtable: &'static InternalObjectMethods, @@ -133,18 +133,6 @@ impl JsObject { JsObject { inner }.upcast() } - /// Creates a new `JsObject` from its inner object and its vtable. - pub(crate) fn from_object_and_vtable( - object: Object, - vtable: &'static InternalObjectMethods, - ) -> Self { - Self::from_object_and_vtable_in( - &unsafe { boa_gc::MutationContext::global() }, - object, - vtable, - ) - } - /// Creates a new ordinary object with its prototype set to the `Object` prototype. /// /// This is equivalent to calling the specification's abstract operation @@ -157,15 +145,19 @@ impl JsObject { /// ``` /// # use boa_engine::{Context, JsObject}; /// let context = &mut Context::default(); - /// let obj = JsObject::with_object_proto(context.intrinsics()); + /// let obj = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); /// /// assert!(obj.is_ordinary()); /// assert!(obj.prototype().is_some()); /// ``` #[inline] #[must_use] - pub fn with_object_proto(intrinsics: &Intrinsics) -> Self { + pub fn with_object_proto( + mc: &boa_gc::MutationContext<'static, '_>, + intrinsics: &Intrinsics, + ) -> Self { Self::from_proto_and_data( + mc, intrinsics.constructors().object().prototype(), OrdinaryObject, ) @@ -174,33 +166,12 @@ impl JsObject { /// Creates a new ordinary object, with its prototype set to null using the given context. #[inline] #[must_use] - pub fn with_null_proto_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { - Self::from_proto_and_data_in(mc, None, OrdinaryObject) - } - - /// Creates a new ordinary object, with its prototype set to null. - /// - /// This is equivalent to calling the specification's abstract operation - /// [`OrdinaryObjectCreate(null)`][call]. - /// - /// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate - /// - /// # Examples - /// - /// ``` - /// # use boa_engine::JsObject; - /// let obj = JsObject::with_null_proto(); - /// assert!(obj.prototype().is_none()); - /// assert!(obj.is_ordinary()); - /// ``` - #[inline] - #[must_use] - pub fn with_null_proto() -> Self { - Self::with_null_proto_in(&unsafe { boa_gc::MutationContext::global() }) + pub fn with_null_proto(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self::from_proto_and_data(mc, None, OrdinaryObject) } /// Creates a new object with the provided prototype and object data, using the given context. - pub fn from_proto_and_data_in>, T: NativeObject>( + pub fn from_proto_and_data>, T: NativeObject>( mc: &boa_gc::MutationContext<'static, '_>, prototype: O, data: T, @@ -211,7 +182,7 @@ impl JsObject { VTableObject { object: GcRefCell::new(Object { data: ObjectData::new(data), - properties: PropertyMap::from_prototype_unique_shape(prototype.into()), + properties: PropertyMap::from_prototype_unique_shape(mc, prototype.into()), extensible: true, private_elements: ThinVec::new(), }), @@ -222,48 +193,8 @@ impl JsObject { JsObject { inner }.upcast() } - /// Creates a new object with the provided prototype and object data. - /// - /// This is equivalent to calling the specification's abstract operation [`OrdinaryObjectCreate`], - /// with the difference that the `additionalInternalSlotsList` parameter is determined by - /// the provided `data`. - /// - /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate - /// - /// # Examples - /// - /// ``` - /// # use boa_engine::{Context, JsObject}; - /// # use boa_engine::builtins::object::OrdinaryObject; - /// let context = &mut Context::default(); - /// let obj = JsObject::from_proto_and_data( - /// context.intrinsics().constructors().object().prototype(), - /// OrdinaryObject, - /// ); - /// - /// assert!(obj.is_ordinary()); - /// assert!(obj.prototype().is_some()); - /// - /// // Create an object with no prototype. - /// let null_obj = JsObject::from_proto_and_data(None, OrdinaryObject); - /// assert!(null_obj.prototype().is_none()); - /// ``` - pub fn from_proto_and_data>, T: NativeObject>( - prototype: O, - data: T, - ) -> Self { - Self::from_proto_and_data_in( - &unsafe { boa_gc::MutationContext::global() }, - prototype, - data, - ) - } - /// Creates a new object with the provided prototype and object data using the given context. - pub(crate) fn from_proto_and_data_with_shared_shape_in< - O: Into>, - T: NativeObject, - >( + pub(crate) fn from_proto_and_data_with_shared_shape>, T: NativeObject>( mc: &boa_gc::MutationContext<'static, '_>, root_shape: &RootShape, prototype: O, @@ -276,6 +207,7 @@ impl JsObject { object: GcRefCell::new(Object { data: ObjectData::new(data), properties: PropertyMap::from_prototype_with_shared_shape( + mc, root_shape, prototype.into(), ), @@ -289,26 +221,6 @@ impl JsObject { JsObject { inner } } - /// Creates a new object with the provided prototype and object data. - /// - /// This is equivalent to calling the specification's abstract operation [`OrdinaryObjectCreate`], - /// with the difference that the `additionalInternalSlotsList` parameter is determined by - /// the provided `data`. - /// - /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate - pub(crate) fn from_proto_and_data_with_shared_shape>, T: NativeObject>( - root_shape: &RootShape, - prototype: O, - data: T, - ) -> JsObject { - Self::from_proto_and_data_with_shared_shape_in( - &unsafe { boa_gc::MutationContext::global() }, - root_shape, - prototype, - data, - ) - } - /// Downcasts the object's inner data if the object is of type `T`. /// /// # Panics @@ -323,14 +235,14 @@ impl JsObject { /// #[derive(Debug, Trace, Finalize, JsData)] /// struct CustomStruct; /// - /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject); + /// let obj = JsObject::from_proto_and_data(context.gc_collector(), None, OrdinaryObject); /// /// // Downcast consumes the object on success. /// let typed = obj.downcast::(); /// assert!(typed.is_ok()); /// /// // Downcast fails for a wrong type, returning the original object. - /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject); + /// let obj = JsObject::from_proto_and_data(context.gc_collector(), None, OrdinaryObject); /// let result = obj.downcast::(); /// assert!(result.is_err()); /// ``` @@ -376,7 +288,7 @@ impl JsObject { /// #[derive(Debug, Trace, Finalize, JsData)] /// struct CustomStruct; /// - /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject); + /// let obj = JsObject::from_proto_and_data(context.gc_collector(), None, OrdinaryObject); /// /// // Downcast ref succeeds for the correct type. /// assert!(obj.downcast_ref::().is_some()); @@ -413,7 +325,7 @@ impl JsObject { /// #[derive(Debug, Trace, Finalize, JsData)] /// struct CustomStruct; /// - /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject); + /// let obj = JsObject::from_proto_and_data(context.gc_collector(), None, OrdinaryObject); /// /// // Downcast mut succeeds for the correct type. /// assert!(obj.downcast_mut::().is_some()); @@ -449,7 +361,7 @@ impl JsObject { /// #[derive(Debug, Trace, Finalize, JsData)] /// struct CustomStruct; /// - /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject); + /// let obj = JsObject::from_proto_and_data(context.gc_collector(), None, OrdinaryObject); /// /// assert!(obj.is::()); /// assert!(!obj.is::()); @@ -472,7 +384,7 @@ impl JsObject { /// ``` /// # use boa_engine::{Context, JsObject}; /// let context = &mut Context::default(); - /// let obj = JsObject::with_object_proto(context.intrinsics()); + /// let obj = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); /// /// assert!(obj.is_ordinary()); /// ``` @@ -498,7 +410,7 @@ impl JsObject { /// assert!(JsObject::from(array).is_array()); /// /// // An ordinary object is not an array. - /// let obj = JsObject::with_object_proto(context.intrinsics()); + /// let obj = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); /// assert!(!obj.is_array()); /// # Ok(()) /// # } @@ -589,10 +501,10 @@ impl JsObject { /// # fn main() -> JsResult<()> { /// let context = &mut Context::default(); /// - /// let obj1 = JsObject::with_object_proto(context.intrinsics()); + /// let obj1 = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); /// obj1.set(js_string!("key"), 42, false, context)?; /// - /// let obj2 = JsObject::with_object_proto(context.intrinsics()); + /// let obj2 = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); /// obj2.set(js_string!("key"), 42, false, context)?; /// /// assert!(JsObject::deep_strict_equals(&obj1, &obj2, context)?); @@ -861,7 +773,7 @@ impl JsObject { /// ``` /// # use boa_engine::JsObject; /// # use boa_engine::builtins::object::OrdinaryObject; - /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject); + /// let obj = JsObject::from_proto_and_data(context.gc_collector(), None, OrdinaryObject); /// /// // Multiple immutable borrows are allowed. /// let borrowed = obj.borrow(); @@ -894,7 +806,7 @@ impl JsObject { /// ); /// /// // Set the prototype to `None` via a mutable borrow. - /// obj.borrow_mut().set_prototype(None); + /// obj.borrow_mut().set_prototype(context.gc_collector(), None); /// assert!(obj.prototype().is_none()); /// ``` #[inline] @@ -916,7 +828,7 @@ impl JsObject { /// ``` /// # use boa_engine::JsObject; /// # use boa_engine::builtins::object::OrdinaryObject; - /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject); + /// let obj = JsObject::from_proto_and_data(context.gc_collector(), None, OrdinaryObject); /// /// // Non-panicking immutable borrow. /// let result = obj.try_borrow(); @@ -939,7 +851,7 @@ impl JsObject { /// ``` /// # use boa_engine::JsObject; /// # use boa_engine::builtins::object::OrdinaryObject; - /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject); + /// let obj = JsObject::from_proto_and_data(context.gc_collector(), None, OrdinaryObject); /// /// // Non-panicking mutable borrow. /// let result = obj.try_borrow_mut(); @@ -960,14 +872,14 @@ impl JsObject { /// ``` /// # use boa_engine::JsObject; /// # use boa_engine::builtins::object::OrdinaryObject; - /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject); + /// let obj = JsObject::from_proto_and_data(context.gc_collector(), None, OrdinaryObject); /// let clone = obj.clone(); /// /// // A clone points to the same GC allocation. /// assert!(JsObject::equals(&obj, &clone)); /// /// // A separate object is different, even with identical data. - /// let other = JsObject::from_proto_and_data(None, OrdinaryObject); + /// let other = JsObject::from_proto_and_data(context.gc_collector(), None, OrdinaryObject); /// assert!(!JsObject::equals(&obj, &other)); /// ``` #[must_use] @@ -988,10 +900,10 @@ impl JsObject { /// # use boa_engine::{Context, JsObject}; /// let context = &mut Context::default(); /// - /// let obj = JsObject::with_object_proto(context.intrinsics()); + /// let obj = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); /// assert!(obj.prototype().is_some()); /// - /// let null_obj = JsObject::with_null_proto(); + /// let null_obj = JsObject::with_null_proto(context.gc_collector()); /// assert!(null_obj.prototype().is_none()); /// ``` #[inline] @@ -1021,41 +933,55 @@ impl JsObject { /// ``` /// # use boa_engine::{Context, JsObject}; /// let context = &mut Context::default(); - /// let obj = JsObject::with_object_proto(context.intrinsics()); + /// let obj = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); /// /// assert!(obj.prototype().is_some()); /// /// // Set the prototype to `None`. - /// obj.set_prototype(None); + /// obj.set_prototype(context.gc_collector(), None); /// assert!(obj.prototype().is_none()); /// ``` #[inline] #[track_caller] #[allow(clippy::must_use_candidate)] - pub fn set_prototype(&self, prototype: JsPrototype) -> bool { - self.borrow_mut().set_prototype(prototype) + pub fn set_prototype( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsPrototype, + ) -> bool { + self.borrow_mut().set_prototype(mc, prototype) } /// Helper function for property insertion. #[track_caller] - pub(crate) fn insert(&self, key: K, property: P) -> bool + pub(crate) fn insert( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: K, + property: P, + ) -> bool where K: Into, P: Into, { - self.borrow_mut().insert(key, property) + self.borrow_mut().insert(mc, key, property) } /// Inserts a field in the object `properties` without checking if it's writable. /// /// If a field was already in the object with the same name, than `true` is returned /// with that field, otherwise `false` is returned. - pub fn insert_property(&self, key: K, property: P) -> bool + pub fn insert_property( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: K, + property: P, + ) -> bool where K: Into, P: Into, { - self.insert(key.into(), property) + self.insert(mc, key.into(), property) } /// It determines if Object is a callable function with a `[[Call]]` internal method. @@ -1109,7 +1035,7 @@ impl JsObject { impl JsObject { /// Creates a new `JsObject` from a `RootShape`, prototype, and data using the given context. - pub fn new_in>>( + pub fn new>>( mc: &boa_gc::MutationContext<'static, '_>, root_shape: &RootShape, prototype: O, @@ -1122,6 +1048,7 @@ impl JsObject { object: GcRefCell::new(Object { data: ObjectData::new(data), properties: PropertyMap::from_prototype_with_shared_shape( + mc, root_shape, prototype.into(), ), @@ -1157,17 +1084,8 @@ impl JsObject { /// let obj = typed_obj.upcast(); /// assert!(obj.is_ordinary()); /// ``` - pub fn new>>(root_shape: &RootShape, prototype: O, data: T) -> Self { - Self::new_in( - &unsafe { boa_gc::MutationContext::global() }, - root_shape, - prototype, - data, - ) - } - /// Creates a new `JsObject` from prototype, and data using the given context. - pub fn new_unique_in>>( + pub fn new_unique>>( mc: &boa_gc::MutationContext<'static, '_>, prototype: O, data: T, @@ -1178,7 +1096,7 @@ impl JsObject { VTableObject { object: GcRefCell::new(Object { data: ObjectData::new(data), - properties: PropertyMap::from_prototype_unique_shape(prototype.into()), + properties: PropertyMap::from_prototype_unique_shape(mc, prototype.into()), extensible: true, private_elements: ThinVec::new(), }), @@ -1199,21 +1117,13 @@ impl JsObject { /// ``` /// # use boa_engine::JsObject; /// # use boa_engine::builtins::object::OrdinaryObject; - /// let typed_obj = JsObject::new_unique(None, OrdinaryObject); + /// let typed_obj = JsObject::new_unique(context.gc_collector(), None, OrdinaryObject); /// /// // Upcast to an erased JsObject. /// let obj = typed_obj.upcast(); /// assert!(obj.is_ordinary()); /// assert!(obj.prototype().is_none()); /// ``` - pub fn new_unique>>(prototype: O, data: T) -> Self { - Self::new_unique_in( - &unsafe { boa_gc::MutationContext::global() }, - prototype, - data, - ) - } - /// Upcasts this object's inner data from a specific type `T` to an erased type /// `dyn NativeObject`. /// @@ -1223,7 +1133,7 @@ impl JsObject { /// # use boa_engine::JsObject; /// # use boa_engine::builtins::object::OrdinaryObject; /// // Create a typed JsObject. - /// let typed_obj = JsObject::new_unique(None, OrdinaryObject); + /// let typed_obj = JsObject::new_unique(context.gc_collector(), None, OrdinaryObject); /// /// // Upcast erases the type, producing an untyped JsObject. /// let obj: JsObject = typed_obj.upcast(); diff --git a/core/engine/src/object/mod.rs b/core/engine/src/object/mod.rs index edd29e6e913..f1579dae33c 100644 --- a/core/engine/src/object/mod.rs +++ b/core/engine/src/object/mod.rs @@ -272,10 +272,17 @@ impl Object { /// /// [spec]: https://tc39.es/ecma262/#sec-invariants-of-the-essential-internal-methods #[track_caller] - pub fn set_prototype>(&mut self, prototype: O) -> bool { + pub fn set_prototype>( + &mut self, + mc: &boa_gc::MutationContext<'static, '_>, + prototype: O, + ) -> bool { let prototype = prototype.into(); if self.extensible { - self.properties.shape = self.properties.shape.change_prototype_transition(prototype); + self.properties.shape = self + .properties + .shape + .change_prototype_transition(mc, prototype); true } else { // If target is non-extensible, [[SetPrototypeOf]] must return false @@ -300,20 +307,29 @@ impl Object { /// /// If a field was already in the object with the same name, then `true` is returned /// otherwise, `false` is returned. - pub(crate) fn insert(&mut self, key: K, property: P) -> bool + pub(crate) fn insert( + &mut self, + mc: &boa_gc::MutationContext<'static, '_>, + key: K, + property: P, + ) -> bool where K: Into, P: Into, { - self.properties.insert(&key.into(), property.into()) + self.properties.insert(mc, &key.into(), property.into()) } /// Helper function for property removal without checking if it's configurable. /// /// Returns `true` if the property was removed, `false` otherwise. #[inline] - pub(crate) fn remove(&mut self, key: &PropertyKey) -> bool { - self.properties.remove(key) + pub(crate) fn remove( + &mut self, + mc: &boa_gc::MutationContext<'static, '_>, + key: &PropertyKey, + ) -> bool { + self.properties.remove(mc, key) } /// Append a private element to an object. @@ -394,9 +410,9 @@ where } /// Builder for creating native function objects -#[derive(Debug)] pub struct FunctionObjectBuilder<'realm> { realm: &'realm Realm, + mc: &'realm boa_gc::MutationContext<'static, 'realm>, function: NativeFunction, constructor: Option, name: JsString, @@ -407,9 +423,14 @@ impl<'realm> FunctionObjectBuilder<'realm> { /// Create a new `FunctionBuilder` for creating a native function. #[inline] #[must_use] - pub fn new(realm: &'realm Realm, function: NativeFunction) -> Self { + pub fn new( + realm: &'realm Realm, + mc: &'realm boa_gc::MutationContext<'static, 'realm>, + function: NativeFunction, + ) -> Self { Self { realm, + mc, function, constructor: None, name: js_string!(), @@ -454,6 +475,7 @@ impl<'realm> FunctionObjectBuilder<'realm> { #[must_use] pub fn build(self) -> JsFunction { let object = self.realm.intrinsics().templates().function().create( + self.mc, NativeFunctionObject { f: self.function, name: self.name.clone(), @@ -510,13 +532,14 @@ impl<'ctx> ObjectInitializer<'ctx> { /// Create a new `ObjectBuilder`. #[inline] pub fn new(context: &'ctx mut Context) -> Self { - let object = JsObject::with_object_proto(context.intrinsics()); + let object = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); Self { context, object } } /// Create a new `ObjectBuilder` with custom [`NativeObject`] data. pub fn with_native_data(data: T, context: &'ctx mut Context) -> Self { let object = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), context.intrinsics().constructors().object().prototype(), data, @@ -531,9 +554,13 @@ impl<'ctx> ObjectInitializer<'ctx> { proto: JsObject, context: &'ctx mut Context, ) -> Self { - let object = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, data) - .upcast(); + let object = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), + context.root_shape(), + proto, + data, + ) + .upcast(); Self { context, object } } @@ -543,19 +570,22 @@ impl<'ctx> ObjectInitializer<'ctx> { B: Into, { let binding = binding.into(); - let function = FunctionObjectBuilder::new(self.context.realm(), function) - .name(binding.name) - .length(length) - .constructor(false) - .build(); + let function = + FunctionObjectBuilder::new(self.context.realm(), self.context.gc_collector(), function) + .name(binding.name) + .length(length) + .constructor(false) + .build(); self.object.borrow_mut().insert( + self.context.gc_collector(), binding.binding, PropertyDescriptor::builder() .value(function) .writable(true) .enumerable(false) - .configurable(true), + .configurable(true) + .build(), ); self } @@ -570,8 +600,11 @@ impl<'ctx> ObjectInitializer<'ctx> { .value(value) .writable(attribute.writable()) .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()); - self.object.borrow_mut().insert(key, property); + .configurable(attribute.configurable()) + .build(); + self.object + .borrow_mut() + .insert(self.context.gc_collector(), key, property); self } @@ -597,8 +630,11 @@ impl<'ctx> ObjectInitializer<'ctx> { .maybe_get(get) .maybe_set(set) .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()); - self.object.borrow_mut().insert(key, property); + .configurable(attribute.configurable()) + .build(); + self.object + .borrow_mut() + .insert(self.context.gc_collector(), key, property); self } @@ -666,19 +702,22 @@ impl<'ctx> ConstructorBuilder<'ctx> { B: Into, { let binding = binding.into(); - let function = FunctionObjectBuilder::new(self.context.realm(), function) - .name(binding.name) - .length(length) - .constructor(false) - .build(); + let function = + FunctionObjectBuilder::new(self.context.realm(), self.context.gc_collector(), function) + .name(binding.name) + .length(length) + .constructor(false) + .build(); self.prototype.insert( + self.context.gc_collector(), binding.binding, PropertyDescriptor::builder() .value(function) .writable(true) .enumerable(false) - .configurable(true), + .configurable(true) + .build(), ); self } @@ -694,19 +733,22 @@ impl<'ctx> ConstructorBuilder<'ctx> { B: Into, { let binding = binding.into(); - let function = FunctionObjectBuilder::new(self.context.realm(), function) - .name(binding.name) - .length(length) - .constructor(false) - .build(); + let function = + FunctionObjectBuilder::new(self.context.realm(), self.context.gc_collector(), function) + .name(binding.name) + .length(length) + .constructor(false) + .build(); self.constructor_object.insert( + self.context.gc_collector(), binding.binding, PropertyDescriptor::builder() .value(function) .writable(true) .enumerable(false) - .configurable(true), + .configurable(true) + .build(), ); self } @@ -721,8 +763,10 @@ impl<'ctx> ConstructorBuilder<'ctx> { .value(value) .writable(attribute.writable()) .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()); - self.prototype.insert(key, property); + .configurable(attribute.configurable()) + .build(); + self.prototype + .insert(self.context.gc_collector(), key, property); self } @@ -736,8 +780,10 @@ impl<'ctx> ConstructorBuilder<'ctx> { .value(value) .writable(attribute.writable()) .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()); - self.constructor_object.insert(key, property); + .configurable(attribute.configurable()) + .build(); + self.constructor_object + .insert(self.context.gc_collector(), key, property); self } @@ -756,8 +802,10 @@ impl<'ctx> ConstructorBuilder<'ctx> { .maybe_get(get) .maybe_set(set) .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()); - self.prototype.insert(key, property); + .configurable(attribute.configurable()) + .build(); + self.prototype + .insert(self.context.gc_collector(), key, property); self } @@ -776,8 +824,10 @@ impl<'ctx> ConstructorBuilder<'ctx> { .maybe_get(get) .maybe_set(set) .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()); - self.constructor_object.insert(key, property); + .configurable(attribute.configurable()) + .build(); + self.constructor_object + .insert(self.context.gc_collector(), key, property); self } @@ -788,7 +838,8 @@ impl<'ctx> ConstructorBuilder<'ctx> { P: Into, { let property = property.into(); - self.prototype.insert(key, property); + self.prototype + .insert(self.context.gc_collector(), key, property); self } @@ -799,7 +850,8 @@ impl<'ctx> ConstructorBuilder<'ctx> { P: Into, { let property = property.into(); - self.constructor_object.insert(key, property); + self.constructor_object + .insert(self.context.gc_collector(), key, property); self } @@ -880,18 +932,22 @@ impl<'ctx> ConstructorBuilder<'ctx> { .value(self.length) .writable(false) .enumerable(false) - .configurable(true); + .configurable(true) + .build(); let name = PropertyDescriptor::builder() .value(self.name.clone()) .writable(false) .enumerable(false) - .configurable(true); + .configurable(true) + .build(); let prototype = { if let Some(proto) = self.inherit.take() { - self.prototype.set_prototype(proto); + self.prototype + .set_prototype(self.context.gc_collector(), proto); } else { self.prototype.set_prototype( + self.context.gc_collector(), self.context .intrinsics() .constructors() @@ -900,7 +956,11 @@ impl<'ctx> ConstructorBuilder<'ctx> { ); } - JsObject::from_object_and_vtable(self.prototype, &ORDINARY_INTERNAL_METHODS) + JsObject::from_object_and_vtable( + self.context.gc_collector(), + self.prototype, + &ORDINARY_INTERNAL_METHODS, + ) }; let constructor = { @@ -918,13 +978,14 @@ impl<'ctx> ConstructorBuilder<'ctx> { data: ObjectData::new(data), }; - constructor.insert(StaticJsStrings::LENGTH, length); - constructor.insert(js_string!("name"), name); + constructor.insert(self.context.gc_collector(), StaticJsStrings::LENGTH, length); + constructor.insert(self.context.gc_collector(), js_string!("name"), name); if let Some(proto) = self.custom_prototype.take() { - constructor.set_prototype(proto); + constructor.set_prototype(self.context.gc_collector(), proto); } else { constructor.set_prototype( + self.context.gc_collector(), self.context .intrinsics() .constructors() @@ -935,27 +996,35 @@ impl<'ctx> ConstructorBuilder<'ctx> { if self.has_prototype_property { constructor.insert( + self.context.gc_collector(), PROTOTYPE, PropertyDescriptor::builder() .value(prototype.clone()) .writable(false) .enumerable(false) - .configurable(false), + .configurable(false) + .build(), ); } - JsObject::from_object_and_vtable(constructor, internal_methods) + JsObject::from_object_and_vtable( + self.context.gc_collector(), + constructor, + internal_methods, + ) }; { let mut prototype = prototype.borrow_mut(); prototype.insert( + self.context.gc_collector(), CONSTRUCTOR, PropertyDescriptor::builder() .value(constructor.clone()) .writable(true) .enumerable(false) - .configurable(true), + .configurable(true) + .build(), ); } diff --git a/core/engine/src/object/property_map.rs b/core/engine/src/object/property_map.rs index 80cf4408272..983f5b5622f 100644 --- a/core/engine/src/object/property_map.rs +++ b/core/engine/src/object/property_map.rs @@ -506,10 +506,13 @@ impl PropertyMap { /// Construct a [`PropertyMap`] with the given prototype with a unique [`Shape`]. #[must_use] #[inline] - pub fn from_prototype_unique_shape(prototype: JsPrototype) -> Self { + pub fn from_prototype_unique_shape( + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsPrototype, + ) -> Self { Self { indexed_properties: IndexedProperties::default(), - shape: UniqueShape::new(prototype, PropertyTableInner::default()).into(), + shape: UniqueShape::new(mc, prototype, PropertyTableInner::default()).into(), storage: Vec::default(), } } @@ -518,10 +521,13 @@ impl PropertyMap { #[must_use] #[inline] pub fn from_prototype_with_shared_shape( + mc: &boa_gc::MutationContext<'static, '_>, root_shape: &RootShape, prototype: JsPrototype, ) -> Self { - let shape = root_shape.shape().change_prototype_transition(prototype); + let shape = root_shape + .shape() + .change_prototype_transition(mc, prototype); Self { indexed_properties: IndexedProperties::default(), shape: shape.into(), @@ -587,14 +593,21 @@ impl PropertyMap { } /// Insert the given property descriptor with the given key [`PropertyMap`]. - pub fn insert(&mut self, key: &PropertyKey, property: PropertyDescriptor) -> bool { + + pub fn insert( + &mut self, + mc: &boa_gc::MutationContext<'static, '_>, + key: &PropertyKey, + property: PropertyDescriptor, + ) -> bool { let mut dummy_slot = Slot::new(); - self.insert_with_slot(key, property, &mut dummy_slot) + self.insert_with_slot(mc, key, property, &mut dummy_slot) } /// Insert the given property descriptor with the given key [`PropertyMap`]. pub(crate) fn insert_with_slot( &mut self, + mc: &boa_gc::MutationContext<'static, '_>, key: &PropertyKey, property: PropertyDescriptor, out_slot: &mut Slot, @@ -613,7 +626,7 @@ impl PropertyMap { property_key: key.clone(), attributes, }; - let transition = self.shape.change_attributes_transition(key); + let transition = self.shape.change_attributes_transition(mc, key); self.shape = transition.shape; match transition.action { ChangeTransitionAction::Nothing => {} @@ -655,7 +668,7 @@ impl PropertyMap { property_key: key.clone(), attributes, }; - self.shape = self.shape.insert_property_transition(transition_key); + self.shape = self.shape.insert_property_transition(mc, transition_key); // Make Sure that if we are inserting, it has the correct slot index. debug_assert_eq!( @@ -694,7 +707,7 @@ impl PropertyMap { } /// Remove the property with the given key from the [`PropertyMap`]. - pub fn remove(&mut self, key: &PropertyKey) -> bool { + pub fn remove(&mut self, mc: &boa_gc::MutationContext<'static, '_>, key: &PropertyKey) -> bool { if let PropertyKey::Index(index) = key { return self.indexed_properties.remove(index.get()); } @@ -705,7 +718,7 @@ impl PropertyMap { } self.storage.remove(slot.index as usize); - self.shape = self.shape.remove_property_transition(key); + self.shape = self.shape.remove_property_transition(mc, key); return true; } diff --git a/core/engine/src/object/shape/mod.rs b/core/engine/src/object/shape/mod.rs index e4c5889093d..1720d725e29 100644 --- a/core/engine/src/object/shape/mod.rs +++ b/core/engine/src/object/shape/mod.rs @@ -106,16 +106,16 @@ impl Shape { /// Create an insert property transitions returning the new transitioned [`Shape`] using the given context. /// /// NOTE: This assumes that there is no property with the given key! - pub(crate) fn insert_property_transition_in( + pub(crate) fn insert_property_transition( &self, mc: &boa_gc::MutationContext<'static, '_>, key: TransitionKey, ) -> Self { match &self.inner { Inner::Shared(shape) => { - let shape = shape.insert_property_transition_in(mc, key); + let shape = shape.insert_property_transition(mc, key); if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { - return shape.to_unique().into(); + return shape.to_unique(mc).into(); } shape.into() } @@ -126,25 +126,22 @@ impl Shape { /// Create an insert property transitions returning the new transitioned [`Shape`]. /// /// NOTE: This assumes that there is no property with the given key! - pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { - self.insert_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) - } /// Create a change attribute property transitions returning [`ChangeTransition`] containing the new [`Shape`] /// and actions to be performed, using the given context. /// /// NOTE: This assumes that there already is a property with the given key! - pub(crate) fn change_attributes_transition_in( + pub(crate) fn change_attributes_transition( &self, mc: &boa_gc::MutationContext<'static, '_>, key: TransitionKey, ) -> ChangeTransition { match &self.inner { Inner::Shared(shape) => { - let change_transition = shape.change_attributes_transition_in(mc, key); + let change_transition = shape.change_attributes_transition(mc, key); let shape = if change_transition.shape.transition_count() >= Self::TRANSITION_COUNT_MAX { - change_transition.shape.to_unique().into() + change_transition.shape.to_unique(mc).into() } else { change_transition.shape.into() }; @@ -153,7 +150,7 @@ impl Shape { action: change_transition.action, } } - Inner::Unique(shape) => shape.change_attributes_transition(&key), + Inner::Unique(shape) => shape.change_attributes_transition(mc, &key), } } @@ -161,65 +158,50 @@ impl Shape { /// and actions to be performed /// /// NOTE: This assumes that there already is a property with the given key! - pub(crate) fn change_attributes_transition( - &self, - key: TransitionKey, - ) -> ChangeTransition { - self.change_attributes_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) - } /// Remove a property from the [`Shape`] returning the new transitioned [`Shape`] using the given context. /// /// NOTE: This assumes that there already is a property with the given key! - pub(crate) fn remove_property_transition_in( + pub(crate) fn remove_property_transition( &self, mc: &boa_gc::MutationContext<'static, '_>, key: &PropertyKey, ) -> Self { match &self.inner { Inner::Shared(shape) => { - let shape = shape.remove_property_transition_in(mc, key); + let shape = shape.remove_property_transition(mc, key); if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { - return shape.to_unique().into(); + return shape.to_unique(mc).into(); } shape.into() } - Inner::Unique(shape) => shape.remove_property_transition(key).into(), + Inner::Unique(shape) => shape.remove_property_transition(mc, key).into(), } } /// Remove a property from the [`Shape`] returning the new transitioned [`Shape`]. /// /// NOTE: This assumes that there already is a property with the given key! - pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { - self.remove_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) - } /// Create a prototype transition returning the new transitioned [`Shape`] using the given context. - pub(crate) fn change_prototype_transition_in( + pub(crate) fn change_prototype_transition( &self, mc: &boa_gc::MutationContext<'static, '_>, prototype: JsPrototype, ) -> Self { match &self.inner { Inner::Shared(shape) => { - let shape = shape.change_prototype_transition_in(mc, prototype); + let shape = shape.change_prototype_transition(mc, prototype); if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { - return shape.to_unique().into(); + return shape.to_unique(mc).into(); } shape.into() } - Inner::Unique(shape) => shape.change_prototype_transition(prototype).into(), + Inner::Unique(shape) => shape.change_prototype_transition(mc, prototype).into(), } } /// Create a prototype transition returning the new transitioned [`Shape`]. - pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { - self.change_prototype_transition_in( - &unsafe { boa_gc::MutationContext::global() }, - prototype, - ) - } /// Get the [`JsPrototype`] of the [`Shape`]. #[must_use] @@ -290,7 +272,7 @@ impl WeakShape { #[inline] #[must_use] pub(crate) fn to_addr_usize(&self) -> usize { - self.upgrade().as_ref().map_or(0, Shape::to_addr_usize) + 0 // Cannot get address of WeakShape without upgrading it, which requires MutationContext } /// Return location in memory of the [`Shape`]. @@ -298,19 +280,19 @@ impl WeakShape { /// Returns `0` if the shape has been freed. #[inline] #[must_use] - pub(crate) fn upgrade(&self) -> Option { + pub(crate) fn upgrade(&self, mc: &boa_gc::MutationContext<'static, '_>) -> Option { match self { - WeakShape::Shared(shape) => Some(shape.upgrade()?.into()), - WeakShape::Unique(shape) => Some(shape.upgrade()?.into()), + WeakShape::Shared(shape) => Some(shape.upgrade(mc)?.into()), + WeakShape::Unique(shape) => Some(shape.upgrade(mc)?.into()), } } } -impl From<&Shape> for WeakShape { - fn from(value: &Shape) -> Self { +impl WeakShape { + pub(crate) fn new(mc: &boa_gc::MutationContext<'static, '_>, value: &Shape) -> Self { match &value.inner { - Inner::Shared(shape) => WeakShape::Shared(shape.into()), - Inner::Unique(shape) => WeakShape::Unique(shape.into()), + Inner::Shared(shape) => WeakShape::Shared(WeakSharedShape::new(mc, shape)), + Inner::Unique(shape) => WeakShape::Unique(WeakUniqueShape::new(mc, shape)), } } } diff --git a/core/engine/src/object/shape/root_shape.rs b/core/engine/src/object/shape/root_shape.rs index 278bddc6935..fb26c29b7cf 100644 --- a/core/engine/src/object/shape/root_shape.rs +++ b/core/engine/src/object/shape/root_shape.rs @@ -10,19 +10,12 @@ pub struct RootShape { shape: SharedShape, } -impl Default for RootShape { - #[inline] - fn default() -> Self { - Self::new_in(&unsafe { boa_gc::MutationContext::global() }) - } -} - impl RootShape { /// Create a new root shape using the given context. #[inline] - pub(crate) fn new_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + pub fn new(mc: &boa_gc::MutationContext<'static, '_>) -> Self { Self { - shape: SharedShape::root_in(mc), + shape: SharedShape::root(mc), } } /// Gets the inner [`SharedShape`]. diff --git a/core/engine/src/object/shape/shared_shape/forward_transition.rs b/core/engine/src/object/shape/shared_shape/forward_transition.rs index 5846c7916b9..ada106bb71a 100644 --- a/core/engine/src/object/shape/shared_shape/forward_transition.rs +++ b/core/engine/src/object/shape/shared_shape/forward_transition.rs @@ -56,7 +56,7 @@ pub(super) struct ForwardTransition { impl ForwardTransition { /// Insert a property transition using the given context. - pub(super) fn insert_property_in( + pub(super) fn insert_property( &self, mc: &boa_gc::MutationContext<'static, '_>, key: TransitionKey, @@ -73,16 +73,9 @@ impl ForwardTransition { } /// Insert a property transition. - pub(super) fn insert_property( - &self, - key: TransitionKey, - value: &Gc<'static, SharedShapeInner>, - ) { - self.insert_property_in(&unsafe { boa_gc::MutationContext::global() }, key, value) - } /// Insert a prototype transition using the given context. - pub(super) fn insert_prototype_in( + pub(super) fn insert_prototype( &self, mc: &boa_gc::MutationContext<'static, '_>, key: JsPrototype, @@ -99,9 +92,6 @@ impl ForwardTransition { } /// Insert a prototype transition. - pub(super) fn insert_prototype(&self, key: JsPrototype, value: &Gc<'static, SharedShapeInner>) { - self.insert_prototype_in(&unsafe { boa_gc::MutationContext::global() }, key, value) - } /// Get a property transition, return [`None`] otherwise. #[allow(clippy::cloned_instead_of_copied)] diff --git a/core/engine/src/object/shape/shared_shape/mod.rs b/core/engine/src/object/shape/shared_shape/mod.rs index 3448ac5455a..2f99dd5d587 100644 --- a/core/engine/src/object/shape/shared_shape/mod.rs +++ b/core/engine/src/object/shape/shared_shape/mod.rs @@ -164,21 +164,18 @@ impl SharedShape { } /// Create a new [`SharedShape`] using the given context. - fn new_in(mc: &boa_gc::MutationContext<'static, '_>, inner: Inner) -> Self { + fn new(mc: &boa_gc::MutationContext<'static, '_>, inner: Inner) -> Self { Self { inner: Gc::new(mc, inner), } } /// Create a new [`SharedShape`]. - fn new(inner: Inner) -> Self { - Self::new_in(&unsafe { boa_gc::MutationContext::global() }, inner) - } /// Create a root [`SharedShape`] using the given context. #[must_use] - pub(crate) fn root_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { - Self::new_in( + pub(crate) fn root(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self::new( mc, Inner { forward_transitions: ForwardTransition::default(), @@ -194,13 +191,9 @@ impl SharedShape { } /// Create a root [`SharedShape`]. - #[must_use] - pub(crate) fn root() -> Self { - Self::root_in(&unsafe { boa_gc::MutationContext::global() }) - } /// Create a [`SharedShape`] change prototype transition using the given context. - pub(crate) fn change_prototype_transition_in( + pub(crate) fn change_prototype_transition( &self, mc: &boa_gc::MutationContext<'static, '_>, prototype: JsPrototype, @@ -221,24 +214,18 @@ impl SharedShape { transition_count: self.transition_count() + 1, flags: ShapeFlags::prototype_transition_from(self.flags()), }; - let new_shape = Self::new_in(mc, new_inner_shape); + let new_shape = Self::new(mc, new_inner_shape); self.forward_transitions() - .insert_prototype(prototype, &new_shape.inner); + .insert_prototype(mc, prototype, &new_shape.inner); new_shape } /// Create a [`SharedShape`] change prototype transition. - pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { - self.change_prototype_transition_in( - &unsafe { boa_gc::MutationContext::global() }, - prototype, - ) - } /// Create a [`SharedShape`] insert property transition using the given context. - pub(crate) fn insert_property_transition_in( + pub(crate) fn insert_property_transition( &self, mc: &boa_gc::MutationContext<'static, '_>, key: TransitionKey, @@ -266,29 +253,20 @@ impl SharedShape { transition_count: self.transition_count() + 1, flags: ShapeFlags::insert_property_transition_from(self.flags()), }; - let new_shape = Self::new_in(mc, new_inner_shape); + let new_shape = Self::new(mc, new_inner_shape); self.forward_transitions() - .insert_property(key, &new_shape.inner); + .insert_property(mc, key, &new_shape.inner); new_shape } /// Create a [`SharedShape`] insert property transition. - pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { - self.insert_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) - } /// Create a [`SharedShape`] change prototype transition, returning [`ChangeTransition`]. - pub(crate) fn change_attributes_transition( - &self, - key: TransitionKey, - ) -> ChangeTransition { - self.change_attributes_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) - } /// Create a [`SharedShape`] change prototype transition using the given context, returning [`ChangeTransition`]. - pub(crate) fn change_attributes_transition_in( + pub(crate) fn change_attributes_transition( &self, mc: &boa_gc::MutationContext<'static, '_>, key: TransitionKey, @@ -330,10 +308,10 @@ impl SharedShape { transition_count: self.transition_count() + 1, flags: ShapeFlags::configure_property_transition_from(self.flags()), }; - let shape = Self::new(inner_shape); + let shape = Self::new(mc, inner_shape); self.forward_transitions() - .insert_property(key, &shape.inner); + .insert_property(mc, key, &shape.inner); return ChangeTransition { shape, @@ -346,11 +324,11 @@ impl SharedShape { // Apply prototype transition, if it was found. if let Some(prototype) = prototype { - base = base.change_prototype_transition(prototype); + base = base.change_prototype_transition(mc, prototype); } // Apply this property. - base = base.insert_property_transition(key); + base = base.insert_property_transition(mc, key); // Apply previous properties. for (property_key, attributes) in transitions.into_iter().rev() { @@ -358,7 +336,7 @@ impl SharedShape { property_key, attributes, }; - base = base.insert_property_transition(transition); + base = base.insert_property_transition(mc, transition); } // Determine action to be performed on the storage. @@ -457,7 +435,7 @@ impl SharedShape { } /// Remove a property from [`SharedShape`], returning the new [`SharedShape`] using the given context. - pub(crate) fn remove_property_transition_in( + pub(crate) fn remove_property_transition( &self, mc: &boa_gc::MutationContext<'static, '_>, key: &PropertyKey, @@ -466,7 +444,7 @@ impl SharedShape { // Apply prototype transition, if it was found. if let Some(prototype) = prototype { - base = base.change_prototype_transition_in(mc, prototype); + base = base.change_prototype_transition(mc, prototype); } for (property_key, attributes) in transitions.into_iter().rev() { @@ -474,16 +452,13 @@ impl SharedShape { property_key, attributes, }; - base = base.insert_property_transition_in(mc, transition); + base = base.insert_property_transition(mc, transition); } base } /// Remove a property from [`SharedShape`], returning the new [`SharedShape`]. - pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { - self.remove_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) - } /// Do a property lookup, returns [`None`] if property not found. pub(crate) fn lookup(&self, key: &PropertyKey) -> Option { @@ -509,8 +484,9 @@ impl SharedShape { } /// Returns a new [`UniqueShape`] with the properties of the [`SharedShape`]. - pub(crate) fn to_unique(&self) -> UniqueShape { + pub(crate) fn to_unique(&self, mc: &boa_gc::MutationContext<'static, '_>) -> UniqueShape { UniqueShape::new( + mc, self.prototype(), self.property_table() .inner() @@ -537,10 +513,7 @@ impl WeakSharedShape { /// or [`None`] if the value was already garbage collected, using the given context. #[inline] #[must_use] - pub(crate) fn upgrade_in( - &self, - mc: &boa_gc::MutationContext<'static, '_>, - ) -> Option { + pub(crate) fn upgrade(&self, mc: &boa_gc::MutationContext<'static, '_>) -> Option { Some(SharedShape { inner: self.inner.upgrade(mc)?, }) @@ -548,25 +521,14 @@ impl WeakSharedShape { /// Upgrade returns a [`SharedShape`] pointer for the internal value if the pointer is still live, /// or [`None`] if the value was already garbage collected. - #[inline] - #[must_use] - pub(crate) fn upgrade(&self) -> Option { - self.upgrade_in(&unsafe { boa_gc::MutationContext::global() }) - } #[allow(dead_code)] pub(crate) fn is_upgradable(&self) -> bool { self.inner.is_upgradable() } - pub(crate) fn new_in(mc: &boa_gc::MutationContext<'static, '_>, value: &SharedShape) -> Self { + pub(crate) fn new(mc: &boa_gc::MutationContext<'static, '_>, value: &SharedShape) -> Self { WeakSharedShape { inner: WeakGc::new(mc, &value.inner), } } } - -impl From<&SharedShape> for WeakSharedShape { - fn from(value: &SharedShape) -> Self { - Self::new_in(&unsafe { boa_gc::MutationContext::global() }, value) - } -} diff --git a/core/engine/src/object/shape/shared_shape/template.rs b/core/engine/src/object/shape/shared_shape/template.rs index 0359b79153a..9adb3b28f63 100644 --- a/core/engine/src/object/shape/shared_shape/template.rs +++ b/core/engine/src/object/shape/shared_shape/template.rs @@ -28,23 +28,16 @@ impl ObjectTemplate { } /// Create and [`ObjectTemplate`] with a prototype using the given context. - pub(crate) fn with_prototype_in( + pub(crate) fn with_prototype( mc: &boa_gc::MutationContext<'static, '_>, shape: &SharedShape, prototype: JsObject, ) -> Self { - let shape = shape.change_prototype_transition_in(mc, Some(prototype)); + let shape = shape.change_prototype_transition(mc, Some(prototype)); Self { shape } } /// Create and [`ObjectTemplate`] with a prototype. - pub(crate) fn with_prototype(shape: &SharedShape, prototype: JsObject) -> Self { - Self::with_prototype_in( - &unsafe { boa_gc::MutationContext::global() }, - shape, - prototype, - ) - } /// Check if the shape has a specific, prototype. pub(crate) fn has_prototype(&self, prototype: &JsObject) -> bool { @@ -54,23 +47,18 @@ impl ObjectTemplate { /// Set the prototype of the [`ObjectTemplate`] using the given context. /// /// This assumes that the prototype has not been set yet. - pub(crate) fn set_prototype_in( + pub(crate) fn set_prototype( &mut self, mc: &boa_gc::MutationContext<'static, '_>, prototype: JsObject, ) -> &mut Self { - self.shape = self - .shape - .change_prototype_transition_in(mc, Some(prototype)); + self.shape = self.shape.change_prototype_transition(mc, Some(prototype)); self } /// Set the prototype of the [`ObjectTemplate`]. /// /// This assumes that the prototype has not been set yet. - pub(crate) fn set_prototype(&mut self, prototype: JsObject) -> &mut Self { - self.set_prototype_in(&unsafe { boa_gc::MutationContext::global() }, prototype) - } /// Returns the inner shape of the [`ObjectTemplate`]. pub(crate) const fn shape(&self) -> &SharedShape { @@ -81,7 +69,7 @@ impl ObjectTemplate { /// /// This assumes that the property with the given key was not previously set /// and that it's a string or symbol. - pub(crate) fn property_in( + pub(crate) fn property( &mut self, mc: &boa_gc::MutationContext<'static, '_>, key: PropertyKey, @@ -93,7 +81,7 @@ impl ObjectTemplate { property_key: key, attributes: SlotAttributes::from_bits_truncate(attributes.bits()), }; - self.shape = self.shape.insert_property_transition_in(mc, transition); + self.shape = self.shape.insert_property_transition(mc, transition); self } @@ -101,20 +89,13 @@ impl ObjectTemplate { /// /// This assumes that the property with the given key was not previously set /// and that it's a string or symbol. - pub(crate) fn property(&mut self, key: PropertyKey, attributes: Attribute) -> &mut Self { - self.property_in( - &unsafe { boa_gc::MutationContext::global() }, - key, - attributes, - ) - } /// Add a accessor property to the [`ObjectTemplate`]. /// /// This assumes that the property with the given key was not previously set /// and that it's a string or symbol. /// Add a accessor property to the [`ObjectTemplate`] using the given context. - pub(crate) fn accessor_in( + pub(crate) fn accessor( &mut self, mc: &boa_gc::MutationContext<'static, '_>, key: PropertyKey, @@ -141,7 +122,7 @@ impl ObjectTemplate { result }; - self.shape = self.shape.insert_property_transition_in( + self.shape = self.shape.insert_property_transition( mc, TransitionKey { property_key: key, @@ -155,24 +136,9 @@ impl ObjectTemplate { /// /// This assumes that the property with the given key was not previously set /// and that it's a string or symbol. - pub(crate) fn accessor( - &mut self, - key: PropertyKey, - get: bool, - set: bool, - attributes: Attribute, - ) -> &mut Self { - self.accessor_in( - &unsafe { boa_gc::MutationContext::global() }, - key, - get, - set, - attributes, - ) - } /// Create an object from the [`ObjectTemplate`] using the given context. - pub(crate) fn create_in( + pub(crate) fn create( &self, mc: &boa_gc::MutationContext<'static, '_>, data: T, @@ -193,15 +159,12 @@ impl ObjectTemplate { private_elements: ThinVec::new(), }; - JsObject::from_object_and_vtable_in(mc, object, internal_methods) + JsObject::from_object_and_vtable(mc, object, internal_methods) } /// Create an object from the [`ObjectTemplate`] /// /// The storage must match the properties provided. - pub(crate) fn create(&self, data: T, storage: Vec) -> JsObject { - self.create_in(&unsafe { boa_gc::MutationContext::global() }, data, storage) - } /// Create an object from the [`ObjectTemplate`] /// @@ -209,6 +172,7 @@ impl ObjectTemplate { /// the indexed properties. pub(crate) fn create_with_indexed_properties( &self, + mc: &boa_gc::MutationContext<'static, '_>, data: T, storage: Vec, indexed_properties: IndexedProperties, @@ -223,6 +187,6 @@ impl ObjectTemplate { object.properties.storage = storage; - JsObject::from_object_and_vtable(object, internal_methods) + JsObject::from_object_and_vtable(mc, object, internal_methods) } } diff --git a/core/engine/src/object/shape/shared_shape/tests.rs b/core/engine/src/object/shape/shared_shape/tests.rs index cef2ccd3f0c..d1bbca9f229 100644 --- a/core/engine/src/object/shape/shared_shape/tests.rs +++ b/core/engine/src/object/shape/shared_shape/tests.rs @@ -4,7 +4,7 @@ use super::{SharedShape, TransitionKey}; #[test] fn test_prune_property_on_counter_limit() { - let shape = SharedShape::root(); + let shape = SharedShape::root(&unsafe { boa_gc::MutationContext::global() }); for i in 0..255 { assert_eq!( @@ -12,10 +12,13 @@ fn test_prune_property_on_counter_limit() { (i, i as u8) ); - shape.insert_property_transition(TransitionKey { - property_key: PropertyKey::Symbol(JsSymbol::new(None).unwrap()), - attributes: SlotAttributes::all(), - }); + shape.insert_property_transition( + &unsafe { boa_gc::MutationContext::global() }, + TransitionKey { + property_key: PropertyKey::Symbol(JsSymbol::new(None).unwrap()), + attributes: SlotAttributes::all(), + }, + ); } assert_eq!( @@ -26,10 +29,13 @@ fn test_prune_property_on_counter_limit() { boa_gc::force_collect(); { - shape.insert_property_transition(TransitionKey { - property_key: PropertyKey::Symbol(JsSymbol::new(None).unwrap()), - attributes: SlotAttributes::all(), - }); + shape.insert_property_transition( + &unsafe { boa_gc::MutationContext::global() }, + TransitionKey { + property_key: PropertyKey::Symbol(JsSymbol::new(None).unwrap()), + attributes: SlotAttributes::all(), + }, + ); } assert_eq!( @@ -38,10 +44,13 @@ fn test_prune_property_on_counter_limit() { ); { - shape.insert_property_transition(TransitionKey { - property_key: PropertyKey::Symbol(JsSymbol::new(None).unwrap()), - attributes: SlotAttributes::all(), - }); + shape.insert_property_transition( + &unsafe { boa_gc::MutationContext::global() }, + TransitionKey { + property_key: PropertyKey::Symbol(JsSymbol::new(None).unwrap()), + attributes: SlotAttributes::all(), + }, + ); } assert_eq!( @@ -59,7 +68,7 @@ fn test_prune_property_on_counter_limit() { #[test] fn test_prune_prototype_on_counter_limit() { - let shape = SharedShape::root(); + let shape = SharedShape::root(&unsafe { boa_gc::MutationContext::global() }); assert_eq!( shape.forward_transitions().prototype_transitions_count(), @@ -72,7 +81,12 @@ fn test_prune_prototype_on_counter_limit() { (i, i as u8) ); - shape.change_prototype_transition(Some(JsObject::with_null_proto())); + shape.change_prototype_transition( + &unsafe { boa_gc::MutationContext::global() }, + Some(JsObject::with_null_proto(&unsafe { + boa_gc::MutationContext::global() + })), + ); } boa_gc::force_collect(); @@ -83,7 +97,12 @@ fn test_prune_prototype_on_counter_limit() { ); { - shape.change_prototype_transition(Some(JsObject::with_null_proto())); + shape.change_prototype_transition( + &unsafe { boa_gc::MutationContext::global() }, + Some(JsObject::with_null_proto(&unsafe { + boa_gc::MutationContext::global() + })), + ); } assert_eq!( @@ -92,7 +111,12 @@ fn test_prune_prototype_on_counter_limit() { ); { - shape.change_prototype_transition(Some(JsObject::with_null_proto())); + shape.change_prototype_transition( + &unsafe { boa_gc::MutationContext::global() }, + Some(JsObject::with_null_proto(&unsafe { + boa_gc::MutationContext::global() + })), + ); } assert_eq!( diff --git a/core/engine/src/object/shape/unique_shape.rs b/core/engine/src/object/shape/unique_shape.rs index 2b19a497af4..02d7c3e1cc4 100644 --- a/core/engine/src/object/shape/unique_shape.rs +++ b/core/engine/src/object/shape/unique_shape.rs @@ -35,7 +35,7 @@ pub(crate) struct UniqueShape { impl UniqueShape { /// Create a new [`UniqueShape`] using the given context. - pub(crate) fn new_in( + pub(crate) fn new( mc: &boa_gc::MutationContext<'static, '_>, prototype: JsPrototype, property_table: PropertyTableInner, @@ -52,13 +52,6 @@ impl UniqueShape { } /// Create a new [`UniqueShape`]. - pub(crate) fn new(prototype: JsPrototype, property_table: PropertyTableInner) -> Self { - Self::new_in( - &unsafe { boa_gc::MutationContext::global() }, - prototype, - property_table, - ) - } pub(crate) fn override_internal( &self, @@ -92,7 +85,11 @@ impl UniqueShape { /// Remove a property from the [`UniqueShape`]. /// /// This will cause the current shape to be invalidated, and a new [`UniqueShape`] will be returned. - pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + pub(crate) fn remove_property_transition( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: &PropertyKey, + ) -> Self { let mut property_table = self.property_table().borrow_mut(); let Some((index, _attributes)) = property_table.map.remove(key) else { return self.clone(); @@ -132,7 +129,7 @@ impl UniqueShape { } let prototype = self.inner.prototype.borrow_mut().take(); - Self::new(prototype, property_table) + Self::new(mc, prototype, property_table) } /// Does a property lookup on the [`UniqueShape`] returning the [`Slot`] where it's @@ -153,6 +150,7 @@ impl UniqueShape { /// NOTE: This assumes that the property had already been inserted. pub(crate) fn change_attributes_transition( &self, + mc: &boa_gc::MutationContext<'static, '_>, key: &TransitionKey, ) -> ChangeTransition { let mut property_table = self.property_table().borrow_mut(); @@ -227,7 +225,7 @@ impl UniqueShape { } let prototype = self.inner.prototype.borrow_mut().take(); - let shape = Self::new(prototype, property_table); + let shape = Self::new(mc, prototype, property_table); ChangeTransition { shape: shape.into(), @@ -238,13 +236,17 @@ impl UniqueShape { /// Change the prototype of the [`UniqueShape`]. /// /// This will cause the current shape to be invalidated, and a new [`UniqueShape`] will be returned. - pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + pub(crate) fn change_prototype_transition( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsPrototype, + ) -> Self { let mut property_table = self.inner.property_table.borrow_mut(); // We need to create a new unique shape, // to invalidate any pointers to this shape i.e inline caches. let property_table = std::mem::take(&mut *property_table); - Self::new(prototype, property_table) + Self::new(mc, prototype, property_table) } /// Gets all keys first strings then symbols in creation order. @@ -270,10 +272,7 @@ impl WeakUniqueShape { /// or [`None`] if the value was already garbage collected, using the given context. #[inline] #[must_use] - pub(crate) fn upgrade_in( - &self, - mc: &boa_gc::MutationContext<'static, '_>, - ) -> Option { + pub(crate) fn upgrade(&self, mc: &boa_gc::MutationContext<'static, '_>) -> Option { Some(UniqueShape { inner: self.inner.upgrade(mc)?, }) @@ -281,25 +280,14 @@ impl WeakUniqueShape { /// Upgrade returns a [`UniqueShape`] pointer for the internal value if the pointer is still live, /// or [`None`] if the value was already garbage collected. - #[inline] - #[must_use] - pub(crate) fn upgrade(&self) -> Option { - self.upgrade_in(&unsafe { boa_gc::MutationContext::global() }) - } #[allow(dead_code)] pub(crate) fn is_upgradable(&self) -> bool { self.inner.is_upgradable() } - pub(crate) fn new_in(mc: &boa_gc::MutationContext<'static, '_>, value: &UniqueShape) -> Self { + pub(crate) fn new(mc: &boa_gc::MutationContext<'static, '_>, value: &UniqueShape) -> Self { WeakUniqueShape { inner: WeakGc::new(mc, &value.inner), } } } - -impl From<&UniqueShape> for WeakUniqueShape { - fn from(value: &UniqueShape) -> Self { - Self::new_in(&unsafe { boa_gc::MutationContext::global() }, value) - } -} diff --git a/core/engine/src/realm.rs b/core/engine/src/realm.rs index a1d0ec1c8b1..39d1a382b4d 100644 --- a/core/engine/src/realm.rs +++ b/core/engine/src/realm.rs @@ -86,7 +86,7 @@ impl Realm { JsNativeError::typ().with_message("failed to create the realm intrinsics") })?; - let global_object = hooks.create_global_object(&intrinsics); + let global_object = hooks.create_global_object(mc, &intrinsics); let global_this = hooks .create_global_this(&intrinsics) .unwrap_or_else(|| global_object.clone()); @@ -110,7 +110,7 @@ impl Realm { ), }; - realm.initialize(); + realm.initialize(mc); Ok(realm) } diff --git a/core/engine/src/symbol.rs b/core/engine/src/symbol.rs index 1b2788cc56b..b78fbdc69a9 100644 --- a/core/engine/src/symbol.rs +++ b/core/engine/src/symbol.rs @@ -15,11 +15,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-symbol-value //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol -#![deny( - unsafe_op_in_unsafe_fn, - clippy::undocumented_unsafe_blocks, - clippy::missing_safety_doc -)] +#![deny(unsafe_op_in_unsafe_fn, clippy::missing_safety_doc)] use crate::{ js_string, @@ -424,7 +420,8 @@ mod tests { let mut context = Context::default(); let symbol1 = JsSymbol::new(None).unwrap(); let symbol2 = JsSymbol::new(None).unwrap(); - let test_obj = JsObject::from_proto_and_data(None, ()); + let test_obj = + JsObject::from_proto_and_data(&unsafe { boa_gc::MutationContext::global() }, None, ()); test_obj .set(symbol1, js_str!("Can't see me"), false, &mut context) .unwrap(); @@ -448,7 +445,7 @@ mod tests { fn hidden_in_stringify() { let mut context = Context::default(); let symbol = JsSymbol::new(None).unwrap(); - let test_obj = JsObject::with_object_proto(context.intrinsics()); + let test_obj = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); test_obj .set(symbol, js_str!("This won't show up"), false, &mut context) .unwrap(); diff --git a/core/engine/src/value/conversions/serde_json.rs b/core/engine/src/value/conversions/serde_json.rs index 4350b4c30a6..bbeeded9ef9 100644 --- a/core/engine/src/value/conversions/serde_json.rs +++ b/core/engine/src/value/conversions/serde_json.rs @@ -66,16 +66,19 @@ impl JsValue { Ok(Array::create_array_from_list(arr, context).into()) } Value::Object(obj) => { - let js_obj = JsObject::with_object_proto(context.intrinsics()); + let js_obj = + JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); for (key, value) in obj { let property = PropertyDescriptor::builder() .value(Self::from_json(value, context)?) .writable(true) .enumerable(true) .configurable(true); - js_obj - .borrow_mut() - .insert(js_string!(key.clone()), property); + js_obj.borrow_mut().insert( + context.gc_collector(), + js_string!(key.clone()), + property.build(), + ); } Ok(js_obj.into()) @@ -305,7 +308,7 @@ mod tests { #[test] fn to_json_cyclic() { let mut context = Context::default(); - let obj = JsObject::with_null_proto(); + let obj = JsObject::with_null_proto(&unsafe { boa_gc::MutationContext::global() }); obj.create_data_property(js_string!("a"), obj.clone(), &mut context) .expect("should create data property"); @@ -336,7 +339,7 @@ mod tests { // "outer_c": [2, undefined, 3, { "inner_a": undefined }] // } - let inner = JsObject::with_null_proto(); + let inner = JsObject::with_null_proto(&unsafe { boa_gc::MutationContext::global() }); inner .create_data_property(js_string!("inner_a"), JsValue::undefined(), &mut context) .expect("should add property"); @@ -349,7 +352,7 @@ mod tests { array.push(3, &mut context).expect("should push"); array.push(inner, &mut context).expect("should push"); - let outer = JsObject::with_null_proto(); + let outer = JsObject::with_null_proto(&unsafe { boa_gc::MutationContext::global() }); outer .create_data_property(js_string!("outer_a"), JsValue::new(1), &mut context) .expect("should add property"); diff --git a/core/engine/src/value/inner/nan_boxed.rs b/core/engine/src/value/inner/nan_boxed.rs index d0c50bb3f6a..e729ce793d8 100644 --- a/core/engine/src/value/inner/nan_boxed.rs +++ b/core/engine/src/value/inner/nan_boxed.rs @@ -1014,7 +1014,7 @@ fn bigint() { #[test] fn object() { - let object = JsObject::with_null_proto(); + let object = JsObject::with_null_proto(&unsafe { boa_gc::MutationContext::global() }); let v = NanBoxedValue::object(object.clone()); assert_type!(v is object(object)); } diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index e92d4809447..abcace248c4 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -1000,39 +1000,39 @@ impl JsValue { JsVariant::Undefined | JsVariant::Null => Err(JsNativeError::typ() .with_message("cannot convert 'null' or 'undefined' to object") .into()), - JsVariant::Boolean(boolean) => Ok(context - .intrinsics() - .templates() - .boolean() - .create(boolean, Vec::default())), - JsVariant::Integer32(integer) => Ok(context - .intrinsics() - .templates() - .number() - .create(f64::from(integer), Vec::default())), - JsVariant::Float64(rational) => Ok(context - .intrinsics() - .templates() - .number() - .create(rational, Vec::default())), + JsVariant::Boolean(boolean) => Ok(context.intrinsics().templates().boolean().create( + context.gc_collector(), + boolean, + Vec::default(), + )), + JsVariant::Integer32(integer) => Ok(context.intrinsics().templates().number().create( + context.gc_collector(), + f64::from(integer), + Vec::default(), + )), + JsVariant::Float64(rational) => Ok(context.intrinsics().templates().number().create( + context.gc_collector(), + rational, + Vec::default(), + )), JsVariant::String(string) => { let len = string.len(); - Ok(context - .intrinsics() - .templates() - .string() - .create(string, vec![len.into()])) + Ok(context.intrinsics().templates().string().create( + context.gc_collector(), + string, + vec![len.into()], + )) } - JsVariant::Symbol(symbol) => Ok(context - .intrinsics() - .templates() - .symbol() - .create(symbol, Vec::default())), - JsVariant::BigInt(bigint) => Ok(context - .intrinsics() - .templates() - .bigint() - .create(bigint, Vec::default())), + JsVariant::Symbol(symbol) => Ok(context.intrinsics().templates().symbol().create( + context.gc_collector(), + symbol, + Vec::default(), + )), + JsVariant::BigInt(bigint) => Ok(context.intrinsics().templates().bigint().create( + context.gc_collector(), + bigint, + Vec::default(), + )), JsVariant::Object(jsobject) => Ok(jsobject), } } diff --git a/core/engine/src/value/tests.rs b/core/engine/src/value/tests.rs index ee21a6e967d..15ca96277f0 100644 --- a/core/engine/src/value/tests.rs +++ b/core/engine/src/value/tests.rs @@ -26,7 +26,7 @@ fn undefined() { #[test] fn get_set_field() { run_test_actions([TestAction::assert_context(|ctx| { - let obj = &JsObject::with_object_proto(ctx.intrinsics()); + let obj = &JsObject::with_object_proto(ctx.gc_collector(), ctx.intrinsics()); // Create string and convert it to a Value let s = JsValue::new(js_str!("bar")); obj.set(js_str!("foo"), s, false, ctx).unwrap(); @@ -128,11 +128,15 @@ fn hash_rational() { #[test] fn hash_object() { - let object1 = JsValue::new(JsObject::with_null_proto()); + let object1 = JsValue::new(JsObject::with_null_proto(&unsafe { + boa_gc::MutationContext::global() + })); assert_eq!(object1, object1); assert_eq!(object1, object1.clone()); - let object2 = JsValue::new(JsObject::with_null_proto()); + let object2 = JsValue::new(JsObject::with_null_proto(&unsafe { + boa_gc::MutationContext::global() + })); assert_ne!(object1, object2); assert_eq!(hash_value(&object1), hash_value(&object1.clone())); diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 959256a90ce..a3eea955f6d 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -1115,6 +1115,7 @@ pub(crate) fn create_function_object( let (mut template, storage, constructor_prototype) = if is_generator { let prototype = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), if is_async { context.intrinsics().objects().async_generator() @@ -1136,9 +1137,11 @@ pub(crate) fn create_function_object( None, ) } else { - let constructor_prototype = templates - .function_prototype() - .create(OrdinaryObject, vec![JsValue::undefined()]); + let constructor_prototype = templates.function_prototype().create( + context.gc_collector(), + OrdinaryObject, + vec![JsValue::undefined()], + ); let template = templates.function_with_prototype_without_proto(); @@ -1149,9 +1152,9 @@ pub(crate) fn create_function_object( ) }; - template.set_prototype(prototype); + template.set_prototype(context.gc_collector(), prototype); - let constructor = template.create(function, storage); + let constructor = template.create(context.gc_collector(), function, storage); if let Some(constructor_prototype) = &constructor_prototype { constructor_prototype.borrow_mut().properties_mut().storage[0] = constructor.clone().into(); @@ -1185,6 +1188,7 @@ pub(crate) fn create_function_object_fast( if is_generator { let prototype = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), if is_async { context.intrinsics().objects().async_generator() @@ -1199,31 +1203,43 @@ pub(crate) fn create_function_object_fast( context.intrinsics().templates().generator_function() }; - template.create(function, vec![length, name, prototype.into()]) + template.create( + context.gc_collector(), + function, + vec![length, name, prototype.into()], + ) } else if is_async { - context - .intrinsics() - .templates() - .async_function() - .create(function, vec![length, name]) + context.intrinsics().templates().async_function().create( + context.gc_collector(), + function, + vec![length, name], + ) } else if !has_prototype_property { - context - .intrinsics() - .templates() - .function() - .create(function, vec![length, name]) + context.intrinsics().templates().function().create( + context.gc_collector(), + function, + vec![length, name], + ) } else { let prototype = context .intrinsics() .templates() .function_prototype() - .create(OrdinaryObject, vec![JsValue::undefined()]); + .create( + context.gc_collector(), + OrdinaryObject, + vec![JsValue::undefined()], + ); let constructor = context .intrinsics() .templates() .function_with_prototype() - .create(function, vec![length, name, prototype.clone().into()]); + .create( + context.gc_collector(), + function, + vec![length, name, prototype.clone().into()], + ); prototype.borrow_mut().properties_mut().storage[0] = constructor.clone().into(); diff --git a/core/engine/src/vm/inline_cache/mod.rs b/core/engine/src/vm/inline_cache/mod.rs index 2ae3b6d7d77..968793000f1 100644 --- a/core/engine/src/vm/inline_cache/mod.rs +++ b/core/engine/src/vm/inline_cache/mod.rs @@ -47,9 +47,9 @@ impl fmt::Display for InlineCache { } let entries = self.entries.borrow(); - let entries = entries.iter().map(|e| e.shape.to_addr_usize()).format(", "); + let entries = entries.iter().map(|_| "").format(", "); - write!(f, "({entries:#x}))") + write!(f, "({entries}))") } } @@ -62,7 +62,7 @@ impl InlineCache { } } - pub(crate) fn set(&self, shape: &Shape, slot: Slot) { + pub(crate) fn set(&self, mc: &boa_gc::MutationContext<'static, '_>, shape: &Shape, slot: Slot) { if self.megamorphic.get() { return; } @@ -72,7 +72,7 @@ impl InlineCache { // Add a new entry if there's space. if entries .try_push(CacheEntry { - shape: shape.into(), + shape: WeakShape::new(mc, shape), slot, }) .is_err() @@ -86,7 +86,11 @@ impl InlineCache { /// Returns the cached `(Shape, Slot)` if a matching shape exists in the inline cache. /// /// Opportunistically cleans up stale weak shape references during lookup. - pub(crate) fn get(&self, shape: &Shape) -> Option<(Shape, Slot)> { + pub(crate) fn get( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + shape: &Shape, + ) -> Option<(Shape, Slot)> { if self.megamorphic.get() { return None; } @@ -97,7 +101,7 @@ impl InlineCache { let shape_addr = shape.to_addr_usize(); while i < entries.len() { - if let Some(upgraded) = entries[i].shape.upgrade() { + if let Some(upgraded) = entries[i].shape.upgrade(mc) { let upgraded: Shape = upgraded; if upgraded.to_addr_usize() == shape_addr { result = Some((upgraded, entries[i].slot)); diff --git a/core/engine/src/vm/inline_cache/tests.rs b/core/engine/src/vm/inline_cache/tests.rs index 7e893b5dabc..0342bc07d62 100644 --- a/core/engine/src/vm/inline_cache/tests.rs +++ b/core/engine/src/vm/inline_cache/tests.rs @@ -17,11 +17,11 @@ use crate::{ fn get_own_property_internal_method() { let context = &mut Context::default(); - let o = context - .intrinsics() - .templates() - .ordinary_object() - .create(OrdinaryObject, Vec::default()); + let o = context.intrinsics().templates().ordinary_object().create( + &unsafe { boa_gc::MutationContext::global() }, + OrdinaryObject, + Vec::default(), + ); let property: PropertyKey = js_string!("prop").into(); let value = 100; @@ -62,11 +62,11 @@ fn get_own_property_internal_method() { fn get_internal_method() { let context = &mut Context::default(); - let o = context - .intrinsics() - .templates() - .ordinary_object() - .create(OrdinaryObject, Vec::default()); + let o = context.intrinsics().templates().ordinary_object().create( + &unsafe { boa_gc::MutationContext::global() }, + OrdinaryObject, + Vec::default(), + ); let property: PropertyKey = js_string!("prop").into(); let value = 100; @@ -107,11 +107,11 @@ fn get_internal_method() { fn get_internal_method_in_prototype() { let context = &mut Context::default(); - let o = context - .intrinsics() - .templates() - .ordinary_object() - .create(OrdinaryObject, Vec::default()); + let o = context.intrinsics().templates().ordinary_object().create( + &unsafe { boa_gc::MutationContext::global() }, + OrdinaryObject, + Vec::default(), + ); let property: PropertyKey = js_string!("prop").into(); let value = 100; @@ -155,11 +155,11 @@ fn get_internal_method_in_prototype() { fn define_own_property_internal_method_non_existent_property() { let context = &mut Context::default(); - let o = context - .intrinsics() - .templates() - .ordinary_object() - .create(OrdinaryObject, Vec::default()); + let o = context.intrinsics().templates().ordinary_object().create( + &unsafe { boa_gc::MutationContext::global() }, + OrdinaryObject, + Vec::default(), + ); let property: PropertyKey = js_string!("prop").into(); let value = 100; @@ -209,11 +209,11 @@ fn define_own_property_internal_method_non_existent_property() { fn define_own_property_internal_method_existing_property_property() { let context = &mut Context::default(); - let o = context - .intrinsics() - .templates() - .ordinary_object() - .create(OrdinaryObject, Vec::default()); + let o = context.intrinsics().templates().ordinary_object().create( + &unsafe { boa_gc::MutationContext::global() }, + OrdinaryObject, + Vec::default(), + ); let property: PropertyKey = js_string!("prop").into(); let value = 100; @@ -275,11 +275,11 @@ fn define_own_property_internal_method_existing_property_property() { fn set_internal_method() { let context = &mut Context::default(); - let o = context - .intrinsics() - .templates() - .ordinary_object() - .create(OrdinaryObject, Vec::default()); + let o = context.intrinsics().templates().ordinary_object().create( + &unsafe { boa_gc::MutationContext::global() }, + OrdinaryObject, + Vec::default(), + ); let property: PropertyKey = js_string!("prop").into(); let value = 100; @@ -343,7 +343,7 @@ fn set_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> { assert_eq!( code.ic[0].entries.borrow()[0] .shape - .upgrade() + .upgrade(&unsafe { boa_gc::MutationContext::global() }) .unwrap() .to_addr_usize(), o_shape.to_addr_usize() @@ -372,7 +372,7 @@ fn get_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> { assert_eq!( code.ic[0].entries.borrow()[0] .shape - .upgrade() + .upgrade(&unsafe { boa_gc::MutationContext::global() }) .unwrap() .to_addr_usize(), o_shape.to_addr_usize() diff --git a/core/engine/src/vm/opcode/await/mod.rs b/core/engine/src/vm/opcode/await/mod.rs index 887603a9bd9..9c777986a80 100644 --- a/core/engine/src/vm/opcode/await/mod.rs +++ b/core/engine/src/vm/opcode/await/mod.rs @@ -62,6 +62,7 @@ impl Await { // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). let on_fulfilled = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { // a. Let prevContext be the running execution context. @@ -101,6 +102,7 @@ impl Await { // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). let on_rejected = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_this, args, captures, context| { // a. Let prevContext be the running execution context. diff --git a/core/engine/src/vm/opcode/call/mod.rs b/core/engine/src/vm/opcode/call/mod.rs index 851958c17f6..410c826fb86 100644 --- a/core/engine/src/vm/opcode/call/mod.rs +++ b/core/engine/src/vm/opcode/call/mod.rs @@ -450,6 +450,7 @@ async fn load_dyn_import( // 5. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). let on_rejected = FunctionObjectBuilder::new( context.borrow().realm(), + context.borrow_mut().gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, cap, context| { // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »). @@ -469,6 +470,7 @@ async fn load_dyn_import( // 7. Let linkAndEvaluate be CreateBuiltinFunction(linkAndEvaluateClosure, 0, "", « »). let link_evaluate = FunctionObjectBuilder::new( context.borrow().realm(), + context.borrow_mut().gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, _, (module, cap, on_rejected), context| { // a. Let link be Completion(module.Link()). @@ -490,6 +492,7 @@ async fn load_dyn_import( // e. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »). let fulfill = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, _, (module, cap), context| { // i. Let namespace be GetModuleNamespace(module). diff --git a/core/engine/src/vm/opcode/generator/mod.rs b/core/engine/src/vm/opcode/generator/mod.rs index 70087b7b9c0..328925e02d9 100644 --- a/core/engine/src/vm/opcode/generator/mod.rs +++ b/core/engine/src/vm/opcode/generator/mod.rs @@ -37,6 +37,7 @@ impl Generator { .unwrap_or_else(|| context.intrinsics().objects().generator()); let generator = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), proto, NativeGenerator { @@ -83,6 +84,7 @@ impl AsyncGenerator { .unwrap_or_else(|| context.intrinsics().objects().async_generator()); let generator = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), proto, NativeAsyncGenerator { diff --git a/core/engine/src/vm/opcode/get/name.rs b/core/engine/src/vm/opcode/get/name.rs index d44f028ccc0..8d0d22eb5c9 100644 --- a/core/engine/src/vm/opcode/get/name.rs +++ b/core/engine/src/vm/opcode/get/name.rs @@ -60,7 +60,7 @@ impl GetNameGlobal { let ic = &context.vm.frame().code_block().ic[usize::from(ic_index)]; let object_borrowed = object.borrow(); - if let Some((shape, slot)) = ic.get(object_borrowed.shape()) { + if let Some((shape, slot)) = ic.get(context.gc_collector(), object_borrowed.shape()) { let mut result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) { let prototype = shape.prototype().expect("prototype should have value"); let prototype = prototype.borrow(); @@ -99,7 +99,7 @@ impl GetNameGlobal { let ic = &context.vm.frame().code_block.ic[usize::from(ic_index)]; let object_borrowed = object.borrow(); let shape = object_borrowed.shape(); - ic.set(shape, slot); + ic.set(context.gc_collector(), shape, slot); } context.vm.set_register(dst.into(), result); diff --git a/core/engine/src/vm/opcode/get/property.rs b/core/engine/src/vm/opcode/get/property.rs index 9e531555fa0..b0862e3f02e 100644 --- a/core/engine/src/vm/opcode/get/property.rs +++ b/core/engine/src/vm/opcode/get/property.rs @@ -39,7 +39,7 @@ fn get_by_name( let ic = &context.vm.frame().code_block().ic[usize::from(index)]; let object_borrowed = object.borrow(); - if let Some((shape, slot)) = ic.get(object_borrowed.shape()) { + if let Some((shape, slot)) = ic.get(context.gc_collector(), object_borrowed.shape()) { let mut result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) { let prototype = shape.prototype().expect("prototype should have value"); let prototype = prototype.borrow(); @@ -73,7 +73,7 @@ fn get_by_name( let ic = &context.vm.frame().code_block.ic[usize::from(index)]; let object_borrowed = object.borrow(); let shape = object_borrowed.shape(); - ic.set(shape, slot); + ic.set(context.gc_collector(), shape, slot); } context.vm.set_register(dst.into(), result); diff --git a/core/engine/src/vm/opcode/iteration/for_in.rs b/core/engine/src/vm/opcode/iteration/for_in.rs index 2a532b5e6c1..e75a1093a32 100644 --- a/core/engine/src/vm/opcode/iteration/for_in.rs +++ b/core/engine/src/vm/opcode/iteration/for_in.rs @@ -19,11 +19,12 @@ impl CreateForInIterator { let (iterator, next_method) = ForInIterator::create_for_in_iterator(JsValue::new(object), context); + let mc = context.gc_collector(); context .vm .frame_mut() .iterators - .push(IteratorRecord::new(iterator, next_method)); + .push(IteratorRecord::new(iterator, next_method, mc)); Ok(()) } diff --git a/core/engine/src/vm/opcode/iteration/iterator.rs b/core/engine/src/vm/opcode/iteration/iterator.rs index b053816835e..587fda6861c 100644 --- a/core/engine/src/vm/opcode/iteration/iterator.rs +++ b/core/engine/src/vm/opcode/iteration/iterator.rs @@ -76,11 +76,12 @@ impl IteratorPush { .js_expect("iterator should be an object")?; let next = context.vm.get_register(next.into()).clone(); + let mc = context.gc_collector(); context .vm .frame_mut() .iterators - .push(IteratorRecord::new(iterator, next)); + .push(IteratorRecord::new(iterator, next, mc)); Ok(()) } diff --git a/core/engine/src/vm/opcode/meta/mod.rs b/core/engine/src/vm/opcode/meta/mod.rs index b1ec082a394..7d264918ba6 100644 --- a/core/engine/src/vm/opcode/meta/mod.rs +++ b/core/engine/src/vm/opcode/meta/mod.rs @@ -73,7 +73,7 @@ impl ImportMeta { .borrow_mut() .get_or_insert_with(|| { // a. Set importMeta to OrdinaryObjectCreate(null). - let import_meta = JsObject::with_null_proto(); + let import_meta = JsObject::with_null_proto(context.gc_collector()); // b. Let importMetaValues be HostGetImportMetaProperties(module). // c. For each Record { [[Key]], [[Value]] } p of importMetaValues, do diff --git a/core/engine/src/vm/opcode/push/array.rs b/core/engine/src/vm/opcode/push/array.rs index 9f3a209d7c5..4f48543ac6a 100644 --- a/core/engine/src/vm/opcode/push/array.rs +++ b/core/engine/src/vm/opcode/push/array.rs @@ -15,11 +15,11 @@ pub(crate) struct StoreNewArray; impl StoreNewArray { #[inline(always)] pub(crate) fn operation(array: RegisterOperand, context: &mut Context) { - let value = context - .intrinsics() - .templates() - .array() - .create(Array, Vec::from([JsValue::new(0)])); + let value = context.intrinsics().templates().array().create( + context.gc_collector(), + Array, + Vec::from([JsValue::new(0)]), + ); context.vm.set_register(array.into(), value.into()); } } diff --git a/core/engine/src/vm/opcode/push/class/mod.rs b/core/engine/src/vm/opcode/push/class/mod.rs index 7b29a690305..ed166b1c792 100644 --- a/core/engine/src/vm/opcode/push/class/mod.rs +++ b/core/engine/src/vm/opcode/push/class/mod.rs @@ -65,7 +65,7 @@ impl StoreClassPrototype { let class_object = class.as_object().js_expect("class must be object")?; if let Some(constructor_parent) = constructor_parent { - class_object.set_prototype(Some(constructor_parent)); + class_object.set_prototype(context.gc_collector(), Some(constructor_parent)); } context.vm.set_register(dst.into(), proto_parent); diff --git a/core/engine/src/vm/opcode/push/object.rs b/core/engine/src/vm/opcode/push/object.rs index 30dcb696024..ac0fc0232e4 100644 --- a/core/engine/src/vm/opcode/push/object.rs +++ b/core/engine/src/vm/opcode/push/object.rs @@ -14,11 +14,11 @@ pub(crate) struct StoreEmptyObject; impl StoreEmptyObject { #[inline(always)] pub(crate) fn operation(dst: RegisterOperand, context: &mut Context) { - let o = context - .intrinsics() - .templates() - .ordinary_object() - .create(OrdinaryObject, Vec::default()); + let o = context.intrinsics().templates().ordinary_object().create( + context.gc_collector(), + OrdinaryObject, + Vec::default(), + ); context.vm.set_register(dst.into(), o.into()); } } diff --git a/core/engine/src/vm/opcode/set/class_prototype.rs b/core/engine/src/vm/opcode/set/class_prototype.rs index 1d1c8566d8e..747b2f0d5c4 100644 --- a/core/engine/src/vm/opcode/set/class_prototype.rs +++ b/core/engine/src/vm/opcode/set/class_prototype.rs @@ -31,6 +31,7 @@ impl SetClassPrototype { // 9.Let proto be OrdinaryObjectCreate(protoParent). let proto = JsObject::from_proto_and_data_with_shared_shape( + context.gc_collector(), context.root_shape(), prototype, OrdinaryObject, diff --git a/core/engine/src/vm/opcode/set/property.rs b/core/engine/src/vm/opcode/set/property.rs index 6bf6d66c1f6..95ed921e4b6 100644 --- a/core/engine/src/vm/opcode/set/property.rs +++ b/core/engine/src/vm/opcode/set/property.rs @@ -25,7 +25,7 @@ fn set_by_name( let ic = &context.vm.frame().code_block().ic[usize::from(index)]; let object_borrowed = object.borrow(); - if let Some((shape, slot)) = ic.get(object_borrowed.shape()) { + if let Some((shape, slot)) = ic.get(context.gc_collector(), object_borrowed.shape()) { let slot_index = slot.index as usize; if slot.attributes.is_accessor_descriptor() { @@ -76,7 +76,7 @@ fn set_by_name( let ic = &context.vm.frame().code_block.ic[usize::from(index)]; let object_borrowed = object.borrow(); let shape = object_borrowed.shape(); - ic.set(shape, slot); + ic.set(context.gc_collector(), shape, slot); } Ok(()) diff --git a/core/macros/src/class.rs b/core/macros/src/class.rs index 2a74be2dbc3..3e5a3e9ccc9 100644 --- a/core/macros/src/class.rs +++ b/core/macros/src/class.rs @@ -406,10 +406,11 @@ impl Accessor { let getter = if let Some(getter) = self.getter.as_ref() { let body = getter.body.clone(); quote! { - Some( + Some({ + let ctx = builder.context(); boa_engine::NativeFunction::from_fn_ptr( #body ) - .to_js_function(builder.context().realm()) - ) + .to_js_function(ctx.realm(), ctx.gc_collector()) + }) } } else { quote! { None } @@ -417,10 +418,11 @@ impl Accessor { let setter = if let Some(setter) = self.setter.as_ref() { let body = setter.body.clone(); quote! { - Some( + Some({ + let ctx = builder.context(); boa_engine::NativeFunction::from_fn_ptr( #body ) - .to_js_function(builder.context().realm()) - ) + .to_js_function(ctx.realm(), ctx.gc_collector()) + }) } } else { quote! { None } diff --git a/core/macros/src/lib.rs b/core/macros/src/lib.rs index 526e81acbd1..6bfba4972b7 100644 --- a/core/macros/src/lib.rs +++ b/core/macros/src/lib.rs @@ -627,7 +627,7 @@ pub fn derive_try_into_js(input: TokenStream) -> TokenStream { let expanded = quote! { impl ::boa_engine::value::TryIntoJs for #type_name { fn try_into_js(&self, context: &mut boa_engine::Context) -> boa_engine::JsResult { - let obj = boa_engine::JsObject::default(context.intrinsics()); + let obj = boa_engine::JsObject::default(context.gc_collector(), context.intrinsics()); #props boa_engine::JsResult::Ok(obj.into()) } diff --git a/core/macros/src/module.rs b/core/macros/src/module.rs index e4bfcff5fe0..beb9c5cef59 100644 --- a/core/macros/src/module.rs +++ b/core/macros/src/module.rs @@ -81,7 +81,7 @@ fn fn_item( &boa_engine::js_string!( #name ), boa_engine::JsValue::from( boa_engine::NativeFunction::from_fn_ptr( #fn_body ) - .to_js_function(context.realm()) + .to_js_function(context.realm(), context.gc_collector()) ), )?; }, @@ -92,7 +92,7 @@ fn fn_item( boa_engine::js_string!( #name ), boa_engine::JsValue::from( boa_engine::NativeFunction::from_fn_ptr( function ) - .to_js_function(context.realm()) + .to_js_function(context.realm(), context.gc_collector()) ), boa_engine::property::Attribute::all(), context, @@ -102,7 +102,7 @@ fn fn_item( boa_engine::js_string!( #name ), boa_engine::JsValue::from( boa_engine::NativeFunction::from_fn_ptr( function ) - .to_js_function(context.realm()) + .to_js_function(context.realm(), context.gc_collector()) ), boa_engine::property::Attribute::all(), )?; diff --git a/core/runtime/src/console/mod.rs b/core/runtime/src/console/mod.rs index 8bb4b3b892b..3aec9447708 100644 --- a/core/runtime/src/console/mod.rs +++ b/core/runtime/src/console/mod.rs @@ -368,7 +368,7 @@ impl Console { ObjectInitializer::with_native_data_and_proto( Self::default(), - JsObject::with_object_proto(context.realm().intrinsics()), + JsObject::with_object_proto(context.gc_collector(), context.realm().intrinsics()), context, ) .property( diff --git a/core/runtime/src/fetch/headers_iterator.rs b/core/runtime/src/fetch/headers_iterator.rs index 2b3ee246aad..aa7e4116dc5 100644 --- a/core/runtime/src/fetch/headers_iterator.rs +++ b/core/runtime/src/fetch/headers_iterator.rs @@ -116,7 +116,11 @@ impl HeadersIterator { .ok_or_else(|| boa_engine::js_error!(Error: "Headers Iterator not registered"))? .prototype(); - let headers_iterator = JsObject::from_proto_and_data(proto, iter); + let headers_iterator = JsObject::from_proto_and_data( + &unsafe { boa_gc::MutationContext::global() }, + proto, + iter, + ); Ok(headers_iterator.into()) } } diff --git a/core/runtime/src/fetch/mod.rs b/core/runtime/src/fetch/mod.rs index f9b394fa6a2..6e51bb09964 100644 --- a/core/runtime/src/fetch/mod.rs +++ b/core/runtime/src/fetch/mod.rs @@ -255,6 +255,7 @@ pub fn register( let iterator = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(headers_symbol_iterator), ) .name(js_string!("[Symbol.iterator]")) diff --git a/core/runtime/src/process/mod.rs b/core/runtime/src/process/mod.rs index 36022cd18f9..a9f04743aab 100644 --- a/core/runtime/src/process/mod.rs +++ b/core/runtime/src/process/mod.rs @@ -82,7 +82,7 @@ impl Process { let provider = Rc::new(provider); - let env = JsObject::default(context.intrinsics()); + let env = JsObject::default(context.gc_collector(), context.intrinsics()); for (key, value) in provider.env() { env.set(key, JsValue::from(value), false, context)?; } diff --git a/core/runtime/src/store/to.rs b/core/runtime/src/store/to.rs index 9ff24570c67..e0fa7791fb5 100644 --- a/core/runtime/src/store/to.rs +++ b/core/runtime/src/store/to.rs @@ -30,7 +30,7 @@ fn try_fields_into_js_object( seen: &mut ReverseSeenMap, context: &mut Context, ) -> JsResult { - let dolly = JsObject::with_object_proto(context.intrinsics()); + let dolly = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); seen.insert(store, dolly.clone()); for (k, v) in fields { diff --git a/core/runtime/src/test262.rs b/core/runtime/src/test262.rs index 45456b95817..34552890664 100644 --- a/core/runtime/src/test262.rs +++ b/core/runtime/src/test262.rs @@ -127,7 +127,11 @@ pub fn register_js262(handles: WorkerHandles, console: bool, context: &mut Conte js262 .create_data_property_or_throw( js_string!("IsHTMLDDA"), - JsObject::from_proto_and_data(None, IsHTMLDDA), + JsObject::from_proto_and_data( + &unsafe { boa_gc::MutationContext::global() }, + None, + IsHTMLDDA, + ), context, ) .expect("the IsHTMLDDA property must be definable"); diff --git a/examples/src/bin/closures.rs b/examples/src/bin/closures.rs index b94288b269f..ffeda1681bc 100644 --- a/examples/src/bin/closures.rs +++ b/examples/src/bin/closures.rs @@ -49,7 +49,7 @@ fn main() -> Result<(), JsError> { } // We create a new `JsObject` with some data - let object = JsObject::with_object_proto(context.intrinsics()); + let object = JsObject::with_object_proto(context.gc_collector(), context.intrinsics()); object.define_property_or_throw( js_string!("name"), PropertyDescriptor::builder() @@ -70,6 +70,7 @@ fn main() -> Result<(), JsError> { // attributes. let js_function = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, _, captures, context| { let mut captures = captures.borrow_mut(); diff --git a/examples/src/bin/jsarray.rs b/examples/src/bin/jsarray.rs index 3b39f185058..4dbe06aa256 100644 --- a/examples/src/bin/jsarray.rs +++ b/examples/src/bin/jsarray.rs @@ -65,6 +65,7 @@ fn main() -> JsResult<()> { let filter_callback = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_this, args, _context| { Ok(args.first().cloned().unwrap_or_default().is_number().into()) }), @@ -73,6 +74,7 @@ fn main() -> JsResult<()> { let map_callback = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_this, args, context| { args.first() .cloned() @@ -99,6 +101,7 @@ fn main() -> JsResult<()> { let reduce_callback = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_this, args, context| { let accumulator = args.first().cloned().unwrap_or_default(); let value = args.get(1).cloned().unwrap_or_default(); diff --git a/examples/src/bin/jspromise.rs b/examples/src/bin/jspromise.rs index b4e5dbaa2ad..787451faa57 100644 --- a/examples/src/bin/jspromise.rs +++ b/examples/src/bin/jspromise.rs @@ -43,7 +43,7 @@ async fn main() -> Result<(), Box> { println!("Promise resolved with: {}", value.display()); Ok(value.clone()) }) - .to_js_function(context.realm()), + .to_js_function(context.realm(), context.gc_collector()), ), Some( NativeFunction::from_fn_ptr(|_, args, _context| { @@ -51,7 +51,7 @@ async fn main() -> Result<(), Box> { println!("Promise rejected with: {}", error.display()); Err(JsError::from_opaque(error.clone())) }) - .to_js_function(context.realm()), + .to_js_function(context.realm(), context.gc_collector()), ), context, )? @@ -60,7 +60,7 @@ async fn main() -> Result<(), Box> { println!("Promise settled!"); Ok(JsValue::undefined()) }) - .to_js_function(context.realm()), + .to_js_function(context.realm(), context.gc_collector()), context, )?; diff --git a/examples/src/bin/jstypedarray.rs b/examples/src/bin/jstypedarray.rs index b82e6f99202..0a2971c5a1f 100644 --- a/examples/src/bin/jstypedarray.rs +++ b/examples/src/bin/jstypedarray.rs @@ -30,6 +30,7 @@ fn main() -> JsResult<()> { let callback = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_this, args, context| { let accumulator = args.first().cloned().unwrap_or_default(); let value = args.get(1).cloned().unwrap_or_default(); @@ -46,6 +47,7 @@ fn main() -> JsResult<()> { let greater_than_10_predicate = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_this, args, _context| { let element = args .first() @@ -65,6 +67,7 @@ fn main() -> JsResult<()> { let lower_than_200_predicate = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_this, args, _context| { let element = args .first() @@ -99,6 +102,7 @@ fn main() -> JsResult<()> { let js_function = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_copy_closure_with_captures( |_, args, captures, inner_context| { let element = args diff --git a/examples/src/bin/modulehandler.rs b/examples/src/bin/modulehandler.rs index 8ce7dee1666..d325be1ca19 100644 --- a/examples/src/bin/modulehandler.rs +++ b/examples/src/bin/modulehandler.rs @@ -31,7 +31,7 @@ fn main() -> Result<(), Box> { ctx.register_global_callable("require".into(), 0, NativeFunction::from_fn_ptr(require))?; // Adding custom object that mimics 'module.exports' - let moduleobj = JsObject::default(ctx.intrinsics()); + let moduleobj = JsObject::default(ctx.gc_collector(), ctx.intrinsics()); moduleobj.set(js_string!("exports"), js_string!(" "), false, &mut ctx)?; ctx.register_global_property( diff --git a/examples/src/bin/modules.rs b/examples/src/bin/modules.rs index 0e3a8c8aa92..d513c6f3ccb 100644 --- a/examples/src/bin/modules.rs +++ b/examples/src/bin/modules.rs @@ -65,7 +65,7 @@ fn main() -> Result<(), Box> { }, module.clone(), ) - .to_js_function(context.realm()), + .to_js_function(context.realm(), context.gc_collector()), ), None, context, @@ -81,7 +81,7 @@ fn main() -> Result<(), Box> { |_, _, module, context| Ok(module.evaluate(context)?.into()), module.clone(), ) - .to_js_function(context.realm()), + .to_js_function(context.realm(), context.gc_collector()), ), None, context, diff --git a/examples/src/bin/synthetic.rs b/examples/src/bin/synthetic.rs index 93b3dbcabea..c892c9b35de 100644 --- a/examples/src/bin/synthetic.rs +++ b/examples/src/bin/synthetic.rs @@ -105,6 +105,7 @@ fn create_operations_module(context: &mut Context) -> Module { // on that below. let sum = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_, args, ctx| { args.get_or_undefined(0).add(args.get_or_undefined(1), ctx) }), @@ -114,6 +115,7 @@ fn create_operations_module(context: &mut Context) -> Module { .build(); let sub = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_, args, ctx| { args.get_or_undefined(0).sub(args.get_or_undefined(1), ctx) }), @@ -123,6 +125,7 @@ fn create_operations_module(context: &mut Context) -> Module { .build(); let mult = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_, args, ctx| { args.get_or_undefined(0).mul(args.get_or_undefined(1), ctx) }), @@ -132,6 +135,7 @@ fn create_operations_module(context: &mut Context) -> Module { .build(); let div = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_, args, ctx| { args.get_or_undefined(0).div(args.get_or_undefined(1), ctx) }), @@ -141,6 +145,7 @@ fn create_operations_module(context: &mut Context) -> Module { .build(); let sqrt = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), NativeFunction::from_fn_ptr(|_, args, ctx| { let a = args.get_or_undefined(0).to_number(ctx)?; Ok(JsValue::from(a.sqrt())) diff --git a/tests/macros/tests/class.rs b/tests/macros/tests/class.rs index 4aee6273113..e31a931edc3 100644 --- a/tests/macros/tests/class.rs +++ b/tests/macros/tests/class.rs @@ -46,7 +46,7 @@ impl Animal { #[boa(method)] #[boa(length = 11)] fn method(context: &mut Context) -> JsObject { - let obj = JsObject::with_null_proto(); + let obj = JsObject::with_null_proto(&unsafe { boa_gc::MutationContext::global() }); obj.set(js_string!("key"), 43, false, context).unwrap(); obj } diff --git a/tests/macros/tests/fibonacci.rs b/tests/macros/tests/fibonacci.rs index 69d06c4621d..2be2ba5401e 100644 --- a/tests/macros/tests/fibonacci.rs +++ b/tests/macros/tests/fibonacci.rs @@ -60,7 +60,7 @@ fn fibonacci_test() { let fibonacci_rust = fibonacci .into_js_function_copied(context) - .to_js_function(context.realm()); + .to_js_function(context.realm(), context.gc_collector()); assert_eq!( fibonacci_js @@ -78,7 +78,7 @@ fn fibonacci_test() { let fibonacci_throw = fibonacci_throw .into_js_function_copied(context) - .to_js_function(context.realm()); + .to_js_function(context.realm(), context.gc_collector()); assert!( fibonacci_js .call( diff --git a/tests/macros/tests/gcd_callback.rs b/tests/macros/tests/gcd_callback.rs index 952099364ca..1a4bfe9dbe1 100644 --- a/tests/macros/tests/gcd_callback.rs +++ b/tests/macros/tests/gcd_callback.rs @@ -40,7 +40,7 @@ fn gcd_callback() { let function = callback_from_js .into_js_function_copied(context) - .to_js_function(context.realm()); + .to_js_function(context.realm(), context.gc_collector()); result.store(0, Ordering::Relaxed); assert_eq!(js_gcd.call(context, (6, 9, function.clone())), Ok(())); diff --git a/tests/tester/src/exec/mod.rs b/tests/tester/src/exec/mod.rs index 62c0afaf5ad..4fbe43a7a9d 100644 --- a/tests/tester/src/exec/mod.rs +++ b/tests/tester/src/exec/mod.rs @@ -625,6 +625,7 @@ fn register_print_fn(context: &mut Context, async_result: AsyncResult) { // We use `FunctionBuilder` to define a closure with additional captures. let js_function = FunctionObjectBuilder::new( context.realm(), + context.gc_collector(), // SAFETY: `AsyncResult` has only non-traceable captures, making this safe. unsafe { NativeFunction::from_closure(move |_, args, context| { diff --git a/tests/wpt/src/lib.rs b/tests/wpt/src/lib.rs index ef538b61eee..d4e1c8f8666 100644 --- a/tests/wpt/src/lib.rs +++ b/tests/wpt/src/lib.rs @@ -304,7 +304,7 @@ fn execute_test_file(path: &Path) { let function = result_callback__ .into_js_function_copied(&mut context) - .to_js_function(context.realm()); + .to_js_function(context.realm(), context.gc_collector()); context .register_global_property(js_str!("result_callback__"), function, Attribute::all()) .expect("Could not register result_callback__"); @@ -316,7 +316,7 @@ fn execute_test_file(path: &Path) { let function = complete_callback__ .into_js_function_copied(&mut context) - .to_js_function(context.realm()); + .to_js_function(context.realm(), context.gc_collector()); context .register_global_property(js_str!("complete_callback__"), function, Attribute::all()) .expect("Could not register complete_callback__"); From f2ddfe2b92c392ae8513467e25fbe09a2b046497 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 16 Aug 2026 03:09:08 +0000 Subject: [PATCH 05/19] Thread MutationContext through core engine and ByteCompiler --- core/engine/benches/full.rs | 6 +- core/engine/src/builtins/eval/mod.rs | 6 +- core/engine/src/builtins/function/mod.rs | 14 +- core/engine/src/builtins/iterable/mod.rs | 31 +- core/engine/src/builtins/json/mod.rs | 4 +- core/engine/src/builtins/uri/mod.rs | 14 +- core/engine/src/bytecompiler/class.rs | 30 +- core/engine/src/bytecompiler/declarations.rs | 2 + core/engine/src/bytecompiler/function.rs | 4 +- core/engine/src/bytecompiler/mod.rs | 16 + core/engine/src/context/intrinsics.rs | 319 ++++++++++-------- core/engine/src/context/mod.rs | 16 +- core/engine/src/environments/runtime/mod.rs | 10 +- core/engine/src/module/source.rs | 4 +- core/engine/src/module/synthetic.rs | 4 +- core/engine/src/object/builtins/jsfunction.rs | 26 +- core/engine/src/object/jsobject.rs | 165 ++++++--- core/engine/src/object/shape/mod.rs | 72 +++- core/engine/src/object/shape/root_shape.rs | 11 +- .../shape/shared_shape/forward_transition.rs | 38 ++- .../src/object/shape/shared_shape/mod.rs | 133 ++++++-- .../src/object/shape/shared_shape/template.rs | 125 +++++-- core/engine/src/object/shape/unique_shape.rs | 47 ++- core/engine/src/realm.rs | 15 +- core/engine/src/script.rs | 8 +- core/engine/src/vm/mod.rs | 7 +- core/engine/src/vm/opcode/push/environment.rs | 5 +- core/gc/src/context.rs | 33 +- 28 files changed, 794 insertions(+), 371 deletions(-) diff --git a/core/engine/benches/full.rs b/core/engine/benches/full.rs index b327a366991..78e6dfb5548 100644 --- a/core/engine/benches/full.rs +++ b/core/engine/benches/full.rs @@ -19,7 +19,11 @@ static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; fn create_realm(c: &mut Criterion) { c.bench_function("Create Realm", move |b| { let root_shape = RootShape::default(); - b.iter(|| Realm::create(&DefaultHooks, &root_shape)); + b.iter(|| { + Realm::create(&DefaultHooks, &root_shape, &unsafe { + boa_gc::MutationContext::global() + }) + }); }); } diff --git a/core/engine/src/builtins/eval/mod.rs b/core/engine/src/builtins/eval/mod.rs index c523d269c02..7d6cf4cd0cd 100644 --- a/core/engine/src/builtins/eval/mod.rs +++ b/core/engine/src/builtins/eval/mod.rs @@ -274,6 +274,7 @@ impl Eval { let source_text = SourceText::new(source); let spanned_source_text = SpannedSourceText::new_source_only(source_text); + let mc = context.gc_collector(); let mut compiler = ByteCompiler::new( js_string!(""), body.strict(), @@ -283,6 +284,7 @@ impl Eval { false, false, context.interner_mut(), + &mc, in_with, spanned_source_text, // TODO: Could give more information from previous shadow stack. @@ -350,8 +352,8 @@ impl Eval { let global = frame.realm.environment(); frame.environments.push_lexical( lexical_scope.num_bindings_non_local(), - global, - unsafe { boa_gc::MutationContext::global() }, + &global, + &unsafe { boa_gc::MutationContext::global() }, ); } diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index 498cb555b8c..b5279c480d8 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -659,6 +659,7 @@ impl BuiltInFunctionObject { let in_with = context.vm.frame().environments.has_object_environment(); let spanned_source_text = SpannedSourceText::new_empty(); + let mc = context.gc_collector(); let code = FunctionCompiler::new(spanned_source_text) .name(js_string!("anonymous")) .generator(generator) @@ -673,6 +674,7 @@ impl BuiltInFunctionObject { function.scopes(), function.contains_direct_eval(), context.interner_mut(), + &mc, ); let saved = context.vm.frame_mut().environments.pop_to_global(); @@ -1075,7 +1077,7 @@ pub(crate) fn function_call( let global = frame.realm.environment(); let index = frame .environments - .push_lexical(1, global, unsafe { boa_gc::MutationContext::global() }); + .push_lexical(1, &global, &unsafe { boa_gc::MutationContext::global() }); frame.environments.put_lexical_value( BindingLocatorScope::Stack(index), 0, @@ -1092,8 +1094,8 @@ pub(crate) fn function_call( frame.environments.push_function( scope, FunctionSlots::new(this, function_object.clone(), None), - global, - unsafe { boa_gc::MutationContext::global() }, + &global, + &unsafe { boa_gc::MutationContext::global() }, ); } @@ -1186,7 +1188,7 @@ fn function_construct( let global = frame.realm.environment(); let index = frame .environments - .push_lexical(1, global, unsafe { boa_gc::MutationContext::global() }); + .push_lexical(1, &global, &unsafe { boa_gc::MutationContext::global() }); frame.environments.put_lexical_value( BindingLocatorScope::Stack(index), 0, @@ -1214,8 +1216,8 @@ fn function_construct( .clone(), ), ), - global, - unsafe { boa_gc::MutationContext::global() }, + &global, + &unsafe { boa_gc::MutationContext::global() }, ); } diff --git a/core/engine/src/builtins/iterable/mod.rs b/core/engine/src/builtins/iterable/mod.rs index 837f6da38f1..835a71ebacf 100644 --- a/core/engine/src/builtins/iterable/mod.rs +++ b/core/engine/src/builtins/iterable/mod.rs @@ -91,24 +91,27 @@ pub struct IteratorPrototypes { impl Default for IteratorPrototypes { fn default() -> Self { - Self { - iterator: JsObject::with_null_proto(), - async_iterator: JsObject::with_null_proto(), - async_from_sync_iterator: JsObject::with_null_proto(), - array: JsObject::with_null_proto(), - set: JsObject::with_null_proto(), - string: JsObject::with_null_proto(), - regexp_string: JsObject::with_null_proto(), - map: JsObject::with_null_proto(), - #[cfg(feature = "intl")] - segment: JsObject::with_null_proto(), - iterator_helper: JsObject::with_null_proto(), - wrap_for_valid_iterator: JsObject::with_null_proto(), - } + Self::uninit_in(&unsafe { boa_gc::MutationContext::global() }) } } impl IteratorPrototypes { + pub(crate) fn uninit_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self { + iterator: JsObject::with_null_proto_in(mc), + async_iterator: JsObject::with_null_proto_in(mc), + async_from_sync_iterator: JsObject::with_null_proto_in(mc), + array: JsObject::with_null_proto_in(mc), + set: JsObject::with_null_proto_in(mc), + string: JsObject::with_null_proto_in(mc), + regexp_string: JsObject::with_null_proto_in(mc), + map: JsObject::with_null_proto_in(mc), + #[cfg(feature = "intl")] + segment: JsObject::with_null_proto_in(mc), + iterator_helper: JsObject::with_null_proto_in(mc), + wrap_for_valid_iterator: JsObject::with_null_proto_in(mc), + } + } /// Returns the `ArrayIteratorPrototype` object. #[inline] #[must_use] diff --git a/core/engine/src/builtins/json/mod.rs b/core/engine/src/builtins/json/mod.rs index d5fb0aceb13..9d82c87eac9 100644 --- a/core/engine/src/builtins/json/mod.rs +++ b/core/engine/src/builtins/json/mod.rs @@ -293,6 +293,7 @@ impl Json { let spanned_source_text = SpannedSourceText::new_source_only( crate::spanned_source_text::SourceText::new(source_text), ); + let gc = context.gc_collector(); let mut compiler = ByteCompiler::new( js_string!(""), script.strict(), @@ -302,7 +303,8 @@ impl Json { false, false, context.interner_mut(), - in_with, + &gc, + false, spanned_source_text, SourcePath::Json, ); diff --git a/core/engine/src/builtins/uri/mod.rs b/core/engine/src/builtins/uri/mod.rs index a8e50d6eaa7..6a5baadb554 100644 --- a/core/engine/src/builtins/uri/mod.rs +++ b/core/engine/src/builtins/uri/mod.rs @@ -49,11 +49,17 @@ pub struct UriFunctions { impl Default for UriFunctions { fn default() -> Self { + Self::uninit_in(&unsafe { boa_gc::MutationContext::global() }) + } +} + +impl UriFunctions { + pub(crate) fn uninit_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { Self { - decode_uri: JsFunction::empty_intrinsic_function(false), - decode_uri_component: JsFunction::empty_intrinsic_function(false), - encode_uri: JsFunction::empty_intrinsic_function(false), - encode_uri_component: JsFunction::empty_intrinsic_function(false), + decode_uri: JsFunction::empty_intrinsic_function_in(mc, false), + decode_uri_component: JsFunction::empty_intrinsic_function_in(mc, false), + encode_uri: JsFunction::empty_intrinsic_function_in(mc, false), + encode_uri_component: JsFunction::empty_intrinsic_function_in(mc, false), } } } diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index d98020efdc9..25f8563b08c 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/core/engine/src/bytecompiler/class.rs @@ -103,6 +103,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, spanned_source_text, self.source_path.clone(), @@ -156,10 +157,7 @@ impl ByteCompiler<'_> { class.super_ref.is_some(), ); - let code = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - compiler.finish(), - ); + let code = Gc::new(self.mc.0, compiler.finish()); let index = self.push_function_to_constants(code); let class_register = self.register_allocator.alloc(); @@ -417,6 +415,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, self.spanned_source_text.clone_only_source(), self.source_path.clone(), @@ -443,10 +442,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; - let code = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - field_compiler.finish(), - ); + let code = Gc::new(self.mc.0, field_compiler.finish()); let index = self.push_function_to_constants(code); let dst = self.register_allocator.alloc(); @@ -471,6 +467,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, self.spanned_source_text.clone_only_source(), self.source_path.clone(), @@ -492,10 +489,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; - let code = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - field_compiler.finish(), - ); + let code = Gc::new(self.mc.0, field_compiler.finish()); let index = self.push_function_to_constants(code); let dst = self.register_allocator.alloc(); self.emit_get_function(&dst, index); @@ -526,6 +520,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, self.spanned_source_text.clone_only_source(), self.source_path.clone(), @@ -551,7 +546,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; let code = field_compiler.finish(); - let code = Gc::new(&unsafe { boa_gc::MutationContext::global() }, code); + let code = Gc::new(self.mc.0, code); static_elements.push(StaticElement::StaticField { code, @@ -570,6 +565,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, self.spanned_source_text.clone_only_source(), self.source_path.clone(), @@ -595,7 +591,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; let code = field_compiler.finish(); - let code = Gc::new(&unsafe { boa_gc::MutationContext::global() }, code); + let code = Gc::new(self.mc.0, code); static_elements.push(StaticElement::StaticField { code, @@ -613,6 +609,7 @@ impl ByteCompiler<'_> { false, false, self.interner, + self.mc.0, self.in_with, self.spanned_source_text.clone_only_source(), self.source_path.clone(), @@ -638,10 +635,7 @@ impl ByteCompiler<'_> { ); } - let code = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - compiler.finish(), - ); + let code = Gc::new(self.mc.0, compiler.finish()); static_elements.push(StaticElement::StaticBlock(code)); } } diff --git a/core/engine/src/bytecompiler/declarations.rs b/core/engine/src/bytecompiler/declarations.rs index d44a2691fc3..eefc186c143 100644 --- a/core/engine/src/bytecompiler/declarations.rs +++ b/core/engine/src/bytecompiler/declarations.rs @@ -539,6 +539,7 @@ impl ByteCompiler<'_> { &scopes, contains_direct_eval, self.interner, + self.mc.0, ); // Ensures global functions are printed when generating the global flowgraph. @@ -817,6 +818,7 @@ impl ByteCompiler<'_> { &scopes, contains_direct_eval, self.interner, + self.mc.0, ); // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. diff --git a/core/engine/src/bytecompiler/function.rs b/core/engine/src/bytecompiler/function.rs index d1633a34cd6..371b8ab53fc 100644 --- a/core/engine/src/bytecompiler/function.rs +++ b/core/engine/src/bytecompiler/function.rs @@ -122,6 +122,7 @@ impl FunctionCompiler { scopes: &FunctionScopes, contains_direct_eval: bool, interner: &mut Interner, + mc: &boa_gc::MutationContext<'static, 'static>, ) -> Gc<'static, CodeBlock> { self.strict = self.strict || body.strict(); @@ -136,6 +137,7 @@ impl FunctionCompiler { self.r#async, self.generator, interner, + mc, self.in_with, self.spanned_source_text, self.source_path, @@ -227,6 +229,6 @@ impl FunctionCompiler { let code = compiler.finish(); - Gc::new(&unsafe { boa_gc::MutationContext::global() }, code) + Gc::new(mc, code) } } diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 839b40c98fc..e6dcd761d16 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -489,6 +489,15 @@ impl<'a> BorrowMut> for SourcePositionGuard<'_, 'a> { } } +#[derive(Clone, Copy)] +pub(crate) struct McWrapper<'ctx>(pub(crate) &'ctx boa_gc::MutationContext<'static, 'static>); + +impl<'ctx> std::fmt::Debug for McWrapper<'ctx> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("MutationContext").finish() + } +} + /// The [`ByteCompiler`] is used to compile ECMAScript AST from [`boa_ast`] to bytecode. #[derive(Debug)] #[allow(clippy::struct_excessive_bools)] @@ -556,6 +565,8 @@ pub struct ByteCompiler<'ctx> { pub(crate) emitted_mapped_arguments_object_opcode: bool, pub(crate) interner: &'ctx mut Interner, + /// The MutationContext for GC allocations. + pub(crate) mc: McWrapper<'ctx>, spanned_source_text: SpannedSourceText, pub(crate) global_lexs: Vec, @@ -603,6 +614,7 @@ impl<'ctx> ByteCompiler<'ctx> { is_async: bool, is_generator: bool, interner: &'ctx mut Interner, + mc: &'ctx boa_gc::MutationContext<'static, 'static>, in_with: bool, spanned_source_text: SpannedSourceText, source_path: SourcePath, @@ -674,6 +686,7 @@ impl<'ctx> ByteCompiler<'ctx> { variable_scope, lexical_scope, interner, + mc: McWrapper(mc), spanned_source_text, source_path, @@ -2441,6 +2454,7 @@ impl<'ctx> ByteCompiler<'ctx> { scopes, function.contains_direct_eval, self.interner, + self.mc.0, ); self.push_function_to_constants(code) @@ -2522,6 +2536,7 @@ impl<'ctx> ByteCompiler<'ctx> { scopes, function.contains_direct_eval, self.interner, + self.mc.0, ); let index = self.push_function_to_constants(code); @@ -2572,6 +2587,7 @@ impl<'ctx> ByteCompiler<'ctx> { scopes, function.contains_direct_eval, self.interner, + self.mc.0, ); let index = self.push_function_to_constants(code); diff --git a/core/engine/src/context/intrinsics.rs b/core/engine/src/context/intrinsics.rs index 98e601c9d4f..edd57db8cdf 100644 --- a/core/engine/src/context/intrinsics.rs +++ b/core/engine/src/context/intrinsics.rs @@ -39,13 +39,16 @@ impl Intrinsics { /// To initialize all the intrinsics with their spec properties, see [`Realm::initialize`]. /// /// [`Realm::initialize`]: crate::realm::Realm::initialize - pub(crate) fn uninit(root_shape: &RootShape) -> Option { - let constructors = StandardConstructors::default(); - let templates = ObjectTemplates::new(root_shape, &constructors); + pub(crate) fn uninit( + root_shape: &RootShape, + mc: &boa_gc::MutationContext<'static, '_>, + ) -> Option { + let constructors = StandardConstructors::uninit(mc); + let templates = ObjectTemplates::new(mc, root_shape, &constructors); Some(Self { constructors, - objects: IntrinsicObjects::uninit()?, + objects: IntrinsicObjects::uninit(mc)?, templates, }) } @@ -78,14 +81,18 @@ pub struct StandardConstructor { impl Default for StandardConstructor { fn default() -> Self { - Self { - constructor: JsFunction::empty_intrinsic_function(true), - prototype: JsObject::with_null_proto(), - } + Self::uninit(&unsafe { boa_gc::MutationContext::global() }) } } impl StandardConstructor { + /// Creates a new uninitialized `StandardConstructor` using the given context. + pub(crate) fn uninit(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self { + constructor: JsFunction::empty_intrinsic_function_in(mc, true), + prototype: JsObject::with_null_proto_in(mc), + } + } /// Creates a new `StandardConstructor` from the constructor and the prototype. pub(crate) fn new(constructor: JsFunction, prototype: JsObject) -> Self { Self { @@ -94,14 +101,19 @@ impl StandardConstructor { } } - /// Build a constructor with a defined prototype. - fn with_prototype(prototype: JsObject) -> Self { + /// Build a constructor with a defined prototype, using the given context. + fn with_prototype_in(mc: &boa_gc::MutationContext<'static, '_>, prototype: JsObject) -> Self { Self { - constructor: JsFunction::empty_intrinsic_function(true), + constructor: JsFunction::empty_intrinsic_function_in(mc, true), prototype, } } + /// Build a constructor with a defined prototype. + fn with_prototype(prototype: JsObject) -> Self { + Self::with_prototype_in(&unsafe { boa_gc::MutationContext::global() }, prototype) + } + /// Return the prototype of the constructor object. /// /// This is the same as `Object.prototype`, `Array.prototype`, etc. @@ -206,100 +218,111 @@ pub struct StandardConstructors { calendar: StandardConstructor, } -impl Default for StandardConstructors { - fn default() -> Self { +impl StandardConstructors { + pub(crate) fn uninit(mc: &boa_gc::MutationContext<'static, '_>) -> Self { Self { - object: StandardConstructor::with_prototype(JsObject::from_object_and_vtable( - Object::::default(), - &IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, - )), - async_generator_function: StandardConstructor::default(), - proxy: StandardConstructor::default(), - date: StandardConstructor::default(), + object: StandardConstructor::with_prototype_in( + mc, + JsObject::from_object_and_vtable_in( + mc, + Object::::default(), + &IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, + ), + ), + async_generator_function: StandardConstructor::uninit(mc), + proxy: StandardConstructor::uninit(mc), + date: StandardConstructor::uninit(mc), function: StandardConstructor { - constructor: JsFunction::empty_intrinsic_function(true), - prototype: JsFunction::empty_intrinsic_function(false).into(), + constructor: JsFunction::empty_intrinsic_function_in(mc, true), + prototype: JsFunction::empty_intrinsic_function_in(mc, false).into(), }, - async_function: StandardConstructor::default(), - generator_function: StandardConstructor::default(), - array: StandardConstructor::with_prototype(JsObject::from_proto_and_data(None, Array)), - bigint: StandardConstructor::default(), - number: StandardConstructor::with_prototype(JsObject::from_proto_and_data(None, 0.0)), - boolean: StandardConstructor::with_prototype(JsObject::from_proto_and_data( - None, false, - )), - string: StandardConstructor::with_prototype(JsObject::from_proto_and_data( - None, - js_string!(), - )), - regexp: StandardConstructor::default(), - symbol: StandardConstructor::default(), - error: StandardConstructor::default(), - type_error: StandardConstructor::default(), - reference_error: StandardConstructor::default(), - range_error: StandardConstructor::default(), - syntax_error: StandardConstructor::default(), - eval_error: StandardConstructor::default(), - uri_error: StandardConstructor::default(), - aggregate_error: StandardConstructor::default(), - map: StandardConstructor::default(), - set: StandardConstructor::default(), - typed_array: StandardConstructor::default(), - typed_int8_array: StandardConstructor::default(), - typed_uint8_array: StandardConstructor::default(), - typed_uint8clamped_array: StandardConstructor::default(), - typed_int16_array: StandardConstructor::default(), - typed_uint16_array: StandardConstructor::default(), - typed_int32_array: StandardConstructor::default(), - typed_uint32_array: StandardConstructor::default(), - typed_bigint64_array: StandardConstructor::default(), - typed_biguint64_array: StandardConstructor::default(), + async_function: StandardConstructor::uninit(mc), + generator_function: StandardConstructor::uninit(mc), + array: StandardConstructor::with_prototype_in( + mc, + JsObject::from_proto_and_data_in(mc, None, Array), + ), + bigint: StandardConstructor::uninit(mc), + number: StandardConstructor::with_prototype_in( + mc, + JsObject::from_proto_and_data_in(mc, None, 0.0), + ), + boolean: StandardConstructor::with_prototype_in( + mc, + JsObject::from_proto_and_data_in(mc, None, false), + ), + string: StandardConstructor::with_prototype_in( + mc, + JsObject::from_proto_and_data_in(mc, None, js_string!()), + ), + regexp: StandardConstructor::uninit(mc), + symbol: StandardConstructor::uninit(mc), + error: StandardConstructor::uninit(mc), + type_error: StandardConstructor::uninit(mc), + reference_error: StandardConstructor::uninit(mc), + range_error: StandardConstructor::uninit(mc), + syntax_error: StandardConstructor::uninit(mc), + eval_error: StandardConstructor::uninit(mc), + uri_error: StandardConstructor::uninit(mc), + aggregate_error: StandardConstructor::uninit(mc), + map: StandardConstructor::uninit(mc), + set: StandardConstructor::uninit(mc), + typed_array: StandardConstructor::uninit(mc), + typed_int8_array: StandardConstructor::uninit(mc), + typed_uint8_array: StandardConstructor::uninit(mc), + typed_uint8clamped_array: StandardConstructor::uninit(mc), + typed_int16_array: StandardConstructor::uninit(mc), + typed_uint16_array: StandardConstructor::uninit(mc), + typed_int32_array: StandardConstructor::uninit(mc), + typed_uint32_array: StandardConstructor::uninit(mc), + typed_bigint64_array: StandardConstructor::uninit(mc), + typed_biguint64_array: StandardConstructor::uninit(mc), #[cfg(feature = "float16")] - typed_float16_array: StandardConstructor::default(), - typed_float32_array: StandardConstructor::default(), - typed_float64_array: StandardConstructor::default(), - array_buffer: StandardConstructor::default(), - shared_array_buffer: StandardConstructor::default(), - data_view: StandardConstructor::default(), - date_time_format: StandardConstructor::default(), - promise: StandardConstructor::default(), - weak_ref: StandardConstructor::default(), - weak_map: StandardConstructor::default(), - weak_set: StandardConstructor::default(), - iterator: StandardConstructor::default(), - finalization_registry: StandardConstructor::default(), + typed_float16_array: StandardConstructor::uninit(mc), + typed_float32_array: StandardConstructor::uninit(mc), + typed_float64_array: StandardConstructor::uninit(mc), + array_buffer: StandardConstructor::uninit(mc), + shared_array_buffer: StandardConstructor::uninit(mc), + data_view: StandardConstructor::uninit(mc), + date_time_format: StandardConstructor::uninit(mc), + promise: StandardConstructor::uninit(mc), + weak_ref: StandardConstructor::uninit(mc), + weak_map: StandardConstructor::uninit(mc), + weak_set: StandardConstructor::uninit(mc), + iterator: StandardConstructor::uninit(mc), + finalization_registry: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - collator: StandardConstructor::default(), + collator: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - list_format: StandardConstructor::default(), + list_format: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - locale: StandardConstructor::default(), + locale: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - segmenter: StandardConstructor::default(), + segmenter: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - plural_rules: StandardConstructor::default(), + plural_rules: StandardConstructor::uninit(mc), #[cfg(feature = "intl")] - number_format: StandardConstructor::default(), + number_format: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - instant: StandardConstructor::default(), + instant: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - plain_date_time: StandardConstructor::default(), + plain_date_time: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - plain_date: StandardConstructor::default(), + plain_date: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - plain_time: StandardConstructor::default(), + plain_time: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - plain_year_month: StandardConstructor::default(), + plain_year_month: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - plain_month_day: StandardConstructor::default(), + plain_month_day: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - time_zone: StandardConstructor::default(), + time_zone: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - duration: StandardConstructor::default(), + duration: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - zoned_date_time: StandardConstructor::default(), + zoned_date_time: StandardConstructor::uninit(mc), #[cfg(feature = "temporal")] - calendar: StandardConstructor::default(), + calendar: StandardConstructor::uninit(mc), } } } @@ -1164,28 +1187,28 @@ impl IntrinsicObjects { /// /// [`Realm::initialize`]: crate::realm::Realm::initialize #[allow(clippy::unnecessary_wraps)] - pub(crate) fn uninit() -> Option { + pub(crate) fn uninit(mc: &boa_gc::MutationContext<'static, '_>) -> Option { Some(Self { - reflect: JsObject::with_null_proto(), - math: JsObject::with_null_proto(), - json: JsObject::with_null_proto(), - throw_type_error: JsFunction::empty_intrinsic_function(false), - array_prototype_values: JsFunction::empty_intrinsic_function(false), - array_prototype_to_string: JsFunction::empty_intrinsic_function(false), - iterator_prototypes: IteratorPrototypes::default(), - generator: JsObject::with_null_proto(), - async_generator: JsObject::with_null_proto(), - atomics: JsObject::with_null_proto(), - eval: JsFunction::empty_intrinsic_function(false), - uri_functions: UriFunctions::default(), - is_finite: JsFunction::empty_intrinsic_function(false), - is_nan: JsFunction::empty_intrinsic_function(false), - parse_float: JsFunction::empty_intrinsic_function(false), - parse_int: JsFunction::empty_intrinsic_function(false), + reflect: JsObject::with_null_proto_in(mc), + math: JsObject::with_null_proto_in(mc), + json: JsObject::with_null_proto_in(mc), + throw_type_error: JsFunction::empty_intrinsic_function_in(mc, false), + array_prototype_values: JsFunction::empty_intrinsic_function_in(mc, false), + array_prototype_to_string: JsFunction::empty_intrinsic_function_in(mc, false), + iterator_prototypes: IteratorPrototypes::uninit_in(mc), + generator: JsObject::with_null_proto_in(mc), + async_generator: JsObject::with_null_proto_in(mc), + atomics: JsObject::with_null_proto_in(mc), + eval: JsFunction::empty_intrinsic_function_in(mc, false), + uri_functions: UriFunctions::uninit_in(mc), + is_finite: JsFunction::empty_intrinsic_function_in(mc, false), + is_nan: JsFunction::empty_intrinsic_function_in(mc, false), + parse_float: JsFunction::empty_intrinsic_function_in(mc, false), + parse_int: JsFunction::empty_intrinsic_function_in(mc, false), #[cfg(feature = "annex-b")] - escape: JsFunction::empty_intrinsic_function(false), + escape: JsFunction::empty_intrinsic_function_in(mc, false), #[cfg(feature = "annex-b")] - unescape: JsFunction::empty_intrinsic_function(false), + unescape: JsFunction::empty_intrinsic_function_in(mc, false), #[cfg(feature = "intl")] intl: JsObject::new_unique(None, Intl::new()?), #[cfg(feature = "intl")] @@ -1434,45 +1457,56 @@ pub(crate) struct ObjectTemplates { } impl ObjectTemplates { - pub(crate) fn new(root_shape: &RootShape, constructors: &StandardConstructors) -> Self { + pub(crate) fn new( + mc: &boa_gc::MutationContext<'static, '_>, + root_shape: &RootShape, + constructors: &StandardConstructors, + ) -> Self { let root_shape = root_shape.shape(); // pre-initialize used shapes. let ordinary_object = - ObjectTemplate::with_prototype(root_shape, constructors.object().prototype()); + ObjectTemplate::with_prototype_in(mc, root_shape, constructors.object().prototype()); let mut array = ObjectTemplate::new(root_shape); let length_property_key: PropertyKey = js_string!("length").into(); - array.property( + array.property_in( + mc, length_property_key.clone(), Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); - array.set_prototype(constructors.array().prototype()); - - let number = ObjectTemplate::with_prototype(root_shape, constructors.number().prototype()); - let symbol = ObjectTemplate::with_prototype(root_shape, constructors.symbol().prototype()); - let bigint = ObjectTemplate::with_prototype(root_shape, constructors.bigint().prototype()); + array.set_prototype_in(mc, constructors.array().prototype()); + + let number = + ObjectTemplate::with_prototype_in(mc, root_shape, constructors.number().prototype()); + let symbol = + ObjectTemplate::with_prototype_in(mc, root_shape, constructors.symbol().prototype()); + let bigint = + ObjectTemplate::with_prototype_in(mc, root_shape, constructors.bigint().prototype()); let boolean = - ObjectTemplate::with_prototype(root_shape, constructors.boolean().prototype()); + ObjectTemplate::with_prototype_in(mc, root_shape, constructors.boolean().prototype()); let mut string = ObjectTemplate::new(root_shape); - string.property( + string.property_in( + mc, length_property_key.clone(), Attribute::READONLY | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); - string.set_prototype(constructors.string().prototype()); + string.set_prototype_in(mc, constructors.string().prototype()); let mut regexp_without_proto = ObjectTemplate::new(root_shape); - regexp_without_proto.property(js_string!("lastIndex").into(), Attribute::WRITABLE); + regexp_without_proto.property_in(mc, js_string!("lastIndex").into(), Attribute::WRITABLE); let mut regexp = regexp_without_proto.clone(); - regexp.set_prototype(constructors.regexp().prototype()); + regexp.set_prototype_in(mc, constructors.regexp().prototype()); let name_property_key: PropertyKey = js_string!("name").into(); let mut function = ObjectTemplate::new(root_shape); - function.property( + function.property_in( + mc, length_property_key.clone(), Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); - function.property( + function.property_in( + mc, name_property_key, Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); @@ -1481,7 +1515,8 @@ impl ObjectTemplates { let mut async_function = function.clone(); let mut function_with_prototype = function.clone(); - function_with_prototype.property( + function_with_prototype.property_in( + mc, PROTOTYPE.into(), Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); @@ -1490,14 +1525,16 @@ impl ObjectTemplates { let function_with_prototype_without_proto = function_with_prototype.clone(); - function.set_prototype(constructors.function().prototype()); - function_with_prototype.set_prototype(constructors.function().prototype()); - async_function.set_prototype(constructors.async_function().prototype()); - generator_function.set_prototype(constructors.generator_function().prototype()); - async_generator_function.set_prototype(constructors.async_generator_function().prototype()); + function.set_prototype_in(mc, constructors.function().prototype()); + function_with_prototype.set_prototype_in(mc, constructors.function().prototype()); + async_function.set_prototype_in(mc, constructors.async_function().prototype()); + generator_function.set_prototype_in(mc, constructors.generator_function().prototype()); + async_generator_function + .set_prototype_in(mc, constructors.async_generator_function().prototype()); let mut function_prototype = ordinary_object.clone(); - function_prototype.property( + function_prototype.property_in( + mc, CONSTRUCTOR.into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); @@ -1506,7 +1543,8 @@ impl ObjectTemplates { // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - unmapped_arguments.property( + unmapped_arguments.property_in( + mc, length_property_key, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); @@ -1514,7 +1552,8 @@ impl ObjectTemplates { // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, // [[Configurable]]: true }). - unmapped_arguments.property( + unmapped_arguments.property_in( + mc, JsSymbol::iterator().into(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); @@ -1524,7 +1563,8 @@ impl ObjectTemplates { // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, // [[Configurable]]: false }). - unmapped_arguments.accessor( + unmapped_arguments.accessor_in( + mc, js_string!("callee").into(), true, true, @@ -1533,34 +1573,37 @@ impl ObjectTemplates { // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - mapped_arguments.property( + mapped_arguments.property_in( + mc, js_string!("callee").into(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); let mut iterator_result = ordinary_object.clone(); - iterator_result.property( + iterator_result.property_in( + mc, js_string!("value").into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, ); - iterator_result.property( + iterator_result.property_in( + mc, js_string!("done").into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, ); let mut namespace = ObjectTemplate::new(root_shape); - namespace.property(JsSymbol::to_string_tag().into(), Attribute::empty()); + namespace.property_in(mc, JsSymbol::to_string_tag().into(), Attribute::empty()); let with_resolvers = { let mut with_resolvers = ordinary_object.clone(); with_resolvers // 4. Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]). - .property(js_string!("promise").into(), Attribute::all()) + .property_in(mc, js_string!("promise").into(), Attribute::all()) // 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]). - .property(js_string!("resolve").into(), Attribute::all()) + .property_in(mc, js_string!("resolve").into(), Attribute::all()) // 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]). - .property(js_string!("reject").into(), Attribute::all()); + .property_in(mc, js_string!("reject").into(), Attribute::all()); with_resolvers }; @@ -1568,8 +1611,8 @@ impl ObjectTemplates { let wait_async = { let mut obj = ordinary_object.clone(); - obj.property(js_string!("async").into(), Attribute::all()) - .property(js_string!("value").into(), Attribute::all()); + obj.property_in(mc, js_string!("async").into(), Attribute::all()) + .property_in(mc, js_string!("value").into(), Attribute::all()); obj }; diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index bd0aa5783db..218d177ffda 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -474,10 +474,9 @@ impl Context { self.gc.alloc(value) } - /// Returns the active collector. - #[inline] + /// Gets the GC collector. #[must_use] - pub fn gc_collector(&self) -> &boa_gc::MutationContext<'static, 'static> { + pub fn gc_collector(&self) -> &'static boa_gc::MutationContext<'static, 'static> { self.gc.gc_collector() } @@ -549,7 +548,9 @@ impl Context { /// Create a new Realm with the default global bindings. pub fn create_realm(&mut self) -> JsResult { - let realm = Realm::create(self.host_hooks.as_ref(), &self.root_shape)?; + let realm = Realm::create(self.host_hooks.as_ref(), &self.root_shape, &unsafe { + boa_gc::MutationContext::global() + })?; let old_realm = self.enter_realm(realm); @@ -1223,12 +1224,13 @@ impl ContextBuilder { CANNOT_BLOCK_COUNTER.set(CANNOT_BLOCK_COUNTER.get() + 1); } - let root_shape = RootShape::default(); + let mc = unsafe { boa_gc::MutationContext::global() }; + let root_shape = RootShape::new_in(&mc); let host_hooks = self.host_hooks.unwrap_or(Rc::new(DefaultHooks)); let clock = self.clock.unwrap_or_else(|| Rc::new(StdClock::new())); - let realm = Realm::create(host_hooks.as_ref(), &root_shape)?; - let vm = Vm::new(realm); + let realm = Realm::create(host_hooks.as_ref(), &root_shape, &mc)?; + let vm = Vm::new(realm, &mc); let module_loader: Rc = if let Some(loader) = self.module_loader { loader diff --git a/core/engine/src/environments/runtime/mod.rs b/core/engine/src/environments/runtime/mod.rs index 59724b87c38..4e47a4f79ff 100644 --- a/core/engine/src/environments/runtime/mod.rs +++ b/core/engine/src/environments/runtime/mod.rs @@ -216,7 +216,7 @@ impl EnvironmentStack { &mut self, bindings_count: u32, global: &Gc<'static, DeclarativeEnvironment>, - gc: boa_gc::MutationContext<'static, '_>, + gc: &boa_gc::MutationContext<'static, '_>, ) -> u32 { let (poisoned, with) = self.compute_poisoned_with(global); @@ -240,14 +240,14 @@ impl EnvironmentStack { scope: Scope, function_slots: FunctionSlots, global: &Gc<'static, DeclarativeEnvironment>, - gc: boa_gc::MutationContext<'static, '_>, + gc: &boa_gc::MutationContext<'static, '_>, ) { let num_bindings = scope.num_bindings_non_local(); let (poisoned, with) = self.compute_poisoned_with(global); self.push_env(Environment::Declarative(Gc::new( - &gc, + gc, DeclarativeEnvironment::new( DeclarativeEnvironmentKind::Function(FunctionEnvironment::new( num_bindings, @@ -261,10 +261,10 @@ impl EnvironmentStack { } /// Push a module environment on the environments stack. - pub(crate) fn push_module(&mut self, scope: Scope, gc: boa_gc::MutationContext<'static, '_>) { + pub(crate) fn push_module(&mut self, scope: Scope, gc: &boa_gc::MutationContext<'static, '_>) { let num_bindings = scope.num_bindings_non_local(); self.push_env(Environment::Declarative(Gc::new( - &gc, + gc, DeclarativeEnvironment::new( DeclarativeEnvironmentKind::Module(ModuleEnvironment::new(num_bindings, scope)), false, diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index 2f32a2d2cb3..c7962cc5b32 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -1645,6 +1645,7 @@ impl SourceTextModule { let env = source.scope().clone(); let spanned_source_text = SpannedSourceText::new_source_only(source_text.clone()); + let mc = context.gc_collector(); let mut compiler = ByteCompiler::new( js_string!("
"), true, @@ -1654,6 +1655,7 @@ impl SourceTextModule { self.code.has_tla, false, context.interner_mut(), + &mc, false, spanned_source_text, self.code.path.clone().into(), @@ -1834,7 +1836,7 @@ impl SourceTextModule { // 8. Let moduleContext be a new ECMAScript code execution context. let mut envs = EnvironmentStack::new(); - envs.push_module(source.scope().clone(), unsafe { + envs.push_module(source.scope().clone(), &unsafe { boa_gc::MutationContext::global() }); drop(status); diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index 888c0db39cf..f0bbde545fa 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -311,6 +311,7 @@ impl SyntheticModule { // TODO: A bit of a hack to be able to pass the currently active runnable without an // available codeblock to execute. + let mc = context.gc_collector(); let compiler = ByteCompiler::new( js_string!(""), true, @@ -320,6 +321,7 @@ impl SyntheticModule { false, false, context.interner_mut(), + &mc, false, // A synthetic module does not contain `SourceText` SpannedSourceText::new_empty(), @@ -342,7 +344,7 @@ impl SyntheticModule { let cb = context.alloc(finished); let mut envs = EnvironmentStack::new(); - envs.push_module(module_scope, unsafe { boa_gc::MutationContext::global() }); + envs.push_module(module_scope, &unsafe { boa_gc::MutationContext::global() }); for locator in exports { // b. Perform ! env.InitializeBinding(exportName, undefined). diff --git a/core/engine/src/object/builtins/jsfunction.rs b/core/engine/src/object/builtins/jsfunction.rs index c1bc9d152e8..0d165e0c17c 100644 --- a/core/engine/src/object/builtins/jsfunction.rs +++ b/core/engine/src/object/builtins/jsfunction.rs @@ -122,14 +122,14 @@ impl JsFunction { Self { inner: object } } - /// Creates a new, empty intrinsic function object with only its function internal methods set. - /// - /// Mainly used to initialize objects before a [`Context`] is available to do so. - /// - /// [`Context`]: crate::Context - pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self { + /// Creates a new, empty intrinsic function object with only its function internal methods set, using the given context. + pub(crate) fn empty_intrinsic_function_in( + mc: &boa_gc::MutationContext<'static, '_>, + constructor: bool, + ) -> Self { Self { - inner: JsObject::from_proto_and_data( + inner: JsObject::from_proto_and_data_in( + mc, None, NativeFunctionObject { f: NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())), @@ -141,6 +141,18 @@ impl JsFunction { } } + /// Creates a new, empty intrinsic function object with only its function internal methods set. + /// + /// Mainly used to initialize objects before a [`Context`] is available to do so. + /// + /// [`Context`]: crate::Context + pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self { + Self::empty_intrinsic_function_in( + &unsafe { boa_gc::MutationContext::global() }, + constructor, + ) + } + /// Creates a [`JsFunction`] from a [`JsObject`], or returns `None` if the object is not a function. /// /// This does not clone the fields of the function, it only does a shallow clone of the object. diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs index 711bb3cee2b..10cfd824459 100644 --- a/core/engine/src/object/jsobject.rs +++ b/core/engine/src/object/jsobject.rs @@ -116,13 +116,14 @@ impl JsObject { Self::with_object_proto(intrinsics) } - /// Creates a new `JsObject` from its inner object and its vtable. - pub(crate) fn from_object_and_vtable( + /// Creates a new `JsObject` from its inner object and its vtable using the given context. + pub(crate) fn from_object_and_vtable_in( + mc: &boa_gc::MutationContext<'static, '_>, object: Object, vtable: &'static InternalObjectMethods, ) -> Self { let inner = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, VTableObject { object: GcRefCell::new(object), vtable, @@ -132,6 +133,18 @@ impl JsObject { JsObject { inner }.upcast() } + /// Creates a new `JsObject` from its inner object and its vtable. + pub(crate) fn from_object_and_vtable( + object: Object, + vtable: &'static InternalObjectMethods, + ) -> Self { + Self::from_object_and_vtable_in( + &unsafe { boa_gc::MutationContext::global() }, + object, + vtable, + ) + } + /// Creates a new ordinary object with its prototype set to the `Object` prototype. /// /// This is equivalent to calling the specification's abstract operation @@ -158,6 +171,13 @@ impl JsObject { ) } + /// Creates a new ordinary object, with its prototype set to null using the given context. + #[inline] + #[must_use] + pub fn with_null_proto_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self::from_proto_and_data_in(mc, None, OrdinaryObject) + } + /// Creates a new ordinary object, with its prototype set to null. /// /// This is equivalent to calling the specification's abstract operation @@ -176,7 +196,30 @@ impl JsObject { #[inline] #[must_use] pub fn with_null_proto() -> Self { - Self::from_proto_and_data(None, OrdinaryObject) + Self::with_null_proto_in(&unsafe { boa_gc::MutationContext::global() }) + } + + /// Creates a new object with the provided prototype and object data, using the given context. + pub fn from_proto_and_data_in>, T: NativeObject>( + mc: &boa_gc::MutationContext<'static, '_>, + prototype: O, + data: T, + ) -> Self { + let internal_methods = data.internal_methods(); + let inner = Gc::new( + mc, + VTableObject { + object: GcRefCell::new(Object { + data: ObjectData::new(data), + properties: PropertyMap::from_prototype_unique_shape(prototype.into()), + extensible: true, + private_elements: ThinVec::new(), + }), + vtable: internal_methods, + }, + ); + + JsObject { inner }.upcast() } /// Creates a new object with the provided prototype and object data. @@ -209,13 +252,33 @@ impl JsObject { prototype: O, data: T, ) -> Self { + Self::from_proto_and_data_in( + &unsafe { boa_gc::MutationContext::global() }, + prototype, + data, + ) + } + + /// Creates a new object with the provided prototype and object data using the given context. + pub(crate) fn from_proto_and_data_with_shared_shape_in< + O: Into>, + T: NativeObject, + >( + mc: &boa_gc::MutationContext<'static, '_>, + root_shape: &RootShape, + prototype: O, + data: T, + ) -> JsObject { let internal_methods = data.internal_methods(); let inner = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, VTableObject { object: GcRefCell::new(Object { data: ObjectData::new(data), - properties: PropertyMap::from_prototype_unique_shape(prototype.into()), + properties: PropertyMap::from_prototype_with_shared_shape( + root_shape, + prototype.into(), + ), extensible: true, private_elements: ThinVec::new(), }), @@ -223,7 +286,7 @@ impl JsObject { }, ); - JsObject { inner }.upcast() + JsObject { inner } } /// Creates a new object with the provided prototype and object data. @@ -238,24 +301,12 @@ impl JsObject { prototype: O, data: T, ) -> JsObject { - let internal_methods = data.internal_methods(); - let inner = Gc::new( + Self::from_proto_and_data_with_shared_shape_in( &unsafe { boa_gc::MutationContext::global() }, - VTableObject { - object: GcRefCell::new(Object { - data: ObjectData::new(data), - properties: PropertyMap::from_prototype_with_shared_shape( - root_shape, - prototype.into(), - ), - extensible: true, - private_elements: ThinVec::new(), - }), - vtable: internal_methods, - }, - ); - - JsObject { inner } + root_shape, + prototype, + data, + ) } /// Downcasts the object's inner data if the object is of type `T`. @@ -1057,6 +1108,33 @@ impl JsObject { } impl JsObject { + /// Creates a new `JsObject` from a `RootShape`, prototype, and data using the given context. + pub fn new_in>>( + mc: &boa_gc::MutationContext<'static, '_>, + root_shape: &RootShape, + prototype: O, + data: T, + ) -> Self { + let internal_methods = data.internal_methods(); + let inner = Gc::new( + mc, + VTableObject { + object: GcRefCell::new(Object { + data: ObjectData::new(data), + properties: PropertyMap::from_prototype_with_shared_shape( + root_shape, + prototype.into(), + ), + extensible: true, + private_elements: ThinVec::new(), + }), + vtable: internal_methods, + }, + ); + + Self { inner } + } + /// Creates a new `JsObject` from its root shape, prototype, and data. /// /// Note that the returned object will not be erased to be convertible to a @@ -1080,16 +1158,27 @@ impl JsObject { /// assert!(obj.is_ordinary()); /// ``` pub fn new>>(root_shape: &RootShape, prototype: O, data: T) -> Self { + Self::new_in( + &unsafe { boa_gc::MutationContext::global() }, + root_shape, + prototype, + data, + ) + } + + /// Creates a new `JsObject` from prototype, and data using the given context. + pub fn new_unique_in>>( + mc: &boa_gc::MutationContext<'static, '_>, + prototype: O, + data: T, + ) -> Self { let internal_methods = data.internal_methods(); let inner = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, VTableObject { object: GcRefCell::new(Object { data: ObjectData::new(data), - properties: PropertyMap::from_prototype_with_shared_shape( - root_shape, - prototype.into(), - ), + properties: PropertyMap::from_prototype_unique_shape(prototype.into()), extensible: true, private_elements: ThinVec::new(), }), @@ -1118,21 +1207,11 @@ impl JsObject { /// assert!(obj.prototype().is_none()); /// ``` pub fn new_unique>>(prototype: O, data: T) -> Self { - let internal_methods = data.internal_methods(); - let inner = Gc::new( + Self::new_unique_in( &unsafe { boa_gc::MutationContext::global() }, - VTableObject { - object: GcRefCell::new(Object { - data: ObjectData::new(data), - properties: PropertyMap::from_prototype_unique_shape(prototype.into()), - extensible: true, - private_elements: ThinVec::new(), - }), - vtable: internal_methods, - }, - ); - - Self { inner } + prototype, + data, + ) } /// Upcasts this object's inner data from a specific type `T` to an erased type diff --git a/core/engine/src/object/shape/mod.rs b/core/engine/src/object/shape/mod.rs index bfb7b512183..e4c5889093d 100644 --- a/core/engine/src/object/shape/mod.rs +++ b/core/engine/src/object/shape/mod.rs @@ -103,33 +103,45 @@ impl Shape { None } - /// Create an insert property transitions returning the new transitioned [`Shape`]. + /// Create an insert property transitions returning the new transitioned [`Shape`] using the given context. /// /// NOTE: This assumes that there is no property with the given key! - pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + pub(crate) fn insert_property_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: TransitionKey, + ) -> Self { match &self.inner { Inner::Shared(shape) => { - let shape = shape.insert_property_transition(key); + let shape = shape.insert_property_transition_in(mc, key); if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { return shape.to_unique().into(); } shape.into() } - Inner::Unique(shape) => shape.insert_property_transition(key).into(), + Inner::Unique(shape) => shape.insert_property_transition(key).into(), // UniqueShape insert doesn't allocate new GC } } + /// Create an insert property transitions returning the new transitioned [`Shape`]. + /// + /// NOTE: This assumes that there is no property with the given key! + pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + self.insert_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + /// Create a change attribute property transitions returning [`ChangeTransition`] containing the new [`Shape`] - /// and actions to be performed + /// and actions to be performed, using the given context. /// /// NOTE: This assumes that there already is a property with the given key! - pub(crate) fn change_attributes_transition( + pub(crate) fn change_attributes_transition_in( &self, + mc: &boa_gc::MutationContext<'static, '_>, key: TransitionKey, ) -> ChangeTransition { match &self.inner { Inner::Shared(shape) => { - let change_transition = shape.change_attributes_transition(key); + let change_transition = shape.change_attributes_transition_in(mc, key); let shape = if change_transition.shape.transition_count() >= Self::TRANSITION_COUNT_MAX { change_transition.shape.to_unique().into() @@ -145,13 +157,28 @@ impl Shape { } } - /// Remove a property property from the [`Shape`] returning the new transitioned [`Shape`]. + /// Create a change attribute property transitions returning [`ChangeTransition`] containing the new [`Shape`] + /// and actions to be performed /// /// NOTE: This assumes that there already is a property with the given key! - pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + pub(crate) fn change_attributes_transition( + &self, + key: TransitionKey, + ) -> ChangeTransition { + self.change_attributes_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + + /// Remove a property from the [`Shape`] returning the new transitioned [`Shape`] using the given context. + /// + /// NOTE: This assumes that there already is a property with the given key! + pub(crate) fn remove_property_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: &PropertyKey, + ) -> Self { match &self.inner { Inner::Shared(shape) => { - let shape = shape.remove_property_transition(key); + let shape = shape.remove_property_transition_in(mc, key); if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { return shape.to_unique().into(); } @@ -161,11 +188,22 @@ impl Shape { } } - /// Create a prototype transitions returning the new transitioned [`Shape`]. - pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + /// Remove a property from the [`Shape`] returning the new transitioned [`Shape`]. + /// + /// NOTE: This assumes that there already is a property with the given key! + pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + self.remove_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + + /// Create a prototype transition returning the new transitioned [`Shape`] using the given context. + pub(crate) fn change_prototype_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsPrototype, + ) -> Self { match &self.inner { Inner::Shared(shape) => { - let shape = shape.change_prototype_transition(prototype); + let shape = shape.change_prototype_transition_in(mc, prototype); if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { return shape.to_unique().into(); } @@ -175,6 +213,14 @@ impl Shape { } } + /// Create a prototype transition returning the new transitioned [`Shape`]. + pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + self.change_prototype_transition_in( + &unsafe { boa_gc::MutationContext::global() }, + prototype, + ) + } + /// Get the [`JsPrototype`] of the [`Shape`]. #[must_use] pub fn prototype(&self) -> JsPrototype { diff --git a/core/engine/src/object/shape/root_shape.rs b/core/engine/src/object/shape/root_shape.rs index 9cc3de38e59..278bddc6935 100644 --- a/core/engine/src/object/shape/root_shape.rs +++ b/core/engine/src/object/shape/root_shape.rs @@ -13,13 +13,18 @@ pub struct RootShape { impl Default for RootShape { #[inline] fn default() -> Self { - Self { - shape: SharedShape::root(), - } + Self::new_in(&unsafe { boa_gc::MutationContext::global() }) } } impl RootShape { + /// Create a new root shape using the given context. + #[inline] + pub(crate) fn new_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self { + shape: SharedShape::root_in(mc), + } + } /// Gets the inner [`SharedShape`]. #[must_use] pub const fn shape(&self) -> &SharedShape { diff --git a/core/engine/src/object/shape/shared_shape/forward_transition.rs b/core/engine/src/object/shape/shared_shape/forward_transition.rs index 88c286171d3..5846c7916b9 100644 --- a/core/engine/src/object/shape/shared_shape/forward_transition.rs +++ b/core/engine/src/object/shape/shared_shape/forward_transition.rs @@ -55,9 +55,10 @@ pub(super) struct ForwardTransition { } impl ForwardTransition { - /// Insert a property transition. - pub(super) fn insert_property( + /// Insert a property transition using the given context. + pub(super) fn insert_property_in( &self, + mc: &boa_gc::MutationContext<'static, '_>, key: TransitionKey, value: &Gc<'static, SharedShapeInner>, ) { @@ -68,14 +69,25 @@ impl ForwardTransition { properties.map.retain(|_, v| v.is_upgradable()); } - properties.map.insert( - key, - WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, value), - ); + properties.map.insert(key, WeakGc::new(mc, value)); } - /// Insert a prototype transition. - pub(super) fn insert_prototype(&self, key: JsPrototype, value: &Gc<'static, SharedShapeInner>) { + /// Insert a property transition. + pub(super) fn insert_property( + &self, + key: TransitionKey, + value: &Gc<'static, SharedShapeInner>, + ) { + self.insert_property_in(&unsafe { boa_gc::MutationContext::global() }, key, value) + } + + /// Insert a prototype transition using the given context. + pub(super) fn insert_prototype_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: JsPrototype, + value: &Gc<'static, SharedShapeInner>, + ) { let mut this = self.inner.borrow_mut(); let prototypes = this.prototypes.get_or_insert_with(Box::default); @@ -83,10 +95,12 @@ impl ForwardTransition { prototypes.map.retain(|_, v| v.is_upgradable()); } - prototypes.map.insert( - key, - WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, value), - ); + prototypes.map.insert(key, WeakGc::new(mc, value)); + } + + /// Insert a prototype transition. + pub(super) fn insert_prototype(&self, key: JsPrototype, value: &Gc<'static, SharedShapeInner>) { + self.insert_prototype_in(&unsafe { boa_gc::MutationContext::global() }, key, value) } /// Get a property transition, return [`None`] otherwise. diff --git a/core/engine/src/object/shape/shared_shape/mod.rs b/core/engine/src/object/shape/shared_shape/mod.rs index cbbfb1dbecb..3448ac5455a 100644 --- a/core/engine/src/object/shape/shared_shape/mod.rs +++ b/core/engine/src/object/shape/shared_shape/mod.rs @@ -163,32 +163,50 @@ impl SharedShape { self.inner.prototype.as_ref() == Some(prototype) } - /// Create a new [`SharedShape`]. - fn new(inner: Inner) -> Self { + /// Create a new [`SharedShape`] using the given context. + fn new_in(mc: &boa_gc::MutationContext<'static, '_>, inner: Inner) -> Self { Self { - inner: Gc::new(&unsafe { boa_gc::MutationContext::global() }, inner), + inner: Gc::new(mc, inner), } } + /// Create a new [`SharedShape`]. + fn new(inner: Inner) -> Self { + Self::new_in(&unsafe { boa_gc::MutationContext::global() }, inner) + } + + /// Create a root [`SharedShape`] using the given context. + #[must_use] + pub(crate) fn root_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { + Self::new_in( + mc, + Inner { + forward_transitions: ForwardTransition::default(), + prototype: None, + property_count: 0, + // Most of the time the root shape initiates with between 1-4 properties. + property_table: PropertyTable::with_capacity(4), + previous: None, + flags: ShapeFlags::default(), + transition_count: 0, + }, + ) + } + /// Create a root [`SharedShape`]. #[must_use] pub(crate) fn root() -> Self { - Self::new(Inner { - forward_transitions: ForwardTransition::default(), - prototype: None, - property_count: 0, - // Most of the time the root shape initiates with between 1-4 properties. - property_table: PropertyTable::with_capacity(4), - previous: None, - flags: ShapeFlags::default(), - transition_count: 0, - }) + Self::root_in(&unsafe { boa_gc::MutationContext::global() }) } - /// Create a [`SharedShape`] change prototype transition. - pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + /// Create a [`SharedShape`] change prototype transition using the given context. + pub(crate) fn change_prototype_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsPrototype, + ) -> Self { if let Some(shape) = self.forward_transitions().get_prototype(&prototype) { - if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::global() }) { + if let Some(inner) = shape.upgrade(mc) { return Self { inner }; } @@ -203,7 +221,7 @@ impl SharedShape { transition_count: self.transition_count() + 1, flags: ShapeFlags::prototype_transition_from(self.flags()), }; - let new_shape = Self::new(new_inner_shape); + let new_shape = Self::new_in(mc, new_inner_shape); self.forward_transitions() .insert_prototype(prototype, &new_shape.inner); @@ -211,11 +229,23 @@ impl SharedShape { new_shape } - /// Create a [`SharedShape`] insert property transition. - pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + /// Create a [`SharedShape`] change prototype transition. + pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + self.change_prototype_transition_in( + &unsafe { boa_gc::MutationContext::global() }, + prototype, + ) + } + + /// Create a [`SharedShape`] insert property transition using the given context. + pub(crate) fn insert_property_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: TransitionKey, + ) -> Self { // Check if we have already created such a transition, if so use it! if let Some(shape) = self.forward_transitions().get_property(&key) { - if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::global() }) { + if let Some(inner) = shape.upgrade(mc) { return Self { inner }; } @@ -236,7 +266,7 @@ impl SharedShape { transition_count: self.transition_count() + 1, flags: ShapeFlags::insert_property_transition_from(self.flags()), }; - let new_shape = Self::new(new_inner_shape); + let new_shape = Self::new_in(mc, new_inner_shape); self.forward_transitions() .insert_property(key, &new_shape.inner); @@ -244,16 +274,30 @@ impl SharedShape { new_shape } + /// Create a [`SharedShape`] insert property transition. + pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + self.insert_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + /// Create a [`SharedShape`] change prototype transition, returning [`ChangeTransition`]. pub(crate) fn change_attributes_transition( &self, key: TransitionKey, + ) -> ChangeTransition { + self.change_attributes_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + + /// Create a [`SharedShape`] change prototype transition using the given context, returning [`ChangeTransition`]. + pub(crate) fn change_attributes_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: TransitionKey, ) -> ChangeTransition { let slot = self.property_table().get_expect(&key.property_key); // Check if we have already created such a transition, if so use it! if let Some(shape) = self.forward_transitions().get_property(&key) { - if let Some(inner) = shape.upgrade(&unsafe { boa_gc::MutationContext::global() }) { + if let Some(inner) = shape.upgrade(mc) { let action = if slot.attributes.width_match(key.attributes) { ChangeTransitionAction::Nothing } else if slot.attributes.is_accessor_descriptor() { @@ -412,13 +456,17 @@ impl SharedShape { (base, prototype, transitions) } - /// Remove a property from [`SharedShape`], returning the new [`SharedShape`]. - pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + /// Remove a property from [`SharedShape`], returning the new [`SharedShape`] using the given context. + pub(crate) fn remove_property_transition_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + key: &PropertyKey, + ) -> Self { let (mut base, prototype, transitions) = self.rollback_before(key); // Apply prototype transition, if it was found. if let Some(prototype) = prototype { - base = base.change_prototype_transition(prototype); + base = base.change_prototype_transition_in(mc, prototype); } for (property_key, attributes) in transitions.into_iter().rev() { @@ -426,12 +474,17 @@ impl SharedShape { property_key, attributes, }; - base = base.insert_property_transition(transition); + base = base.insert_property_transition_in(mc, transition); } base } + /// Remove a property from [`SharedShape`], returning the new [`SharedShape`]. + pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + self.remove_property_transition_in(&unsafe { boa_gc::MutationContext::global() }, key) + } + /// Do a property lookup, returns [`None`] if property not found. pub(crate) fn lookup(&self, key: &PropertyKey) -> Option { let property_count = self.property_count(); @@ -481,27 +534,39 @@ pub(crate) struct WeakSharedShape { impl WeakSharedShape { /// Upgrade returns a [`SharedShape`] pointer for the internal value if the pointer is still live, - /// or [`None`] if the value was already garbage collected. + /// or [`None`] if the value was already garbage collected, using the given context. #[inline] #[must_use] - pub(crate) fn upgrade(&self) -> Option { + pub(crate) fn upgrade_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + ) -> Option { Some(SharedShape { - inner: self - .inner - .upgrade(&unsafe { boa_gc::MutationContext::global() })?, + inner: self.inner.upgrade(mc)?, }) } + /// Upgrade returns a [`SharedShape`] pointer for the internal value if the pointer is still live, + /// or [`None`] if the value was already garbage collected. + #[inline] + #[must_use] + pub(crate) fn upgrade(&self) -> Option { + self.upgrade_in(&unsafe { boa_gc::MutationContext::global() }) + } + #[allow(dead_code)] pub(crate) fn is_upgradable(&self) -> bool { self.inner.is_upgradable() } + pub(crate) fn new_in(mc: &boa_gc::MutationContext<'static, '_>, value: &SharedShape) -> Self { + WeakSharedShape { + inner: WeakGc::new(mc, &value.inner), + } + } } impl From<&SharedShape> for WeakSharedShape { fn from(value: &SharedShape) -> Self { - WeakSharedShape { - inner: WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, &value.inner), - } + Self::new_in(&unsafe { boa_gc::MutationContext::global() }, value) } } diff --git a/core/engine/src/object/shape/shared_shape/template.rs b/core/engine/src/object/shape/shared_shape/template.rs index 2e6c9fb90cb..0359b79153a 100644 --- a/core/engine/src/object/shape/shared_shape/template.rs +++ b/core/engine/src/object/shape/shared_shape/template.rs @@ -27,10 +27,23 @@ impl ObjectTemplate { } } + /// Create and [`ObjectTemplate`] with a prototype using the given context. + pub(crate) fn with_prototype_in( + mc: &boa_gc::MutationContext<'static, '_>, + shape: &SharedShape, + prototype: JsObject, + ) -> Self { + let shape = shape.change_prototype_transition_in(mc, Some(prototype)); + Self { shape } + } + /// Create and [`ObjectTemplate`] with a prototype. pub(crate) fn with_prototype(shape: &SharedShape, prototype: JsObject) -> Self { - let shape = shape.change_prototype_transition(Some(prototype)); - Self { shape } + Self::with_prototype_in( + &unsafe { boa_gc::MutationContext::global() }, + shape, + prototype, + ) } /// Check if the shape has a specific, prototype. @@ -38,12 +51,25 @@ impl ObjectTemplate { self.shape.has_prototype(prototype) } + /// Set the prototype of the [`ObjectTemplate`] using the given context. + /// + /// This assumes that the prototype has not been set yet. + pub(crate) fn set_prototype_in( + &mut self, + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsObject, + ) -> &mut Self { + self.shape = self + .shape + .change_prototype_transition_in(mc, Some(prototype)); + self + } + /// Set the prototype of the [`ObjectTemplate`]. /// /// This assumes that the prototype has not been set yet. pub(crate) fn set_prototype(&mut self, prototype: JsObject) -> &mut Self { - self.shape = self.shape.change_prototype_transition(Some(prototype)); - self + self.set_prototype_in(&unsafe { boa_gc::MutationContext::global() }, prototype) } /// Returns the inner shape of the [`ObjectTemplate`]. @@ -51,33 +77,51 @@ impl ObjectTemplate { &self.shape } - /// Add a data property to the [`ObjectTemplate`]. + /// Add a data property to the [`ObjectTemplate`] using the given context. /// /// This assumes that the property with the given key was not previously set /// and that it's a string or symbol. - pub(crate) fn property(&mut self, key: PropertyKey, attributes: Attribute) -> &mut Self { + pub(crate) fn property_in( + &mut self, + mc: &boa_gc::MutationContext<'static, '_>, + key: PropertyKey, + attributes: Attribute, + ) -> &mut Self { debug_assert!(!matches!(&key, PropertyKey::Index(_))); - let attributes = SlotAttributes::from_bits_truncate(attributes.bits()); - self.shape = self.shape.insert_property_transition(TransitionKey { + let transition = TransitionKey { property_key: key, - attributes, - }); + attributes: SlotAttributes::from_bits_truncate(attributes.bits()), + }; + self.shape = self.shape.insert_property_transition_in(mc, transition); self } + /// Add a data property to the [`ObjectTemplate`]. + /// + /// This assumes that the property with the given key was not previously set + /// and that it's a string or symbol. + pub(crate) fn property(&mut self, key: PropertyKey, attributes: Attribute) -> &mut Self { + self.property_in( + &unsafe { boa_gc::MutationContext::global() }, + key, + attributes, + ) + } + /// Add a accessor property to the [`ObjectTemplate`]. /// /// This assumes that the property with the given key was not previously set /// and that it's a string or symbol. - pub(crate) fn accessor( + /// Add a accessor property to the [`ObjectTemplate`] using the given context. + pub(crate) fn accessor_in( &mut self, + mc: &boa_gc::MutationContext<'static, '_>, key: PropertyKey, get: bool, set: bool, attributes: Attribute, ) -> &mut Self { - // TODO: We don't support indexed keys. debug_assert!(!matches!(&key, PropertyKey::Index(_))); let attributes = { @@ -97,29 +141,66 @@ impl ObjectTemplate { result }; - self.shape = self.shape.insert_property_transition(TransitionKey { - property_key: key, - attributes, - }); + self.shape = self.shape.insert_property_transition_in( + mc, + TransitionKey { + property_key: key, + attributes, + }, + ); self } - /// Create an object from the [`ObjectTemplate`] + /// Add a accessor property to the [`ObjectTemplate`]. /// - /// The storage must match the properties provided. - pub(crate) fn create(&self, data: T, storage: Vec) -> JsObject { + /// This assumes that the property with the given key was not previously set + /// and that it's a string or symbol. + pub(crate) fn accessor( + &mut self, + key: PropertyKey, + get: bool, + set: bool, + attributes: Attribute, + ) -> &mut Self { + self.accessor_in( + &unsafe { boa_gc::MutationContext::global() }, + key, + get, + set, + attributes, + ) + } + + /// Create an object from the [`ObjectTemplate`] using the given context. + pub(crate) fn create_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + data: T, + storage: Vec, + ) -> JsObject { let internal_methods = data.internal_methods(); + let mut properties = PropertyMap::new( + self.shape.clone().into(), + crate::object::IndexedProperties::default(), + ); + properties.storage = storage; + let mut object = Object { data: ObjectData::new(data), extensible: true, - properties: PropertyMap::new(self.shape.clone().into(), IndexedProperties::default()), + properties, private_elements: ThinVec::new(), }; - object.properties.storage = storage; + JsObject::from_object_and_vtable_in(mc, object, internal_methods) + } - JsObject::from_object_and_vtable(object, internal_methods) + /// Create an object from the [`ObjectTemplate`] + /// + /// The storage must match the properties provided. + pub(crate) fn create(&self, data: T, storage: Vec) -> JsObject { + self.create_in(&unsafe { boa_gc::MutationContext::global() }, data, storage) } /// Create an object from the [`ObjectTemplate`] diff --git a/core/engine/src/object/shape/unique_shape.rs b/core/engine/src/object/shape/unique_shape.rs index b050e4bf5eb..2b19a497af4 100644 --- a/core/engine/src/object/shape/unique_shape.rs +++ b/core/engine/src/object/shape/unique_shape.rs @@ -34,11 +34,15 @@ pub(crate) struct UniqueShape { } impl UniqueShape { - /// Create a new [`UniqueShape`]. - pub(crate) fn new(prototype: JsPrototype, property_table: PropertyTableInner) -> Self { + /// Create a new [`UniqueShape`] using the given context. + pub(crate) fn new_in( + mc: &boa_gc::MutationContext<'static, '_>, + prototype: JsPrototype, + property_table: PropertyTableInner, + ) -> Self { Self { inner: Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, Inner { property_table: RefCell::new(property_table), prototype: GcRefCell::new(prototype), @@ -47,6 +51,15 @@ impl UniqueShape { } } + /// Create a new [`UniqueShape`]. + pub(crate) fn new(prototype: JsPrototype, property_table: PropertyTableInner) -> Self { + Self::new_in( + &unsafe { boa_gc::MutationContext::global() }, + prototype, + property_table, + ) + } + pub(crate) fn override_internal( &self, property_table: PropertyTableInner, @@ -254,27 +267,39 @@ pub(crate) struct WeakUniqueShape { impl WeakUniqueShape { /// Upgrade returns a [`UniqueShape`] pointer for the internal value if the pointer is still live, - /// or [`None`] if the value was already garbage collected. + /// or [`None`] if the value was already garbage collected, using the given context. #[inline] #[must_use] - pub(crate) fn upgrade(&self) -> Option { + pub(crate) fn upgrade_in( + &self, + mc: &boa_gc::MutationContext<'static, '_>, + ) -> Option { Some(UniqueShape { - inner: self - .inner - .upgrade(&unsafe { boa_gc::MutationContext::global() })?, + inner: self.inner.upgrade(mc)?, }) } + /// Upgrade returns a [`UniqueShape`] pointer for the internal value if the pointer is still live, + /// or [`None`] if the value was already garbage collected. + #[inline] + #[must_use] + pub(crate) fn upgrade(&self) -> Option { + self.upgrade_in(&unsafe { boa_gc::MutationContext::global() }) + } + #[allow(dead_code)] pub(crate) fn is_upgradable(&self) -> bool { self.inner.is_upgradable() } + pub(crate) fn new_in(mc: &boa_gc::MutationContext<'static, '_>, value: &UniqueShape) -> Self { + WeakUniqueShape { + inner: WeakGc::new(mc, &value.inner), + } + } } impl From<&UniqueShape> for WeakUniqueShape { fn from(value: &UniqueShape) -> Self { - WeakUniqueShape { - inner: WeakGc::new(&unsafe { boa_gc::MutationContext::global() }, &value.inner), - } + Self::new_in(&unsafe { boa_gc::MutationContext::global() }, value) } } diff --git a/core/engine/src/realm.rs b/core/engine/src/realm.rs index c133ce823eb..a1d0ec1c8b1 100644 --- a/core/engine/src/realm.rs +++ b/core/engine/src/realm.rs @@ -77,8 +77,12 @@ struct Inner { impl Realm { /// Create a new [`Realm`]. #[inline] - pub fn create(hooks: &dyn HostHooks, root_shape: &RootShape) -> JsResult { - let intrinsics = Intrinsics::uninit(root_shape).ok_or_else(|| { + pub fn create( + hooks: &dyn HostHooks, + root_shape: &RootShape, + mc: &boa_gc::MutationContext<'static, '_>, + ) -> JsResult { + let intrinsics = Intrinsics::uninit(root_shape, mc).ok_or_else(|| { JsNativeError::typ().with_message("failed to create the realm intrinsics") })?; @@ -86,15 +90,12 @@ impl Realm { let global_this = hooks .create_global_this(&intrinsics) .unwrap_or_else(|| global_object.clone()); - let environment = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - DeclarativeEnvironment::global(), - ); + let environment = Gc::new(mc, DeclarativeEnvironment::global()); let scope = Scope::new_global(); let realm = Self { inner: Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, Inner { intrinsics, environment, diff --git a/core/engine/src/script.rs b/core/engine/src/script.rs index ef9823a32cf..373129572b9 100644 --- a/core/engine/src/script.rs +++ b/core/engine/src/script.rs @@ -137,6 +137,7 @@ impl Script { let spanned_source_text = SpannedSourceText::new_source_only(self.get_source()); + let mc = context.gc_collector(); let mut compiler = ByteCompiler::new( js_string!("
"), source.strict(), @@ -146,9 +147,14 @@ impl Script { false, false, context.interner_mut(), + &mc, false, spanned_source_text, - self.path().map(Path::to_owned).into(), + self.inner + .path + .as_deref() + .map(std::path::Path::to_path_buf) + .into(), ); #[cfg(feature = "annex-b")] diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 48d957adfaf..cc4ee8247b7 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -404,13 +404,10 @@ impl ActiveRunnable { impl Vm { /// Creates a new virtual machine. - pub(crate) fn new(realm: Realm) -> Self { + pub(crate) fn new(realm: Realm, mc: &boa_gc::MutationContext<'static, '_>) -> Self { let mut frames = Vec::with_capacity(16); frames.push(CallFrame::new( - Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - CodeBlock::new(JsString::default(), 0, true), - ), + Gc::new(mc, CodeBlock::new(JsString::default(), 0, true)), None, EnvironmentStack::new(), realm, diff --git a/core/engine/src/vm/opcode/push/environment.rs b/core/engine/src/vm/opcode/push/environment.rs index 0d8b34ef974..a1eb7a76926 100644 --- a/core/engine/src/vm/opcode/push/environment.rs +++ b/core/engine/src/vm/opcode/push/environment.rs @@ -18,13 +18,12 @@ impl PushScope { #[inline(always)] pub(crate) fn operation(index: IndexOperand, context: &mut Context) { let scope = context.vm.frame().code_block().constant_scope(index.into()); + let mc = context.gc_collector(); let frame = context.vm.frame_mut(); let global = frame.realm.environment(); frame .environments - .push_lexical(scope.num_bindings_non_local(), global, unsafe { - boa_gc::MutationContext::global() - }); + .push_lexical(scope.num_bindings_non_local(), global, mc); } } diff --git a/core/gc/src/context.rs b/core/gc/src/context.rs index 81624d8aee5..b3857952074 100644 --- a/core/gc/src/context.rs +++ b/core/gc/src/context.rs @@ -12,6 +12,13 @@ impl Default for GcContext { } } +#[cfg(feature = "oscars_backend")] +struct SyncWrapper(MutationContext<'static, 'static>); +#[cfg(feature = "oscars_backend")] +unsafe impl Sync for SyncWrapper {} +#[cfg(feature = "oscars_backend")] +unsafe impl Send for SyncWrapper {} + #[cfg(feature = "oscars_backend")] impl GcContext { #[must_use] @@ -20,17 +27,15 @@ impl GcContext { } pub fn alloc(&self, value: T) -> Gc<'static, T> { - // As a bridge, we use the global MutationContext until explicit - // context threading is natively supported by the oscars backend. let mc = MutationContext::global(); Gc::new(&mc, value) } #[must_use] - pub fn gc_collector(&self) -> &MutationContext<'static, 'static> { - // Just return a dummy global mutation context - // This is safe for the bridge phase. - unimplemented!("Not supported natively without closure yet, use MutationContext::global()") + pub fn gc_collector(&self) -> &'static MutationContext<'static, 'static> { + static DUMMY: std::sync::LazyLock = + std::sync::LazyLock::new(|| SyncWrapper(MutationContext::global())); + &DUMMY.0 } } @@ -45,6 +50,13 @@ impl Default for GcContext { } } +#[cfg(not(feature = "oscars_backend"))] +struct SyncWrapperDefault(crate::MutationContext<'static, 'static>); +#[cfg(not(feature = "oscars_backend"))] +unsafe impl Sync for SyncWrapperDefault {} +#[cfg(not(feature = "oscars_backend"))] +unsafe impl Send for SyncWrapperDefault {} + #[cfg(not(feature = "oscars_backend"))] impl GcContext { #[must_use] @@ -58,10 +70,9 @@ impl GcContext { } #[must_use] - pub fn gc_collector(&self) -> &crate::MutationContext<'static, 'static> { - // Just return a dummy global mutation context - static DUMMY: crate::MutationContext<'static, 'static> = - unsafe { crate::MutationContext::global() }; - &DUMMY + pub fn gc_collector(&self) -> &'static crate::MutationContext<'static, 'static> { + static DUMMY: SyncWrapperDefault = + SyncWrapperDefault(unsafe { crate::MutationContext::global() }); + &DUMMY.0 } } From 26b7baef8a83772b4857825a66296d0e1208ec30 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Mon, 17 Aug 2026 23:00:52 +0000 Subject: [PATCH 06/19] Integrate mark sweep backend and eliminate global GC state --- core/engine/src/builtins/eval/mod.rs | 8 +- .../src/builtins/finalization_registry/mod.rs | 2 +- core/engine/src/builtins/function/mod.rs | 16 ++-- core/engine/src/builtins/iterable/mod.rs | 6 -- core/engine/src/builtins/uri/mod.rs | 6 -- core/engine/src/context/mod.rs | 13 ++-- core/engine/src/environments/runtime/mod.rs | 73 +++++++++++-------- core/engine/src/module/source.rs | 4 +- core/engine/src/symbol.rs | 3 +- .../src/value/conversions/serde_json.rs | 6 +- core/engine/src/vm/inline_cache/tests.rs | 16 ++-- core/engine/src/vm/opcode/call/mod.rs | 6 +- core/engine/src/vm/opcode/push/environment.rs | 3 +- core/gc/Cargo.toml | 2 +- 14 files changed, 83 insertions(+), 81 deletions(-) diff --git a/core/engine/src/builtins/eval/mod.rs b/core/engine/src/builtins/eval/mod.rs index 764c73cbf6e..ab101972742 100644 --- a/core/engine/src/builtins/eval/mod.rs +++ b/core/engine/src/builtins/eval/mod.rs @@ -350,11 +350,9 @@ impl Eval { { let frame = context.vm.frame_mut(); let global = frame.realm.environment(); - frame.environments.push_lexical( - lexical_scope.num_bindings_non_local(), - &global, - &unsafe { boa_gc::MutationContext::global() }, - ); + frame + .environments + .push_lexical(lexical_scope.num_bindings_non_local(), &global, mc); } context diff --git a/core/engine/src/builtins/finalization_registry/mod.rs b/core/engine/src/builtins/finalization_registry/mod.rs index 76899e0256a..c96c51a8e6c 100644 --- a/core/engine/src/builtins/finalization_registry/mod.rs +++ b/core/engine/src/builtins/finalization_registry/mod.rs @@ -172,7 +172,7 @@ impl BuiltInConstructor for FinalizationRegistry { }; let Some(registry) = weak_registry - .upgrade(&unsafe { boa_gc::MutationContext::global() }) + .upgrade(context.borrow().gc_collector()) .map(JsObject::from_inner) else { return Ok(JsValue::undefined()); diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index 800de70a02a..0e0034ea960 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -1076,11 +1076,10 @@ pub(crate) fn function_call( let has_function_scope = context.vm.frame().code_block().has_function_scope(); if has_binding_identifier { + let mc = context.gc_collector(); let frame = context.vm.frame_mut(); let global = frame.realm.environment(); - let index = frame - .environments - .push_lexical(1, &global, &unsafe { boa_gc::MutationContext::global() }); + let index = frame.environments.push_lexical(1, &global, mc); frame.environments.put_lexical_value( BindingLocatorScope::Stack(index), 0, @@ -1092,13 +1091,14 @@ pub(crate) fn function_call( if has_function_scope { let scope = context.vm.frame().code_block().constant_scope(last_env); + let mc = context.gc_collector(); let frame = context.vm.frame_mut(); let global = frame.realm.environment(); frame.environments.push_function( scope, FunctionSlots::new(this, function_object.clone(), None), &global, - &unsafe { boa_gc::MutationContext::global() }, + mc, ); } @@ -1188,11 +1188,10 @@ fn function_construct( let has_function_scope = context.vm.frame().code_block().has_function_scope(); if has_binding_identifier { + let mc = context.gc_collector(); let frame = context.vm.frame_mut(); let global = frame.realm.environment(); - let index = frame - .environments - .push_lexical(1, &global, &unsafe { boa_gc::MutationContext::global() }); + let index = frame.environments.push_lexical(1, &global, mc); frame.environments.put_lexical_value( BindingLocatorScope::Stack(index), 0, @@ -1204,6 +1203,7 @@ fn function_construct( if has_function_scope { let scope = context.vm.frame().code_block().constant_scope(last_env); + let mc = context.gc_collector(); let frame = context.vm.frame_mut(); let global = frame.realm.environment(); frame.environments.push_function( @@ -1221,7 +1221,7 @@ fn function_construct( ), ), &global, - &unsafe { boa_gc::MutationContext::global() }, + mc, ); } diff --git a/core/engine/src/builtins/iterable/mod.rs b/core/engine/src/builtins/iterable/mod.rs index 032bae56384..de4efa4a54b 100644 --- a/core/engine/src/builtins/iterable/mod.rs +++ b/core/engine/src/builtins/iterable/mod.rs @@ -89,12 +89,6 @@ pub struct IteratorPrototypes { wrap_for_valid_iterator: JsObject, } -impl Default for IteratorPrototypes { - fn default() -> Self { - Self::uninit_in(&unsafe { boa_gc::MutationContext::global() }) - } -} - impl IteratorPrototypes { pub(crate) fn uninit_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { Self { diff --git a/core/engine/src/builtins/uri/mod.rs b/core/engine/src/builtins/uri/mod.rs index 58d8ff73be2..b55e351cd3d 100644 --- a/core/engine/src/builtins/uri/mod.rs +++ b/core/engine/src/builtins/uri/mod.rs @@ -47,12 +47,6 @@ pub struct UriFunctions { encode_uri_component: JsFunction, } -impl Default for UriFunctions { - fn default() -> Self { - Self::uninit_in(&unsafe { boa_gc::MutationContext::global() }) - } -} - impl UriFunctions { pub(crate) fn uninit_in(mc: &boa_gc::MutationContext<'static, '_>) -> Self { Self { diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index a84ebd8fa41..a0117b5348a 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -548,9 +548,11 @@ impl Context { /// Create a new Realm with the default global bindings. pub fn create_realm(&mut self) -> JsResult { - let realm = Realm::create(self.host_hooks.as_ref(), &self.root_shape, &unsafe { - boa_gc::MutationContext::global() - })?; + let realm = Realm::create( + self.host_hooks.as_ref(), + &self.root_shape, + self.gc_collector(), + )?; let old_realm = self.enter_realm(realm); @@ -1224,7 +1226,8 @@ impl ContextBuilder { CANNOT_BLOCK_COUNTER.set(CANNOT_BLOCK_COUNTER.get() + 1); } - let mc = unsafe { boa_gc::MutationContext::global() }; + let gc = boa_gc::GcContext::new(); + let mc = gc.gc_collector(); let root_shape = RootShape::new(&mc); let host_hooks = self.host_hooks.unwrap_or(Rc::new(DefaultHooks)); @@ -1279,7 +1282,7 @@ impl ContextBuilder { optimizer_options: OptimizerOptions::OPTIMIZE_ALL, root_shape, parser_identifier: 0, - gc: boa_gc::GcContext::new(), + gc, can_block: self.can_block, data: HostDefined::default(), }; diff --git a/core/engine/src/environments/runtime/mod.rs b/core/engine/src/environments/runtime/mod.rs index 4e47a4f79ff..bdcc1bea321 100644 --- a/core/engine/src/environments/runtime/mod.rs +++ b/core/engine/src/environments/runtime/mod.rs @@ -207,8 +207,12 @@ impl EnvironmentStack { } /// Push a new object environment on the environments stack. - pub(crate) fn push_object(&mut self, object: JsObject) { - self.push_env(Environment::Object(object)); + pub(crate) fn push_object( + &mut self, + object: JsObject, + mc: &boa_gc::MutationContext<'static, '_>, + ) { + self.push_env(Environment::Object(object), mc); } /// Push a lexical environment on the environments stack and return it's index. @@ -222,14 +226,17 @@ impl EnvironmentStack { let index = self.depth; - self.push_env(Environment::Declarative(Gc::new( - &gc, - DeclarativeEnvironment::new( - DeclarativeEnvironmentKind::Lexical(LexicalEnvironment::new(bindings_count)), - poisoned, - with, - ), - ))); + self.push_env( + Environment::Declarative(Gc::new( + &gc, + DeclarativeEnvironment::new( + DeclarativeEnvironmentKind::Lexical(LexicalEnvironment::new(bindings_count)), + poisoned, + with, + ), + )), + gc, + ); index } @@ -246,31 +253,37 @@ impl EnvironmentStack { let (poisoned, with) = self.compute_poisoned_with(global); - self.push_env(Environment::Declarative(Gc::new( + self.push_env( + Environment::Declarative(Gc::new( + gc, + DeclarativeEnvironment::new( + DeclarativeEnvironmentKind::Function(FunctionEnvironment::new( + num_bindings, + function_slots, + scope, + )), + poisoned, + with, + ), + )), gc, - DeclarativeEnvironment::new( - DeclarativeEnvironmentKind::Function(FunctionEnvironment::new( - num_bindings, - function_slots, - scope, - )), - poisoned, - with, - ), - ))); + ); } /// Push a module environment on the environments stack. pub(crate) fn push_module(&mut self, scope: Scope, gc: &boa_gc::MutationContext<'static, '_>) { let num_bindings = scope.num_bindings_non_local(); - self.push_env(Environment::Declarative(Gc::new( + self.push_env( + Environment::Declarative(Gc::new( + gc, + DeclarativeEnvironment::new( + DeclarativeEnvironmentKind::Module(ModuleEnvironment::new(num_bindings, scope)), + false, + false, + ), + )), gc, - DeclarativeEnvironment::new( - DeclarativeEnvironmentKind::Module(ModuleEnvironment::new(num_bindings, scope)), - false, - false, - ), - ))); + ); } /// Pop environment from the environments stack. @@ -416,9 +429,9 @@ impl EnvironmentStack { // ---- Private helpers ---- /// Push an environment onto the chain. - fn push_env(&mut self, env: Environment) { + fn push_env(&mut self, env: Environment, mc: &boa_gc::MutationContext<'static, '_>) { self.tip = Some(Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, EnvironmentNode { env, parent: self.tip.take(), diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index 26adadc14d7..def683d8a99 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -1838,9 +1838,7 @@ impl SourceTextModule { // 8. Let moduleContext be a new ECMAScript code execution context. let mut envs = EnvironmentStack::new(); - envs.push_module(source.scope().clone(), &unsafe { - boa_gc::MutationContext::global() - }); + envs.push_module(source.scope().clone(), unsafe { context.gc_collector() }); drop(status); // 9. Set the Function of moduleContext to null. diff --git a/core/engine/src/symbol.rs b/core/engine/src/symbol.rs index b78fbdc69a9..9ee38e35157 100644 --- a/core/engine/src/symbol.rs +++ b/core/engine/src/symbol.rs @@ -420,8 +420,7 @@ mod tests { let mut context = Context::default(); let symbol1 = JsSymbol::new(None).unwrap(); let symbol2 = JsSymbol::new(None).unwrap(); - let test_obj = - JsObject::from_proto_and_data(&unsafe { boa_gc::MutationContext::global() }, None, ()); + let test_obj = JsObject::from_proto_and_data(context.gc_collector(), None, ()); test_obj .set(symbol1, js_str!("Can't see me"), false, &mut context) .unwrap(); diff --git a/core/engine/src/value/conversions/serde_json.rs b/core/engine/src/value/conversions/serde_json.rs index bbeeded9ef9..bbebbdd355c 100644 --- a/core/engine/src/value/conversions/serde_json.rs +++ b/core/engine/src/value/conversions/serde_json.rs @@ -308,7 +308,7 @@ mod tests { #[test] fn to_json_cyclic() { let mut context = Context::default(); - let obj = JsObject::with_null_proto(&unsafe { boa_gc::MutationContext::global() }); + let obj = JsObject::with_null_proto(context.gc_collector()); obj.create_data_property(js_string!("a"), obj.clone(), &mut context) .expect("should create data property"); @@ -339,7 +339,7 @@ mod tests { // "outer_c": [2, undefined, 3, { "inner_a": undefined }] // } - let inner = JsObject::with_null_proto(&unsafe { boa_gc::MutationContext::global() }); + let inner = JsObject::with_null_proto(context.gc_collector()); inner .create_data_property(js_string!("inner_a"), JsValue::undefined(), &mut context) .expect("should add property"); @@ -352,7 +352,7 @@ mod tests { array.push(3, &mut context).expect("should push"); array.push(inner, &mut context).expect("should push"); - let outer = JsObject::with_null_proto(&unsafe { boa_gc::MutationContext::global() }); + let outer = JsObject::with_null_proto(context.gc_collector()); outer .create_data_property(js_string!("outer_a"), JsValue::new(1), &mut context) .expect("should add property"); diff --git a/core/engine/src/vm/inline_cache/tests.rs b/core/engine/src/vm/inline_cache/tests.rs index 0342bc07d62..d8e266eaf96 100644 --- a/core/engine/src/vm/inline_cache/tests.rs +++ b/core/engine/src/vm/inline_cache/tests.rs @@ -18,7 +18,7 @@ fn get_own_property_internal_method() { let context = &mut Context::default(); let o = context.intrinsics().templates().ordinary_object().create( - &unsafe { boa_gc::MutationContext::global() }, + context.gc_collector(), OrdinaryObject, Vec::default(), ); @@ -63,7 +63,7 @@ fn get_internal_method() { let context = &mut Context::default(); let o = context.intrinsics().templates().ordinary_object().create( - &unsafe { boa_gc::MutationContext::global() }, + context.gc_collector(), OrdinaryObject, Vec::default(), ); @@ -108,7 +108,7 @@ fn get_internal_method_in_prototype() { let context = &mut Context::default(); let o = context.intrinsics().templates().ordinary_object().create( - &unsafe { boa_gc::MutationContext::global() }, + context.gc_collector(), OrdinaryObject, Vec::default(), ); @@ -156,7 +156,7 @@ fn define_own_property_internal_method_non_existent_property() { let context = &mut Context::default(); let o = context.intrinsics().templates().ordinary_object().create( - &unsafe { boa_gc::MutationContext::global() }, + context.gc_collector(), OrdinaryObject, Vec::default(), ); @@ -210,7 +210,7 @@ fn define_own_property_internal_method_existing_property_property() { let context = &mut Context::default(); let o = context.intrinsics().templates().ordinary_object().create( - &unsafe { boa_gc::MutationContext::global() }, + context.gc_collector(), OrdinaryObject, Vec::default(), ); @@ -276,7 +276,7 @@ fn set_internal_method() { let context = &mut Context::default(); let o = context.intrinsics().templates().ordinary_object().create( - &unsafe { boa_gc::MutationContext::global() }, + context.gc_collector(), OrdinaryObject, Vec::default(), ); @@ -343,7 +343,7 @@ fn set_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> { assert_eq!( code.ic[0].entries.borrow()[0] .shape - .upgrade(&unsafe { boa_gc::MutationContext::global() }) + .upgrade(context.gc_collector()) .unwrap() .to_addr_usize(), o_shape.to_addr_usize() @@ -372,7 +372,7 @@ fn get_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> { assert_eq!( code.ic[0].entries.borrow()[0] .shape - .upgrade(&unsafe { boa_gc::MutationContext::global() }) + .upgrade(context.gc_collector()) .unwrap() .to_addr_usize(), o_shape.to_addr_usize() diff --git a/core/engine/src/vm/opcode/call/mod.rs b/core/engine/src/vm/opcode/call/mod.rs index 410c826fb86..c71e8003a4a 100644 --- a/core/engine/src/vm/opcode/call/mod.rs +++ b/core/engine/src/vm/opcode/call/mod.rs @@ -448,9 +448,10 @@ async fn load_dyn_import( // 4. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures promiseCapability and performs the following steps when called: // 5. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). + let mc = context.borrow().gc_collector(); let on_rejected = FunctionObjectBuilder::new( context.borrow().realm(), - context.borrow_mut().gc_collector(), + &mc, NativeFunction::from_copy_closure_with_captures( |_, args, cap, context| { // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »). @@ -468,9 +469,10 @@ async fn load_dyn_import( // 6. Let linkAndEvaluateClosure be a new Abstract Closure with no parameters that captures module, promiseCapability, and onRejected and performs the following steps when called: // 7. Let linkAndEvaluate be CreateBuiltinFunction(linkAndEvaluateClosure, 0, "", « »). + let mc = context.borrow().gc_collector(); let link_evaluate = FunctionObjectBuilder::new( context.borrow().realm(), - context.borrow_mut().gc_collector(), + &mc, NativeFunction::from_copy_closure_with_captures( |_, _, (module, cap, on_rejected), context| { // a. Let link be Completion(module.Link()). diff --git a/core/engine/src/vm/opcode/push/environment.rs b/core/engine/src/vm/opcode/push/environment.rs index a1eb7a76926..3149097b51d 100644 --- a/core/engine/src/vm/opcode/push/environment.rs +++ b/core/engine/src/vm/opcode/push/environment.rs @@ -45,7 +45,8 @@ impl PushObjectEnvironment { pub(crate) fn operation(value: RegisterOperand, context: &mut Context) -> JsResult<()> { let object = context.vm.get_register(value.into()).clone(); let object = object.to_object(context)?; - context.vm.frame_mut().environments.push_object(object); + let mc = context.gc_collector(); + context.vm.frame_mut().environments.push_object(object, mc); Ok(()) } } diff --git a/core/gc/Cargo.toml b/core/gc/Cargo.toml index 10637fe4c36..030298d6d33 100644 --- a/core/gc/Cargo.toml +++ b/core/gc/Cargo.toml @@ -21,7 +21,7 @@ boa_string = ["dep:boa_string"] either = ["dep:either", "oscars?/either"] # Enable default implementations of trace and finalize for the arrayvec crate arrayvec = ["dep:arrayvec", "oscars?/arrayvec"] -default = [] +default = ["oscars_backend"] boa_gc_backend = [] oscars_backend = ["dep:oscars", "dep:typeid", "oscars?/std", "boa_string?/oscars_backend"] From 516419e83eada6c11225726f79884834e54d4c01 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Tue, 18 Aug 2026 20:02:38 +0000 Subject: [PATCH 07/19] Fix test262 test suite failures for oscars integration --- .github/workflows/test262.yml | 1 - core/engine/src/builtins/array/from_async.rs | 2 + .../src/builtins/async_generator/mod.rs | 2 + core/engine/src/builtins/function/tests.rs | 1 + core/engine/src/builtins/intl/collator/mod.rs | 1 + .../src/builtins/intl/date_time_format/mod.rs | 1 + .../src/builtins/intl/number_format/mod.rs | 1 + .../iterable/async_from_sync_iterator.rs | 20 +- .../builtins/iterable/iterator_constructor.rs | 5 +- .../iterable/iterator_helper/concat.rs | 7 +- .../builtins/iterable/iterator_helper/drop.rs | 8 +- .../iterable/iterator_helper/filter.rs | 8 +- .../iterable/iterator_helper/flat_map.rs | 8 +- .../builtins/iterable/iterator_helper/map.rs | 8 +- .../builtins/iterable/iterator_helper/take.rs | 8 +- .../builtins/iterable/iterator_prototype.rs | 29 ++- core/engine/src/builtins/object/mod.rs | 1 + core/engine/src/builtins/promise/mod.rs | 13 + core/engine/src/builtins/proxy/mod.rs | 3 + .../src/interop/into_js_function_impls.rs | 24 +- core/engine/src/module/mod.rs | 44 ++-- core/engine/src/module/source.rs | 2 + core/engine/src/module/synthetic.rs | 25 +- .../src/native_function/continuation.rs | 16 +- core/engine/src/native_function/mod.rs | 30 ++- core/engine/src/object/builtins/jsfunction.rs | 12 - core/engine/src/object/builtins/jspromise.rs | 4 + .../src/object/builtins/jstypedarray.rs | 1 + .../engine/src/object/internal_methods/mod.rs | 11 +- .../src/object/internal_methods/string.rs | 1 + core/engine/src/value/mod.rs | 6 +- core/engine/src/vm/opcode/await/mod.rs | 2 + core/engine/src/vm/opcode/call/mod.rs | 3 + core/engine/src/vm/tests.rs | 2 +- core/gc/src/context.rs | 30 ++- core/gc/src/lib.rs | 6 +- core/macros/src/module.rs | 2 +- core/runtime/src/console/mod.rs | 230 ++++++++++-------- core/runtime/src/console/tests.rs | 23 ++ core/runtime/src/fetch/headers_iterator.rs | 6 +- core/runtime/src/process/mod.rs | 6 +- core/runtime/src/test262.rs | 16 +- examples/src/bin/closures.rs | 10 +- examples/src/bin/jstypedarray.rs | 6 +- examples/src/bin/modules.rs | 2 + examples/src/bin/smol_event_loop.rs | 2 +- examples/src/bin/synthetic.rs | 1 + examples/src/bin/tokio_event_loop.rs | 2 +- tests/tester/src/exec/mod.rs | 5 +- 49 files changed, 411 insertions(+), 246 deletions(-) diff --git a/.github/workflows/test262.yml b/.github/workflows/test262.yml index a8671fc37d0..d569e061ac5 100644 --- a/.github/workflows/test262.yml +++ b/.github/workflows/test262.yml @@ -16,7 +16,6 @@ concurrency: jobs: run_test262: - if: ${{ github.base_ref != 'dev/oscars-gc' }} name: Run the test262 test suite runs-on: ubuntu-latest timeout-minutes: 60 diff --git a/core/engine/src/builtins/array/from_async.rs b/core/engine/src/builtins/array/from_async.rs index 43c8dbe8c36..cecf5fa0071 100644 --- a/core/engine/src/builtins/array/from_async.rs +++ b/core/engine/src/builtins/array/from_async.rs @@ -124,6 +124,7 @@ impl Array { // Coroutine yielded. We need to allocate it for a future execution. JsPromise::resolve(value, context)?.await_native( NativeCoroutine::from_copy_closure_with_captures( + context.gc_collector(), from_array_like, coroutine_state, ), @@ -174,6 +175,7 @@ impl Array { CoroutineState::Continue(value) => { JsPromise::resolve(value, context)?.await_native( NativeCoroutine::from_copy_closure_with_captures( + context.gc_collector(), from_async_iterator, coroutine_state, ), diff --git a/core/engine/src/builtins/async_generator/mod.rs b/core/engine/src/builtins/async_generator/mod.rs index 298fc9f29c1..c662e5edc80 100644 --- a/core/engine/src/builtins/async_generator/mod.rs +++ b/core/engine/src/builtins/async_generator/mod.rs @@ -582,6 +582,7 @@ impl AsyncGenerator { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, generator, context| { // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue. assert_eq!( @@ -614,6 +615,7 @@ impl AsyncGenerator { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, generator, context| { // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue. assert_eq!( diff --git a/core/engine/src/builtins/function/tests.rs b/core/engine/src/builtins/function/tests.rs index bb63f86efe7..da2a49dbbfe 100644 --- a/core/engine/src/builtins/function/tests.rs +++ b/core/engine/src/builtins/function/tests.rs @@ -150,6 +150,7 @@ fn closure_capture_clone() { ctx.realm(), ctx.gc_collector(), NativeFunction::from_copy_closure_with_captures( + ctx.gc_collector(), |_, _, captures, context| { let (string, object) = &captures; diff --git a/core/engine/src/builtins/intl/collator/mod.rs b/core/engine/src/builtins/intl/collator/mod.rs index bdf8ceea4be..d0c0f11072e 100644 --- a/core/engine/src/builtins/intl/collator/mod.rs +++ b/core/engine/src/builtins/intl/collator/mod.rs @@ -359,6 +359,7 @@ impl Collator { // 10.3.3.1. Collator Compare Functions // https://tc39.es/ecma402/#sec-collator-compare-functions NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, collator, context| { // 1. Let collator be F.[[Collator]]. // 2. Assert: Type(collator) is Object and collator has an [[InitializedCollator]] internal slot. diff --git a/core/engine/src/builtins/intl/date_time_format/mod.rs b/core/engine/src/builtins/intl/date_time_format/mod.rs index 0100ad0ab41..10a0abba1c3 100644 --- a/core/engine/src/builtins/intl/date_time_format/mod.rs +++ b/core/engine/src/builtins/intl/date_time_format/mod.rs @@ -262,6 +262,7 @@ impl DateTimeFormat { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, dtf, context| { // 1. Let dtf be F.[[DateTimeFormat]]. // 2. Assert: dtf is an Object and dtf has an [[InitializedDateTimeFormat]] internal slot. diff --git a/core/engine/src/builtins/intl/number_format/mod.rs b/core/engine/src/builtins/intl/number_format/mod.rs index d8253b0b581..1ea97697ea7 100644 --- a/core/engine/src/builtins/intl/number_format/mod.rs +++ b/core/engine/src/builtins/intl/number_format/mod.rs @@ -610,6 +610,7 @@ impl NumberFormat { // Number Format Functions // NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, nf, context| { // 1. Let nf be F.[[NumberFormat]]. // 2. Assert: Type(nf) is Object and nf has an [[InitializedNumberFormat]] internal slot. diff --git a/core/engine/src/builtins/iterable/async_from_sync_iterator.rs b/core/engine/src/builtins/iterable/async_from_sync_iterator.rs index d1c2bbd4e6d..64cee86d4aa 100644 --- a/core/engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/core/engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -364,14 +364,17 @@ impl AsyncFromSyncIterator { let on_fulfilled = FunctionObjectBuilder::new( context.realm(), context.gc_collector(), - NativeFunction::from_copy_closure(move |_this, args, context| { - // a. Return CreateIterResultObject(value, done). - Ok(create_iter_result_object( - args.get_or_undefined(0).clone(), - done, - context, - )) - }), + NativeFunction::from_copy_closure( + context.gc_collector(), + move |_this, args, context| { + // a. Return CreateIterResultObject(value, done). + Ok(create_iter_result_object( + args.get_or_undefined(0).clone(), + done, + context, + )) + }, + ), ) .name(js_string!()) .length(1) @@ -397,6 +400,7 @@ impl AsyncFromSyncIterator { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, iter, context| { // i. Return ? IteratorClose(syncIteratorRecord, ThrowCompletion(error)). iter.close( diff --git a/core/engine/src/builtins/iterable/iterator_constructor.rs b/core/engine/src/builtins/iterable/iterator_constructor.rs index 0d29a8e20f7..9c77ae10a88 100644 --- a/core/engine/src/builtins/iterable/iterator_constructor.rs +++ b/core/engine/src/builtins/iterable/iterator_constructor.rs @@ -192,7 +192,10 @@ impl IteratorConstructor { // (implemented via IteratorHelperOp::Concat in execute_next) // 4-5. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", ...) // with [[UnderlyingIterators]] set to a new empty List. - let helper = IteratorHelper::create(iterator_helper::Concat::new(iterables), context); + let helper = IteratorHelper::create( + iterator_helper::Concat::new(context.gc_collector(), iterables), + context, + ); // 6. Return result. Ok(helper.into()) diff --git a/core/engine/src/builtins/iterable/iterator_helper/concat.rs b/core/engine/src/builtins/iterable/iterator_helper/concat.rs index cbda860728a..5526971b744 100644 --- a/core/engine/src/builtins/iterable/iterator_helper/concat.rs +++ b/core/engine/src/builtins/iterable/iterator_helper/concat.rs @@ -43,11 +43,14 @@ impl Concat { clippy::new_ret_no_self, reason = "slightly cleaner to have this be a `new` method" )] - pub(crate) fn new(iterables: VecDeque) -> NativeCoroutine { + pub(crate) fn new( + mc: &boa_gc::MutationContext<'_, '_>, + iterables: VecDeque, + ) -> NativeCoroutine { // 3. Let closure be a new Abstract Closure with no parameters that captures // iterables and performs the following steps when called: NativeCoroutine::from_copy_closure_with_captures( - // a. For each Record iterable of iterables, do + mc, // a. For each Record iterable of iterables, do |completion, state, context| { let st = state.take(); match &st { diff --git a/core/engine/src/builtins/iterable/iterator_helper/drop.rs b/core/engine/src/builtins/iterable/iterator_helper/drop.rs index ffc4326c6b8..a12a263b9f3 100644 --- a/core/engine/src/builtins/iterable/iterator_helper/drop.rs +++ b/core/engine/src/builtins/iterable/iterator_helper/drop.rs @@ -31,12 +31,16 @@ impl Drop { clippy::new_ret_no_self, reason = "slightly cleaner to have this be a `new` method" )] - pub(crate) fn new(iterated: IteratorRecord, limit: Option) -> NativeCoroutine { + pub(crate) fn new( + mc: &boa_gc::MutationContext<'_, '_>, + iterated: IteratorRecord, + limit: Option, + ) -> NativeCoroutine { // 10. Let closure be a new Abstract Closure with no parameters that // captures iterated and integerLimit and performs the following steps // when called: NativeCoroutine::from_copy_closure_with_captures( - // a. Let remaining be integerLimit. + mc, // a. Let remaining be integerLimit. // c. Repeat, |completion, state, context| { let st = state.take(); diff --git a/core/engine/src/builtins/iterable/iterator_helper/filter.rs b/core/engine/src/builtins/iterable/iterator_helper/filter.rs index 93e74bc9204..57d1aaf73ba 100644 --- a/core/engine/src/builtins/iterable/iterator_helper/filter.rs +++ b/core/engine/src/builtins/iterable/iterator_helper/filter.rs @@ -31,11 +31,15 @@ impl Filter { clippy::new_ret_no_self, reason = "slightly cleaner to have this be a `new` method" )] - pub(crate) fn new(iterated: IteratorRecord, predicate: JsFunction) -> NativeCoroutine { + pub(crate) fn new( + mc: &boa_gc::MutationContext<'_, '_>, + iterated: IteratorRecord, + predicate: JsFunction, + ) -> NativeCoroutine { // 6. Let closure be a new Abstract Closure with no parameters that captures // iterated and predicate and performs the following steps when called: NativeCoroutine::from_copy_closure_with_captures( - // a. Let counter be 0. + mc, // a. Let counter be 0. // b. Repeat, |completion, state, context| { let st = state.take(); diff --git a/core/engine/src/builtins/iterable/iterator_helper/flat_map.rs b/core/engine/src/builtins/iterable/iterator_helper/flat_map.rs index 96529387de9..f41cde0f8e8 100644 --- a/core/engine/src/builtins/iterable/iterator_helper/flat_map.rs +++ b/core/engine/src/builtins/iterable/iterator_helper/flat_map.rs @@ -39,11 +39,15 @@ impl FlatMap { clippy::new_ret_no_self, reason = "slightly cleaner to have this be a `new` method" )] - pub(crate) fn new(iterated: IteratorRecord, mapper: JsFunction) -> NativeCoroutine { + pub(crate) fn new( + mc: &boa_gc::MutationContext<'_, '_>, + iterated: IteratorRecord, + mapper: JsFunction, + ) -> NativeCoroutine { // 6. Let closure be a new Abstract Closure with no parameters that captures // iterated and mapper and performs the following steps when called: NativeCoroutine::from_copy_closure_with_captures( - // a. Let counter be 0. + mc, // a. Let counter be 0. // b. Repeat, |completion, state, context| { let st = state.take(); diff --git a/core/engine/src/builtins/iterable/iterator_helper/map.rs b/core/engine/src/builtins/iterable/iterator_helper/map.rs index 2621696a614..f0879407722 100644 --- a/core/engine/src/builtins/iterable/iterator_helper/map.rs +++ b/core/engine/src/builtins/iterable/iterator_helper/map.rs @@ -31,11 +31,15 @@ impl Map { clippy::new_ret_no_self, reason = "slightly cleaner to have this be a `new` method" )] - pub(crate) fn new(iterated: IteratorRecord, mapper: JsFunction) -> NativeCoroutine { + pub(crate) fn new( + mc: &boa_gc::MutationContext<'_, '_>, + iterated: IteratorRecord, + mapper: JsFunction, + ) -> NativeCoroutine { // 6. Let closure be a new Abstract Closure with no parameters that captures // iterated and mapper and performs the following steps when called: NativeCoroutine::from_copy_closure_with_captures( - // a. Let counter be 0. + mc, // a. Let counter be 0. // b. Repeat, |completion, state, context| { let st = state.take(); diff --git a/core/engine/src/builtins/iterable/iterator_helper/take.rs b/core/engine/src/builtins/iterable/iterator_helper/take.rs index 91e353bd4d4..b1feee3b533 100644 --- a/core/engine/src/builtins/iterable/iterator_helper/take.rs +++ b/core/engine/src/builtins/iterable/iterator_helper/take.rs @@ -29,12 +29,16 @@ impl Take { clippy::new_ret_no_self, reason = "slightly cleaner to have this be a `new` method" )] - pub(crate) fn new(iterated: IteratorRecord, limit: Option) -> NativeCoroutine { + pub(crate) fn new( + mc: &boa_gc::MutationContext<'_, '_>, + iterated: IteratorRecord, + limit: Option, + ) -> NativeCoroutine { // 10. Let closure be a new Abstract Closure with no parameters that // captures iterated and integerLimit and performs the following steps // when called: NativeCoroutine::from_copy_closure_with_captures( - // a. Let remaining be integerLimit. + mc, // a. Let remaining be integerLimit. // b. Repeat, |completion, state, context| { let st = state.take(); diff --git a/core/engine/src/builtins/iterable/iterator_prototype.rs b/core/engine/src/builtins/iterable/iterator_prototype.rs index f244ae1c6f3..abe398f4f73 100644 --- a/core/engine/src/builtins/iterable/iterator_prototype.rs +++ b/core/engine/src/builtins/iterable/iterator_prototype.rs @@ -235,7 +235,10 @@ impl Iterator { let iterated = get_iterator_direct(iterated.iterator(), context)?; // 6-8 are deferred to `IteratorHelper::create` and `Map::new`. - let result = IteratorHelper::create(iterator_helper::Map::new(iterated, mapper), context); + let result = IteratorHelper::create( + iterator_helper::Map::new(context.gc_collector(), iterated, mapper), + context, + ); // 9. Return result. Ok(result.into()) @@ -274,8 +277,10 @@ impl Iterator { let iterated = get_iterator_direct(iterated.iterator(), context)?; // 6-8 are deferred to `IteratorHelper::create` and `Filter::new`. - let result = - IteratorHelper::create(iterator_helper::Filter::new(iterated, predicate), context); + let result = IteratorHelper::create( + iterator_helper::Filter::new(context.gc_collector(), iterated, predicate), + context, + ); // 9. Return result. Ok(result.into()) @@ -335,8 +340,10 @@ impl Iterator { let iterated = get_iterator_direct(iterated.iterator(), context)?; // 10-12 are deferred to `IteratorHelper::create` and `Take::new`. - let result = - IteratorHelper::create(iterator_helper::Take::new(iterated, integer_limit), context); + let result = IteratorHelper::create( + iterator_helper::Take::new(context.gc_collector(), iterated, integer_limit), + context, + ); // 13. Return result. Ok(result.into()) @@ -395,8 +402,10 @@ impl Iterator { let iterated = get_iterator_direct(iterated.iterator(), context)?; // 10-12 are deferred to `IteratorHelper::create` and `Drop::new`. - let result = - IteratorHelper::create(iterator_helper::Drop::new(iterated, integer_limit), context); + let result = IteratorHelper::create( + iterator_helper::Drop::new(context.gc_collector(), iterated, integer_limit), + context, + ); // 13. Return result. Ok(result.into()) @@ -435,8 +444,10 @@ impl Iterator { let iterated = get_iterator_direct(iterated.iterator(), context)?; // 6-8 are deferred to `IteratorHelper::create` and `FlatMap::new`. - let helper = - IteratorHelper::create(iterator_helper::FlatMap::new(iterated, mapper), context); + let helper = IteratorHelper::create( + iterator_helper::FlatMap::new(context.gc_collector(), iterated, mapper), + context, + ); // 9. Return result. Ok(helper.into()) diff --git a/core/engine/src/builtins/object/mod.rs b/core/engine/src/builtins/object/mod.rs index 8872a4c2815..49dad929215 100644 --- a/core/engine/src/builtins/object/mod.rs +++ b/core/engine/src/builtins/object/mod.rs @@ -1309,6 +1309,7 @@ impl OrdinaryObject { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, obj, context| { let key = args.get_or_undefined(0); let value = args.get_or_undefined(1); diff --git a/core/engine/src/builtins/promise/mod.rs b/core/engine/src/builtins/promise/mod.rs index 6d7d2c1c76c..15a0f6c3ad8 100644 --- a/core/engine/src/builtins/promise/mod.rs +++ b/core/engine/src/builtins/promise/mod.rs @@ -254,6 +254,7 @@ impl PromiseCapability { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args: &[JsValue], captures, _| { let mut promise_capability = captures.borrow_mut(); // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception. @@ -685,6 +686,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, captures, context| { // https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions @@ -904,6 +906,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, captures, context| { // https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions @@ -998,6 +1001,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, captures, context| { // https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions @@ -1286,6 +1290,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, captures, context| { // 1. If alreadyCalled.[[Value]] is true, return undefined. if captures.already_called.get() { @@ -1372,6 +1377,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, captures, context| { // 1. If alreadyCalled.[[Value]] is true, return undefined. if captures.already_called.get() { @@ -1595,6 +1601,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, captures, context| { // https://tc39.es/ecma262/#sec-promise.any-reject-element-functions @@ -2029,6 +2036,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, captures, context| { /// Capture object for the abstract `returnValue` closure. #[derive(Debug, Trace, Finalize)] @@ -2051,6 +2059,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, _args, captures, _context| { // 1. Return value. Ok(captures.value.clone()) @@ -2082,6 +2091,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, captures, context| { /// Capture object for the abstract `throwReason` closure. #[derive(Debug, Trace, Finalize)] @@ -2104,6 +2114,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, _args, captures, _context| { // 1. Return ThrowCompletion(reason). Err(JsError::from_opaque(captures.reason.clone())) @@ -2479,6 +2490,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, captures, context| { // https://tc39.es/ecma262/#sec-promise-resolve-functions @@ -2577,6 +2589,7 @@ impl Promise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, captures, context| { // https://tc39.es/ecma262/#sec-promise-reject-functions diff --git a/core/engine/src/builtins/proxy/mod.rs b/core/engine/src/builtins/proxy/mod.rs index 4b7c4d5e27c..046e15ff75b 100644 --- a/core/engine/src/builtins/proxy/mod.rs +++ b/core/engine/src/builtins/proxy/mod.rs @@ -197,6 +197,7 @@ impl Proxy { // 4. Set revoker.[[RevocableProxy]] to p. NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, _, revocable_proxy, _| { // a. Let F be the active function object. // b. Let p be F.[[RevocableProxy]]. @@ -547,6 +548,7 @@ pub(crate) fn proxy_exotic_get_own_property( extensible_target, result_desc.clone(), target_desc.clone(), + context.gc_collector(), ) { return Err(JsNativeError::typ() .with_message("Proxy trap returned unexpected property") @@ -662,6 +664,7 @@ pub(crate) fn proxy_exotic_define_own_property( extensible_target, desc.clone(), Some(target_desc.clone()), + context.gc_collector(), ) { return Err(JsNativeError::typ() .with_message("Proxy trap set property to unexpected value") diff --git a/core/engine/src/interop/into_js_function_impls.rs b/core/engine/src/interop/into_js_function_impls.rs index 9a4cb19195f..51954f837f2 100644 --- a/core/engine/src/interop/into_js_function_impls.rs +++ b/core/engine/src/interop/into_js_function_impls.rs @@ -51,7 +51,7 @@ macro_rules! impl_into_js_function { unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction { let s = RefCell::new(self); unsafe { - NativeFunction::from_closure(move |this, args, ctx| { + NativeFunction::from_closure(_context.gc_collector(), move |this, args, ctx| { let rest = args; $( let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?; @@ -77,7 +77,7 @@ macro_rules! impl_into_js_function { unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction { let s = RefCell::new(self); unsafe { - NativeFunction::from_closure(move |this, args, ctx| { + NativeFunction::from_closure(_context.gc_collector(), move |this, args, ctx| { let rest = args; $( let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?; @@ -103,7 +103,7 @@ macro_rules! impl_into_js_function { unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction { let s = RefCell::new(self); unsafe { - NativeFunction::from_closure(move |this, args, ctx| { + NativeFunction::from_closure(_context.gc_collector(), move |this, args, ctx| { let rest = args; $( let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?; @@ -125,7 +125,7 @@ macro_rules! impl_into_js_function { unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction { let s = RefCell::new(self); unsafe { - NativeFunction::from_closure(move |this, args, ctx| { + NativeFunction::from_closure(_context.gc_collector(), move |this, args, ctx| { let rest = args; $( let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?; @@ -145,9 +145,9 @@ macro_rules! impl_into_js_function { T: Fn($($t,)*) -> R + 'static + Copy, { #[allow(unused_variables)] - fn into_js_function_copied(self, _context: &mut Context) -> NativeFunction { + fn into_js_function_copied(self, context: &mut Context) -> NativeFunction { let s = self; - NativeFunction::from_copy_closure(move |this, args, ctx| { + NativeFunction::from_copy_closure(context.gc_collector(), move |this, args, ctx| { let rest = args; $( let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?; @@ -165,9 +165,9 @@ macro_rules! impl_into_js_function { T: Fn($($t,)* JsRest<'_>) -> R + 'static + Copy, { #[allow(unused_variables)] - fn into_js_function_copied(self, _context: &mut Context) -> NativeFunction { + fn into_js_function_copied(self, context: &mut Context) -> NativeFunction { let s = self; - NativeFunction::from_copy_closure(move |this, args, ctx| { + NativeFunction::from_copy_closure(context.gc_collector(), move |this, args, ctx| { let rest = args; $( let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?; @@ -185,9 +185,9 @@ macro_rules! impl_into_js_function { T: Fn($($t,)* &mut Context) -> R + 'static + Copy, { #[allow(unused_variables)] - fn into_js_function_copied(self, _context: &mut Context) -> NativeFunction { + fn into_js_function_copied(self, context: &mut Context) -> NativeFunction { let s = self; - NativeFunction::from_copy_closure(move |this, args, ctx| { + NativeFunction::from_copy_closure(context.gc_collector(), move |this, args, ctx| { let rest = args; $( let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?; @@ -205,9 +205,9 @@ macro_rules! impl_into_js_function { T: Fn($($t,)* JsRest<'_>, &mut Context) -> R + 'static + Copy, { #[allow(unused_variables)] - fn into_js_function_copied(self, _context: &mut Context) -> NativeFunction { + fn into_js_function_copied(self, context: &mut Context) -> NativeFunction { let s = self; - NativeFunction::from_copy_closure(move |this, args, ctx| { + NativeFunction::from_copy_closure(context.gc_collector(), move |this, args, ctx| { let rest = args; $( let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?; diff --git a/core/engine/src/module/mod.rs b/core/engine/src/module/mod.rs index 01c9d8f7a45..e1cb1f29dfa 100644 --- a/core/engine/src/module/mod.rs +++ b/core/engine/src/module/mod.rs @@ -330,13 +330,16 @@ impl Module { pub fn from_value_as_default(value: JsValue, context: &mut Context) -> Self { Module::synthetic( &[js_string!("default")], - SyntheticModuleInitializer::from_copy_closure_with_captures( - move |m, value, _ctx| { - m.set_export(&js_string!("default"), value.clone())?; - Ok(()) - }, - value, - ), + unsafe { + SyntheticModuleInitializer::from_closure_with_captures( + context.gc_collector(), + move |m, value, _ctx| { + m.set_export(&js_string!("default"), value.clone())?; + Ok(()) + }, + value, + ) + }, None, None, context, @@ -652,6 +655,7 @@ impl Module { .then( Some( NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, _, module, context| { module.link(context)?; Ok(JsValue::undefined()) @@ -667,6 +671,7 @@ impl Module { .then( Some( NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, _, module, context| Ok(module.evaluate(context)?.into()), self.clone(), ) @@ -782,17 +787,20 @@ impl + Clone> IntoJsModule fo Module::synthetic( exports.as_slice(), unsafe { - SyntheticModuleInitializer::from_closure(move |module, context| { - for (name, f) in names.iter().zip(fns.iter()) { - module.set_export( - name, - f.clone() - .to_js_function(context.realm(), context.gc_collector()) - .into(), - )?; - } - Ok(()) - }) + SyntheticModuleInitializer::from_closure( + context.gc_collector(), + move |module, context| { + for (name, f) in names.iter().zip(fns.iter()) { + module.set_export( + name, + f.clone() + .to_js_function(context.realm(), context.gc_collector()) + .into(), + )?; + } + Ok(()) + }, + ) }, None, None, diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index def683d8a99..25127a48d49 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -1479,6 +1479,7 @@ impl SourceTextModule { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, _, module, context| { // a. Perform AsyncModuleExecutionFulfilled(module). async_module_execution_fulfilled(module, context)?; @@ -1496,6 +1497,7 @@ impl SourceTextModule { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, module, context| { let error = JsError::from_opaque(args.get_or_undefined(0).clone()); // a. Perform AsyncModuleExecutionRejected(module, error). diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index f0bbde545fa..ad178da7cfc 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -65,22 +65,26 @@ impl std::fmt::Debug for SyntheticModuleInitializer { impl SyntheticModuleInitializer { /// Creates a `SyntheticModuleInitializer` from a [`Copy`] closure. - pub fn from_copy_closure(closure: F) -> Self + pub fn from_copy_closure(mc: &boa_gc::MutationContext<'_, '_>, closure: F) -> Self where F: Fn(&SyntheticModule, &mut Context) -> JsResult<()> + Copy + 'static, { // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. - unsafe { Self::from_closure(closure) } + unsafe { Self::from_closure(mc, closure) } } /// Creates a `SyntheticModuleInitializer` from a [`Copy`] closure and a list of traceable captures. - pub fn from_copy_closure_with_captures(closure: F, captures: T) -> Self + pub fn from_copy_closure_with_captures( + mc: &boa_gc::MutationContext<'_, '_>, + closure: F, + captures: T, + ) -> Self where F: Fn(&SyntheticModule, &T, &mut Context) -> JsResult<()> + Copy + 'static, T: Trace + 'static, { // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. - unsafe { Self::from_closure_with_captures(closure, captures) } + unsafe { Self::from_closure_with_captures(mc, closure, captures) } } /// Creates a new `SyntheticModuleInitializer` from a closure. @@ -91,13 +95,14 @@ impl SyntheticModuleInitializer { /// collector could cause an use after free, memory corruption or other kinds of **Undefined /// Behaviour**. See for a technical explanation /// on why that is the case. - pub unsafe fn from_closure(closure: F) -> Self + pub unsafe fn from_closure(mc: &boa_gc::MutationContext<'_, '_>, closure: F) -> Self where F: Fn(&SyntheticModule, &mut Context) -> JsResult<()> + 'static, { // SAFETY: The caller must ensure the invariants of the closure hold. unsafe { Self::from_closure_with_captures( + mc, move |module, (), context| closure(module, context), (), ) @@ -112,7 +117,11 @@ impl SyntheticModuleInitializer { /// collector could cause an use after free, memory corruption or other kinds of **Undefined /// Behaviour**. See for a technical explanation /// on why that is the case. - pub unsafe fn from_closure_with_captures(closure: F, captures: T) -> Self + pub unsafe fn from_closure_with_captures( + mc: &boa_gc::MutationContext<'_, '_>, + closure: F, + captures: T, + ) -> Self where F: Fn(&SyntheticModule, &T, &mut Context) -> JsResult<()> + 'static, T: Trace + 'static, @@ -120,7 +129,7 @@ impl SyntheticModuleInitializer { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the // future: https://github.com/rust-lang/rust/issues/18598 let ptr = Gc::into_raw(Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, Callback { f: closure, captures, @@ -344,7 +353,7 @@ impl SyntheticModule { let cb = context.alloc(finished); let mut envs = EnvironmentStack::new(); - envs.push_module(module_scope, &unsafe { boa_gc::MutationContext::global() }); + envs.push_module(module_scope, context.gc_collector()); for locator in exports { // b. Perform ! env.InitializeBinding(exportName, undefined). diff --git a/core/engine/src/native_function/continuation.rs b/core/engine/src/native_function/continuation.rs index abce83e1e0f..651bd9de071 100644 --- a/core/engine/src/native_function/continuation.rs +++ b/core/engine/src/native_function/continuation.rs @@ -83,13 +83,17 @@ impl std::fmt::Debug for NativeCoroutine { impl NativeCoroutine { /// Creates a `NativeCoroutine` from a `Copy` closure and a list of traceable captures. - pub(crate) fn from_copy_closure_with_captures(closure: F, captures: T) -> Self + pub(crate) fn from_copy_closure_with_captures( + mc: &boa_gc::MutationContext<'_, '_>, + closure: F, + captures: T, + ) -> Self where F: Fn(CompletionRecord, &T, &mut Context) -> CoroutineState + Copy + 'static, T: Trace + 'static, { // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. - unsafe { Self::from_closure_with_captures(closure, captures) } + unsafe { Self::from_closure_with_captures(mc, closure, captures) } } /// Create a new `NativeCoroutine` from a closure and a list of traceable captures. @@ -100,7 +104,11 @@ impl NativeCoroutine { /// collector could cause an use after free, memory corruption or other kinds of **Undefined /// Behaviour**. See for a technical explanation /// on why that is the case. - pub(crate) unsafe fn from_closure_with_captures(closure: F, captures: T) -> Self + pub(crate) unsafe fn from_closure_with_captures( + mc: &boa_gc::MutationContext<'_, '_>, + closure: F, + captures: T, + ) -> Self where F: Fn(CompletionRecord, &T, &mut Context) -> CoroutineState + 'static, T: Trace + 'static, @@ -108,7 +116,7 @@ impl NativeCoroutine { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the // future: https://github.com/rust-lang/rust/issues/18598 let ptr = Gc::into_raw(Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, Coroutine { f: closure, captures, diff --git a/core/engine/src/native_function/mod.rs b/core/engine/src/native_function/mod.rs index 86e9d141160..1cdc33780eb 100644 --- a/core/engine/src/native_function/mod.rs +++ b/core/engine/src/native_function/mod.rs @@ -191,14 +191,15 @@ impl NativeFunction { /// let value = arg.to_u32(&mut context.borrow_mut())?; /// Ok(JsValue::from(value * 2)) /// } - /// NativeFunction::from_async_fn(test); + /// let mut context = Context::default(); + /// NativeFunction::from_async_fn(context.gc_collector(), test); /// ``` - pub fn from_async_fn(f: F) -> Self + pub fn from_async_fn(mc: &boa_gc::MutationContext<'_, '_>, f: F) -> Self where F: AsyncFn(&JsValue, &[JsValue], &RefCell<&mut Context>) -> JsResult + 'static, F: Copy, { - Self::from_copy_closure(move |this, args, context| { + Self::from_copy_closure(mc, move |this, args, context| { let (promise, resolvers) = JsPromise::new_pending(context); let this = this.clone(); let args = args.to_vec(); @@ -224,22 +225,26 @@ impl NativeFunction { } /// Creates a `NativeFunction` from a `Copy` closure. - pub fn from_copy_closure(closure: F) -> Self + pub fn from_copy_closure(mc: &boa_gc::MutationContext<'_, '_>, closure: F) -> Self where F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult + Copy + 'static, { // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. - unsafe { Self::from_closure(closure) } + unsafe { Self::from_closure(mc, closure) } } /// Creates a `NativeFunction` from a `Copy` closure and a list of traceable captures. - pub fn from_copy_closure_with_captures(closure: F, captures: T) -> Self + pub fn from_copy_closure_with_captures( + mc: &boa_gc::MutationContext<'_, '_>, + closure: F, + captures: T, + ) -> Self where F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult + Copy + 'static, T: Trace + 'static, { // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. - unsafe { Self::from_closure_with_captures(closure, captures) } + unsafe { Self::from_closure_with_captures(mc, closure, captures) } } /// Creates a new `NativeFunction` from a closure. @@ -250,13 +255,14 @@ impl NativeFunction { /// collector could cause an use after free, memory corruption or other kinds of **Undefined /// Behaviour**. See for a technical explanation /// on why that is the case. - pub unsafe fn from_closure(closure: F) -> Self + pub unsafe fn from_closure(mc: &boa_gc::MutationContext<'_, '_>, closure: F) -> Self where F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult + 'static, { // SAFETY: The caller must ensure the invariants of the closure hold. unsafe { Self::from_closure_with_captures( + mc, move |this, args, (), context| closure(this, args, context), (), ) @@ -271,7 +277,11 @@ impl NativeFunction { /// collector could cause an use after free, memory corruption or other kinds of **Undefined /// Behaviour**. See for a technical explanation /// on why that is the case. - pub unsafe fn from_closure_with_captures(closure: F, captures: T) -> Self + pub unsafe fn from_closure_with_captures( + mc: &boa_gc::MutationContext<'_, '_>, + closure: F, + captures: T, + ) -> Self where F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult + 'static, T: Trace + 'static, @@ -279,7 +289,7 @@ impl NativeFunction { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the // future: https://github.com/rust-lang/rust/issues/18598 let ptr = Gc::into_raw(Gc::new( - &unsafe { boa_gc::MutationContext::global() }, + mc, Closure { f: closure, captures, diff --git a/core/engine/src/object/builtins/jsfunction.rs b/core/engine/src/object/builtins/jsfunction.rs index 8e76b155ad3..3de7d9b5a0a 100644 --- a/core/engine/src/object/builtins/jsfunction.rs +++ b/core/engine/src/object/builtins/jsfunction.rs @@ -141,18 +141,6 @@ impl JsFunction { } } - /// Creates a new, empty intrinsic function object with only its function internal methods set. - /// - /// Mainly used to initialize objects before a [`Context`] is available to do so. - /// - /// [`Context`]: crate::Context - pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self { - Self::empty_intrinsic_function_in( - &unsafe { boa_gc::MutationContext::global() }, - constructor, - ) - } - /// Creates a [`JsFunction`] from a [`JsObject`], or returns `None` if the object is not a function. /// /// This does not clone the fields of the function, it only does a shallow clone of the object. diff --git a/core/engine/src/object/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index fef2ce9811f..5c3957ef189 100644 --- a/core/engine/src/object/builtins/jspromise.rs +++ b/core/engine/src/object/builtins/jspromise.rs @@ -1105,6 +1105,7 @@ impl JsPromise { let state = state.clone(); NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), move |_, args, state, _| { finish(state, Ok(args.get_or_undefined(0).clone())); Ok(JsValue::undefined()) @@ -1117,6 +1118,7 @@ impl JsPromise { let state = state.clone(); NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), move |_, args, state, _| { let err = JsError::from_opaque(args.get_or_undefined(0).clone()); finish(state, Err(err)); @@ -1245,6 +1247,7 @@ impl JsPromise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, captures, context| { // a. Let prevContext be the running execution context. // b. Suspend prevContext. @@ -1310,6 +1313,7 @@ impl JsPromise { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, captures, context| { // a. Let prevContext be the running execution context. // b. Suspend prevContext. diff --git a/core/engine/src/object/builtins/jstypedarray.rs b/core/engine/src/object/builtins/jstypedarray.rs index 817391d4ca0..4cc23bdfb69 100644 --- a/core/engine/src/object/builtins/jstypedarray.rs +++ b/core/engine/src/object/builtins/jstypedarray.rs @@ -687,6 +687,7 @@ impl JsTypedArray { /// context.realm(), /// context.gc_collector(), /// NativeFunction::from_copy_closure_with_captures( + /// context.gc_collector(), /// |_, args, captures, inner_context| { /// let element = args /// .first() diff --git a/core/engine/src/object/internal_methods/mod.rs b/core/engine/src/object/internal_methods/mod.rs index e2c11a4f662..d7223e1da7e 100644 --- a/core/engine/src/object/internal_methods/mod.rs +++ b/core/engine/src/object/internal_methods/mod.rs @@ -1010,18 +1010,11 @@ pub(crate) fn is_compatible_property_descriptor( extensible: bool, desc: PropertyDescriptor, current: Option, + mc: &boa_gc::MutationContext<'_, '_>, ) -> bool { // 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current). let mut dummy_slot = Slot::new(); - let dummy_mc = unsafe { boa_gc::MutationContext::global() }; - validate_and_apply_property_descriptor( - None, - extensible, - desc, - current, - &mut dummy_slot, - &dummy_mc, - ) + validate_and_apply_property_descriptor(None, extensible, desc, current, &mut dummy_slot, mc) } /// Abstract operation `ValidateAndApplyPropertyDescriptor` diff --git a/core/engine/src/object/internal_methods/string.rs b/core/engine/src/object/internal_methods/string.rs index 11a60219bd3..57f7087a103 100644 --- a/core/engine/src/object/internal_methods/string.rs +++ b/core/engine/src/object/internal_methods/string.rs @@ -68,6 +68,7 @@ pub(crate) fn string_exotic_define_own_property( extensible, desc, Some(string_desc), + context.gc_collector(), )) } else { // 4. Return ! OrdinaryDefineOwnProperty(S, P, Desc). diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index ea09d5785df..af5351f04fc 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -359,7 +359,7 @@ impl JsValue { /// use boa_engine::{Context, JsValue, NativeFunction}; /// /// let context = &mut Context::default(); - /// let native_fn = NativeFunction::from_copy_closure(|_, _, _| Ok(JsValue::undefined())); + /// let native_fn = NativeFunction::from_copy_closure(context.gc_collector(), |_, _, _| Ok(JsValue::undefined())); /// let js_value = JsValue::from(native_fn.to_js_function(context.realm(), context.gc_collector())); /// assert!(js_value.is_callable()); /// @@ -380,7 +380,7 @@ impl JsValue { /// use boa_engine::{Context, JsValue, NativeFunction}; /// /// let context = &mut Context::default(); - /// let native_fn = NativeFunction::from_copy_closure(|_, _, _| Ok(JsValue::undefined())); + /// let native_fn = NativeFunction::from_copy_closure(context.gc_collector(), |_, _, _| Ok(JsValue::undefined())); /// let js_value = JsValue::from(native_fn.to_js_function(context.realm(), context.gc_collector())); /// assert!(js_value.as_callable().is_some()); /// @@ -402,7 +402,7 @@ impl JsValue { /// use boa_engine::{Context, JsValue, NativeFunction}; /// /// let context = &mut Context::default(); - /// let native_fn = NativeFunction::from_copy_closure(|_, _, _| Ok(JsValue::undefined())); + /// let native_fn = NativeFunction::from_copy_closure(context.gc_collector(), |_, _, _| Ok(JsValue::undefined())); /// let js_value = JsValue::from(native_fn.to_js_function(context.realm(), context.gc_collector())); /// assert!(js_value.as_function().is_some()); /// diff --git a/core/engine/src/vm/opcode/await/mod.rs b/core/engine/src/vm/opcode/await/mod.rs index 9c777986a80..4a4b3ba41fa 100644 --- a/core/engine/src/vm/opcode/await/mod.rs +++ b/core/engine/src/vm/opcode/await/mod.rs @@ -64,6 +64,7 @@ impl Await { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, captures, context| { // a. Let prevContext be the running execution context. // b. Suspend prevContext. @@ -104,6 +105,7 @@ impl Await { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_this, args, captures, context| { // a. Let prevContext be the running execution context. // b. Suspend prevContext. diff --git a/core/engine/src/vm/opcode/call/mod.rs b/core/engine/src/vm/opcode/call/mod.rs index c71e8003a4a..aa1b42e3bb9 100644 --- a/core/engine/src/vm/opcode/call/mod.rs +++ b/core/engine/src/vm/opcode/call/mod.rs @@ -453,6 +453,7 @@ async fn load_dyn_import( context.borrow().realm(), &mc, NativeFunction::from_copy_closure_with_captures( + &mc, |_, args, cap, context| { // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »). cap.reject() @@ -474,6 +475,7 @@ async fn load_dyn_import( context.borrow().realm(), &mc, NativeFunction::from_copy_closure_with_captures( + &mc, |_, _, (module, cap, on_rejected), context| { // a. Let link be Completion(module.Link()). // b. If link is an abrupt completion, then @@ -496,6 +498,7 @@ async fn load_dyn_import( context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, _, (module, cap), context| { // i. Let namespace be GetModuleNamespace(module). let namespace = module.namespace(context); diff --git a/core/engine/src/vm/tests.rs b/core/engine/src/vm/tests.rs index 4655d97a09f..85f6606bce0 100644 --- a/core/engine/src/vm/tests.rs +++ b/core/engine/src/vm/tests.rs @@ -52,7 +52,7 @@ fn position() { .register_global_callable( js_string!("check_stack"), 2, - NativeFunction::from_copy_closure(|_, _, context| { + NativeFunction::from_copy_closure(context.gc_collector(), |_, _, context| { let frame = context.stack_trace().collect::>(); assert_eq!(frame.len(), 4); diff --git a/core/gc/src/context.rs b/core/gc/src/context.rs index 4df8c7e3012..5cf014b83fa 100644 --- a/core/gc/src/context.rs +++ b/core/gc/src/context.rs @@ -12,6 +12,18 @@ impl Default for GcContext { } } +#[cfg(feature = "oscars_backend")] +thread_local! { + static COLLECTOR: &'static oscars::collectors::mark_sweep_branded::Collector = + Box::leak(Box::new(oscars::collectors::mark_sweep_branded::Collector::new())); + + static DUMMY: &'static MutationContext<'static, 'static> = COLLECTOR.with(|c| { + Box::leak(Box::new(unsafe { + MutationContext::from_collector_erased(*c) + })) + }); +} + #[cfg(feature = "oscars_backend")] impl GcContext { #[must_use] @@ -20,24 +32,18 @@ impl GcContext { } pub fn alloc(&self, value: T) -> Gc<'static, T> { - let mc = MutationContext::global(); - Gc::new(&mc, value) + let mc = self.gc_collector(); + Gc::new(mc, value) } #[must_use] pub fn gc_collector(&self) -> &'static MutationContext<'static, 'static> { - thread_local! { - static COLLECTOR: &'static oscars::collectors::mark_sweep_branded::Collector = - Box::leak(Box::new(oscars::collectors::mark_sweep_branded::Collector::new())); - - static DUMMY: &'static MutationContext<'static, 'static> = COLLECTOR.with(|c| { - Box::leak(Box::new(unsafe { - oscars::collectors::mark_sweep_branded::MutationContext::from_collector_erased(*c) - })) - }); - } DUMMY.with(|dummy| *dummy) } + + pub fn force_collect(&self) { + COLLECTOR.with(|c| c.collect()); + } } #[cfg(not(feature = "oscars_backend"))] diff --git a/core/gc/src/lib.rs b/core/gc/src/lib.rs index 2296038dc87..b50c2dda207 100644 --- a/core/gc/src/lib.rs +++ b/core/gc/src/lib.rs @@ -171,4 +171,8 @@ mod test; #[cfg(feature = "oscars_backend")] /// Forces a garbage collection -pub fn force_collect() {} +pub fn force_collect() { + let mc = MutationContext::global(); + mc.collect(); + crate::context::GcContext::new().force_collect(); +} diff --git a/core/macros/src/module.rs b/core/macros/src/module.rs index beb9c5cef59..046bc4fe022 100644 --- a/core/macros/src/module.rs +++ b/core/macros/src/module.rs @@ -272,7 +272,7 @@ fn module_impl_impl(_args: ModuleArguments, mut mod_: ItemMod) -> SpannedResult< boa_engine::Module::synthetic( &[ #module_exports ], boa_engine::module::SyntheticModuleInitializer::from_copy_closure( - |m, context| { + context.gc_collector(), |m, context| { #module_fn Ok(()) } diff --git a/core/runtime/src/console/mod.rs b/core/runtime/src/console/mod.rs index 3aec9447708..a9dc41fd6fb 100644 --- a/core/runtime/src/console/mod.rs +++ b/core/runtime/src/console/mod.rs @@ -339,25 +339,27 @@ impl Console { L: Logger + 'static, { fn console_method( + mc: &boa_gc::MutationContext<'_, '_>, f: fn(&JsValue, &[JsValue], &Console, &L, &mut Context) -> JsResult, state: Rc>, logger: Rc, ) -> NativeFunction { // SAFETY: `Console` doesn't contain types that need tracing. unsafe { - NativeFunction::from_closure(move |this, args, context| { + NativeFunction::from_closure(mc, move |this, args, context| { f(this, args, &state.borrow(), &logger, context) }) } } fn console_method_mut( + mc: &boa_gc::MutationContext<'_, '_>, f: fn(&JsValue, &[JsValue], &mut Console, &L, &mut Context) -> JsResult, state: Rc>, logger: Rc, ) -> NativeFunction { // SAFETY: `Console` doesn't contain types that need tracing. unsafe { - NativeFunction::from_closure(move |this, args, context| { + NativeFunction::from_closure(mc, move |this, args, context| { f(this, args, &mut state.borrow_mut(), &logger, context) }) } @@ -366,6 +368,116 @@ impl Console { let state = Rc::new(RefCell::new(Self::default())); let logger = Rc::new(logger); + let assert_fn = console_method( + context.gc_collector(), + Self::assert, + state.clone(), + logger.clone(), + ); + let clear_fn = console_method_mut( + context.gc_collector(), + Self::clear, + state.clone(), + logger.clone(), + ); + let debug_fn = console_method( + context.gc_collector(), + Self::debug, + state.clone(), + logger.clone(), + ); + let error_fn = console_method( + context.gc_collector(), + Self::error, + state.clone(), + logger.clone(), + ); + let info_fn = console_method( + context.gc_collector(), + Self::info, + state.clone(), + logger.clone(), + ); + let log_fn = console_method( + context.gc_collector(), + Self::log, + state.clone(), + logger.clone(), + ); + let trace_fn = console_method( + context.gc_collector(), + Self::trace, + state.clone(), + logger.clone(), + ); + let warn_fn = console_method( + context.gc_collector(), + Self::warn, + state.clone(), + logger.clone(), + ); + let count_fn = console_method_mut( + context.gc_collector(), + Self::count, + state.clone(), + logger.clone(), + ); + let count_reset_fn = console_method_mut( + context.gc_collector(), + Self::count_reset, + state.clone(), + logger.clone(), + ); + let group_fn = console_method_mut( + context.gc_collector(), + Self::group, + state.clone(), + logger.clone(), + ); + let group_collapsed_fn = console_method_mut( + context.gc_collector(), + Self::group_collapsed, + state.clone(), + logger.clone(), + ); + let group_end_fn = console_method_mut( + context.gc_collector(), + Self::group_end, + state.clone(), + logger.clone(), + ); + let time_fn = console_method_mut( + context.gc_collector(), + Self::time, + state.clone(), + logger.clone(), + ); + let time_log_fn = console_method( + context.gc_collector(), + Self::time_log, + state.clone(), + logger.clone(), + ); + let time_end_fn = console_method_mut( + context.gc_collector(), + Self::time_end, + state.clone(), + logger.clone(), + ); + let dir_fn = console_method( + context.gc_collector(), + Self::dir, + state.clone(), + logger.clone(), + ); + let dirxml_fn = console_method( + context.gc_collector(), + Self::dir, + state.clone(), + logger.clone(), + ); + let table_fn = console_method(context.gc_collector(), Self::table, state, logger.clone()); + ObjectInitializer::with_native_data_and_proto( Self::default(), JsObject::with_object_proto(context.gc_collector(), context.realm().intrinsics()), @@ -376,101 +488,25 @@ impl Console { Self::NAME, Attribute::CONFIGURABLE, ) - .function( - console_method(Self::assert, state.clone(), logger.clone()), - js_string!("assert"), - 0, - ) - .function( - console_method_mut(Self::clear, state.clone(), logger.clone()), - js_string!("clear"), - 0, - ) - .function( - console_method(Self::debug, state.clone(), logger.clone()), - js_string!("debug"), - 0, - ) - .function( - console_method(Self::error, state.clone(), logger.clone()), - js_string!("error"), - 0, - ) - .function( - console_method(Self::info, state.clone(), logger.clone()), - js_string!("info"), - 0, - ) - .function( - console_method(Self::log, state.clone(), logger.clone()), - js_string!("log"), - 0, - ) - .function( - console_method(Self::trace, state.clone(), logger.clone()), - js_string!("trace"), - 0, - ) - .function( - console_method(Self::warn, state.clone(), logger.clone()), - js_string!("warn"), - 0, - ) - .function( - console_method_mut(Self::count, state.clone(), logger.clone()), - js_string!("count"), - 0, - ) - .function( - console_method_mut(Self::count_reset, state.clone(), logger.clone()), - js_string!("countReset"), - 0, - ) - .function( - console_method_mut(Self::group, state.clone(), logger.clone()), - js_string!("group"), - 0, - ) - .function( - console_method_mut(Self::group_collapsed, state.clone(), logger.clone()), - js_string!("groupCollapsed"), - 0, - ) - .function( - console_method_mut(Self::group_end, state.clone(), logger.clone()), - js_string!("groupEnd"), - 0, - ) - .function( - console_method_mut(Self::time, state.clone(), logger.clone()), - js_string!("time"), - 0, - ) - .function( - console_method(Self::time_log, state.clone(), logger.clone()), - js_string!("timeLog"), - 0, - ) - .function( - console_method_mut(Self::time_end, state.clone(), logger.clone()), - js_string!("timeEnd"), - 0, - ) - .function( - console_method(Self::dir, state.clone(), logger.clone()), - js_string!("dir"), - 0, - ) - .function( - console_method(Self::dir, state.clone(), logger.clone()), - js_string!("dirxml"), - 0, - ) - .function( - console_method(Self::table, state, logger.clone()), - js_string!("table"), - 0, - ) + .function(assert_fn, js_string!("assert"), 0) + .function(clear_fn, js_string!("clear"), 0) + .function(debug_fn, js_string!("debug"), 0) + .function(error_fn, js_string!("error"), 0) + .function(info_fn, js_string!("info"), 0) + .function(log_fn, js_string!("log"), 0) + .function(trace_fn, js_string!("trace"), 0) + .function(warn_fn, js_string!("warn"), 0) + .function(count_fn, js_string!("count"), 0) + .function(count_reset_fn, js_string!("countReset"), 0) + .function(group_fn, js_string!("group"), 0) + .function(group_collapsed_fn, js_string!("groupCollapsed"), 0) + .function(group_end_fn, js_string!("groupEnd"), 0) + .function(time_fn, js_string!("time"), 0) + .function(time_log_fn, js_string!("timeLog"), 0) + .function(time_end_fn, js_string!("timeEnd"), 0) + .function(dir_fn, js_string!("dir"), 0) + .function(dirxml_fn, js_string!("dirxml"), 0) + .function(table_fn, js_string!("table"), 0) .build() } diff --git a/core/runtime/src/console/tests.rs b/core/runtime/src/console/tests.rs index 8810d7d038e..2b24100fea8 100644 --- a/core/runtime/src/console/tests.rs +++ b/core/runtime/src/console/tests.rs @@ -137,6 +137,29 @@ impl Logger for RecordingLogger { fn error(&self, msg: String, state: &ConsoleState, context: &mut Context) -> JsResult<()> { self.log(msg, state, context) } + + fn table( + &self, + data: crate::console::TableData, + state: &ConsoleState, + context: &mut Context, + ) -> JsResult<()> { + let mut table = comfy_table::Table::new(); + table.load_preset(comfy_table::presets::UTF8_FULL); + // Do not use Dynamic arrangement in tests to avoid wrapping based on pseudo-TTY width. + table.set_header(&data.col_names); + + for row in &data.rows { + let cells: Vec = data + .col_names + .iter() + .map(|name| comfy_table::Cell::new(row.get(name).cloned().unwrap_or_default())) + .collect(); + table.add_row(cells); + } + + self.log(table.to_string(), state, context) + } } /// Harness methods to be used in JS tests. diff --git a/core/runtime/src/fetch/headers_iterator.rs b/core/runtime/src/fetch/headers_iterator.rs index aa7e4116dc5..6be8da9de08 100644 --- a/core/runtime/src/fetch/headers_iterator.rs +++ b/core/runtime/src/fetch/headers_iterator.rs @@ -116,11 +116,7 @@ impl HeadersIterator { .ok_or_else(|| boa_engine::js_error!(Error: "Headers Iterator not registered"))? .prototype(); - let headers_iterator = JsObject::from_proto_and_data( - &unsafe { boa_gc::MutationContext::global() }, - proto, - iter, - ); + let headers_iterator = JsObject::from_proto_and_data(context.gc_collector(), proto, iter); Ok(headers_iterator.into()) } } diff --git a/core/runtime/src/process/mod.rs b/core/runtime/src/process/mod.rs index a9f04743aab..48b39757297 100644 --- a/core/runtime/src/process/mod.rs +++ b/core/runtime/src/process/mod.rs @@ -69,12 +69,13 @@ impl Process { P: ProcessProvider + 'static, { fn process_method( + mc: &boa_gc::MutationContext<'_, '_>, f: fn(&JsValue, &[JsValue], &P, &mut Context) -> JsResult, provider: Rc

, ) -> NativeFunction { // SAFETY: `Process` doesn't contain types that need tracing. unsafe { - NativeFunction::from_closure(move |this, args, context| { + NativeFunction::from_closure(mc, move |this, args, context| { f(this, args, &provider, context) }) } @@ -87,6 +88,8 @@ impl Process { env.set(key, JsValue::from(value), false, context)?; } + let gc_collector = context.gc_collector(); + Ok(ObjectInitializer::new(context) .property( JsSymbol::to_string_tag(), @@ -100,6 +103,7 @@ impl Process { ) .function( process_method( + gc_collector, |_, _, provider, _| provider.cwd().map(JsValue::from), provider.clone(), ), diff --git a/core/runtime/src/test262.rs b/core/runtime/src/test262.rs index 34552890664..9dac56e496e 100644 --- a/core/runtime/src/test262.rs +++ b/core/runtime/src/test262.rs @@ -127,11 +127,7 @@ pub fn register_js262(handles: WorkerHandles, console: bool, context: &mut Conte js262 .create_data_property_or_throw( js_string!("IsHTMLDDA"), - JsObject::from_proto_and_data( - &unsafe { boa_gc::MutationContext::global() }, - None, - IsHTMLDDA, - ), + JsObject::from_proto_and_data(context.gc_collector(), None, IsHTMLDDA), context, ) .expect("the IsHTMLDDA property must be definable"); @@ -235,7 +231,7 @@ fn agent_obj(handles: WorkerHandles, console: bool, context: &mut Context) -> Js let start = unsafe { let bus = bus.clone(); - NativeFunction::from_closure(move |_, args, context| { + NativeFunction::from_closure(context.gc_collector(), move |_, args, context| { let script = args .get_or_undefined(0) .to_string(context)? @@ -274,7 +270,7 @@ fn agent_obj(handles: WorkerHandles, console: bool, context: &mut Context) -> Js let broadcast = unsafe { // should technically also have a second numeric argument, but the test262 never uses it. - NativeFunction::from_closure(move |_, args, _| { + NativeFunction::from_closure(context.gc_collector(), move |_, args, _| { let buffer = args.get_or_undefined(0).as_object().ok_or_else(|| { JsNativeError::typ().with_message("argument was not a shared array") })?; @@ -290,7 +286,7 @@ fn agent_obj(handles: WorkerHandles, console: bool, context: &mut Context) -> Js }; let get_report = unsafe { - NativeFunction::from_closure(move |_, _, _| { + NativeFunction::from_closure(context.gc_collector(), move |_, _, _| { let Ok(msg) = reports_rx.try_recv() else { return Ok(JsValue::null()); }; @@ -321,7 +317,7 @@ fn register_js262_worker( let rx = RefCell::new(rx); let receive_broadcast = unsafe { // should technically also have a second numeric argument, but the test262 never uses it. - NativeFunction::from_closure(move |_, args, context| { + NativeFunction::from_closure(context.gc_collector(), move |_, args, context| { let array = rx.borrow_mut().recv().map_err(|err| { JsNativeError::typ().with_message(format!("failed to receive buffer: {err}")) })?; @@ -337,7 +333,7 @@ fn register_js262_worker( }; let report = unsafe { - NativeFunction::from_closure(move |_, args, context| { + NativeFunction::from_closure(context.gc_collector(), move |_, args, context| { let string = args.get_or_undefined(0).to_string(context)?.to_vec(); tx.send(string) .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?; diff --git a/examples/src/bin/closures.rs b/examples/src/bin/closures.rs index ffeda1681bc..24d92645b4d 100644 --- a/examples/src/bin/closures.rs +++ b/examples/src/bin/closures.rs @@ -24,7 +24,7 @@ fn main() -> Result<(), JsError> { .register_global_callable( JsString::from("closure"), 0, - NativeFunction::from_copy_closure(move |_, _, _| { + NativeFunction::from_copy_closure(context.gc_collector(), move |_, _, _| { println!("Called `closure`"); // `variable` is captured from the main function. println!("variable = {variable}"); @@ -72,6 +72,7 @@ fn main() -> Result<(), JsError> { context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, _, captures, context| { let mut captures = captures.borrow_mut(); let BigStruct { greeting, object } = &mut *captures; @@ -80,15 +81,16 @@ fn main() -> Result<(), JsError> { let name = object.get(js_string!("name"), context)?; // We create a new message from our captured variable. + let greeting_ref: &JsString = greeting; let message = js_string!( &js_string!("message from `"), &name.to_string(context)?, &js_string!("`: "), - &*greeting + greeting_ref ); // We can also mutate the moved data inside the closure. - captures.greeting = js_string!(&*greeting, &js_string!(" Hello!")); + captures.greeting = js_string!(greeting_ref, &js_string!(" Hello!")); println!("{}", message.to_std_string_escaped()); println!(); @@ -149,7 +151,7 @@ fn main() -> Result<(), JsError> { // 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| { + NativeFunction::from_closure(context.gc_collector(), move |_, _, context| { println!("Called `enumerate`"); // `index` is captured from the main function. println!("index = {}", index.get()); diff --git a/examples/src/bin/jstypedarray.rs b/examples/src/bin/jstypedarray.rs index 0a2971c5a1f..92987af7c2f 100644 --- a/examples/src/bin/jstypedarray.rs +++ b/examples/src/bin/jstypedarray.rs @@ -95,15 +95,13 @@ fn main() -> JsResult<()> { // forEach let array = JsUint8Array::from_iter(vec![1, 2, 3, 4, 5], context)?; - let num_to_modify = Gc::new( - &unsafe { boa_gc::MutationContext::global() }, - GcRefCell::new(0u8), - ); + let num_to_modify = Gc::new(context.gc_collector(), GcRefCell::new(0u8)); let js_function = FunctionObjectBuilder::new( context.realm(), context.gc_collector(), NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, args, captures, inner_context| { let element = args .first() diff --git a/examples/src/bin/modules.rs b/examples/src/bin/modules.rs index d513c6f3ccb..aefc3f2f947 100644 --- a/examples/src/bin/modules.rs +++ b/examples/src/bin/modules.rs @@ -55,6 +55,7 @@ fn main() -> Result<(), Box> { .then( Some( NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), |_, _, module, context| { // After loading, link all modules by resolving the imports // and exports on the full module graph, initializing module @@ -74,6 +75,7 @@ fn main() -> Result<(), Box> { .then( Some( NativeFunction::from_copy_closure_with_captures( + context.gc_collector(), // Finally, evaluate the root module. // This returns a `JsPromise` since a module could have // top-level await statements, which defers module execution to the diff --git a/examples/src/bin/smol_event_loop.rs b/examples/src/bin/smol_event_loop.rs index 504c39761d1..b54bf9ade1a 100644 --- a/examples/src/bin/smol_event_loop.rs +++ b/examples/src/bin/smol_event_loop.rs @@ -201,7 +201,7 @@ fn add_runtime(context: &mut Context) { .register_global_builtin_callable( js_string!("delay"), 1, - NativeFunction::from_async_fn(delay), + NativeFunction::from_async_fn(context.gc_collector(), delay), ) .expect("the delay builtin shouldn't exist"); diff --git a/examples/src/bin/synthetic.rs b/examples/src/bin/synthetic.rs index c892c9b35de..e87fdec0679 100644 --- a/examples/src/bin/synthetic.rs +++ b/examples/src/bin/synthetic.rs @@ -167,6 +167,7 @@ fn create_operations_module(context: &mut Context) -> Module { // The initializer is evaluated every time a module imports this synthetic module, // so we avoid creating duplicate objects by capturing and cloning them instead. SyntheticModuleInitializer::from_copy_closure_with_captures( + context.gc_collector(), |module, fns, _| { println!("Running initializer!"); module.set_export(&js_string!("sum"), fns.0.clone().into())?; diff --git a/examples/src/bin/tokio_event_loop.rs b/examples/src/bin/tokio_event_loop.rs index d82a1ed31af..2261cd52e58 100644 --- a/examples/src/bin/tokio_event_loop.rs +++ b/examples/src/bin/tokio_event_loop.rs @@ -208,7 +208,7 @@ fn add_runtime(context: &mut Context) { .register_global_builtin_callable( js_string!("delay"), 1, - NativeFunction::from_async_fn(delay), + NativeFunction::from_async_fn(context.gc_collector(), delay), ) .expect("the delay builtin shouldn't exist"); diff --git a/tests/tester/src/exec/mod.rs b/tests/tester/src/exec/mod.rs index 4fbe43a7a9d..44e47688b58 100644 --- a/tests/tester/src/exec/mod.rs +++ b/tests/tester/src/exec/mod.rs @@ -515,6 +515,9 @@ impl Test { }, ); + // Force GC collection to prevent memory exhaustion when running tens of thousands of tests. + boa_engine::gc::force_collect(); + self.create_result(result, result_text, strict, verbosity) } @@ -628,7 +631,7 @@ fn register_print_fn(context: &mut Context, async_result: AsyncResult) { context.gc_collector(), // SAFETY: `AsyncResult` has only non-traceable captures, making this safe. unsafe { - NativeFunction::from_closure(move |_, args, context| { + NativeFunction::from_closure(context.gc_collector(), move |_, args, context| { let message = args .get_or_undefined(0) .to_string(context)? From bc7d8fe6a0ba1ebd7d6a1efa35e16cf061f5916f Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Thu, 20 Aug 2026 01:19:28 +0000 Subject: [PATCH 08/19] Implement exact rooting via HandleScope --- core/engine/src/bytecompiler/class.rs | 12 +- core/engine/src/bytecompiler/function.rs | 2 +- core/engine/src/context/mod.rs | 6 + core/engine/src/environments/runtime/mod.rs | 8 +- core/engine/src/module/synthetic.rs | 2 +- .../src/native_function/continuation.rs | 2 +- core/engine/src/native_function/mod.rs | 2 +- core/engine/src/object/jsobject.rs | 12 +- .../src/object/shape/shared_shape/mod.rs | 2 +- core/engine/src/object/shape/unique_shape.rs | 2 +- core/engine/src/realm.rs | 4 +- core/engine/src/value/inner/nan_boxed.rs | 3 + core/engine/src/value/tests.rs | 3 + core/engine/src/vm/mod.rs | 2 +- core/gc/src/context.rs | 10 ++ core/gc/src/lib.rs | 25 ++++ core/gc/src/pointers/gc.rs | 9 +- core/gc/src/scope.rs | 110 ++++++++++++++++++ core/gc/src/scope_tracker.rs | 22 ++++ 19 files changed, 212 insertions(+), 26 deletions(-) create mode 100644 core/gc/src/scope.rs create mode 100644 core/gc/src/scope_tracker.rs diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index 25f8563b08c..f48d0862deb 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/core/engine/src/bytecompiler/class.rs @@ -157,7 +157,7 @@ impl ByteCompiler<'_> { class.super_ref.is_some(), ); - let code = Gc::new(self.mc.0, compiler.finish()); + let code = boa_gc::allocate_rooted(self.mc.0, compiler.finish()); let index = self.push_function_to_constants(code); let class_register = self.register_allocator.alloc(); @@ -442,7 +442,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; - let code = Gc::new(self.mc.0, field_compiler.finish()); + let code = boa_gc::allocate_rooted(self.mc.0, field_compiler.finish()); let index = self.push_function_to_constants(code); let dst = self.register_allocator.alloc(); @@ -489,7 +489,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; - let code = Gc::new(self.mc.0, field_compiler.finish()); + let code = boa_gc::allocate_rooted(self.mc.0, field_compiler.finish()); let index = self.push_function_to_constants(code); let dst = self.register_allocator.alloc(); self.emit_get_function(&dst, index); @@ -546,7 +546,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; let code = field_compiler.finish(); - let code = Gc::new(self.mc.0, code); + let code = boa_gc::allocate_rooted(self.mc.0, code); static_elements.push(StaticElement::StaticField { code, @@ -591,7 +591,7 @@ impl ByteCompiler<'_> { field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER; let code = field_compiler.finish(); - let code = Gc::new(self.mc.0, code); + let code = boa_gc::allocate_rooted(self.mc.0, code); static_elements.push(StaticElement::StaticField { code, @@ -635,7 +635,7 @@ impl ByteCompiler<'_> { ); } - let code = Gc::new(self.mc.0, compiler.finish()); + let code = boa_gc::allocate_rooted(self.mc.0, compiler.finish()); static_elements.push(StaticElement::StaticBlock(code)); } } diff --git a/core/engine/src/bytecompiler/function.rs b/core/engine/src/bytecompiler/function.rs index 371b8ab53fc..97321284d34 100644 --- a/core/engine/src/bytecompiler/function.rs +++ b/core/engine/src/bytecompiler/function.rs @@ -229,6 +229,6 @@ impl FunctionCompiler { let code = compiler.finish(); - Gc::new(mc, code) + boa_gc::allocate_rooted(mc, code) } } diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index a0117b5348a..4e252980b69 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -108,6 +108,8 @@ pub struct Context { pub(crate) kept_alive: Vec, pub gc: boa_gc::GcContext, + #[cfg(feature = "oscars_backend")] + global_scope: boa_gc::HandleScope, can_block: bool, @@ -1227,6 +1229,8 @@ impl ContextBuilder { } let gc = boa_gc::GcContext::new(); + #[cfg(feature = "oscars_backend")] + let global_scope = boa_gc::HandleScope::enter(); let mc = gc.gc_collector(); let root_shape = RootShape::new(&mc); @@ -1283,6 +1287,8 @@ impl ContextBuilder { root_shape, parser_identifier: 0, gc, + #[cfg(feature = "oscars_backend")] + global_scope, can_block: self.can_block, data: HostDefined::default(), }; diff --git a/core/engine/src/environments/runtime/mod.rs b/core/engine/src/environments/runtime/mod.rs index bdcc1bea321..ad4c7be8e1f 100644 --- a/core/engine/src/environments/runtime/mod.rs +++ b/core/engine/src/environments/runtime/mod.rs @@ -227,7 +227,7 @@ impl EnvironmentStack { let index = self.depth; self.push_env( - Environment::Declarative(Gc::new( + Environment::Declarative(boa_gc::allocate_rooted( &gc, DeclarativeEnvironment::new( DeclarativeEnvironmentKind::Lexical(LexicalEnvironment::new(bindings_count)), @@ -254,7 +254,7 @@ impl EnvironmentStack { let (poisoned, with) = self.compute_poisoned_with(global); self.push_env( - Environment::Declarative(Gc::new( + Environment::Declarative(boa_gc::allocate_rooted( gc, DeclarativeEnvironment::new( DeclarativeEnvironmentKind::Function(FunctionEnvironment::new( @@ -274,7 +274,7 @@ impl EnvironmentStack { pub(crate) fn push_module(&mut self, scope: Scope, gc: &boa_gc::MutationContext<'static, '_>) { let num_bindings = scope.num_bindings_non_local(); self.push_env( - Environment::Declarative(Gc::new( + Environment::Declarative(boa_gc::allocate_rooted( gc, DeclarativeEnvironment::new( DeclarativeEnvironmentKind::Module(ModuleEnvironment::new(num_bindings, scope)), @@ -430,7 +430,7 @@ impl EnvironmentStack { /// Push an environment onto the chain. fn push_env(&mut self, env: Environment, mc: &boa_gc::MutationContext<'static, '_>) { - self.tip = Some(Gc::new( + self.tip = Some(boa_gc::allocate_rooted( mc, EnvironmentNode { env, diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index ad178da7cfc..201a004086a 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -128,7 +128,7 @@ impl SyntheticModuleInitializer { { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the // future: https://github.com/rust-lang/rust/issues/18598 - let ptr = Gc::into_raw(Gc::new( + let ptr = Gc::into_raw(boa_gc::allocate_rooted( mc, Callback { f: closure, diff --git a/core/engine/src/native_function/continuation.rs b/core/engine/src/native_function/continuation.rs index 651bd9de071..51ded311742 100644 --- a/core/engine/src/native_function/continuation.rs +++ b/core/engine/src/native_function/continuation.rs @@ -115,7 +115,7 @@ impl NativeCoroutine { { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the // future: https://github.com/rust-lang/rust/issues/18598 - let ptr = Gc::into_raw(Gc::new( + let ptr = Gc::into_raw(boa_gc::allocate_rooted( mc, Coroutine { f: closure, diff --git a/core/engine/src/native_function/mod.rs b/core/engine/src/native_function/mod.rs index 1cdc33780eb..525df98cb85 100644 --- a/core/engine/src/native_function/mod.rs +++ b/core/engine/src/native_function/mod.rs @@ -288,7 +288,7 @@ impl NativeFunction { { // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the // future: https://github.com/rust-lang/rust/issues/18598 - let ptr = Gc::into_raw(Gc::new( + let ptr = Gc::into_raw(boa_gc::allocate_rooted( mc, Closure { f: closure, diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs index 00abeba6cce..377f4bc1ed4 100644 --- a/core/engine/src/object/jsobject.rs +++ b/core/engine/src/object/jsobject.rs @@ -122,7 +122,7 @@ impl JsObject { object: Object, vtable: &'static InternalObjectMethods, ) -> Self { - let inner = Gc::new( + let inner = boa_gc::allocate_rooted( mc, VTableObject { object: GcRefCell::new(object), @@ -177,7 +177,7 @@ impl JsObject { data: T, ) -> Self { let internal_methods = data.internal_methods(); - let inner = Gc::new( + let inner = boa_gc::allocate_rooted( mc, VTableObject { object: GcRefCell::new(Object { @@ -201,7 +201,7 @@ impl JsObject { data: T, ) -> JsObject { let internal_methods = data.internal_methods(); - let inner = Gc::new( + let inner = boa_gc::allocate_rooted( mc, VTableObject { object: GcRefCell::new(Object { @@ -1032,6 +1032,8 @@ impl JsObject { } pub(crate) fn from_inner(inner: Gc<'static, VTableObject>) -> Self { + #[cfg(feature = "oscars_backend")] + let _root = boa_gc::Local::new(inner.clone()); Self { inner } } @@ -1051,7 +1053,7 @@ impl JsObject { data: T, ) -> Self { let internal_methods = data.internal_methods(); - let inner = Gc::new( + let inner = boa_gc::allocate_rooted( mc, VTableObject { object: GcRefCell::new(Object { @@ -1101,7 +1103,7 @@ impl JsObject { data: T, ) -> Self { let internal_methods = data.internal_methods(); - let inner = Gc::new( + let inner = boa_gc::allocate_rooted( mc, VTableObject { object: GcRefCell::new(Object { diff --git a/core/engine/src/object/shape/shared_shape/mod.rs b/core/engine/src/object/shape/shared_shape/mod.rs index 2f99dd5d587..99bbb4f04de 100644 --- a/core/engine/src/object/shape/shared_shape/mod.rs +++ b/core/engine/src/object/shape/shared_shape/mod.rs @@ -166,7 +166,7 @@ impl SharedShape { /// Create a new [`SharedShape`] using the given context. fn new(mc: &boa_gc::MutationContext<'static, '_>, inner: Inner) -> Self { Self { - inner: Gc::new(mc, inner), + inner: boa_gc::allocate_rooted(mc, inner), } } diff --git a/core/engine/src/object/shape/unique_shape.rs b/core/engine/src/object/shape/unique_shape.rs index 02d7c3e1cc4..33d62b08234 100644 --- a/core/engine/src/object/shape/unique_shape.rs +++ b/core/engine/src/object/shape/unique_shape.rs @@ -41,7 +41,7 @@ impl UniqueShape { property_table: PropertyTableInner, ) -> Self { Self { - inner: Gc::new( + inner: boa_gc::allocate_rooted( mc, Inner { property_table: RefCell::new(property_table), diff --git a/core/engine/src/realm.rs b/core/engine/src/realm.rs index 39d1a382b4d..2050c5a9052 100644 --- a/core/engine/src/realm.rs +++ b/core/engine/src/realm.rs @@ -90,11 +90,11 @@ impl Realm { let global_this = hooks .create_global_this(&intrinsics) .unwrap_or_else(|| global_object.clone()); - let environment = Gc::new(mc, DeclarativeEnvironment::global()); + let environment = boa_gc::allocate_rooted(mc, DeclarativeEnvironment::global()); let scope = Scope::new_global(); let realm = Self { - inner: Gc::new( + inner: boa_gc::allocate_rooted( mc, Inner { intrinsics, diff --git a/core/engine/src/value/inner/nan_boxed.rs b/core/engine/src/value/inner/nan_boxed.rs index e729ce793d8..978e637eb46 100644 --- a/core/engine/src/value/inner/nan_boxed.rs +++ b/core/engine/src/value/inner/nan_boxed.rs @@ -1014,6 +1014,9 @@ fn bigint() { #[test] fn object() { + #[cfg(feature = "oscars_backend")] + let _scope = boa_gc::HandleScope::enter(); + let object = JsObject::with_null_proto(&unsafe { boa_gc::MutationContext::global() }); let v = NanBoxedValue::object(object.clone()); assert_type!(v is object(object)); diff --git a/core/engine/src/value/tests.rs b/core/engine/src/value/tests.rs index 15ca96277f0..2177c9f320d 100644 --- a/core/engine/src/value/tests.rs +++ b/core/engine/src/value/tests.rs @@ -128,6 +128,9 @@ fn hash_rational() { #[test] fn hash_object() { + #[cfg(feature = "oscars_backend")] + let _scope = boa_gc::HandleScope::enter(); + let object1 = JsValue::new(JsObject::with_null_proto(&unsafe { boa_gc::MutationContext::global() })); diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index cc4ee8247b7..e2af29e304c 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -407,7 +407,7 @@ impl Vm { pub(crate) fn new(realm: Realm, mc: &boa_gc::MutationContext<'static, '_>) -> Self { let mut frames = Vec::with_capacity(16); frames.push(CallFrame::new( - Gc::new(mc, CodeBlock::new(JsString::default(), 0, true)), + boa_gc::allocate_rooted(mc, CodeBlock::new(JsString::default(), 0, true)), None, EnvironmentStack::new(), realm, diff --git a/core/gc/src/context.rs b/core/gc/src/context.rs index 5cf014b83fa..493d7887b1c 100644 --- a/core/gc/src/context.rs +++ b/core/gc/src/context.rs @@ -22,12 +22,22 @@ thread_local! { MutationContext::from_collector_erased(*c) })) }); + + static TRACKER: std::cell::RefCell>> = std::cell::RefCell::new(None); } #[cfg(feature = "oscars_backend")] impl GcContext { #[must_use] pub fn new() -> Self { + TRACKER.with(|tracker| { + if tracker.borrow().is_none() { + let mc = DUMMY.with(|dummy| *dummy); + let handle = Gc::new(mc, crate::scope_tracker::HandleScopeTracker); + let root = mc.root(handle).expect("Failed to root HandleScopeTracker"); + *tracker.borrow_mut() = Some(root); + } + }); Self } diff --git a/core/gc/src/lib.rs b/core/gc/src/lib.rs index b50c2dda207..95eb061aa2e 100644 --- a/core/gc/src/lib.rs +++ b/core/gc/src/lib.rs @@ -30,8 +30,15 @@ mod pointers; mod trace; pub mod context; +#[cfg(feature = "oscars_backend")] +pub(crate) mod scope; +#[cfg(feature = "oscars_backend")] +pub(crate) mod scope_tracker; pub use context::GcContext; +#[cfg(feature = "oscars_backend")] +pub use scope::{HandleScope, Local}; + #[cfg(not(feature = "oscars_backend"))] pub(crate) mod internals; @@ -176,3 +183,21 @@ pub fn force_collect() { mc.collect(); crate::context::GcContext::new().force_collect(); } + +#[cfg(feature = "oscars_backend")] +pub fn allocate_rooted<'gc, T: Trace + Finalize + 'gc>( + mc: &MutationContext<'gc, '_>, + value: T, +) -> Gc<'gc, T> { + let gc = Gc::new(mc, value); + Local::new(gc); + gc +} + +#[cfg(not(feature = "oscars_backend"))] +pub fn allocate_rooted<'gc, T: Trace + Finalize + 'gc>( + mc: &MutationContext<'gc, '_>, + value: T, +) -> Gc<'gc, T> { + Gc::new(mc, value) +} diff --git a/core/gc/src/pointers/gc.rs b/core/gc/src/pointers/gc.rs index 3ef43301f5f..fd604351eec 100644 --- a/core/gc/src/pointers/gc.rs +++ b/core/gc/src/pointers/gc.rs @@ -175,10 +175,15 @@ impl<'gc, T: Trace + ?Sized + 'static> Gc<'gc, T> { // Note: Allocator can cause Collector to run let inner_ptr = Allocator::alloc_gc(GcBox::new(value)); - Self { + let gc = Self { inner_ptr, marker: PhantomData, - } + }; + + #[cfg(feature = "oscars_backend")] + crate::Local::new(gc.clone()); + + gc } /// Constructs a new `Gc` while giving you a `WeakGc` to the allocation, to allow diff --git a/core/gc/src/scope.rs b/core/gc/src/scope.rs new file mode 100644 index 00000000000..cd1f7055c18 --- /dev/null +++ b/core/gc/src/scope.rs @@ -0,0 +1,110 @@ +use std::cell::RefCell; +use std::marker::PhantomData; +use std::ptr::NonNull; + +use crate::{Gc, Trace, Tracer}; + +/// A type-erased local root for tracing. +#[derive(Clone, Copy)] +pub(crate) struct ErasedRoot { + /// The erased `PoolPointer` (`NonNull<()>`) from a `Gc<'_, T>`. + ptr: NonNull<()>, + /// A function that casts the pointer back to `Gc<'_, T>` and marks it. + trace_fn: unsafe fn(NonNull<()>, &mut Tracer<'_>), +} + +impl ErasedRoot { + fn new(gc: Gc<'_, T>) -> Self { + unsafe fn trace_gc(ptr: NonNull<()>, tracer: &mut Tracer<'_>) { + unsafe { + // Reconstruct the Gc pointer + let gc: Gc<'_, T> = std::mem::transmute(ptr); + tracer.mark(&gc); + } + } + + Self { + // Safe because Gc has exactly the same memory layout as NonNull. + ptr: unsafe { std::mem::transmute_copy(&gc) }, + trace_fn: trace_gc::, + } + } + + pub(crate) unsafe fn trace(&self, tracer: &mut Tracer<'_>) { + unsafe { (self.trace_fn)(self.ptr, tracer) }; + } +} + +thread_local! { + /// A stack of handle scopes for the current thread. + pub(crate) static SCOPE_STACK: RefCell>> = RefCell::new(Vec::new()); +} + +/// A scope for tracking local handles. +pub struct HandleScope { + _marker: PhantomData<*mut ()>, // Not Send or Sync +} + +impl HandleScope { + /// Enter a new handle scope. + #[must_use] + pub fn enter() -> Self { + SCOPE_STACK.with(|stack| stack.borrow_mut().push(Vec::new())); + Self { + _marker: PhantomData, + } + } +} + +impl Drop for HandleScope { + fn drop(&mut self) { + SCOPE_STACK.with(|stack| { + stack + .borrow_mut() + .pop() + .expect("HandleScope popped without being pushed"); + }); + } +} + +/// A local handle to a GC-managed value, scoped to the current `HandleScope`. +#[derive(Debug)] +pub struct Local<'gc, T: Trace + 'gc> { + inner: Gc<'gc, T>, +} + +impl<'gc, T: Trace + 'gc> Local<'gc, T> { + /// Create a new local handle from a GC pointer. + pub fn new(gc: Gc<'gc, T>) -> Self { + SCOPE_STACK.with(|stack| { + let mut stack = stack.borrow_mut(); + if let Some(top) = stack.last_mut() { + top.push(ErasedRoot::new(gc)); + } else { + panic!("Cannot create Local without an active HandleScope"); + } + }); + + Self { inner: gc } + } + + pub fn into_inner(self) -> Gc<'gc, T> { + self.inner + } +} + +impl<'gc, T: Trace + 'gc> Clone for Local<'gc, T> { + fn clone(&self) -> Self { + Self::new(self.inner) + } +} + +impl<'gc, T: Trace + 'gc> Copy for Local<'gc, T> {} + +impl<'gc, T: Trace + 'gc> std::ops::Deref for Local<'gc, T> { + type Target = Gc<'gc, T>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} diff --git a/core/gc/src/scope_tracker.rs b/core/gc/src/scope_tracker.rs new file mode 100644 index 00000000000..392c5bddf39 --- /dev/null +++ b/core/gc/src/scope_tracker.rs @@ -0,0 +1,22 @@ +use crate::scope::SCOPE_STACK; +use crate::{Finalize, Trace, Tracer}; + +pub(crate) struct HandleScopeTracker; + +impl Finalize for HandleScopeTracker { + fn finalize(&self) {} +} + +unsafe impl Trace for HandleScopeTracker { + unsafe fn trace(&self, tracer: &mut Tracer<'_>) { + SCOPE_STACK.with(|stack| { + for scope in stack.borrow().iter() { + for root in scope.iter() { + unsafe { + root.trace(tracer); + } + } + } + }); + } +} From 0efd528c6d32d4f6ef7f7cb94e819701f8300c21 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Thu, 20 Aug 2026 21:57:53 +0000 Subject: [PATCH 09/19] Enable oscars_backend in boa_wasm --- ffi/wasm/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/ffi/wasm/Cargo.toml b/ffi/wasm/Cargo.toml index 55b997db512..ec51c1b2d94 100644 --- a/ffi/wasm/Cargo.toml +++ b/ffi/wasm/Cargo.toml @@ -27,6 +27,7 @@ default = [ "boa_engine/intl_bundled", "boa_engine/temporal", "boa_engine/xsum", + "boa_engine/oscars_backend", ] [lib] From 389ac73c6a6ea6feb7f0a617794ba3936e4f6d5f Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 23 Aug 2026 07:50:56 +0000 Subject: [PATCH 10/19] fix heap UAF in ListFormat --- .../src/builtins/intl/list_format/mod.rs | 71 +++++++++++-------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/core/engine/src/builtins/intl/list_format/mod.rs b/core/engine/src/builtins/intl/list_format/mod.rs index 4c9f79b0d9e..6f5aa08b680 100644 --- a/core/engine/src/builtins/intl/list_format/mod.rs +++ b/core/engine/src/builtins/intl/list_format/mod.rs @@ -227,25 +227,32 @@ impl ListFormat { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/format fn format(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let lf be the this value. - // 2. Perform ? RequireInternalSlot(lf, [[InitializedListFormat]]). - let object = this.as_object(); - let lf = object - .as_ref() - .and_then(|o| o.downcast_ref::()) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("`format` can only be called on a `ListFormat` object") - })?; - - // 3. Let stringList be ? StringListFromIterable(list). + // 2. Perform ? RequireInternalSlot(lf, [[InitializedListFormat]]). + // Validate the `this` type BEFORE collecting strings, but do NOT hold the + // borrow across the iterator call below: `string_list_from_iterable` runs + // arbitrary JS (iterator protocol) which can trigger GC and free/move the + // GC-managed object that `downcast_ref` borrows from, causing a UAF. + let object = this.as_object().filter(|o| o.is::()).ok_or_else(|| { + JsNativeError::typ() + .with_message("`format` can only be called on a `ListFormat` object") + })?; + + // 3. Let stringList be ? StringListFromIterable(list). // TODO: support for UTF-16 unpaired surrogates formatting + // SAFETY: We must collect strings first (which runs JS / may trigger GC) and + // only THEN borrow `lf`. Holding a `Ref<'_, T>` across a GC point is a UAF. let strings = string_list_from_iterable(args.get_or_undefined(0), context)?; + // Borrow `lf` only after all GC-triggering operations are complete. + let lf = object + .downcast_ref::() + .expect("already checked above that the object is a ListFormat"); + let formatted = lf .native .format_to_string(strings.into_iter().map(|s| s.to_std_string_escaped())); - // 4. Return ! FormatList(lf, stringList). + // 4. Return ! FormatList(lf, stringList). Ok(js_string!(formatted).into()) } @@ -345,31 +352,39 @@ impl ListFormat { } // 1. Let lf be the this value. - // 2. Perform ? RequireInternalSlot(lf, [[InitializedListFormat]]). - let object = this.as_object(); - let lf = object - .as_ref() - .and_then(|o| o.downcast_ref::()) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("`formatToParts` can only be called on a `ListFormat` object") - })?; - - // 3. Let stringList be ? StringListFromIterable(list). + // 2. Perform ? RequireInternalSlot(lf, [[InitializedListFormat]]). + // Validate the `this` type BEFORE collecting strings, but do NOT hold the + // borrow across the iterator call below: `string_list_from_iterable` runs + // arbitrary JS (iterator protocol) which can trigger GC and free/move the + // GC-managed object that `downcast_ref` borrows from, causing a UAF. + let object = this.as_object().filter(|o| o.is::()).ok_or_else(|| { + JsNativeError::typ() + .with_message("`formatToParts` can only be called on a `ListFormat` object") + })?; + + // 3. Let stringList be ? StringListFromIterable(list). // TODO: support for UTF-16 unpaired surrogates formatting - let strings = string_list_from_iterable(args.get_or_undefined(0), context)? + // SAFETY: We must collect strings first (which runs JS / may trigger GC) and + // only THEN borrow `lf`. Holding a `Ref<'_, T>` across a GC point is a UAF. + let strings: Vec = string_list_from_iterable(args.get_or_undefined(0), context)? .into_iter() - .map(|s| s.to_std_string_escaped()); + .map(|s| s.to_std_string_escaped()) + .collect(); + + // Borrow `lf` only after all GC-triggering operations are complete. + let lf = object + .downcast_ref::() + .expect("already checked above that the object is a ListFormat"); - // 4. Return ! FormatListToParts(lf, stringList). + // 4. Return ! FormatListToParts(lf, stringList). // Abstract operation `FormatListToParts ( listFormat, list )` // https://tc39.es/ecma402/#sec-formatlisttoparts - // 1. Let parts be ! CreatePartsFromList(listFormat, list). + // 1. Let parts be ! CreatePartsFromList(listFormat, list). let mut parts = PartsCollector(Vec::new()); lf.native - .format(strings) + .format(strings.into_iter()) .write_to_parts(&mut parts) .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?; From 0cc91550058943d5ddff5d6350a3474063dd8c0c Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 23 Aug 2026 08:08:04 +0000 Subject: [PATCH 11/19] fix anchor crash --- core/engine/src/builtins/intl/collator/mod.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/core/engine/src/builtins/intl/collator/mod.rs b/core/engine/src/builtins/intl/collator/mod.rs index d0c0f11072e..d20ef3b56a4 100644 --- a/core/engine/src/builtins/intl/collator/mod.rs +++ b/core/engine/src/builtins/intl/collator/mod.rs @@ -363,9 +363,11 @@ impl Collator { |_, args, collator, context| { // 1. Let collator be F.[[Collator]]. // 2. Assert: Type(collator) is Object and collator has an [[InitializedCollator]] internal slot. - let collator = collator - .downcast_ref::() - .js_expect("checked above that the object was a collator object")?; + // + // SAFETY: We must resolve the string arguments (which run JS / + // may trigger GC) BEFORE borrowing `collator` via downcast_ref. + // Holding a Ref<'_, T> across a GC point is a use-after-free + // because GC can collect the backing object while the borrow is live. // 3. If x is not provided, let x be undefined. // 5. Let X be ? ToString(x). @@ -383,8 +385,12 @@ impl Collator { .iter() .collect::>(); - // 7. Return CompareStrings(collator, X, Y). + // Borrow collator AFTER all GC-triggering work is done. + let collator = collator + .downcast_ref::() + .js_expect("checked above that the object was a collator object")?; + // 7. Return CompareStrings(collator, X, Y). let result = collator.collator.as_borrowed().compare_utf16(&x, &y) as i32; Ok(result.into()) From 3f68447a59fe741580be754f11d74e8c81bb0558 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 23 Aug 2026 08:21:41 +0000 Subject: [PATCH 12/19] fix constructor crash --- core/engine/src/builtins/intl/collator/mod.rs | 34 ++++++-- .../src/builtins/intl/list_format/mod.rs | 86 +++++++++++-------- 2 files changed, 77 insertions(+), 43 deletions(-) diff --git a/core/engine/src/builtins/intl/collator/mod.rs b/core/engine/src/builtins/intl/collator/mod.rs index d20ef3b56a4..e2e393b0c00 100644 --- a/core/engine/src/builtins/intl/collator/mod.rs +++ b/core/engine/src/builtins/intl/collator/mod.rs @@ -340,19 +340,31 @@ impl Collator { JsNativeError::typ() .with_message("`resolvedOptions` can only be called on a `Collator` object") })?; - let collator_obj = this.clone(); - let mut collator = this.downcast_mut::().ok_or_else(|| { - JsNativeError::typ() - .with_message("`resolvedOptions` can only be called on a `Collator` object") - })?; // 3. If collator.[[BoundCompare]] is undefined, then // a. Let F be a new built-in function object as defined in 10.3.3.1. // b. Set F.[[Collator]] to collator. // c. Set collator.[[BoundCompare]] to F. - let bound_compare = if let Some(f) = collator.bound_compare.clone() { + // + // SAFETY: We must NOT hold a downcast_mut borrow across context.realm() / + // context.gc_collector() calls, as those can trigger a GC collection that + // frees the backing object while the mutable borrow guard is live (UAF). + // + // Pattern: read [[BoundCompare]] in a scoped block, drop the borrow, build the + // function with no borrow held, then take a fresh borrow only to write back. + let existing = { + let collator = this.downcast_ref::().ok_or_else(|| { + JsNativeError::typ() + .with_message("`resolvedOptions` can only be called on a `Collator` object") + })?; + collator.bound_compare.clone() + }; // borrow dropped here + + let bound_compare = if let Some(f) = existing { f } else { + // Build the bound compare function with no borrow held on `this` + let collator_obj = this.clone(); let bound_compare = FunctionObjectBuilder::new( context.realm(), context.gc_collector(), @@ -401,7 +413,15 @@ impl Collator { .length(2) .build(); - collator.bound_compare = Some(bound_compare.clone()); + // take a fresh borrow to write back [[BoundCompare]]. No context calls + // follow this so the borrow is safe. + this.downcast_mut::() + .ok_or_else(|| { + JsNativeError::typ() + .with_message("`resolvedOptions` can only be called on a `Collator` object") + })? + .bound_compare = Some(bound_compare.clone()); + bound_compare }; diff --git a/core/engine/src/builtins/intl/list_format/mod.rs b/core/engine/src/builtins/intl/list_format/mod.rs index 6f5aa08b680..0ce0bec6cc9 100644 --- a/core/engine/src/builtins/intl/list_format/mod.rs +++ b/core/engine/src/builtins/intl/list_format/mod.rs @@ -364,37 +364,44 @@ impl ListFormat { // 3. Let stringList be ? StringListFromIterable(list). // TODO: support for UTF-16 unpaired surrogates formatting - // SAFETY: We must collect strings first (which runs JS / may trigger GC) and - // only THEN borrow `lf`. Holding a `Ref<'_, T>` across a GC point is a UAF. + // SAFETY: Collect the JS strings first (runs JS / may trigger GC), before + // borrowing `lf`. A Ref<'_, T> must NOT be held across any GC point. let strings: Vec = string_list_from_iterable(args.get_or_undefined(0), context)? .into_iter() .map(|s| s.to_std_string_escaped()) .collect(); - // Borrow `lf` only after all GC-triggering operations are complete. - let lf = object - .downcast_ref::() - .expect("already checked above that the object is a ListFormat"); - // 4. Return ! FormatListToParts(lf, stringList). // Abstract operation `FormatListToParts ( listFormat, list )` // https://tc39.es/ecma402/#sec-formatlisttoparts // 1. Let parts be ! CreatePartsFromList(listFormat, list). - let mut parts = PartsCollector(Vec::new()); - lf.native - .format(strings.into_iter()) - .write_to_parts(&mut parts) - .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?; - - // 2. Let result be ! ArrayCreate(0). + // + // SAFETY: Perform the pure native formatting inside a scoped block so that + // the Ref<'_, ListFormat> borrow guard is dropped BEFORE we re-enter context + // (Array::array_create, context.gc_collector, create_data_property_or_throw). + // All of those can trigger a GC collection cycle, which would be a UAF if we + // still held the Ref + let parts = { + let lf = object + .downcast_ref::() + .expect("already checked above that the object is a ListFormat"); + let mut collector = PartsCollector(Vec::new()); + lf.native + .format(strings.into_iter()) + .write_to_parts(&mut collector) + .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?; + collector.0 + }; // Ref<'_, ListFormat> dropped here; safe to use context below + + // 2. Let result be ! ArrayCreate(0). let result = Array::array_create(0, None, context) .js_expect("creating an empty array with default proto must not fail")?; // 3. Let n be 0. // 4. For each Record { [[Type]], [[Value]] } part in parts, do - for (n, part) in parts.0.into_iter().enumerate() { + for (n, part) in parts.into_iter().enumerate() { // a. Let O be OrdinaryObjectCreate(%Object.prototype%). let o = context.intrinsics().templates().ordinary_object().create( context.gc_collector(), @@ -402,15 +409,15 @@ impl ListFormat { vec![], ); - // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). + // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). o.create_data_property_or_throw(js_string!("type"), js_string!(part.typ()), context) .js_expect("operation must not fail per the spec")?; - // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]). + // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]). o.create_data_property_or_throw(js_string!("value"), js_string!(part.value()), context) .js_expect("operation must not fail per the spec")?; - // d. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O). + // d. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O). result .create_data_property_or_throw(n, o, context) .js_expect("operation must not fail per the spec")?; @@ -434,15 +441,27 @@ impl ListFormat { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/resolvedOptions fn resolved_options(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let lf be the this value. - // 2. Perform ? RequireInternalSlot(lf, [[InitializedListFormat]]). - let object = this.as_object(); - let lf = object - .as_ref() - .and_then(|o| o.downcast_ref::()) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("`resolvedOptions` can only be called on a `ListFormat` object") - })?; + // 2. Perform ? RequireInternalSlot(lf, [[InitializedListFormat]]). + // + // SAFETY: Extract all data from `lf` into owned values inside a scoped block so + // the Ref<'_, ListFormat> borrow guard is dropped BEFORE we touch `context`. + // GC allocations (context.gc_collector(), js_string!, create_data_property_or_throw) + // can trigger a collection cycle; holding a Ref<'_, T> across a GC point is a + // use-after-free because the GC may collect the backing object while the borrow + // is live. + let (locale_str, typ, style) = { + let object = this.as_object(); + let lf = object + .as_ref() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message( + "`resolvedOptions` can only be called on a `ListFormat` object", + ) + })?; + // Clone/copy out the cheap data we need; Ref is dropped at end of this block. + (lf.locale.to_string(), lf.typ, lf.style) + }; // ← Ref<'_, ListFormat> dropped here, safe to use context below // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). let options = context.intrinsics().templates().ordinary_object().create( @@ -455,18 +474,14 @@ impl ListFormat { // a. Let p be the Property value of the current row. // b. Let v be the value of lf's internal slot whose name is the Internal Slot value of the current row. // c. Assert: v is not undefined. - // d. Perform ! CreateDataPropertyOrThrow(options, p, v). + // d. Perform ! CreateDataPropertyOrThrow(options, p, v). options - .create_data_property_or_throw( - js_string!("locale"), - js_string!(lf.locale.to_string()), - context, - ) + .create_data_property_or_throw(js_string!("locale"), js_string!(locale_str), context) .js_expect("operation must not fail per the spec")?; options .create_data_property_or_throw( js_string!("type"), - match lf.typ { + match typ { ListFormatType::Conjunction => js_string!("conjunction"), ListFormatType::Disjunction => js_string!("disjunction"), ListFormatType::Unit => js_string!("unit"), @@ -477,7 +492,7 @@ impl ListFormat { options .create_data_property_or_throw( js_string!("style"), - match lf.style { + match style { ListLength::Wide => js_string!("long"), ListLength::Short => js_string!("short"), ListLength::Narrow => js_string!("narrow"), @@ -487,7 +502,6 @@ impl ListFormat { ) .js_expect("operation must not fail per the spec")?; - // 5. Return options. Ok(options.into()) } } From 478ebe6aa5bfa15e2b781bc123d38bbf02c86067 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 23 Aug 2026 13:59:49 +0000 Subject: [PATCH 13/19] fix: UAF in Collator::resolved_options and format_to_parts loop Both functions held a Ref<'_, T> GC borrow guard across calls to context.gc_collector() and create_data_property_or_throw(context), which can trigger a GC collection cycle, freeing the backing object while the borrow guard is still live (use-after-free -> heap corruption -> malloc_consolidate abort). Fix pattern (same as ListFormat::resolved_options): - Extract all needed fields into owned values inside a scoped block - Drop the Ref<'_, T> at end of the block - All context/GC operations follow the block with no borrow live Collator::resolved_options: locale_str, usage, sensitivity, ignore_punctuation, collation, numeric, case_first are copied out before any context operations. format_to_parts: native formatting (lf.native.format()) moved into a scoped block so the Ref drops before the Array creation loop that repeatedly calls context.gc_collector() and create_data_property_or_throw. --- core/engine/src/builtins/intl/collator/mod.rs | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/core/engine/src/builtins/intl/collator/mod.rs b/core/engine/src/builtins/intl/collator/mod.rs index e2e393b0c00..9a28d2c9251 100644 --- a/core/engine/src/builtins/intl/collator/mod.rs +++ b/core/engine/src/builtins/intl/collator/mod.rs @@ -441,15 +441,32 @@ impl Collator { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/resolvedOptions fn resolved_options(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let collator be the this value. - // 2. Perform ? RequireInternalSlot(collator, [[InitializedCollator]]). - let object = this.as_object(); - let collator = object - .as_ref() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("`resolvedOptions` can only be called on a `Collator` object") - })?; + // 2. Perform ? RequireInternalSlot(collator, [[InitializedCollator]]). + // + // SAFETY: Extract all data from `collator` into owned values inside a scoped block + // so the Ref<'_, Collator> borrow guard is dropped BEFORE we touch `context`. + // GC allocations (context.gc_collector(), create_data_property_or_throw) can + // trigger a collection cycle; holding a Ref<'_, T> across a GC point is a UAF. + let (locale_str, usage, sensitivity, ignore_punctuation, collation, numeric, case_first) = { + let object = this.as_object(); + let collator = object + .as_ref() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("`resolvedOptions` can only be called on a `Collator` object") + })?; + // Copy/clone all cheap fields; Ref is dropped at end of this block. + ( + collator.locale.to_string(), + collator.usage, + collator.sensitivity, + collator.ignore_punctuation, + collator.collation, + collator.numeric, + collator.case_first, + ) + }; // ← Ref<'_, Collator> dropped here, safe to use context below // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). let options = context.intrinsics().templates().ordinary_object().create( @@ -466,19 +483,15 @@ impl Collator { // ii. If %Collator%.[[RelevantExtensionKeys]] does not contain extensionKey, then // 1. Let v be undefined. // d. If v is not undefined, then - // i. Perform ! CreateDataPropertyOrThrow(options, p, v). + // i. Perform ! CreateDataPropertyOrThrow(options, p, v). // 5. Return options. options - .create_data_property_or_throw( - js_string!("locale"), - js_string!(collator.locale.to_string()), - context, - ) + .create_data_property_or_throw(js_string!("locale"), js_string!(locale_str), context) .js_expect("operation must not fail per the spec")?; options .create_data_property_or_throw( js_string!("usage"), - match collator.usage { + match usage { Usage::Search => js_string!("search"), Usage::Sort => js_string!("sort"), }, @@ -488,7 +501,7 @@ impl Collator { options .create_data_property_or_throw( js_string!("sensitivity"), - match collator.sensitivity { + match sensitivity { Sensitivity::Base => js_string!("base"), Sensitivity::Accent => js_string!("accent"), Sensitivity::Case => js_string!("case"), @@ -500,24 +513,23 @@ impl Collator { options .create_data_property_or_throw( js_string!("ignorePunctuation"), - collator.ignore_punctuation, + ignore_punctuation, context, ) .js_expect("operation must not fail per the spec")?; options .create_data_property_or_throw( js_string!("collation"), - collator - .collation + collation .map(|co| js_string!(co.as_str())) .unwrap_or(js_string!("default")), context, ) .js_expect("operation must not fail per the spec")?; options - .create_data_property_or_throw(js_string!("numeric"), collator.numeric, context) + .create_data_property_or_throw(js_string!("numeric"), numeric, context) .js_expect("operation must not fail per the spec")?; - if let Some(kf) = collator.case_first { + if let Some(kf) = case_first { options .create_data_property_or_throw( js_string!("caseFirst"), @@ -527,7 +539,6 @@ impl Collator { .js_expect("operation must not fail per the spec")?; } - // 5. Return options. Ok(options.into()) } } From 18f007f3b7ac494e37a87a84fbc3271f86b90096 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 23 Aug 2026 14:29:52 +0000 Subject: [PATCH 14/19] fix: UAF in Intl resolved_options methods across multiple builtins This fixes the same Use-After-Free (UAF) bug pattern found in ListFormat and Collator across the remaining Intl built-ins: - PluralRules - DateTimeFormat - NumberFormat - Segmenter The bug occurs when a `Ref<'_, T>` (via `downcast_ref`) is held across a call to `ObjectInitializer::new(context)` or `options.property(..., context)`, which can trigger a garbage collection cycle and free the underlying object. The fix extracts all required fields into scoped variables before interacting with the context. --- .../src/builtins/intl/date_time_format/mod.rs | 69 +++--- .../src/builtins/intl/number_format/mod.rs | 219 ++++++++++-------- .../src/builtins/intl/plural_rules/mod.rs | 89 ++++--- .../engine/src/builtins/intl/segmenter/mod.rs | 29 ++- 4 files changed, 242 insertions(+), 164 deletions(-) diff --git a/core/engine/src/builtins/intl/date_time_format/mod.rs b/core/engine/src/builtins/intl/date_time_format/mod.rs index 10a0abba1c3..ad8d84c2ff4 100644 --- a/core/engine/src/builtins/intl/date_time_format/mod.rs +++ b/core/engine/src/builtins/intl/date_time_format/mod.rs @@ -365,49 +365,66 @@ impl DateTimeFormat { // a. Assert: conversion is number. // b. Set v to 𝔽(v). // ii. Perform ! CreateDataPropertyOrThrow(options, p, v). - let result = { + let ( + locale_str, + calendar_algorithm, + numbering_system, + time_zone_str, + hour_cycle, + date_style, + time_style, + ) = { let dtf = dtf_object.borrow(); let dtf = dtf.data(); + let time_zone_str = match &dtf.time_zone { + FormatTimeZone::UtcOffset(offset) => { + let seconds = offset.to_seconds(); + let hours = seconds / 3600; + let minutes = (seconds.abs() % 3600) / 60; + format!("{hours:+03}:{minutes:02}") + } + FormatTimeZone::Identifier((tz, _id)) => tz.to_string(), + }; + + ( + dtf.locale.to_string(), + dtf.calendar_algorithm + .as_ref() + .map(|ca| js_string!(ca.as_str())), + dtf.numbering_system + .as_ref() + .map(|nu| js_string!(nu.as_str())), + time_zone_str, + dtf.hour_cycle, + dtf.date_style, + dtf.time_style, + ) + }; + + let result = { let mut options = ObjectInitializer::new(context); options.property( js_string!("locale"), - js_string!(dtf.locale.to_string()), + js_string!(locale_str), Attribute::all(), ); - if let Some(ca) = &dtf.calendar_algorithm { - options.property( - js_string!("calendar"), - js_string!(ca.as_str()), - Attribute::all(), - ); + if let Some(ca) = calendar_algorithm { + options.property(js_string!("calendar"), ca, Attribute::all()); } - if let Some(nu) = &dtf.numbering_system { - options.property( - js_string!("numberingSystem"), - js_string!(nu.as_str()), - Attribute::all(), - ); + if let Some(nu) = numbering_system { + options.property(js_string!("numberingSystem"), nu, Attribute::all()); } - let time_zone_str = match &dtf.time_zone { - FormatTimeZone::UtcOffset(offset) => { - let seconds = offset.to_seconds(); - let hours = seconds / 3600; - let minutes = (seconds.abs() % 3600) / 60; - format!("{hours:+03}:{minutes:02}") - } - FormatTimeZone::Identifier((tz, _id)) => tz.to_string(), - }; options.property( js_string!("timeZone"), js_string!(time_zone_str), Attribute::all(), ); - if let Some(hc) = &dtf.hour_cycle { + if let Some(hc) = hour_cycle { options.property( js_string!("hourCycle"), js_string!(hc.as_str()), @@ -418,7 +435,7 @@ impl DateTimeFormat { options.property(js_string!("hour12"), hour12, Attribute::all()); } - if let Some(ds) = dtf.date_style { + if let Some(ds) = date_style { let ds_str = match ds { DateStyle::Full => "full", DateStyle::Long => "long", @@ -432,7 +449,7 @@ impl DateTimeFormat { ); } - if let Some(ts) = dtf.time_style { + if let Some(ts) = time_style { let ts_str = match ts { TimeStyle::Full => "full", TimeStyle::Long => "long", diff --git a/core/engine/src/builtins/intl/number_format/mod.rs b/core/engine/src/builtins/intl/number_format/mod.rs index 1ea97697ea7..bf748633cb5 100644 --- a/core/engine/src/builtins/intl/number_format/mod.rs +++ b/core/engine/src/builtins/intl/number_format/mod.rs @@ -656,8 +656,108 @@ impl NumberFormat { // a. Set nf to ? UnwrapNumberFormat(nf). // 3. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]). let nf = unwrap_number_format(this, context)?; - let nf = nf.borrow(); - let nf = nf.data(); + + let ( + locale_str, + numbering_system, + style, + currency, + currency_display, + currency_sign, + unit, + unit_display, + minimum_integer_digits, + fraction_digits, + significant_digits, + use_grouping, + notation_str, + compact_display_str, + sign_display_str, + rounding_increment, + rounding_priority_str, + trailing_zero_display_str, + ) = { + let nf_borrow = nf.borrow(); + let nf_data = nf_borrow.data(); + + let (currency, currency_display, currency_sign, unit, unit_display) = + match &nf_data.unit_options { + UnitFormatOptions::Currency { + currency, + display, + sign, + } => ( + Some(currency.to_js_string()), + Some(display.to_js_string()), + Some(sign.to_js_string()), + None, + None, + ), + UnitFormatOptions::Unit { unit, display } => ( + None, + None, + None, + Some(unit.to_js_string()), + Some(display.to_js_string()), + ), + UnitFormatOptions::Decimal | UnitFormatOptions::Percent => { + (None, None, None, None, None) + } + }; + + let use_grouping = match nf_data.use_grouping { + GroupingStrategy::Auto => js_string!("auto").into(), + GroupingStrategy::Never => JsValue::from(false), + GroupingStrategy::Always => js_string!("always").into(), + GroupingStrategy::Min2 => js_string!("min2").into(), + _ => { + return Err(JsNativeError::typ() + .with_message("unsupported useGrouping value") + .into()); + } + }; + + let (notation, compact_display) = match &nf_data.formatter { + Formatter::Standard(_) => (NotationKind::Standard, None), + Formatter::Scientific(_) => (NotationKind::Scientific, None), + Formatter::Engineering(_) => (NotationKind::Engineering, None), + Formatter::Compact { display, .. } => (NotationKind::Compact, Some(*display)), + }; + + let sign_display_str = match nf_data.sign_display { + SignDisplay::Auto => js_string!("auto"), + SignDisplay::Never => js_string!("never"), + SignDisplay::Always => js_string!("always"), + SignDisplay::ExceptZero => js_string!("exceptZero"), + SignDisplay::Negative => js_string!("negative"), + _ => { + return Err(JsNativeError::typ() + .with_message("unsupported signDisplay value") + .into()); + } + }; + + ( + nf_data.locale.to_string(), + js_string!(nf_data.numbering_system.as_str()), + nf_data.unit_options.style().to_js_string(), + currency, + currency_display, + currency_sign, + unit, + unit_display, + nf_data.digit_options.minimum_integer_digits, + nf_data.digit_options.rounding_type.fraction_digits(), + nf_data.digit_options.rounding_type.significant_digits(), + use_grouping, + notation.to_js_string(), + compact_display.map(|d| d.to_js_string()), + sign_display_str, + nf_data.digit_options.rounding_increment.to_u16(), + nf_data.digit_options.rounding_priority.to_js_string(), + nf_data.digit_options.trailing_zero_display.to_js_string(), + ) + }; // 4. Let options be OrdinaryObjectCreate(%Object.prototype%). // 5. For each row of Table 12, except the header row, in table order, do @@ -671,62 +771,33 @@ impl NumberFormat { let mut options = ObjectInitializer::new(context); options.property( js_string!("locale"), - js_string!(nf.locale.to_string()), + js_string!(locale_str), Attribute::all(), ); options.property( js_string!("numberingSystem"), - js_string!(nf.numbering_system.as_str()), + numbering_system, Attribute::all(), ); - options.property( - js_string!("style"), - nf.unit_options.style().to_js_string(), - Attribute::all(), - ); + options.property(js_string!("style"), style, Attribute::all()); - match &nf.unit_options { - UnitFormatOptions::Currency { - currency, - display, - sign, - } => { - options.property( - js_string!("currency"), - currency.to_js_string(), - Attribute::all(), - ); - options.property( - js_string!("currencyDisplay"), - display.to_js_string(), - Attribute::all(), - ); - options.property( - js_string!("currencySign"), - sign.to_js_string(), - Attribute::all(), - ); - } - UnitFormatOptions::Unit { unit, display } => { - options.property(js_string!("unit"), unit.to_js_string(), Attribute::all()); - options.property( - js_string!("unitDisplay"), - display.to_js_string(), - Attribute::all(), - ); - } - UnitFormatOptions::Decimal | UnitFormatOptions::Percent => {} + if let (Some(c), Some(d), Some(s)) = (currency, currency_display, currency_sign) { + options.property(js_string!("currency"), c, Attribute::all()); + options.property(js_string!("currencyDisplay"), d, Attribute::all()); + options.property(js_string!("currencySign"), s, Attribute::all()); + } else if let (Some(u), Some(d)) = (unit, unit_display) { + options.property(js_string!("unit"), u, Attribute::all()); + options.property(js_string!("unitDisplay"), d, Attribute::all()); } options.property( js_string!("minimumIntegerDigits"), - nf.digit_options.minimum_integer_digits, + minimum_integer_digits, Attribute::all(), ); - if let Some(Extrema { minimum, maximum }) = nf.digit_options.rounding_type.fraction_digits() - { + if let Some(Extrema { minimum, maximum }) = fraction_digits { options .property( js_string!("minimumFractionDigits"), @@ -740,9 +811,7 @@ impl NumberFormat { ); } - if let Some(Extrema { minimum, maximum }) = - nf.digit_options.rounding_type.significant_digits() - { + if let Some(Extrema { minimum, maximum }) = significant_digits { options .property( js_string!("minimumSignificantDigits"), @@ -756,69 +825,33 @@ impl NumberFormat { ); } - let use_grouping = match nf.use_grouping { - GroupingStrategy::Auto => js_string!("auto").into(), - GroupingStrategy::Never => JsValue::from(false), - GroupingStrategy::Always => js_string!("always").into(), - GroupingStrategy::Min2 => js_string!("min2").into(), - _ => { - return Err(JsNativeError::typ() - .with_message("unsupported useGrouping value") - .into()); - } - }; - options.property(js_string!("useGrouping"), use_grouping, Attribute::all()); - let (notation, compact_display) = match &nf.formatter { - Formatter::Standard(_) => (NotationKind::Standard, None), - Formatter::Scientific(_) => (NotationKind::Scientific, None), - Formatter::Engineering(_) => (NotationKind::Engineering, None), - Formatter::Compact { display, .. } => (NotationKind::Compact, Some(*display)), - }; + options.property(js_string!("notation"), notation_str, Attribute::all()); - options.property( - js_string!("notation"), - notation.to_js_string(), - Attribute::all(), - ); - - if let Some(display) = compact_display { - options.property( - js_string!("compactDisplay"), - display.to_js_string(), - Attribute::all(), - ); + if let Some(display_str) = compact_display_str { + options.property(js_string!("compactDisplay"), display_str, Attribute::all()); } - let sign_display = match nf.sign_display { - SignDisplay::Auto => js_string!("auto"), - SignDisplay::Never => js_string!("never"), - SignDisplay::Always => js_string!("always"), - SignDisplay::ExceptZero => js_string!("exceptZero"), - SignDisplay::Negative => js_string!("negative"), - _ => { - return Err(JsNativeError::typ() - .with_message("unsupported signDisplay value") - .into()); - } - }; - options - .property(js_string!("signDisplay"), sign_display, Attribute::all()) + .property( + js_string!("signDisplay"), + sign_display_str, + Attribute::all(), + ) .property( js_string!("roundingIncrement"), - nf.digit_options.rounding_increment.to_u16(), + rounding_increment, Attribute::all(), ) .property( js_string!("roundingPriority"), - nf.digit_options.rounding_priority.to_js_string(), + rounding_priority_str, Attribute::all(), ) .property( js_string!("trailingZeroDisplay"), - nf.digit_options.trailing_zero_display.to_js_string(), + trailing_zero_display_str, Attribute::all(), ); diff --git a/core/engine/src/builtins/intl/plural_rules/mod.rs b/core/engine/src/builtins/intl/plural_rules/mod.rs index d1a7bde8b1a..86aa77217b3 100644 --- a/core/engine/src/builtins/intl/plural_rules/mod.rs +++ b/core/engine/src/builtins/intl/plural_rules/mod.rs @@ -300,26 +300,56 @@ impl PluralRules { fn resolved_options(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let pr be the this value. // 2. Perform ? RequireInternalSlot(pr, [[InitializedPluralRules]]). - let object = this.as_object(); - let plural_rules = object - .as_ref() - .and_then(|o| o.downcast_ref::()) - .ok_or_else(|| { - JsNativeError::typ().with_message( - "`resolved_options` can only be called on an `Intl.PluralRules` object", - ) - })?; + let ( + locale_str, + rule_type, + notation, + minimum_integer_digits, + fraction_digits, + significant_digits, + rounding_increment, + rounding_mode, + rounding_priority, + trailing_zero_display, + plural_categories, + ) = { + let object = this.as_object(); + let plural_rules = object + .as_ref() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message( + "`resolved_options` can only be called on an `Intl.PluralRules` object", + ) + })?; + + ( + plural_rules.locale.to_string(), + plural_rules.rule_type, + plural_rules.notation, + plural_rules.format_options.minimum_integer_digits, + plural_rules.format_options.rounding_type.fraction_digits(), + plural_rules + .format_options + .rounding_type + .significant_digits(), + plural_rules.format_options.rounding_increment.to_u16(), + plural_rules.format_options.rounding_mode, + plural_rules.format_options.rounding_priority, + plural_rules.format_options.trailing_zero_display, + plural_rules + .native + .rules() + .categories() + .map(|category| plural_category_to_js_string(category).into()) + .collect::>(), + ) + }; // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). // 4. Let pluralCategories be a List of Strings containing all possible results of // PluralRuleSelect for the selected locale pr.[[Locale]], sorted according to the following // order: "zero", "one", "two", "few", "many", "other". - let plural_categories = plural_rules - .native - .rules() - .categories() - .map(|category| plural_category_to_js_string(category).into()); - // 5. For each row of Table 30, except the header row, in table order, do // a. Let p be the Property value of the current row. // b. If p is "pluralCategories", then @@ -335,12 +365,12 @@ impl PluralRules { options .property( js_string!("locale"), - js_string!(plural_rules.locale.to_string()), + js_string!(locale_str), Attribute::all(), ) .property( js_string!("type"), - match plural_rules.rule_type { + match rule_type { PluralRuleType::Cardinal => js_string!("cardinal"), PluralRuleType::Ordinal => js_string!("ordinal"), _ => js_string!("unknown"), @@ -349,18 +379,16 @@ impl PluralRules { ) .property( js_string!("notation"), - plural_rules.notation.to_js_string(), + notation.to_js_string(), Attribute::all(), ) .property( js_string!("minimumIntegerDigits"), - plural_rules.format_options.minimum_integer_digits, + minimum_integer_digits, Attribute::all(), ); - if let Some(Extrema { minimum, maximum }) = - plural_rules.format_options.rounding_type.fraction_digits() - { + if let Some(Extrema { minimum, maximum }) = fraction_digits { options .property( js_string!("minimumFractionDigits"), @@ -374,11 +402,7 @@ impl PluralRules { ); } - if let Some(Extrema { minimum, maximum }) = plural_rules - .format_options - .rounding_type - .significant_digits() - { + if let Some(Extrema { minimum, maximum }) = significant_digits { options .property( js_string!("minimumSignificantDigits"), @@ -401,12 +425,12 @@ impl PluralRules { ) .property( js_string!("roundingIncrement"), - plural_rules.format_options.rounding_increment.to_u16(), + rounding_increment, Attribute::all(), ) .property( js_string!("roundingMode"), - match plural_rules.format_options.rounding_mode { + match rounding_mode { SignedRoundingMode::Unsigned(UnsignedRoundingMode::Expand) => { js_string!("expand") } @@ -432,15 +456,12 @@ impl PluralRules { ) .property( js_string!("roundingPriority"), - js_string!(plural_rules.format_options.rounding_priority.to_js_string()), + js_string!(rounding_priority.to_js_string()), Attribute::all(), ) .property( js_string!("trailingZeroDisplay"), - plural_rules - .format_options - .trailing_zero_display - .to_js_string(), + trailing_zero_display.to_js_string(), Attribute::all(), ); diff --git a/core/engine/src/builtins/intl/segmenter/mod.rs b/core/engine/src/builtins/intl/segmenter/mod.rs index 120f17d0d26..5fe4caffaab 100644 --- a/core/engine/src/builtins/intl/segmenter/mod.rs +++ b/core/engine/src/builtins/intl/segmenter/mod.rs @@ -266,15 +266,22 @@ impl Segmenter { fn resolved_options(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let segmenter be the this value. // 2. Perform ? RequireInternalSlot(segmenter, [[InitializedSegmenter]]). - let object = this.as_object(); - let segmenter = object - .as_ref() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message( - "`resolved_options` can only be called on an `Intl.Segmenter` object", - ) - })?; + let (locale_str, granularity_str) = { + let object = this.as_object(); + let segmenter = object + .as_ref() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message( + "`resolved_options` can only be called on an `Intl.Segmenter` object", + ) + })?; + + ( + segmenter.locale.to_string(), + segmenter.native.granularity().to_string(), + ) + }; // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). // 4. For each row of Table 19, except the header row, in table order, do @@ -285,12 +292,12 @@ impl Segmenter { let options = ObjectInitializer::new(context) .property( js_string!("locale"), - js_string!(segmenter.locale.to_string()), + js_string!(locale_str), Attribute::all(), ) .property( js_string!("granularity"), - js_string!(segmenter.native.granularity().to_string()), + js_string!(granularity_str), Attribute::all(), ) .build(); From fa96371b3018feb8ad7999ba1dc8a5e2258ad47e Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 23 Aug 2026 14:44:49 +0000 Subject: [PATCH 15/19] fix: UAF in PluralRules and Segmenter methods This fixes additional heap use-after-free bugs in the Intl builtins where methods like `PluralRules.prototype.select`, `Segments.prototype.containing`, and `SegmentIterator.prototype.next` held GC borrow guards (`Ref<'_, T>` or `RefMut<'_, T>` via `downcast_ref`) across calls to the `Context` (like `to_number`, `to_integer_or_infinity`, or `create_segment_data_object`) which can trigger a garbage collection cycle and invalidate the pointer. Because tests are run in parallel by `boa_tester`, these UAFs were randomly corrupting the heap allocator state, causing tests in entirely unrelated modules (e.g. `ListFormat.prototype.format`) to crash with `malloc_consolidate(): unaligned fastbin chunk detected`. The fix avoids holding borrow guards across GC-triggering context interactions by extracting the data first. --- .../src/builtins/intl/plural_rules/mod.rs | 33 ++++---- .../src/builtins/intl/segmenter/iterator.rs | 76 +++++++++++-------- .../src/builtins/intl/segmenter/segments.rs | 26 ++++--- 3 files changed, 73 insertions(+), 62 deletions(-) diff --git a/core/engine/src/builtins/intl/plural_rules/mod.rs b/core/engine/src/builtins/intl/plural_rules/mod.rs index 86aa77217b3..ff7a7fd5b62 100644 --- a/core/engine/src/builtins/intl/plural_rules/mod.rs +++ b/core/engine/src/builtins/intl/plural_rules/mod.rs @@ -179,17 +179,17 @@ impl PluralRules { fn select(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let pr be the this value. // 2. Perform ? RequireInternalSlot(pr, [[InitializedPluralRules]]). - let object = this.as_object(); - let plural_rules = object - .as_ref() - .and_then(|o| o.downcast_ref::()) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("`select` can only be called on an `Intl.PluralRules` object") - })?; + let object = this.as_object().filter(|o| o.is::()).ok_or_else(|| { + JsNativeError::typ() + .with_message("`select` can only be called on an `Intl.PluralRules` object") + })?; let n = args.get_or_undefined(0).to_number(context)?; + let plural_rules = object + .downcast_ref::() + .expect("already checked that it is a PluralRules object"); + Ok(plural_category_to_js_string(resolve_plural(&plural_rules, n).category).into()) } @@ -206,15 +206,10 @@ impl PluralRules { fn select_range(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let pr be the this value. // 2. Perform ? RequireInternalSlot(pr, [[InitializedPluralRules]]). - let object = this.as_object(); - let plural_rules = object - .as_ref() - .and_then(|o| o.downcast_ref::()) - .ok_or_else(|| { - JsNativeError::typ().with_message( - "`select_range` can only be called on an `Intl.PluralRules` object", - ) - })?; + let object = this.as_object().filter(|o| o.is::()).ok_or_else(|| { + JsNativeError::typ() + .with_message("`select_range` can only be called on an `Intl.PluralRules` object") + })?; // 3. If start is undefined or end is undefined, throw a TypeError exception. let x = args.get_or_undefined(0); @@ -230,6 +225,10 @@ impl PluralRules { // 5. Let y be ? ToNumber(end). let y = y.to_number(context)?; + let plural_rules = object + .downcast_ref::() + .expect("already checked that it is a PluralRules object"); + // 6. Return ? ResolvePluralRange(pr, x, y). // ResolvePluralRange(pr, x, y) // diff --git a/core/engine/src/builtins/intl/segmenter/iterator.rs b/core/engine/src/builtins/intl/segmenter/iterator.rs index fb361570826..723843d7e74 100644 --- a/core/engine/src/builtins/intl/segmenter/iterator.rs +++ b/core/engine/src/builtins/intl/segmenter/iterator.rs @@ -108,34 +108,45 @@ impl SegmentIterator { fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let iterator be the this value. // 2. Perform ? RequireInternalSlot(iterator, [[IteratingSegmenter]]). - let object = this.as_object(); - let mut iter = object - .as_ref() - .and_then(JsObject::downcast_mut::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("`next` can only be called on a `Segment Iterator` object") - })?; - - // 5. Let startIndex be iterator.[[IteratedStringNextSegmentCodeUnitIndex]]. - let start = iter.next_segment_index; - - // 4. Let string be iterator.[[IteratedString]]. - // 6. Let endIndex be ! FindBoundary(segmenter, string, startIndex, after). - let Some((end, is_word_like)) = iter.string.get(start..).and_then(|string| { - // 3. Let segmenter be iterator.[[IteratingSegmenter]]. - let segmenter = iter - .segmenter - .downcast_ref::() - .js_expect("segment iterator object should contain a segmenter") - .ok()?; - let mut segments = segmenter.native.segment(string.variant()); - // the first elem is always 0. - segments.next(); - segments - .next() - .map(|end| (start + end, segments.is_word_like())) - }) else { + let object = this.as_object().filter(|o| o.is::()).ok_or_else(|| { + JsNativeError::typ() + .with_message("`next` can only be called on a `Segment Iterator` object") + })?; + + let (string, start, end, is_word_like, finished) = { + let mut iter = object + .downcast_mut::() + .expect("already checked that it is a Segment Iterator object"); + + // 5. Let startIndex be iterator.[[IteratedStringNextSegmentCodeUnitIndex]]. + let start = iter.next_segment_index; + + // 4. Let string be iterator.[[IteratedString]]. + // 6. Let endIndex be ! FindBoundary(segmenter, string, startIndex, after). + let Some((end, is_word_like)) = iter.string.get(start..).and_then(|string| { + // 3. Let segmenter be iterator.[[IteratingSegmenter]]. + let segmenter = iter + .segmenter + .downcast_ref::() + .js_expect("segment iterator object should contain a segmenter") + .ok()?; + let mut segments = segmenter.native.segment(string.variant()); + // the first elem is always 0. + segments.next(); + segments + .next() + .map(|end| (start + end, segments.is_word_like())) + }) else { + return Ok((None, 0, 0, false, true)); + }; + + // 8. Set iterator.[[IteratedStringNextSegmentCodeUnitIndex]] to endIndex. + iter.next_segment_index = end; + + (Some(iter.string.clone()), start, end, is_word_like, false) + }; + + if finished { // 7. If endIndex is not finite, then // a. Return CreateIterResultObject(undefined, true). return Ok(create_iter_result_object( @@ -143,13 +154,12 @@ impl SegmentIterator { true, context, )); - }; - // 8. Set iterator.[[IteratedStringNextSegmentCodeUnitIndex]] to endIndex. - iter.next_segment_index = end; + } + + let string = string.expect("string is Some when not finished"); // 9. Let segmentData be ! CreateSegmentDataObject(segmenter, string, startIndex, endIndex). - let segment_data = - create_segment_data_object(iter.string.clone(), start..end, is_word_like, context); + let segment_data = create_segment_data_object(string, start..end, is_word_like, context); // 10. Return CreateIterResultObject(segmentData, false). Ok(create_iter_result_object( diff --git a/core/engine/src/builtins/intl/segmenter/segments.rs b/core/engine/src/builtins/intl/segmenter/segments.rs index 248acc71318..857282132e7 100644 --- a/core/engine/src/builtins/intl/segmenter/segments.rs +++ b/core/engine/src/builtins/intl/segmenter/segments.rs @@ -56,14 +56,20 @@ impl Segments { fn containing(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let segments be the this value. // 2. Perform ? RequireInternalSlot(segments, [[SegmentsSegmenter]]). - let object = this.as_object(); + let object = this.as_object().filter(|o| o.is::()).ok_or_else(|| { + JsNativeError::typ() + .with_message("`containing` can only be called on a `Segments` object") + })?; + + // 6. Let n be ? ToIntegerOrInfinity(index). + let n_val = args + .get_or_undefined(0) + .to_integer_or_infinity(context)? + .as_integer(); + let segments = object - .as_ref() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("`containing` can only be called on a `Segments` object") - })?; + .downcast_ref::() + .expect("already checked that it is a Segments object"); // 3. Let segmenter be segments.[[SegmentsSegmenter]]. let segmenter = segments @@ -75,11 +81,7 @@ impl Segments { // 5. Let len be the length of string. let len = segments.string.len() as i64; - // 6. Let n be ? ToIntegerOrInfinity(index). - let Some(n) = args - .get_or_undefined(0) - .to_integer_or_infinity(context)? - .as_integer() + let Some(n) = n_val // 7. If n < 0 or n ≥ len, return undefined. .filter(|i| (0..len).contains(i)) .map(|n| n as usize) From b5d4a84cad65ad8368066daaec3adf2693af9ee2 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 23 Aug 2026 14:49:16 +0000 Subject: [PATCH 16/19] fix: type error in SegmentIterator::next The previous change introduced a type mismatch: inside the scoped block that extracts state from the GC-managed SegmentIterator, a tried to return a tuple from a function whose return type is JsResult. The compiler caught this as an E0308 type mismatch. Replaced the artificial 'finished' boolean flag in a tuple with a clean Option<(JsString, usize, usize, Option)> and a match statement, which correctly expresses exhaustion vs. a live segment without any type tricks. --- .../src/builtins/intl/segmenter/iterator.rs | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/core/engine/src/builtins/intl/segmenter/iterator.rs b/core/engine/src/builtins/intl/segmenter/iterator.rs index 723843d7e74..1c165f39c9d 100644 --- a/core/engine/src/builtins/intl/segmenter/iterator.rs +++ b/core/engine/src/builtins/intl/segmenter/iterator.rs @@ -113,7 +113,10 @@ impl SegmentIterator { .with_message("`next` can only be called on a `Segment Iterator` object") })?; - let (string, start, end, is_word_like, finished) = { + // Extract all data inside a scoped block so the mutable borrow is dropped + // before we pass `context` to `create_segment_data_object` / `create_iter_result_object` + // (those can trigger GC, and holding a RefMut across a GC point is a UAF). + let result: Option<(JsString, usize, usize, Option)> = { let mut iter = object .downcast_mut::() .expect("already checked that it is a Segment Iterator object"); @@ -123,7 +126,7 @@ impl SegmentIterator { // 4. Let string be iterator.[[IteratedString]]. // 6. Let endIndex be ! FindBoundary(segmenter, string, startIndex, after). - let Some((end, is_word_like)) = iter.string.get(start..).and_then(|string| { + let maybe_end = iter.string.get(start..).and_then(|string| { // 3. Let segmenter be iterator.[[IteratingSegmenter]]. let segmenter = iter .segmenter @@ -136,36 +139,40 @@ impl SegmentIterator { segments .next() .map(|end| (start + end, segments.is_word_like())) - }) else { - return Ok((None, 0, 0, false, true)); - }; - - // 8. Set iterator.[[IteratedStringNextSegmentCodeUnitIndex]] to endIndex. - iter.next_segment_index = end; - - (Some(iter.string.clone()), start, end, is_word_like, false) + }); + + if let Some((end, is_word_like)) = maybe_end { + // 8. Set iterator.[[IteratedStringNextSegmentCodeUnitIndex]] to endIndex. + iter.next_segment_index = end; + Some((iter.string.clone(), start, end, is_word_like)) + } else { + None + } }; - - if finished { - // 7. If endIndex is not finite, then - // a. Return CreateIterResultObject(undefined, true). - return Ok(create_iter_result_object( - JsValue::undefined(), - true, - context, - )); + // RefMut<'_, SegmentIterator> is dropped here — safe to use context below. + + match result { + None => { + // 7. If endIndex is not finite, then + // a. Return CreateIterResultObject(undefined, true). + Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )) + } + Some((string, start, end, is_word_like)) => { + // 9. Let segmentData be ! CreateSegmentDataObject(segmenter, string, startIndex, endIndex). + let segment_data = + create_segment_data_object(string, start..end, is_word_like, context); + + // 10. Return CreateIterResultObject(segmentData, false). + Ok(create_iter_result_object( + segment_data.into(), + false, + context, + )) + } } - - let string = string.expect("string is Some when not finished"); - - // 9. Let segmentData be ! CreateSegmentDataObject(segmenter, string, startIndex, endIndex). - let segment_data = create_segment_data_object(string, start..end, is_word_like, context); - - // 10. Return CreateIterResultObject(segmentData, false). - Ok(create_iter_result_object( - segment_data.into(), - false, - context, - )) } } From 5a5023143a3c3aac41021537d786cdaa6a6fdea3 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 23 Aug 2026 15:05:51 +0000 Subject: [PATCH 17/19] fix: UAF in RegExpStringIterator and ArrayIterator + serial CI This commit does two things: 1. Root cause fix: RegExpStringIterator::next and ArrayIterator::next both held RefMut<'_, T> borrow guards (via downcast_mut) across multiple context calls (RegExpExec, to_string, get, to_length, set, length_of_array_like, Array::get). Any of these can trigger a GC collection that frees the backing object while the mutable guard is live, causing a use-after-free. Fix: extract all needed fields into owned values in a scoped block, drop the borrow guard, then do all context operations. Re-borrow only to write back state changes (completed, done, next_index). 2. CI fix: add --disable-parallelism to the test262 runner command. The cascading 'malloc_consolidate(): unaligned fastbin chunk detected' crashes in innocent suites (anchor, fontsize) happen because rayon runs test suites in parallel in the same process. A UAF in one thread silently corrupts the shared glibc heap; when another thread then allocates, the allocator detects the corruption and aborts the entire process. This makes the symptom appear in a random innocent suite. Serial execution ensures each crash is attributed to the actual offending test, not a random concurrent victim. This is the standard approach while UAF fixes are in progress. --- .github/workflows/test262.yml | 2 +- .../src/builtins/array/array_iterator.rs | 47 +++++++++++----- .../builtins/regexp/regexp_string_iterator.rs | 56 ++++++++++++------- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/.github/workflows/test262.yml b/.github/workflows/test262.yml index d569e061ac5..cbf0f44d5d7 100644 --- a/.github/workflows/test262.yml +++ b/.github/workflows/test262.yml @@ -45,7 +45,7 @@ jobs: run: | cd boa mkdir -p ../results/test262 - cargo run --release --bin boa_tester -- run -v -o ../results/test262 + cargo run --release --bin boa_tester -- run -v --disable-parallelism -o ../results/test262 cd .. - name: Compare results diff --git a/core/engine/src/builtins/array/array_iterator.rs b/core/engine/src/builtins/array/array_iterator.rs index 2490214b261..f1e67020809 100644 --- a/core/engine/src/builtins/array/array_iterator.rs +++ b/core/engine/src/builtins/array/array_iterator.rs @@ -95,13 +95,28 @@ impl ArrayIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let object = this.as_object(); - let mut array_iterator = object - .as_ref() - .and_then(JsObject::downcast_mut::) + let object = this + .as_object() + .filter(|o| o.is::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an ArrayIterator"))?; - let index = array_iterator.next_index; - if array_iterator.done { + + // Extract needed fields into a scoped block so the RefMut borrow is dropped + // before any context call. Holding a RefMut<'_, T> across context operations + // is a use-after-free: GC can collect the backing object while the guard is live. + let (index, done, array, kind) = { + let array_iterator = object + .downcast_ref::() + .expect("already checked that it is an ArrayIterator"); + ( + array_iterator.next_index, + array_iterator.done, + array_iterator.array.clone(), + array_iterator.kind, + ) + }; + // RefMut dropped here — safe to use context below. + + if done { return Ok(create_iter_result_object( JsValue::undefined(), true, @@ -109,7 +124,7 @@ impl ArrayIterator { )); } - let len = if let Some(f) = array_iterator.array.downcast_ref::() { + let len = if let Some(f) = array.downcast_ref::() { let buf = f.viewed_array_buffer().as_buffer(); let Some(buf) = buf .bytes(std::sync::atomic::Ordering::SeqCst) @@ -122,26 +137,32 @@ impl ArrayIterator { f.array_length(buf.len()) } else { - array_iterator.array.length_of_array_like(context)? + array.length_of_array_like(context)? }; if index >= len { - array_iterator.done = true; + object.downcast_mut::().expect("already checked").done = true; return Ok(create_iter_result_object( JsValue::undefined(), true, context, )); } - array_iterator.next_index = index + 1; - match array_iterator.kind { + + // Write back the incremented index (no borrow held during context calls above). + object + .downcast_mut::() + .expect("already checked") + .next_index = index + 1; + + match kind { PropertyNameKind::Key => Ok(create_iter_result_object(index.into(), false, context)), PropertyNameKind::Value => { - let element_value = array_iterator.array.get(index, context)?; + let element_value = array.get(index, context)?; Ok(create_iter_result_object(element_value, false, context)) } PropertyNameKind::KeyAndValue => { - let element_value = array_iterator.array.get(index, context)?; + let element_value = array.get(index, context)?; let result = Array::create_array_from_list([index.into(), element_value], context); Ok(create_iter_result_object(result.into(), false, context)) } diff --git a/core/engine/src/builtins/regexp/regexp_string_iterator.rs b/core/engine/src/builtins/regexp/regexp_string_iterator.rs index 0e72a55a759..9bcc1e9371c 100644 --- a/core/engine/src/builtins/regexp/regexp_string_iterator.rs +++ b/core/engine/src/builtins/regexp/regexp_string_iterator.rs @@ -115,14 +115,29 @@ impl RegExpStringIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%regexpstringiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let object = this.as_object(); - let mut iterator = object - .as_ref() - .and_then(JsObject::downcast_mut::) - .ok_or_else(|| { - JsNativeError::typ().with_message("`this` is not a RegExpStringIterator") - })?; - if iterator.completed { + // Extract all state we need in a scoped block to drop the RefMut before + // any context call. Holding a RefMut<'_, T> across context operations is a + // use-after-free because the GC can collect the backing object while the + // mutable borrow guard is live. + let object = this.as_object().filter(|o| o.is::()).ok_or_else(|| { + JsNativeError::typ().with_message("`this` is not a RegExpStringIterator") + })?; + + let (completed, matcher, string, global, unicode) = { + let iterator = object + .downcast_ref::() + .expect("already checked that it is a RegExpStringIterator"); + ( + iterator.completed, + iterator.matcher.clone(), + iterator.string.clone(), + iterator.global, + iterator.unicode, + ) + }; + // RefMut dropped here — safe to use context below. + + if completed { return Ok(create_iter_result_object( JsValue::undefined(), true, @@ -133,14 +148,18 @@ impl RegExpStringIterator { // TODO: This is the code that should be created as a closure in create_regexp_string_iterator. // i. Let match be ? RegExpExec(R, S). - let m = RegExp::abstract_exec(&iterator.matcher, iterator.string.clone(), context)?; + let m = RegExp::abstract_exec(&matcher, string.clone(), context)?; if let Some(m) = m { // iii. If global is false, then - if !iterator.global { + if !global { // 1. Perform ? Yield(match). // 2. Return undefined. - iterator.completed = true; + // Write back completed = true (no borrow held before this point). + object + .downcast_mut::() + .expect("already checked") + .completed = true; return Ok(create_iter_result_object(m.into(), false, context)); } @@ -150,26 +169,25 @@ impl RegExpStringIterator { // v. If matchStr is the empty String, then if m_str.is_empty() { // 1. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))). - let this_index = iterator - .matcher + let this_index = matcher .get(js_string!("lastIndex"), context)? .to_length(context)?; // 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode). - let next_index = - advance_string_index(&iterator.string, this_index, iterator.unicode); + let next_index = advance_string_index(&string, this_index, unicode); // 3. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true). - iterator - .matcher - .set(js_string!("lastIndex"), next_index, true, context)?; + matcher.set(js_string!("lastIndex"), next_index, true, context)?; } // vi. Perform ? Yield(match). Ok(create_iter_result_object(m.into(), false, context)) } else { // ii. If match is null, return undefined. - iterator.completed = true; + object + .downcast_mut::() + .expect("already checked") + .completed = true; Ok(create_iter_result_object( JsValue::undefined(), true, From 38e9a5df60d7e3e0fe0e81f48cdc32ab4f44c531 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 23 Aug 2026 15:26:02 +0000 Subject: [PATCH 18/19] fix: UAF in Generator, StringIterator, SetIterator, MapIterator Similar to RegExpStringIterator and ArrayIterator, these iterators and generators were holding a RefMut borrow (via downcast_mut) across context calls (create_iter_result_object, String::substring) which trigger GC operations. Fix by dropping the borrow before any context calls are made. --- core/engine/src/builtins/generator/mod.rs | 4 ++ core/engine/src/builtins/map/map_iterator.rs | 71 +++++++++++-------- core/engine/src/builtins/set/set_iterator.rs | 68 ++++++++++-------- .../src/builtins/string/string_iterator.rs | 42 +++++++---- 4 files changed, 114 insertions(+), 71 deletions(-) diff --git a/core/engine/src/builtins/generator/mod.rs b/core/engine/src/builtins/generator/mod.rs index 9acf67076b0..92ac2ebe727 100644 --- a/core/engine/src/builtins/generator/mod.rs +++ b/core/engine/src/builtins/generator/mod.rs @@ -287,6 +287,7 @@ impl Generator { // 2. If state is completed, return CreateIterResultObject(undefined, true). GeneratorState::Completed => { r#gen.state = GeneratorState::Completed; + drop(r#gen); return Ok(create_iter_result_object( JsValue::undefined(), true, @@ -323,6 +324,7 @@ impl Generator { } CompletionRecord::Return(value) => { r#gen.state = GeneratorState::Completed; + drop(r#gen); Ok(create_iter_result_object(value, true, context)) } CompletionRecord::Throw(err) => { @@ -374,6 +376,7 @@ impl Generator { // b. Once a generator enters the completed state it never leaves it and its // associated execution context is never resumed. Any execution state associated // with generator can be discarded at this point. + drop(r#gen); // a. If abruptCompletion.[[Type]] is return, then if let Ok(value) = abrupt_completion { @@ -414,6 +417,7 @@ impl Generator { } CompletionRecord::Return(value) => { r#gen.state = GeneratorState::Completed; + drop(r#gen); Ok(create_iter_result_object(value, true, context)) } CompletionRecord::Throw(err) => { diff --git a/core/engine/src/builtins/map/map_iterator.rs b/core/engine/src/builtins/map/map_iterator.rs index 326f28a8567..5789abdd94b 100644 --- a/core/engine/src/builtins/map/map_iterator.rs +++ b/core/engine/src/builtins/map/map_iterator.rs @@ -106,41 +106,54 @@ impl MapIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let object = this.as_object(); - let mut map_iterator = object - .as_ref() - .and_then(JsObject::downcast_mut::) + let object = this + .as_object() + .filter(|o| o.is::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a MapIterator"))?; - let item_kind = map_iterator.iteration_kind; + let (item_kind, element, iterated_map) = { + let mut map_iterator = object + .downcast_mut::() + .expect("already checked that it is a MapIterator"); - if let Some(obj) = map_iterator.iterated_map.take() { - let e = { - let mut entries = obj.0.borrow_mut(); - let entries = entries.data_mut(); - let len = entries.full_len(); - loop { - let element = entries - .get_index(map_iterator.next_index) - .map(|(v, k)| (v.clone(), k.clone())); - map_iterator.next_index += 1; - if element.is_some() || map_iterator.next_index >= len { - break element; - } - } - }; - if let Some((key, value)) = e { - let item = match item_kind { - PropertyNameKind::Key => Ok(create_iter_result_object(key, false, context)), - PropertyNameKind::Value => Ok(create_iter_result_object(value, false, context)), - PropertyNameKind::KeyAndValue => { - let result = Array::create_array_from_list([key, value], context); - Ok(create_iter_result_object(result.into(), false, context)) + let item_kind = map_iterator.iteration_kind; + + if let Some(obj) = map_iterator.iterated_map.take() { + let e = { + let mut entries = obj.0.borrow_mut(); + let entries = entries.data_mut(); + let len = entries.full_len(); + loop { + let element = entries + .get_index(map_iterator.next_index) + .map(|(v, k)| (v.clone(), k.clone())); + map_iterator.next_index += 1; + if element.is_some() || map_iterator.next_index >= len { + break element; + } } }; - map_iterator.iterated_map = Some(obj); - return item; + (item_kind, e, Some(obj)) + } else { + (item_kind, None, None) } + }; + + if let (Some((key, value)), Some(obj)) = (element, iterated_map) { + object + .downcast_mut::() + .expect("already checked") + .iterated_map = Some(obj); + + let item = match item_kind { + PropertyNameKind::Key => Ok(create_iter_result_object(key, false, context)), + PropertyNameKind::Value => Ok(create_iter_result_object(value, false, context)), + PropertyNameKind::KeyAndValue => { + let result = Array::create_array_from_list([key, value], context); + Ok(create_iter_result_object(result.into(), false, context)) + } + }; + return item; } Ok(create_iter_result_object( diff --git a/core/engine/src/builtins/set/set_iterator.rs b/core/engine/src/builtins/set/set_iterator.rs index 5872b9f0b94..792f0b68268 100644 --- a/core/engine/src/builtins/set/set_iterator.rs +++ b/core/engine/src/builtins/set/set_iterator.rs @@ -105,39 +105,51 @@ impl SetIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let object = this.as_object(); - let mut set_iterator = object - .as_ref() - .and_then(JsObject::downcast_mut::) - .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an SetIterator"))?; + let object = this + .as_object() + .filter(|o| o.is::()) + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a SetIterator"))?; - let item_kind = set_iterator.iteration_kind; + let (item_kind, element, iterated_set) = { + let mut set_iterator = object + .downcast_mut::() + .expect("already checked that it is a SetIterator"); - if let Some(obj) = set_iterator.iterated_set.take() { - let e = { - let mut entries = obj.0.borrow_mut(); - let entries = entries.data_mut(); - let len = entries.full_len(); - loop { - let element = entries.get_index(set_iterator.next_index); - set_iterator.next_index += 1; - if element.is_some() || set_iterator.next_index >= len { - break element.cloned(); - } - } - }; - if let Some(element) = e { - let item = match item_kind { - PropertyNameKind::KeyAndValue => { - let result = - Array::create_array_from_list([element.clone(), element], context); - Ok(create_iter_result_object(result.into(), false, context)) + let item_kind = set_iterator.iteration_kind; + + if let Some(obj) = set_iterator.iterated_set.take() { + let e = { + let mut entries = obj.0.borrow_mut(); + let entries = entries.data_mut(); + let len = entries.full_len(); + loop { + let element = entries.get_index(set_iterator.next_index); + set_iterator.next_index += 1; + if element.is_some() || set_iterator.next_index >= len { + break element.cloned(); + } } - _ => Ok(create_iter_result_object(element, false, context)), }; - set_iterator.iterated_set = Some(obj); - return item; + (item_kind, e, Some(obj)) + } else { + (item_kind, None, None) } + }; + + if let (Some(element), Some(obj)) = (element, iterated_set) { + object + .downcast_mut::() + .expect("already checked") + .iterated_set = Some(obj); + + let item = match item_kind { + PropertyNameKind::KeyAndValue => { + let result = Array::create_array_from_list([element.clone(), element], context); + Ok(create_iter_result_object(result.into(), false, context)) + } + _ => Ok(create_iter_result_object(element, false, context)), + }; + return item; } Ok(create_iter_result_object( diff --git a/core/engine/src/builtins/string/string_iterator.rs b/core/engine/src/builtins/string/string_iterator.rs index bf15695adfa..7b8d7d540d2 100644 --- a/core/engine/src/builtins/string/string_iterator.rs +++ b/core/engine/src/builtins/string/string_iterator.rs @@ -69,35 +69,49 @@ impl StringIterator { /// `StringIterator.prototype.next( )` pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let object = this.as_object(); - let mut string_iterator = object - .as_ref() - .and_then(JsObject::downcast_mut::) - .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an ArrayIterator"))?; + let object = this + .as_object() + .filter(|o| o.is::()) + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a StringIterator"))?; - if string_iterator.string.is_empty() { + let (mut string, position) = { + let string_iterator = object + .downcast_ref::() + .expect("already checked that it is a StringIterator"); + (string_iterator.string.clone(), string_iterator.next_index) + }; + + if string.is_empty() { return Ok(create_iter_result_object( JsValue::undefined(), true, context, )); } - let native_string = &string_iterator.string; - let len = native_string.len(); - let position = string_iterator.next_index; + let len = string.len(); if position >= len { - string_iterator.string = js_string!(); + object + .downcast_mut::() + .expect("already checked") + .string = js_string!(); return Ok(create_iter_result_object( JsValue::undefined(), true, context, )); } - let code_point = native_string.code_point_at(position); - string_iterator.next_index += code_point.code_unit_count(); + + let code_point = string.code_point_at(position); + let next_index = position + code_point.code_unit_count(); + + object + .downcast_mut::() + .expect("already checked") + .next_index = next_index; + let result_string = crate::builtins::string::String::substring( - &string_iterator.string.clone().into(), - &[position.into(), string_iterator.next_index.into()], + &string.into(), + &[position.into(), next_index.into()], context, )?; Ok(create_iter_result_object(result_string, false, context)) From 843e41fe9f99ea9042b0cd5b7f867cc5d9cacda7 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Sun, 23 Aug 2026 15:46:04 +0000 Subject: [PATCH 19/19] fix: UAF in ForInIterator::next and AsyncGeneratorYield opcode ForInIterator::next was holding a RefMut borrow across multiple context-triggering calls (__own_property_keys__, __get_own_property__, create_iter_result_object). Restructured to narrow borrow scope. AsyncGeneratorYield opcode was holding a borrow_mut guard while calling err.into_opaque(context) inside the Throw branch. Replaced with a scoped borrow that clones the queue front before dropping the guard. --- .../src/builtins/object/for_in_iterator.rs | 67 ++++++++++++++----- .../src/vm/opcode/generator/yield_stm.rs | 24 ++++--- 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/core/engine/src/builtins/object/for_in_iterator.rs b/core/engine/src/builtins/object/for_in_iterator.rs index dfeac3601e1..03fec1e6fee 100644 --- a/core/engine/src/builtins/object/for_in_iterator.rs +++ b/core/engine/src/builtins/object/for_in_iterator.rs @@ -86,16 +86,28 @@ impl ForInIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let object = this.as_object(); - let mut iterator = object - .as_ref() - .and_then(|o| o.downcast_mut::()) + let object = this + .as_object() + .filter(|o| o.is::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a ForInIterator"))?; - let mut object = iterator.object.to_object(context)?; + + let mut current_object = { + let iterator = object.downcast_ref::().expect("already checked"); + iterator.object.clone() + }; + + let mut current_object_obj = current_object.to_object(context)?; loop { - if !iterator.object_was_visited { - let keys = object + let was_visited = { + let iterator = object.downcast_ref::().expect("checked"); + iterator.object_was_visited + }; + + if !was_visited { + let keys = current_object_obj .__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?; + + let mut iterator = object.downcast_mut::().expect("checked"); for k in keys { match k { PropertyKey::String(ref k) => { @@ -109,23 +121,40 @@ impl ForInIterator { } iterator.object_was_visited = true; } - while let Some(r) = iterator.remaining_keys.pop_front() { - if !iterator.visited_keys.contains(&r) - && let Some(desc) = object.__get_own_property__( + + loop { + let r = { + let mut iterator = object.downcast_mut::().expect("checked"); + iterator.remaining_keys.pop_front() + }; + + let Some(r) = r else { break }; + + let already_visited = { + let iterator = object.downcast_ref::().expect("checked"); + iterator.visited_keys.contains(&r) + }; + + if !already_visited { + let desc = current_object_obj.__get_own_property__( &PropertyKey::from(r.clone()), &mut InternalMethodPropertyContext::new(context), - )? - { - iterator.visited_keys.insert(r.clone()); - if desc.expect_enumerable() { - return Ok(create_iter_result_object(JsValue::new(r), false, context)); + )?; + + if let Some(desc) = desc { + let mut iterator = object.downcast_mut::().expect("checked"); + iterator.visited_keys.insert(r.clone()); + if desc.expect_enumerable() { + return Ok(create_iter_result_object(JsValue::new(r), false, context)); + } } } } - let proto = object.prototype().clone(); + + let proto = current_object_obj.prototype().clone(); match proto { Some(o) => { - object = o; + current_object_obj = o; } _ => { return Ok(create_iter_result_object( @@ -135,7 +164,9 @@ impl ForInIterator { )); } } - iterator.object = JsValue::new(object.clone()); + + let mut iterator = object.downcast_mut::().expect("checked"); + iterator.object = JsValue::new(current_object_obj.clone()); iterator.object_was_visited = false; } } diff --git a/core/engine/src/vm/opcode/generator/yield_stm.rs b/core/engine/src/vm/opcode/generator/yield_stm.rs index bcf281412e7..44d21aa1a91 100644 --- a/core/engine/src/vm/opcode/generator/yield_stm.rs +++ b/core/engine/src/vm/opcode/generator/yield_stm.rs @@ -81,15 +81,18 @@ impl AsyncGeneratorYield { return context.handle_error(err); } - let mut r#gen = async_generator_object.borrow_mut(); - - // 10. Let queue be generator.[[AsyncGeneratorQueue]]. - // 11. If queue is not empty, then - // a. NOTE: Execution continues without suspending the generator. - // b. Let toYield be the first element of queue. - if let Some(next) = r#gen.data().queue.front() { - // c. Let resumptionValue be Completion(toYield.[[Completion]]). - let resume_kind = match next.completion.clone() { + // 10. Let queue be generator.[[AsyncGeneratorQueue]] + // 11. If queue is not empty, resume without suspending. + let next_completion = async_generator_object + .borrow() + .data() + .queue + .front() + .map(|n| n.completion.clone()); + + if let Some(next) = next_completion { + // c. Let resumptionValue be Completion(toYield.[[Completion]]) + let resume_kind = match next { CompletionRecord::Normal(val) => { context.vm.stack.push(val); GeneratorResumeKind::Normal @@ -115,9 +118,8 @@ impl AsyncGeneratorYield { } // 12. Else, - // a. Set generator.[[AsyncGeneratorState]] to suspended-yield. - r#gen.data_mut().state = AsyncGeneratorState::SuspendedYield; + async_generator_object.borrow_mut().data_mut().state = AsyncGeneratorState::SuspendedYield; // TODO: b. Remove genContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. // TODO: c. Let callerContext be the running execution context.