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/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 7b0f9246640..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. @@ -320,10 +322,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 = context.alloc(finished); // Strict calls don't need extensions, since all strict eval calls push a new // function environment before evaluating. @@ -350,9 +350,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..3252ed47984 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_collector(), 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_collector(), 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_collector(), 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,19 +332,17 @@ 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_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(&unsafe { boa_gc::MutationContext::dummy() }); + 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(&unsafe { boa_gc::MutationContext::dummy() }) + .value(context.gc_collector()) .and_then(|v| v.0.take()); // ii. Set removed to 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..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(); @@ -1073,7 +1075,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, @@ -1090,7 +1094,8 @@ pub(crate) fn function_call( frame.environments.push_function( scope, FunctionSlots::new(this, function_object.clone(), None), - global, + &global, + &unsafe { boa_gc::MutationContext::global() }, ); } @@ -1181,7 +1186,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, @@ -1209,7 +1216,8 @@ fn function_construct( .clone(), ), ), - global, + &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/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 be01ebb234b..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,15 +303,14 @@ impl Json { false, false, context.interner_mut(), - in_with, + &gc, + false, spanned_source_text, SourcePath::Json, ); compiler.compile_statement_list(script.statements(), true, false); - Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - compiler.finish(), - ) + let finished = compiler.finish(); + context.alloc(finished) }; let realm = context.realm().clone(); @@ -815,7 +815,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..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( - &unsafe { boa_gc::MutationContext::dummy() }, - 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,10 +653,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 = context.alloc(GcRefCell::new(Vec::new())); // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -874,10 +868,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 = context.alloc(GcRefCell::new(Vec::new())); // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -1244,10 +1235,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 = context.alloc(GcRefCell::new(Vec::new())); // 4. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -1557,10 +1545,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 = context.alloc(GcRefCell::new(Vec::new())); // 2. Let remainingElementsCount be the Record { [[Value]]: 1 }. let remaining_elements_count = Rc::new(Cell::new(1)); @@ -2460,10 +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( - &unsafe { boa_gc::MutationContext::dummy() }, - 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/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/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/builtins/weak/weak_ref.rs b/core/engine/src/builtins/weak/weak_ref.rs index 77f136812ac..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(&unsafe { boa_gc::MutationContext::dummy() }, 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(&unsafe { boa_gc::MutationContext::dummy() }) { + if let Some(object) = weak_ref.upgrade(context.gc_collector()) { 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..91e88290846 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_collector()), ) .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..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(&unsafe { boa_gc::MutationContext::dummy() }), + NativeWeakSet::new(context.gc_collector()), ) .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..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::dummy() }, - 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::dummy() }, - 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::dummy() }, - 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::dummy() }, 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::dummy() }, 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::dummy() }, - 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 b862326fc7b..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::dummy() }, 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 78453d26078..218d177ffda 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,6 +465,21 @@ impl Context { &self.vm.frame().realm } + /// Allocates a value on the Gc heap. + #[inline] + pub fn alloc( + &self, + value: T, + ) -> boa_gc::Gc<'static, T> { + self.gc.alloc(value) + } + + /// Gets the GC collector. + #[must_use] + pub fn gc_collector(&self) -> &'static boa_gc::MutationContext<'static, 'static> { + self.gc.gc_collector() + } + /// Set the value of trace on the context #[cfg(feature = "trace")] #[inline] @@ -531,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); @@ -1205,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 @@ -1259,6 +1279,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/environments/runtime/mod.rs b/core/engine/src/environments/runtime/mod.rs index 8f7b2dd26c6..4e47a4f79ff 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..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( - &unsafe { boa_gc::MutationContext::dummy() }, - 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( - &unsafe { boa_gc::MutationContext::dummy() }, - 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,10 +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( - &unsafe { boa_gc::MutationContext::dummy() }, - GcRefCell::new(JsValue::undefined()), - )); + context.insert_data(context.alloc(GcRefCell::new(JsValue::undefined()))); let module = unsafe { vec![ @@ -912,7 +903,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..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(), @@ -1824,17 +1826,19 @@ impl SourceTextModule { compiler.compile_module_item_list(source.items()); ( - Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - compiler.finish(), - ), + { + let finished = compiler.finish(); + context.alloc(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(), &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 613561c9558..f0bbde545fa 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), } } } @@ -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(), @@ -338,13 +340,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 = context.alloc(finished); let mut envs = EnvironmentStack::new(); - envs.push_module(module_scope); + 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/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/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/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index 85aa1b26b2b..08e7af96556 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}; @@ -1093,13 +1094,10 @@ impl JsPromise { } } - let state = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - GcRefCell::new(Inner { - result: None, - task: None, - }), - ); + let state = context.alloc(GcRefCell::new(Inner { + result: None, + task: None, + })); let resolve = { let state = state.clone(); @@ -1429,6 +1427,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..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(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 e752f696b95..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(&unsafe { boa_gc::MutationContext::dummy() }), + 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 13d14095cc8..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(&unsafe { boa_gc::MutationContext::dummy() }), + NativeWeakSet::new(context.gc_collector()), ) .upcast(), } diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs index cd30c5dceb4..10cfd824459 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 } } @@ -122,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::dummy() }, + mc, VTableObject { object: GcRefCell::new(object), vtable, @@ -138,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 @@ -164,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 @@ -182,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. @@ -215,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::dummy() }, + 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(), }), @@ -229,7 +286,7 @@ impl JsObject { }, ); - JsObject { inner }.upcast() + JsObject { inner } } /// Creates a new object with the provided prototype and object data. @@ -244,24 +301,12 @@ impl JsObject { prototype: O, data: T, ) -> JsObject { - let internal_methods = data.internal_methods(); - let inner = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - 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 } + 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`. @@ -1063,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 @@ -1086,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::dummy() }, + 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(), }), @@ -1124,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( - &unsafe { boa_gc::MutationContext::dummy() }, - 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 } + 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 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/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 11934d51b79..5846c7916b9 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}; @@ -53,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>, ) { @@ -66,14 +69,25 @@ impl ForwardTransition { properties.map.retain(|_, v| v.is_upgradable()); } - properties.map.insert( - key, - WeakGc::new(&unsafe { boa_gc::MutationContext::dummy() }, 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); @@ -81,13 +95,16 @@ impl ForwardTransition { prototypes.map.retain(|_, v| v.is_upgradable()); } - prototypes.map.insert( - key, - WeakGc::new(&unsafe { boa_gc::MutationContext::dummy() }, 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. + #[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 +112,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 +141,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 +152,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..3448ac5455a 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}; @@ -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::dummy() }, 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::dummy() }) { + 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::dummy() }) { + 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::dummy() }) { + 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,22 +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::dummy() })?, + 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::dummy() }, &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 6947489a526..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::dummy() }, + 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, @@ -58,7 +71,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`]. @@ -251,22 +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::dummy() })?, + 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::dummy() }, &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 84bf5c39cf2..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::dummy() }, - DeclarativeEnvironment::global(), - ); + let environment = Gc::new(mc, DeclarativeEnvironment::global()); let scope = Scope::new_global(); let realm = Self { inner: Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, + mc, Inner { intrinsics, environment, diff --git a/core/engine/src/script.rs b/core/engine/src/script.rs index f11dbc61168..373129572b9 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( - &unsafe { boa_gc::MutationContext::dummy() }, - 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, + }), }) } @@ -140,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(), @@ -149,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")] @@ -162,10 +165,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(); + context.alloc(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..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::dummy() }, - 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/await/mod.rs b/core/engine/src/vm/opcode/await/mod.rs index ad2c5fcbd54..887603a9bd9 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 = 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, "", « »). @@ -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..a1eb7a76926 100644 --- a/core/engine/src/vm/opcode/push/environment.rs +++ b/core/engine/src/vm/opcode/push/environment.rs @@ -18,11 +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); + .push_lexical(scope.num_bindings_non_local(), global, mc); } } @@ -81,10 +82,7 @@ impl PushPrivateEnvironment { } let ptr: *const _ = class.as_ref(); - let environment = Gc::new( - &unsafe { boa_gc::MutationContext::dummy() }, - 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/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..10637fe4c36 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 @@ -34,7 +34,8 @@ 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] 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/context.rs b/core/gc/src/context.rs new file mode 100644 index 00000000000..b3857952074 --- /dev/null +++ b/core/gc/src/context.rs @@ -0,0 +1,78 @@ +#[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")] +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] + pub fn new() -> Self { + Self + } + + pub fn alloc(&self, value: T) -> Gc<'static, T> { + let mc = MutationContext::global(); + Gc::new(&mc, value) + } + + #[must_use] + pub fn gc_collector(&self) -> &'static MutationContext<'static, 'static> { + static DUMMY: std::sync::LazyLock = + std::sync::LazyLock::new(|| SyncWrapper(MutationContext::global())); + &DUMMY.0 + } +} + +#[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"))] +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] + 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) -> &'static crate::MutationContext<'static, 'static> { + static DUMMY: SyncWrapperDefault = + SyncWrapperDefault(unsafe { crate::MutationContext::global() }); + &DUMMY.0 + } +} diff --git a/core/gc/src/lib.rs b/core/gc/src/lib.rs index dec09a9c077..2296038dc87 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; @@ -24,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; @@ -49,9 +57,108 @@ 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::mark_sweep_branded::{Finalize, Gc, 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::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::mark_sweep_branded::MutationContext<'static, 'static>; + +#[cfg(feature = "oscars_backend")] +/// Type alias for `WeakGc` +pub type WeakGc = oscars::collectors::mark_sweep_branded::WeakGc<'static, T>; + +#[cfg(feature = "oscars_backend")] +pub use oscars::collectors::mark_sweep_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 +168,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..534078e3292 --- /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 `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. + +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..b0c3db4fde3 100644 --- a/core/gc/src/pointers/mutation_context.rs +++ b/core/gc/src/pointers/mutation_context.rs @@ -12,9 +12,15 @@ 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, } } + + /// Creates a global context (polyfill for the oscars backend). + #[must_use] + pub const unsafe fn global() -> Self { + 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..89ee9a52791 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 = ["mark_sweep_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..9b46d002345 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::mark_sweep_branded::Trace for JsString { + // SAFETY: Empty trace is safe. + #[inline] + unsafe fn trace(&self, _tracer: &mut oscars::collectors::mark_sweep_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::mark_sweep_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());