From 02ab0b1ca2284405f4e357556a6aa0f165483e6b Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 21 Nov 2025 10:22:04 +0200 Subject: [PATCH 1/6] fix: avoid call stack overflow --- nova_vm/src/ecmascript/builtins/bound_function.rs | 2 ++ .../src/ecmascript/builtins/builtin_function.rs | 2 ++ .../promise_finally_functions.rs | 1 + .../promise_resolving_functions.rs | 1 + .../src/ecmascript/builtins/ecmascript_function.rs | 1 + .../array_objects/array_prototype.rs | 6 ++++-- nova_vm/src/ecmascript/execution/agent.rs | 14 ++++++++++++++ 7 files changed, 25 insertions(+), 2 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/bound_function.rs b/nova_vm/src/ecmascript/builtins/bound_function.rs index 0c50d8156..94f815f0a 100644 --- a/nova_vm/src/ecmascript/builtins/bound_function.rs +++ b/nova_vm/src/ecmascript/builtins/bound_function.rs @@ -169,6 +169,7 @@ impl<'a> FunctionInternalProperties<'a> for BoundFunction<'a> { arguments_list: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + agent.check_call_depth(gc.nogc()).unbind()?; let f = self.bind(gc.nogc()); let arguments_list = arguments_list.bind(gc.nogc()); // 1. Let target be F.[[BoundTargetFunction]]. @@ -219,6 +220,7 @@ impl<'a> FunctionInternalProperties<'a> for BoundFunction<'a> { new_target: Function, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Object<'gc>> { + agent.check_call_depth(gc.nogc()).unbind()?; let arguments_list = arguments_list.bind(gc.nogc()); let new_target = new_target.bind(gc.nogc()); // 1. Let target be F.[[BoundTargetFunction]]. diff --git a/nova_vm/src/ecmascript/builtins/builtin_function.rs b/nova_vm/src/ecmascript/builtins/builtin_function.rs index 0001ee46f..ad92426d2 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_function.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_function.rs @@ -665,6 +665,8 @@ fn builtin_call_or_construct<'gc>( new_target: Option, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + agent.check_call_depth(gc.nogc()).unbind()?; + let f = f.bind(gc.nogc()); let this_argument = this_argument.bind(gc.nogc()); let arguments_list = arguments_list.bind(gc.nogc()); diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_finally_functions.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_finally_functions.rs index c32833aa6..aa19a7e96 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_finally_functions.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_finally_functions.rs @@ -159,6 +159,7 @@ impl<'a> FunctionInternalProperties<'a> for BuiltinPromiseFinallyFunction<'a> { arguments_list: ArgumentsList, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + agent.check_call_depth(gc.nogc()).unbind()?; let f = self.bind(gc.nogc()); match f.get(agent).resolve_type { PromiseFinallyFunctionType::ResolveFinally { on_finally, c } => { diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_resolving_functions.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_resolving_functions.rs index 56888b3d0..d376f9bff 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_resolving_functions.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_resolving_functions.rs @@ -108,6 +108,7 @@ impl<'a> FunctionInternalProperties<'a> for BuiltinPromiseResolvingFunction<'a> arguments_list: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + agent.check_call_depth(gc.nogc()).unbind()?; let arguments_list = arguments_list.get(0).bind(gc.nogc()); let promise_capability = agent[self].promise_capability.clone(); match agent[self].resolve_type { diff --git a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs index 3cacf89df..4042f52de 100644 --- a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs +++ b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs @@ -330,6 +330,7 @@ impl<'a> FunctionInternalProperties<'a> for ECMAScriptFunction<'a> { arguments_list: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + agent.check_call_depth(gc.nogc()).unbind()?; let f = self.bind(gc.nogc()); let mut id = 0; ndt::javascript_call_start!(|| { diff --git a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_prototype.rs b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_prototype.rs index 0642a4874..85c1028ad 100644 --- a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_prototype.rs @@ -1755,7 +1755,8 @@ impl ArrayPrototype { .unbind()? .bind(gc.nogc()); // c. If element is neither undefined nor null, then - if !element.is_undefined() && !element.is_null() { + if !element.is_undefined() && !element.is_null() && element != o.get(agent).into_value() + { // i. Let S be ? ToString(element). let s = to_string(agent, element.unbind(), gc.reborrow()) .unbind()? @@ -1777,7 +1778,8 @@ impl ArrayPrototype { .unbind()? .bind(gc.nogc()); // c. If element is neither undefined nor null, then - if !element.is_undefined() && !element.is_null() { + if !element.is_undefined() && !element.is_null() && element != o.get(agent).into_value() + { // i. Let S be ? ToString(element). let s = to_string(agent, element.unbind(), gc.reborrow()) .unbind()? diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index dfc306967..5f451ea57 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -1060,6 +1060,20 @@ impl Agent { self.execution_context_stack.last().unwrap() } + pub(crate) fn check_call_depth<'gc>(&mut self, gc: NoGcScope<'gc, '_>) -> JsResult<'gc, ()> { + // Experimental number that caused stack overflow on local machine. A + // better limit creation logic would be nice. + if self.execution_context_stack.len() > 3500 { + Err(self.throw_exception_with_static_message( + ExceptionType::RangeError, + "Maximum call stack size exceeded", + gc, + )) + } else { + Ok(()) + } + } + /// Returns the realm of the previous execution context. /// /// See steps 6-8 of [27.6.3.8 AsyncGeneratorYield ( value )](https://tc39.es/ecma262/#sec-asyncgeneratoryield). From 93f9853b6e076b61e2b0e21d5eb6b6e76c1f8720 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 21 Nov 2025 22:32:47 +0200 Subject: [PATCH 2/6] fix(engine): throw errors on more allocation failures --- .../operations_on_iterator_objects.rs | 6 +- .../operations_on_objects.rs | 18 ++- .../builders/ordinary_object_builder.rs | 3 +- nova_vm/src/ecmascript/builtins/arguments.rs | 14 +- .../builtins/array/abstract_operations.rs | 6 +- .../builtins/builtin_constructor.rs | 3 +- .../ecmascript/builtins/builtin_function.rs | 9 +- .../async_generator_abstract_operations.rs | 5 +- .../async_generator_prototype.rs | 5 +- .../generator_objects.rs | 32 +++-- .../async_from_sync_iterator_objects.rs | 9 +- .../promise_group_record.rs | 1 + .../promise_jobs.rs | 3 +- .../promise_objects/promise_constructor.rs | 3 +- .../builtins/ecmascript_function.rs | 9 +- nova_vm/src/ecmascript/builtins/error.rs | 3 +- .../function_objects/function_prototype.rs | 3 +- .../object_objects/object_constructor.rs | 41 ++++-- .../array_objects/array_constructor.rs | 5 +- .../array_iterator_prototype.rs | 9 +- .../map_iterator_prototype.rs | 9 +- .../set_iterator_prototype.rs | 9 +- nova_vm/src/ecmascript/builtins/ordinary.rs | 15 +- .../src/ecmascript/builtins/ordinary/shape.rs | 84 ++++++----- nova_vm/src/ecmascript/builtins/regexp.rs | 2 +- .../structured_data/atomics_object.rs | 1 + .../regexp_string_iterator_prototype.rs | 19 ++- .../string_objects/string_iterator_objects.rs | 6 +- nova_vm/src/ecmascript/execution/agent.rs | 2 +- nova_vm/src/ecmascript/execution/realm.rs | 13 +- .../types/language/function/into_function.rs | 3 +- .../src/ecmascript/types/language/object.rs | 20 +-- .../types/language/object/internal_slots.rs | 3 +- .../types/language/object/property_storage.rs | 18 ++- .../types/spec/property_descriptor.rs | 3 +- .../src/engine/bytecode/bytecode_compiler.rs | 8 +- .../bytecode_compiler/template_literals.rs | 3 +- nova_vm/src/engine/bytecode/iterator.rs | 28 ++-- nova_vm/src/engine/bytecode/vm.rs | 2 +- .../bytecode/vm/execute_instructions.rs | 21 +-- nova_vm/src/heap/element_array.rs | 131 +++++++++++------- nova_vm/src/heap/heap_gc.rs | 4 +- 42 files changed, 382 insertions(+), 209 deletions(-) diff --git a/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs b/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs index 445074ad2..5cc7c8756 100644 --- a/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs +++ b/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs @@ -25,7 +25,7 @@ use crate::{ }, engine::{ ScopableCollection, ScopedCollection, VmIteratorRecord, - context::{Bindable, GcScope, bindable_handle}, + context::{Bindable, GcScope, NoGcScope, bindable_handle}, rootable::Scopable, }, heap::{ @@ -609,7 +609,8 @@ pub(crate) fn create_iter_result_object<'a>( agent: &mut Agent, value: Value<'a>, done: bool, -) -> OrdinaryObject<'a> { + gc: NoGcScope<'a, '_>, +) -> JsResult<'a, OrdinaryObject<'a>> { // 1. Let obj be OrdinaryObjectCreate(%Object.prototype%). // 2. Perform ! CreateDataPropertyOrThrow(obj, "value", value). // 3. Perform ! CreateDataPropertyOrThrow(obj, "done", done). @@ -644,6 +645,7 @@ pub(crate) fn create_iter_result_object<'a>( }, ], ) + .map_err(|err| agent.throw_allocation_exception(err, gc)) } /// ### [7.4.16 IteratorToList ( iteratorRecord )](https://tc39.es/ecma262/#sec-iteratortolist) diff --git a/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs b/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs index 32394100a..89a8226b7 100644 --- a/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs +++ b/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs @@ -2248,7 +2248,7 @@ pub(crate) fn try_copy_data_properties_into_object<'gc, 'b>( } } - TryResult::Continue(OrdinaryObject::create_object( + let obj = OrdinaryObject::create_object( agent, Some( agent @@ -2258,7 +2258,13 @@ pub(crate) fn try_copy_data_properties_into_object<'gc, 'b>( .into_object(), ), &entries, - )) + ); + if let Ok(obj) = obj { + TryResult::Continue(obj) + } else { + // We failed to allocate: try run GC. + TryError::GcError.into() + } } /// ### [7.3.25 CopyDataProperties ( target, source, excludedItems )](https://tc39.es/ecma262/#sec-copydataproperties) @@ -2327,7 +2333,7 @@ pub(crate) fn copy_data_properties_into_object<'a, 'b>( i += 1; } - let object = OrdinaryObject::create_object( + let object = match OrdinaryObject::create_object( agent, Some( agent @@ -2337,8 +2343,10 @@ pub(crate) fn copy_data_properties_into_object<'a, 'b>( .into_object(), ), &entries, - ) - .bind(gc.nogc()); + ) { + Ok(obj) => obj, + Err(err) => return Err(agent.throw_allocation_exception(err, gc.into_nogc())), + }; if broke { let _ = keys.drain(..i); diff --git a/nova_vm/src/ecmascript/builders/ordinary_object_builder.rs b/nova_vm/src/ecmascript/builders/ordinary_object_builder.rs index b3c8ade13..84d698c0b 100644 --- a/nova_vm/src/ecmascript/builders/ordinary_object_builder.rs +++ b/nova_vm/src/ecmascript/builders/ordinary_object_builder.rs @@ -361,7 +361,8 @@ pub(super) fn create_intrinsic_backing_object( let (cap, index) = agent .heap .elements - .allocate_keys_with_capacity(properties_count); + .allocate_keys_with_capacity(properties_count) + .expect("Intrinsic object's property allocation failed"); let cap = cap.make_intrinsic(); let keys_memory = agent.heap.elements.get_keys_uninit_raw(cap, index); for (slot, key) in keys_memory.iter_mut().zip(properties.iter().map(|e| e.0)) { diff --git a/nova_vm/src/ecmascript/builtins/arguments.rs b/nova_vm/src/ecmascript/builtins/arguments.rs index 4d26d069d..4da307d37 100644 --- a/nova_vm/src/ecmascript/builtins/arguments.rs +++ b/nova_vm/src/ecmascript/builtins/arguments.rs @@ -26,6 +26,8 @@ //! //! ECMAScript implementations of arguments exotic objects have historically contained an accessor property named "caller". Prior to ECMAScript 2017, this specification included the definition of a throwing "caller" property on ordinary arguments objects. Since implementations do not contain this extension any longer, ECMAScript 2017 dropped the requirement for a throwing "caller" accessor. +use std::collections::TryReserveError; + use ahash::AHashMap; use crate::{ @@ -124,7 +126,7 @@ pub(crate) fn create_unmapped_arguments_object<'a, 'b>( agent: &mut Agent, arguments_list: &ScopedArgumentsList<'b>, gc: NoGcScope<'a, 'b>, -) -> Object<'a> { +) -> Result, TryReserveError> { // 1. Let len be the number of elements in argumentsList. let len = arguments_list.len(agent); // SAFETY: GC is not allowed in this scope, and no other scoped values are @@ -136,11 +138,11 @@ pub(crate) fn create_unmapped_arguments_object<'a, 'b>( // 2. Let obj be OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »). let prototype = agent.current_realm_record().intrinsics().object_prototype(); let mut shape = ObjectShape::get_shape_for_prototype(agent, Some(prototype.into_object())); - shape = shape.get_child_shape(agent, BUILTIN_STRING_MEMORY.length.to_property_key()); - shape = shape.get_child_shape(agent, BUILTIN_STRING_MEMORY.callee.into()); - shape = shape.get_child_shape(agent, WellKnownSymbolIndexes::Iterator.into()); + shape = shape.get_child_shape(agent, BUILTIN_STRING_MEMORY.length.to_property_key())?; + shape = shape.get_child_shape(agent, BUILTIN_STRING_MEMORY.callee.into())?; + shape = shape.get_child_shape(agent, WellKnownSymbolIndexes::Iterator.into())?; for index in 0..len { - shape = shape.get_child_shape(agent, index.into()); + shape = shape.get_child_shape(agent, index.into())?; } let obj = OrdinaryObject::create_object_with_shape(agent, shape) .expect("Failed to create Arguments object storage"); @@ -213,7 +215,7 @@ pub(crate) fn create_unmapped_arguments_object<'a, 'b>( // [[Configurable]]: false // }). // 9. Return obj. - Object::Arguments(obj) + Ok(Object::Arguments(obj)) } // 10.4.4.7 CreateMappedArgumentsObject ( func, formals, argumentsList, env ) diff --git a/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs b/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs index 60ff8c78d..1072bc513 100644 --- a/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs @@ -63,7 +63,11 @@ pub(crate) fn array_create<'a>( { None } else { - Some(OrdinaryObject::create_object(agent, Some(proto), &[]).bind(gc)) + Some( + OrdinaryObject::create_object(agent, Some(proto), &[]) + .expect("Should perform GC here") + .bind(gc), + ) } } else { None diff --git a/nova_vm/src/ecmascript/builtins/builtin_constructor.rs b/nova_vm/src/ecmascript/builtins/builtin_constructor.rs index 36f2cc5cd..47e54c3f0 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_constructor.rs @@ -351,7 +351,8 @@ pub(crate) fn create_builtin_constructor<'a>( }, }; let entries = [length_entry, name_entry, prototype_entry]; - let backing_object = OrdinaryObject::create_intrinsic_object(agent, args.prototype, &entries); + let backing_object = OrdinaryObject::create_intrinsic_object(agent, args.prototype, &entries) + .expect("Should perform GC here"); // 13. Return func. agent diff --git a/nova_vm/src/ecmascript/builtins/builtin_function.rs b/nova_vm/src/ecmascript/builtins/builtin_function.rs index ad92426d2..83b1f55e0 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_function.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_function.rs @@ -814,11 +814,10 @@ pub fn create_builtin_function<'a>( configurable: true, }, }; - Some(OrdinaryObject::create_object( - agent, - Some(prototype), - &[length_entry, name_entry], - )) + Some( + OrdinaryObject::create_object(agent, Some(prototype), &[length_entry, name_entry]) + .expect("Should perform GC here"), + ) } } else { None diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs index bd0761295..17637708a 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs @@ -127,7 +127,8 @@ fn async_generator_complete_step( agent.set_current_realm(realm); } // iii. Let iteratorResult be CreateIteratorResultObject(value, done). - let iterator_result = create_iter_result_object(agent, value, done); + let iterator_result = + create_iter_result_object(agent, value, done, gc).expect("Should perform GC here"); // iv. Set the running execution context's Realm to oldRealm. if set_realm { agent.set_current_realm(old_realm); @@ -136,7 +137,7 @@ fn async_generator_complete_step( } else { // c. Else, // i. Let iteratorResult be CreateIteratorResultObject(value, done). - create_iter_result_object(agent, value, done) + create_iter_result_object(agent, value, done, gc).expect("Should perform GC here") }; // d. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »). unwrap_try(promise_capability.try_resolve(agent, iterator_result.into_value(), gc)); diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_prototype.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_prototype.rs index cbfe7dd85..e35d47fbd 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_prototype.rs @@ -86,7 +86,10 @@ impl AsyncGeneratorPrototype { // 6. If state is completed, then if state.is_completed() { // a. Let iteratorResult be CreateIteratorResultObject(undefined, true). - let iterator_result = create_iter_result_object(agent, Value::Undefined, true); + let iterator_result = + create_iter_result_object(agent, Value::Undefined, true, gc.nogc()) + .unbind()? + .bind(gc.nogc()); // b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »). promise_capability .unbind() diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/generator_objects.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/generator_objects.rs index b61fab8af..668de187d 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/generator_objects.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/generator_objects.rs @@ -57,7 +57,8 @@ impl Generator<'_> { } GeneratorState::Completed => { // 2. If state is completed, return CreateIterResultObject(undefined, true). - return Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()); + return create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()); } GeneratorState::SuspendedStart(_) | GeneratorState::SuspendedYield(_) => { // 3. Assert: state is either suspended-start or suspended-yield. @@ -121,7 +122,8 @@ impl Generator<'_> { // j. Else if result is a return completion, then // i. Let resultValue be result.[[Value]]. // l. Return CreateIterResultObject(resultValue, true). - Ok(create_iter_result_object(agent, result_value, true).into_value()) + create_iter_result_object(agent, result_value, true, gc.into_nogc()) + .map(|o| o.into_value()) } ExecutionResult::Throw(err) => { // GeneratorStart step 4: @@ -243,7 +245,8 @@ impl Generator<'_> { match execution_result { ExecutionResult::Return(result) => { agent[generator].generator_state = Some(GeneratorState::Completed); - Ok(create_iter_result_object(agent, result.unbind(), true).into_value()) + create_iter_result_object(agent, result.unbind(), true, gc.into_nogc()) + .map(|o| o.into_value()) } ExecutionResult::Throw(err) => { agent[generator].generator_state = Some(GeneratorState::Completed); @@ -285,9 +288,13 @@ impl Generator<'_> { // 3. If abruptCompletion is a return completion, then // i. Return CreateIteratorResultObject(abruptCompletion.[[Value]], true). - return Ok( - create_iter_result_object(agent, abrupt_completion.unbind(), true).into_value(), - ); + return create_iter_result_object( + agent, + abrupt_completion.unbind(), + true, + gc.into_nogc(), + ) + .map(|o| o.into_value()); } GeneratorState::SuspendedYield(_) => { // 4. Assert: state is suspended-yield. @@ -302,9 +309,13 @@ impl Generator<'_> { GeneratorState::Completed => { // 3. If abruptCompletion is a return completion, then // i. Return CreateIteratorResultObject(abruptCompletion.[[Value]], true). - return Ok( - create_iter_result_object(agent, abrupt_completion.unbind(), true).into_value(), - ); + return create_iter_result_object( + agent, + abrupt_completion.unbind(), + true, + gc.into_nogc(), + ) + .map(|o| o.into_value()); } }; @@ -362,7 +373,8 @@ impl Generator<'_> { match execution_result { ExecutionResult::Return(result) => { agent[generator].generator_state = Some(GeneratorState::Completed); - Ok(create_iter_result_object(agent, result, true).into_value()) + create_iter_result_object(agent, result, true, gc.into_nogc()) + .map(|o| o.into_value()) } ExecutionResult::Throw(err) => { agent[generator].generator_state = Some(GeneratorState::Completed); diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/iteration/async_from_sync_iterator_objects.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/iteration/async_from_sync_iterator_objects.rs index 64aa76dcb..7fad79562 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/iteration/async_from_sync_iterator_objects.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/iteration/async_from_sync_iterator_objects.rs @@ -157,8 +157,13 @@ impl AsyncFromSyncIteratorPrototype { // 8. If return is undefined, then let Some(r#return) = r#return else { // a. Let iteratorResult be CreateIteratorResultObject(value, true). - let iterator_result = - create_iter_result_object(agent, value.unwrap_or(Value::Undefined), true); + let iterator_result = create_iter_result_object( + agent, + value.unwrap_or(Value::Undefined), + true, + gc.nogc(), + ) + .expect("Should perform GC here"); // b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »). unwrap_try(promise_capability.try_resolve( agent, diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs index 801f27503..61fef1fca 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs @@ -209,6 +209,7 @@ impl<'a> PromiseGroup<'a> { ), &entries, ) + .expect("Should perform GC here") .bind(gc); obj.into_value().unbind() diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_jobs.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_jobs.rs index d8aa5dcd9..bea64c201 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_jobs.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_jobs.rs @@ -208,7 +208,8 @@ impl PromiseReactionJob { // called: // a. Return CreateIteratorResultObject(v, done). ( - Ok(create_iter_result_object(agent, argument, done).into_value()), + create_iter_result_object(agent, argument, done, gc.nogc()) + .map(|o| o.into_value()), capability, ) } diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index c98496415..969ebb311 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -713,7 +713,8 @@ impl PromiseConstructor { reject_function.into_value(), ), ], - ); + ) + .expect("Should perform GC here"); // 7. Return obj. Ok(obj.into_value()) diff --git a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs index 4042f52de..0c1986925 100644 --- a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs +++ b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs @@ -836,11 +836,10 @@ pub(crate) fn ordinary_function_create<'agent, 'program, 'gc>( .function_prototype() .into_object() { - function.object_index = Some(OrdinaryObject::create_object( - agent, - Some(function_prototype), - &[], - )); + function.object_index = Some( + OrdinaryObject::create_object(agent, Some(function_prototype), &[]) + .expect("Should perform GC here"), + ); } // 18. Set F.[[Fields]] to a new empty List. diff --git a/nova_vm/src/ecmascript/builtins/error.rs b/nova_vm/src/ecmascript/builtins/error.rs index 2952451b1..b3e251a8b 100644 --- a/nova_vm/src/ecmascript/builtins/error.rs +++ b/nova_vm/src/ecmascript/builtins/error.rs @@ -131,7 +131,8 @@ impl<'a> InternalSlots<'a> for Error<'a> { OrdinaryObject::create_object(agent, Some(prototype), &[cause_entry]) } else { OrdinaryObject::create_object(agent, Some(prototype), &[]) - }; + } + .expect("Should perform GC here"); self.set_backing_object(agent, backing_object); backing_object } diff --git a/nova_vm/src/ecmascript/builtins/fundamental_objects/function_objects/function_prototype.rs b/nova_vm/src/ecmascript/builtins/fundamental_objects/function_objects/function_prototype.rs index dccc362bc..f3300aa9e 100644 --- a/nova_vm/src/ecmascript/builtins/fundamental_objects/function_objects/function_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/fundamental_objects/function_objects/function_prototype.rs @@ -560,7 +560,8 @@ fn create_throw_type_error_backing_object( configurable: false, }, }; - let object = OrdinaryObject::create_object(agent, Some(prototype), &[length_entry, name_entry]); + let object = OrdinaryObject::create_object(agent, Some(prototype), &[length_entry, name_entry]) + .expect("Should perform GC here"); // The value of the [[Extensible]] internal slot of this function is false. object.internal_set_extensible(agent, false); object diff --git a/nova_vm/src/ecmascript/builtins/fundamental_objects/object_objects/object_constructor.rs b/nova_vm/src/ecmascript/builtins/fundamental_objects/object_objects/object_constructor.rs index 815fbf42c..aa4b27d0a 100644 --- a/nova_vm/src/ecmascript/builtins/fundamental_objects/object_objects/object_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/fundamental_objects/object_objects/object_constructor.rs @@ -412,7 +412,7 @@ impl ObjectConstructor { ) -> JsResult<'gc, Value<'gc>> { let nogc = gc.nogc(); let o = arguments.get(0).bind(nogc); - let properties = arguments.get(1).bind(nogc); + let mut properties = arguments.get(1).bind(nogc); let proto = if o == Value::Null { None } else if let Ok(o) = Object::try_from(o) { @@ -430,7 +430,18 @@ impl ObjectConstructor { gc.into_nogc(), )); }; - let obj = OrdinaryObject::create_object(agent, proto, &[]).bind(nogc); + let obj = if let Ok(obj) = OrdinaryObject::create_object(agent, proto, &[]) { + obj + } else { + let scoped_properties = properties.scope(agent, gc.nogc()); + let proto = proto.map(|p| p.scope(agent, gc.nogc())); + agent.gc(gc.reborrow()); + // SAFETY: not shared. + let proto = proto.map(|p| unsafe { p.take(agent).bind(gc.nogc()) }); + // SAFETY: not shared. + properties = unsafe { scoped_properties.take(agent) }.bind(gc.nogc()); + OrdinaryObject::create_object(agent, proto, &[]).expect("Failed to allocate after GC") + }; if properties != Value::Undefined { Ok(object_define_properties( agent, @@ -695,7 +706,7 @@ impl ObjectConstructor { } } if valid { - let object = OrdinaryObject::create_object( + let object = match OrdinaryObject::create_object( agent, Some( agent @@ -705,7 +716,12 @@ impl ObjectConstructor { .into_object(), ), &object_entries, - ); + ) { + Ok(o) => o, + Err(err) => { + return Err(agent.throw_allocation_exception(err, gc.into_nogc())); + } + }; return Ok(object.into_value().unbind()); } } @@ -810,7 +826,7 @@ impl ObjectConstructor { i += 1; } // 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%). - let descriptors = OrdinaryObject::create_object( + let descriptors = match OrdinaryObject::create_object( agent, Some( agent @@ -820,8 +836,12 @@ impl ObjectConstructor { .into_object(), ), &descriptors, - ) - .bind(gc.nogc()); + ) { + Ok(o) => o, + Err(err) => { + return Err(agent.throw_allocation_exception(err, gc.into_nogc())); + } + }; if i < own_keys.len() { let _ = own_keys.drain(..i); let obj = scoped_obj.unwrap_or_else(|| obj.scope(agent, gc.nogc())); @@ -919,7 +939,12 @@ impl ObjectConstructor { ) }) .collect::>(); - let object = OrdinaryObject::create_object(agent, None, &entries).bind(gc); + let object = match OrdinaryObject::create_object(agent, None, &entries) { + Ok(o) => o, + Err(err) => { + return Err(agent.throw_allocation_exception(err, gc.into_nogc())); + } + }; // 4. Return obj. Ok(object.into_value()) diff --git a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_constructor.rs b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_constructor.rs index e7f8d447c..8cea69692 100644 --- a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_constructor.rs @@ -146,14 +146,13 @@ impl ArrayConstructor { int_len as usize, proto.map(|p| p.get(agent)), gc, - ) - .unwrap(); + )?; // e. Perform ! Set(array, "length", intLen, true). debug_assert_eq!(array.len(agent), int_len); array } else { // b. Let array be ! ArrayCreate(0, proto). - let array = array_create(agent, 1, 1, proto, gc).unwrap(); + let array = array_create(agent, 1, 1, proto, gc)?; // i. Perform ! CreateDataPropertyOrThrow(array, "0", len). unwrap_try(try_create_data_property_or_throw( agent, diff --git a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_iterator_objects/array_iterator_prototype.rs b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_iterator_objects/array_iterator_prototype.rs index b06779715..6e1154b35 100644 --- a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_iterator_objects/array_iterator_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_iterator_objects/array_iterator_prototype.rs @@ -64,7 +64,8 @@ impl ArrayIteratorPrototype { // 23.1.5.1 CreateArrayIterator ( array, kind ), step 1. b // NOTE: We set `array` to None when the generator in the spec text has returned. let Some(array) = agent[iterator].array else { - return Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()); + return create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()); }; let mut array = array.bind(gc.nogc()); @@ -114,7 +115,8 @@ impl ArrayIteratorPrototype { // iii. If index ≥ len, return NormalCompletion(undefined). if agent[iterator].next_index >= len { agent[iterator].array = None; - return Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()); + return create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()); } // iv. Let indexNumber be 𝔽(index). @@ -188,7 +190,8 @@ impl ArrayIteratorPrototype { }; // vii. Perform ? GeneratorYield(CreateIteratorResultObject(result, false)). - Ok(create_iter_result_object(agent, result.unbind(), false).into_value()) + create_iter_result_object(agent, result.unbind(), false, gc.into_nogc()) + .map(|o| o.into_value()) } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>) { diff --git a/nova_vm/src/ecmascript/builtins/keyed_collections/map_objects/map_iterator_objects/map_iterator_prototype.rs b/nova_vm/src/ecmascript/builtins/keyed_collections/map_objects/map_iterator_objects/map_iterator_prototype.rs index 1d52df8ea..8b0c5887a 100644 --- a/nova_vm/src/ecmascript/builtins/keyed_collections/map_objects/map_iterator_objects/map_iterator_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/keyed_collections/map_objects/map_iterator_objects/map_iterator_prototype.rs @@ -53,7 +53,8 @@ impl MapIteratorPrototype { // 24.1.5.1 CreateMapIterator ( map, kind ), step 2 // NOTE: We set `map` to None when the generator in the spec text has returned. let Some(map) = agent[iterator].map else { - return Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()); + return create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()); }; // a. Let entries be map.[[MapData]]. @@ -98,14 +99,16 @@ impl MapIteratorPrototype { }; // 4. Perform ? GeneratorYield(CreateIteratorResultObject(result, false)). - return Ok(create_iter_result_object(agent, result, false).into_value()); + return create_iter_result_object(agent, result, false, gc.into_nogc()) + .map(|o| o.into_value()); } debug_assert_eq!(agent[iterator].next_index, agent[map].keys(gc).len()); // e. Return undefined. agent[iterator].map = None; - Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()) + create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()) } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>) { diff --git a/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator_prototype.rs b/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator_prototype.rs index f2d18ab91..04cbd2ddf 100644 --- a/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator_prototype.rs @@ -53,7 +53,8 @@ impl SetIteratorPrototype { // 24.2.6.1 CreateSetIterator ( set, kind ) // NOTE: We set `set` to None when the generator in the spec text has returned. let Some(set) = agent[iterator].set else { - return Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()); + return create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()); }; // b. Let entries be set.[[SetData]]. @@ -86,14 +87,16 @@ impl SetIteratorPrototype { } }; - return Ok(create_iter_result_object(agent, result.unbind(), false).into_value()); + return create_iter_result_object(agent, result.unbind(), false, gc.into_nogc()) + .map(|o| o.into_value()); } debug_assert_eq!(agent[iterator].next_index, agent[set].values(gc).len()); // e. Return undefined. agent[iterator].set = None; - Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()) + create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()) } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>) { diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index b60079a45..fa3a46156 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -187,7 +187,8 @@ pub(crate) fn ordinary_set_prototype_of( { prototype .get_or_create_backing_object(agent) - .make_intrinsic(agent); + .make_intrinsic(agent) + .expect("Should perform GC here"); } // 8. Set O.[[Prototype]] to V. @@ -1501,7 +1502,8 @@ pub(crate) fn ordinary_delete( // a. Remove the own property with name P from O. backing_object .property_storage() - .remove(agent, o, property_key); + .remove(agent, o, property_key) + .expect("Should perform GC here"); // b. Return true. return true; @@ -1600,7 +1602,9 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( ) -> Object<'a> { let Some(proto_intrinsics) = proto_intrinsics else { assert!(prototype.is_none()); - return OrdinaryObject::create_object(agent, None, &[]).into(); + return OrdinaryObject::create_object(agent, None, &[]) + .expect("Should perform GC here") + .into(); }; let object = match proto_intrinsics { @@ -1648,6 +1652,7 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( ), &[], ) + .expect("Should perform GC here") .into(), ProtoIntrinsics::RangeError => agent .heap @@ -1762,6 +1767,7 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( ), &[], ) + .expect("Should perform GC here") .into_object(), ProtoIntrinsics::Map => agent.heap.create(MapHeapData::default()).into_object(), ProtoIntrinsics::MapIterator => agent @@ -1813,7 +1819,8 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( if !prototype.is_proxy() && !prototype.is_module() { prototype .get_or_create_backing_object(agent) - .make_intrinsic(agent); + .make_intrinsic(agent) + .expect("Should perform GC here"); } object.internal_set_prototype(agent, Some(prototype)); } diff --git a/nova_vm/src/ecmascript/builtins/ordinary/shape.rs b/nova_vm/src/ecmascript/builtins/ordinary/shape.rs index eefa0f0d7..32858f1f7 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary/shape.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary/shape.rs @@ -2,7 +2,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use std::{cmp::Ordering, marker::PhantomData, num::NonZeroU32, ptr::NonNull}; +use std::{ + cmp::Ordering, collections::TryReserveError, marker::PhantomData, num::NonZeroU32, ptr::NonNull, +}; use ahash::AHashMap; use hashbrown::{HashTable, hash_table::Entry}; @@ -299,7 +301,11 @@ impl<'a> ObjectShape<'a> { /// ## Safety /// /// This is only safe to use on intrinsic Object Shapes. - unsafe fn push_key(self, agent: &mut Agent, key: PropertyKey<'a>) { + unsafe fn push_key( + self, + agent: &mut Agent, + key: PropertyKey<'a>, + ) -> Result<(), TryReserveError> { debug_assert_eq!(self.values_capacity(agent), self.len(agent).into()); let ObjectShapeRecord { prototype: _, @@ -308,8 +314,9 @@ impl<'a> ObjectShape<'a> { values_cap, len, } = self.get_direct_mut(&mut agent.heap.object_shapes); - unsafe { agent.heap.elements.push_key(keys_cap, keys, len, key) }; + unsafe { agent.heap.elements.push_key(keys_cap, keys, len, key) }?; *values_cap = (*len).into(); + Ok(()) } /// Get or create an Object Shape with the given key added to this shape. @@ -320,18 +327,23 @@ impl<'a> ObjectShape<'a> { /// /// > NOTE: This function will create a new Object Shape if an existing one /// > cannot be found. - pub(crate) fn get_child_shape(self, agent: &mut Agent, key: PropertyKey<'a>) -> Self { + #[must_use] + pub(crate) fn get_child_shape( + self, + agent: &mut Agent, + key: PropertyKey<'a>, + ) -> Result { if self.is_intrinsic(agent) { // SAFETY: self is intrinsic. - unsafe { self.push_key(agent, key) }; - return self; + unsafe { self.push_key(agent, key)? }; + return Ok(self); } let frozen = !self.extensible(); if let Some(mut next_shape) = self.get_transition_to(agent, key) { if frozen { next_shape.set_extensible(false); } - return next_shape; + return Ok(next_shape); } let prototype = self.get_prototype(agent); let len = self.len(agent) as usize; @@ -354,7 +366,7 @@ impl<'a> ObjectShape<'a> { let (new_keys_cap, new_keys_index) = agent .heap .elements - .copy_keys_with_addition(cap, keys_index, len as u32, key); + .copy_keys_with_addition(cap, keys_index, len as u32, key)?; ObjectShapeRecord::create(prototype, new_keys_index, new_keys_cap, new_len) }; let mut child = agent @@ -364,7 +376,7 @@ impl<'a> ObjectShape<'a> { if frozen { child.set_extensible(false); } - child + Ok(child) } /// Get an ancestor Object Shape with the given number of keys. @@ -457,7 +469,11 @@ impl<'a> ObjectShape<'a> { /// /// > NOTE: This function will create a new Object Shape, or possibly /// > multiple ones, if an existing one cannot be found. - pub(crate) fn get_shape_with_removal(self, agent: &mut Agent, index: u32) -> Self { + pub(crate) fn get_shape_with_removal( + self, + agent: &mut Agent, + index: u32, + ) -> Result { let len = self.len(agent); debug_assert!(index < len); let keys_cap = self.keys_capacity(agent); @@ -476,12 +492,12 @@ impl<'a> ObjectShape<'a> { // border here. data.values_cap = data.len.into(); debug_assert_eq!(self.values_capacity(agent), self.len(agent).into()); - return self; + return Ok(self); } let prototype = self.get_prototype(agent); if len == 1 { // Removing the last property; just get the prototype shape. - return Self::get_shape_for_prototype(agent, prototype); + return Ok(Self::get_shape_for_prototype(agent, prototype)); } let ancestor_shape = self.get_ancestor_shape(agent, index); if let Some(mut parent_shape) = ancestor_shape { @@ -501,17 +517,17 @@ impl<'a> ObjectShape<'a> { keys_index, len, index as usize, - ); + )?; // Create remaining shapes using the final key storage. - return parent_shape.create_shapes_for_property_storage( + return Ok(parent_shape.create_shapes_for_property_storage( agent, prototype, new_cap, new_keys_index, len.wrapping_sub(1), - ); + )); } - parent_shape + Ok(parent_shape) } else { unreachable!() } @@ -527,7 +543,7 @@ impl<'a> ObjectShape<'a> { agent: &mut Agent, private_fields: &[PrivateField<'a>], insertion_index: usize, - ) -> (Self, usize) { + ) -> Result<(Self, usize), TryReserveError> { let ObjectShapeRecord { prototype: _, keys, @@ -539,7 +555,7 @@ impl<'a> ObjectShape<'a> { agent .heap .elements - .reserve_keys_raw(keys, keys_cap, *len, private_fields_count); + .reserve_keys_raw(keys, keys_cap, *len, private_fields_count)?; let keys = agent.heap.elements.get_keys_uninit_raw(*keys_cap, *keys); keys.copy_within( insertion_index..*len as usize, @@ -553,7 +569,7 @@ impl<'a> ObjectShape<'a> { } *len += private_fields_count; *values_cap = (*len).into(); - (self, insertion_index) + Ok((self, insertion_index)) } /// Get an Object Shape with the given private field keys added. Returns @@ -575,7 +591,7 @@ impl<'a> ObjectShape<'a> { self, agent: &mut Agent, private_fields: NonNull<[PrivateField<'a>]>, - ) -> (Self, usize) { + ) -> Result<(Self, usize), TryReserveError> { // SAFETY: User guarantees that the fields are not backed by memory // that we're going to be mutating. let private_fields = unsafe { private_fields.as_ref() }; @@ -611,9 +627,9 @@ impl<'a> ObjectShape<'a> { // We're inserting the fields at the end. for field in private_fields { // SAFETY: self is intrinsic. - unsafe { self.push_key(agent, field.get_key().into()) }; + unsafe { self.push_key(agent, field.get_key().into())? }; } - return (self, insertion_index); + return Ok((self, insertion_index)); } // SAFETY: self is intrinsic. return unsafe { @@ -626,9 +642,9 @@ impl<'a> ObjectShape<'a> { // each. let mut shape = self; for field in private_fields { - shape = shape.get_child_shape(agent, field.get_key().into()); + shape = shape.get_child_shape(agent, field.get_key().into())?; } - return (shape, insertion_index); + return Ok((shape, insertion_index)); } // We're inserting fields into the start or middle of a shape. We need // to first find our common ancestor shape. @@ -639,7 +655,7 @@ impl<'a> ObjectShape<'a> { if let Some(mut parent_shape) = ancestor_shape { for field in private_fields { let key = field.get_key(); - parent_shape = parent_shape.get_child_shape(agent, key.into()); + parent_shape = parent_shape.get_child_shape(agent, key.into())?; } for i in insertion_index..original_len as usize { // Add old keys to parent shape. @@ -647,9 +663,9 @@ impl<'a> ObjectShape<'a> { .heap .elements .get_keys_raw(cap, keys_index, original_len)[i]; - parent_shape = parent_shape.get_child_shape(agent, key); + parent_shape = parent_shape.get_child_shape(agent, key)?; } - (parent_shape, insertion_index) + Ok((parent_shape, insertion_index)) } else { // Couldn't find a matching ancestor shape. This means that our // source shape comes from eg. an intrinsic which doesn't have a @@ -667,7 +683,7 @@ impl<'a> ObjectShape<'a> { let mut parent_shape = Self::get_shape_for_prototype(agent, prototype); for field in private_fields { let key = field.get_key(); - parent_shape = parent_shape.get_child_shape(agent, key.into()); + parent_shape = parent_shape.get_child_shape(agent, key.into())?; } for i in 0..original_len as usize { // Add old keys to parent shape. @@ -675,9 +691,9 @@ impl<'a> ObjectShape<'a> { .heap .elements .get_keys_raw(cap, keys_index, original_len)[i]; - parent_shape = parent_shape.get_child_shape(agent, key); + parent_shape = parent_shape.get_child_shape(agent, key)?; } - (parent_shape, 0) + Ok((parent_shape, 0)) } } @@ -746,7 +762,7 @@ impl<'a> ObjectShape<'a> { } /// Create an intrinsic copy of the given Object Shape. - pub(crate) fn make_intrinsic(self, agent: &mut Agent) -> Self { + pub(crate) fn make_intrinsic(self, agent: &mut Agent) -> Result { let properties_count = self.len(agent); let prototype = self.get_prototype(agent); // Note: intrinsics must always own their keys uniquely, so a copy must @@ -758,14 +774,14 @@ impl<'a> ObjectShape<'a> { cap, keys, properties_count, - ); + )?; let cap = cap.make_intrinsic(); - agent.heap.create(ObjectShapeRecord::create( + Ok(agent.heap.create(ObjectShapeRecord::create( prototype, index, cap, properties_count as usize, - )) + ))) } /// Create basic shapes for a new Realm's intrinsics. diff --git a/nova_vm/src/ecmascript/builtins/regexp.rs b/nova_vm/src/ecmascript/builtins/regexp.rs index 8e7ffcb87..f492b24ca 100644 --- a/nova_vm/src/ecmascript/builtins/regexp.rs +++ b/nova_vm/src/ecmascript/builtins/regexp.rs @@ -180,7 +180,7 @@ impl<'a> InternalSlots<'a> for RegExp<'a> { configurable: false, }, }], - ); + ).expect("Should perform GC here"); self.set_backing_object(agent, backing_object); backing_object } diff --git a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs index 2f6dd3aca..f3ad03ce5 100644 --- a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs +++ b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs @@ -1647,6 +1647,7 @@ fn create_wait_result_object<'gc>( ObjectEntry::new_data_entry(BUILTIN_STRING_MEMORY.value.into(), value), ], ) + .expect("Should perform GC here") } #[derive(Debug)] diff --git a/nova_vm/src/ecmascript/builtins/text_processing/regexp_objects/regexp_string_iterator_prototype.rs b/nova_vm/src/ecmascript/builtins/text_processing/regexp_objects/regexp_string_iterator_prototype.rs index fd310b6c1..bdf6cd796 100644 --- a/nova_vm/src/ecmascript/builtins/text_processing/regexp_objects/regexp_string_iterator_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/text_processing/regexp_objects/regexp_string_iterator_prototype.rs @@ -79,7 +79,8 @@ impl RegExpStringIteratorPrototype { // 4. If O.[[Done]] is true, then if o.done(agent) { // a. Return CreateIteratorResultObject(undefined, true). - return Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()); + return create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()); } // 5. Let R be O.[[IteratingRegExp]]. let r = o.iterating_regexp(agent); @@ -99,16 +100,21 @@ impl RegExpStringIteratorPrototype { // a. Set O.[[Done]] to true. scoped_o.get(agent).set_done(agent); // b. Return CreateIteratorResultObject(undefined, true). - return Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()); + return create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()); }; // 11. If global is false, then if !global { // a. Set O.[[Done]] to true. scoped_o.get(agent).set_done(agent); // b. Return CreateIteratorResultObject(match, false). - return Ok( - create_iter_result_object(agent, r#match.into_value().unbind(), false).into_value(), - ); + return create_iter_result_object( + agent, + r#match.into_value().unbind(), + false, + gc.into_nogc(), + ) + .map(|o| o.into_value()); } // 12. Let matchStr be ? ToString(? Get(match, "0")). let match_str = if let Some(s) = try_result_into_js(try_get_result_into_value(try_get( @@ -171,7 +177,8 @@ impl RegExpStringIteratorPrototype { r#match = unsafe { scoped_match.take(agent) }.bind(gc.nogc()); } // 14. Return CreateIteratorResultObject(match, false). - Ok(create_iter_result_object(agent, r#match.into_value().unbind(), false).into_value()) + create_iter_result_object(agent, r#match.into_value().unbind(), false, gc.into_nogc()) + .map(|o| o.into_value()) } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>) { diff --git a/nova_vm/src/ecmascript/builtins/text_processing/string_objects/string_iterator_objects.rs b/nova_vm/src/ecmascript/builtins/text_processing/string_objects/string_iterator_objects.rs index 01f402a3f..16267ea33 100644 --- a/nova_vm/src/ecmascript/builtins/text_processing/string_objects/string_iterator_objects.rs +++ b/nova_vm/src/ecmascript/builtins/text_processing/string_objects/string_iterator_objects.rs @@ -176,7 +176,8 @@ impl StringIteratorPrototype { }; // 2. If state is completed, return CreateIteratorResultObject(undefined, true). if generator.is_completed(agent) { - return Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()); + return create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()); } let StringIteratorHeapData { s, position, .. } = generator.get_data(agent); let position = *position; @@ -196,7 +197,8 @@ impl StringIteratorPrototype { generator.get_data_mut(agent).position = next_index; // v. Perform ? GeneratorYield(CreateIteratorResultObject(resultString, false)). // 11. Return ? result. - Ok(create_iter_result_object(agent, result_string.into_value(), false).into_value()) + create_iter_result_object(agent, result_string.into_value(), false, gc.into_nogc()) + .map(|o| o.into_value()) } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>) { diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index 5f451ea57..c4472df7d 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -1053,7 +1053,7 @@ impl Agent { error: TryReserveError, gc: NoGcScope<'a, '_>, ) -> JsError<'a> { - self.throw_exception(ExceptionType::ReferenceError, error.to_string(), gc) + self.throw_exception(ExceptionType::RangeError, error.to_string(), gc) } pub(crate) fn running_execution_context(&self) -> &ExecutionContext { diff --git a/nova_vm/src/ecmascript/execution/realm.rs b/nova_vm/src/ecmascript/execution/realm.rs index 0c34166d9..9b101de22 100644 --- a/nova_vm/src/ecmascript/execution/realm.rs +++ b/nova_vm/src/ecmascript/execution/realm.rs @@ -368,11 +368,14 @@ pub(crate) fn set_realm_global_object( // a. Let intrinsics be realmRec.[[Intrinsics]]. let intrinsics = &agent.get_realm_record_by_id(realm_id).intrinsics; // b. Set globalObj to OrdinaryObjectCreate(intrinsics.[[%Object.prototype%]]). - Object::Object(OrdinaryObject::create_intrinsic_object( - agent, - Some(intrinsics.object_prototype().into()), - &[], - )) + Object::Object( + OrdinaryObject::create_intrinsic_object( + agent, + Some(intrinsics.object_prototype().into()), + &[], + ) + .expect("Should perform GC here"), + ) }); // 2. Assert: globalObj is an Object. diff --git a/nova_vm/src/ecmascript/types/language/function/into_function.rs b/nova_vm/src/ecmascript/types/language/function/into_function.rs index 3744486b6..7d2d6c667 100644 --- a/nova_vm/src/ecmascript/types/language/function/into_function.rs +++ b/nova_vm/src/ecmascript/types/language/function/into_function.rs @@ -132,7 +132,8 @@ impl<'a, T: 'a + FunctionInternalProperties<'a>> InternalSlots<'a> for T { }, }; let backing_object = - OrdinaryObject::create_object(agent, Some(prototype), &[length_entry, name_entry]); + OrdinaryObject::create_object(agent, Some(prototype), &[length_entry, name_entry]) + .expect("Should perform GC here"); self.set_backing_object(agent, backing_object.unbind()); backing_object.unbind() } diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 621c96ab7..744784fdf 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -364,14 +364,16 @@ impl<'a> OrdinaryObject<'a> { /// Turn an OrdinaryObject's Object Shape into an intrinsic. /// /// For objects with an intrinsic shape, this is a no-op. - pub(crate) fn make_intrinsic(self, agent: &mut Agent) { + #[must_use] + pub(crate) fn make_intrinsic(self, agent: &mut Agent) -> Result<(), TryReserveError> { let shape = self.object_shape(agent); if shape.is_intrinsic(agent) { // Already an intrinsic shape, nothing to do. - return; + return Ok(()); } - let new_shape = shape.make_intrinsic(agent); + let new_shape = shape.make_intrinsic(agent)?; self.get_mut(agent).set_shape(new_shape); + Ok(()) } pub(crate) fn get_property_storage<'b>(self, agent: &'b Agent) -> PropertyStorageRef<'b, 'a> { @@ -511,13 +513,13 @@ impl<'a> OrdinaryObject<'a> { agent: &mut Agent, prototype: Option>, entries: &[ObjectEntry<'a>], - ) -> Self { + ) -> Result { let base_shape = ObjectShape::get_shape_for_prototype(agent, prototype); let mut shape = base_shape; for e in entries { - shape = shape.get_child_shape(agent, e.key); + shape = shape.get_child_shape(agent, e.key)?; } - Self::create_object_internal(agent, shape, entries) + Ok(Self::create_object_internal(agent, shape, entries)) } pub(crate) fn create_object_with_shape_and_data_properties( @@ -566,12 +568,12 @@ impl<'a> OrdinaryObject<'a> { agent: &mut Agent, prototype: Option>, entries: &[ObjectEntry<'a>], - ) -> Self { + ) -> Result { let properties_count = entries.len(); let (cap, index) = agent .heap .elements - .allocate_keys_with_capacity(properties_count); + .allocate_keys_with_capacity(properties_count)?; let cap = cap.make_intrinsic(); let keys_memory = agent.heap.elements.get_keys_uninit_raw(cap, index); for (slot, key) in keys_memory.iter_mut().zip(entries.iter().map(|e| e.key)) { @@ -583,7 +585,7 @@ impl<'a> OrdinaryObject<'a> { cap, properties_count, )); - Self::create_object_internal(agent, shape, entries) + Ok(Self::create_object_internal(agent, shape, entries)) } /// Attempts to make this ordinary Object a copy of some source ordinary diff --git a/nova_vm/src/ecmascript/types/language/object/internal_slots.rs b/nova_vm/src/ecmascript/types/language/object/internal_slots.rs index 9fc1776de..012fdb3a9 100644 --- a/nova_vm/src/ecmascript/types/language/object/internal_slots.rs +++ b/nova_vm/src/ecmascript/types/language/object/internal_slots.rs @@ -40,7 +40,8 @@ where fn create_backing_object(self, agent: &mut Agent) -> OrdinaryObject<'static> { assert!(self.get_backing_object(agent).is_none()); let prototype = self.internal_prototype(agent); - let backing_object = OrdinaryObject::create_object(agent, prototype, &[]); + let backing_object = + OrdinaryObject::create_object(agent, prototype, &[]).expect("Should perform GC here"); self.set_backing_object(agent, backing_object); backing_object } diff --git a/nova_vm/src/ecmascript/types/language/object/property_storage.rs b/nova_vm/src/ecmascript/types/language/object/property_storage.rs index 2387635a0..7a3aa3332 100644 --- a/nova_vm/src/ecmascript/types/language/object/property_storage.rs +++ b/nova_vm/src/ecmascript/types/language/object/property_storage.rs @@ -135,7 +135,7 @@ impl<'a> PropertyStorage<'a> { elements_vector.reserve(&mut agent.heap.elements, new_len)?; // SAFETY: User says so. let (new_shape, insertion_index) = - unsafe { old_shape.add_private_fields(agent, private_fields) }; + unsafe { old_shape.add_private_fields(agent, private_fields)? }; // SAFETY: insertion index is <= old_len; old_len + private_fields.len() was checked. let insertion_end_index = unsafe { insertion_index.unchecked_add(private_fields.len()) }; // Note: use saturating_mul to avoid a panic site. @@ -405,7 +405,7 @@ impl<'a> PropertyStorage<'a> { let mut elements_vector = object.get_elements_vector(agent); elements_vector.reserve(&mut agent.heap.elements, new_len)?; elements_vector.len = new_len; - let new_shape = old_shape.get_child_shape(agent, key); + let new_shape = old_shape.get_child_shape(agent, key)?; agent.heap.alloc_counter += core::mem::size_of::>() + if desc.is_some() { core::mem::size_of::<(u32, ElementDescriptor)>() @@ -461,7 +461,12 @@ impl<'a> PropertyStorage<'a> { Ok(()) } - pub fn remove(self, agent: &mut Agent, o: Object, key: PropertyKey<'a>) { + pub fn remove( + self, + agent: &mut Agent, + o: Object, + key: PropertyKey<'a>, + ) -> Result<(), TryReserveError> { let object = self.0; let old_shape = object.object_shape(agent); @@ -476,14 +481,14 @@ impl<'a> PropertyStorage<'a> { .map(|res| res.0); let Some(index) = result else { // No match; nothing to delete. - return; + return Ok(()); }; // Note: keys can only go up to 2^32. let index = index as u32; let old_len = keys.len() as u32; let new_len = old_len - 1; - let new_shape = old_shape.get_shape_with_removal(agent, index); + let new_shape = old_shape.get_shape_with_removal(agent, index)?; let new_cap = new_shape.values_capacity(agent); if new_cap != old_cap { @@ -500,7 +505,7 @@ impl<'a> PropertyStorage<'a> { new_cap, old_len, index, - ); + )?; object.get_mut(agent).set_values(new_values); } else { // Capacity of the property storage isn't changing, so we can @@ -551,5 +556,6 @@ impl<'a> PropertyStorage<'a> { } else { object.get_mut(agent).set_shape(new_shape); } + Ok(()) } } diff --git a/nova_vm/src/ecmascript/types/spec/property_descriptor.rs b/nova_vm/src/ecmascript/types/spec/property_descriptor.rs index a973fac16..0f5c45c4e 100644 --- a/nova_vm/src/ecmascript/types/spec/property_descriptor.rs +++ b/nova_vm/src/ecmascript/types/spec/property_descriptor.rs @@ -279,7 +279,8 @@ impl<'a> PropertyDescriptor<'a> { .into_object(), ), &entries, - ); + ) + .expect("Should perform GC here"); // 10. Return obj. Some(obj.bind(gc)) diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler.rs b/nova_vm/src/engine/bytecode/bytecode_compiler.rs index 95a641995..807980535 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler.rs @@ -556,7 +556,9 @@ fn create_object_with_shape<'s>( unreachable!() }; let identifier = ctx.create_property_key(&id.name); - shape = shape.get_child_shape(ctx.get_agent_mut(), identifier); + shape = shape + .get_child_shape(ctx.get_agent_mut(), identifier) + .expect("Should perform GC here"); if is_anonymous_function_definition(&prop.value) { ctx.add_instruction_with_constant(Instruction::StoreConstant, identifier); ctx.name_identifier = Some(NamedEvaluationParameter::Result); @@ -1908,7 +1910,9 @@ fn compile_create_iterator_result_object(ctx: &mut CompileContext, done: bool) { .bind(gc); let shape = ObjectShape::get_shape_for_prototype(agent, Some(prototype.into_object())) .get_child_shape(agent, BUILTIN_STRING_MEMORY.value.to_property_key()) - .get_child_shape(agent, BUILTIN_STRING_MEMORY.done.to_property_key()); + .expect("Should perform GC here") + .get_child_shape(agent, BUILTIN_STRING_MEMORY.done.to_property_key()) + .expect("Should perform GC here"); ctx.add_instruction(Instruction::Load); ctx.add_instruction_with_constant(Instruction::LoadConstant, done); ctx.add_instruction_with_shape(Instruction::ObjectCreateWithShape, shape); diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler/template_literals.rs b/nova_vm/src/engine/bytecode/bytecode_compiler/template_literals.rs index bb8e2009a..e0a493c9c 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler/template_literals.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler/template_literals.rs @@ -162,7 +162,8 @@ pub(super) fn get_template_object<'a>( }, // }). }], - ); + ) + .expect("Should perform GC here"); template.set_backing_object(agent, template_backing_object.unbind()); // 15. Perform ! SetIntegrityLevel(template, frozen). unwrap_try(template.try_prevent_extensions(agent, gc)); diff --git a/nova_vm/src/engine/bytecode/iterator.rs b/nova_vm/src/engine/bytecode/iterator.rs index cc4a5e2ff..0165cdd4d 100644 --- a/nova_vm/src/engine/bytecode/iterator.rs +++ b/nova_vm/src/engine/bytecode/iterator.rs @@ -49,11 +49,12 @@ pub struct ActiveIterator<'a> { fn convert_to_iter_result_object<'a>( agent: &mut Agent, result: Option>, -) -> OrdinaryObject<'a> { + gc: NoGcScope<'a, '_>, +) -> JsResult<'a, OrdinaryObject<'a>> { if let Some(result) = result { - create_iter_result_object(agent, result, false) + create_iter_result_object(agent, result, false, gc) } else { - create_iter_result_object(agent, Value::Undefined, true) + create_iter_result_object(agent, Value::Undefined, true, gc) } } @@ -74,7 +75,7 @@ impl<'a> ActiveIterator<'a> { &mut self, agent: &mut Agent, value: Option, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { match self.get(agent) { VmIteratorRecord::InvalidIterator { .. } => { @@ -85,8 +86,10 @@ impl<'a> ActiveIterator<'a> { unreachable!() } VmIteratorRecord::ArrayValues(_) => ArrayValuesIterator::new(self) - .next(agent, gc) - .map(|r| convert_to_iter_result_object(agent, r).into_value()), + .next(agent, gc.reborrow()) + .unbind() + .and_then(|r| convert_to_iter_result_object(agent, r.unbind(), gc.into_nogc())) + .map(|o| o.into_value()), VmIteratorRecord::AsyncFromSyncGenericIterator(_) => { Ok(AsyncFromSyncGenericIterator::new(self) .call_next(agent, value, gc) @@ -95,13 +98,14 @@ impl<'a> ActiveIterator<'a> { VmIteratorRecord::GenericIterator(_) => { GenericIterator::new(self).call_next(agent, value, gc) } - VmIteratorRecord::SliceIterator(slice_ref) => Ok(convert_to_iter_result_object( - agent, - slice_ref.unshift(agent, gc.into_nogc()), - ) - .into_value()), + VmIteratorRecord::SliceIterator(slice_ref) => { + let gc = gc.into_nogc(); + convert_to_iter_result_object(agent, slice_ref.unshift(agent, gc), gc) + .map(|o| o.into_value()) + } VmIteratorRecord::EmptySliceIterator => { - Ok(create_iter_result_object(agent, Value::Undefined, true).into_value()) + create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc()) + .map(|o| o.into_value()) } } } diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index bc90c54b4..f82f026e3 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -666,7 +666,7 @@ impl Vm { Instruction::ArrayElision => execute_array_elision(agent, vm, gc)?, Instruction::BitwiseNot => execute_bitwise_not(agent, vm, gc)?, Instruction::CreateUnmappedArgumentsObject => { - execute_create_unmapped_arguments_object(agent, vm, gc.into_nogc()) + execute_create_unmapped_arguments_object(agent, vm, gc.into_nogc())? } Instruction::CopyDataProperties => execute_copy_data_properties(agent, vm, gc)?, Instruction::CopyDataPropertiesIntoObject => { diff --git a/nova_vm/src/engine/bytecode/vm/execute_instructions.rs b/nova_vm/src/engine/bytecode/vm/execute_instructions.rs index d7730afb6..ba45d49ac 100644 --- a/nova_vm/src/engine/bytecode/vm/execute_instructions.rs +++ b/nova_vm/src/engine/bytecode/vm/execute_instructions.rs @@ -2951,19 +2951,21 @@ pub(super) fn execute_async_iterator_close_with_error( false } -pub(super) fn execute_create_unmapped_arguments_object( +pub(super) fn execute_create_unmapped_arguments_object<'gc>( agent: &mut Agent, vm: &mut Vm, - gc: NoGcScope, -) { + gc: NoGcScope<'gc, '_>, +) -> JsResult<'gc, ()> { let Some(VmIteratorRecord::SliceIterator(slice)) = vm.iterator_stack.last() else { unreachable!() }; - vm.result = Some( - create_unmapped_arguments_object(agent, slice, gc) - .into_value() - .unbind(), - ); + match create_unmapped_arguments_object(agent, slice, gc) { + Ok(o) => { + vm.result = Some(o.into_value().unbind()); + Ok(()) + } + Err(err) => Err(agent.throw_allocation_exception(err, gc)), + } } pub(super) fn execute_get_new_target(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { @@ -3027,7 +3029,8 @@ pub(super) fn execute_import_meta(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) .into_iter() .map(|(key, value)| ObjectEntry::new_data_entry(key, value)) .collect::>(), - ); + ) + .expect("Should perform GC here"); // d. Perform HostFinalizeImportMeta(importMeta, module). agent .host_hooks diff --git a/nova_vm/src/heap/element_array.rs b/nova_vm/src/heap/element_array.rs index f99966bd1..1cbe43480 100644 --- a/nova_vm/src/heap/element_array.rs +++ b/nova_vm/src/heap/element_array.rs @@ -314,6 +314,7 @@ impl<'gc> ElementsVector<'gc> { elements.reserve_elements(self, new_len) } + #[must_use] pub(crate) fn push( &mut self, elements: &mut ElementArrays, @@ -1248,6 +1249,7 @@ impl ElementArray { } } + #[must_use] fn push( &mut self, source: &[Option], @@ -1255,6 +1257,9 @@ impl ElementArray { ) -> Result, TryReserveError> { let length = source.len(); self.values.try_reserve(1)?; + if descriptors.is_some() { + self.descriptors.try_reserve(1)?; + } let remaining = self.values.spare_capacity_mut(); assert!(length <= N); let last = remaining.get_mut(0).unwrap(); @@ -1300,14 +1305,18 @@ impl ElementArray { Ok(index) } + #[must_use] fn push_with_removal( &mut self, source: ElementStorageRef, removal_index: u32, - ) -> ElementIndex<'static> { + ) -> Result, TryReserveError> { let source_length = source.values.len(); let target_length = source_length - 1; - self.values.reserve(1); + self.values.try_reserve(1)?; + if source.descriptors.is_some() { + self.descriptors.try_reserve(1)?; + } let remaining = self.values.spare_capacity_mut(); assert!((removal_index as usize) < source_length); assert!(target_length <= N); @@ -1347,7 +1356,7 @@ impl ElementArray { let inserted_new = self.descriptors.insert(key, descriptors).is_none(); debug_assert!(inserted_new); } - key + Ok(key) } } @@ -1402,9 +1411,13 @@ impl PropertyKeyArray { self.keys[index.into_index()].as_mut_slice() } - fn push(&mut self, source: &[PropertyKey]) -> PropertyKeyIndex<'static> { + #[must_use] + fn push( + &mut self, + source: &[PropertyKey], + ) -> Result, TryReserveError> { let length = source.len(); - self.keys.reserve(1); + self.keys.try_reserve(1)?; let remaining = self.keys.spare_capacity_mut(); assert!(length <= N); let last = remaining.get_mut(0).unwrap(); @@ -1427,17 +1440,18 @@ impl PropertyKeyArray { unsafe { self.keys.set_len(self.keys.len() + 1); } - PropertyKeyIndex::last_property_key_index(&self.keys) + Ok(PropertyKeyIndex::last_property_key_index(&self.keys)) } + #[must_use] fn push_with_removal( &mut self, source: &[PropertyKey], removal_index: usize, - ) -> PropertyKeyIndex<'static> { + ) -> Result, TryReserveError> { let source_length = source.len(); let target_length = source_length - 1; - self.keys.reserve(1); + self.keys.try_reserve(1)?; let remaining = self.keys.spare_capacity_mut(); assert!(removal_index < source_length); assert!(target_length <= N); @@ -1463,7 +1477,7 @@ impl PropertyKeyArray { unsafe { self.keys.set_len(self.keys.len() + 1); } - PropertyKeyIndex::last_property_key_index(&self.keys) + Ok(PropertyKeyIndex::last_property_key_index(&self.keys)) } unsafe fn push_key(&mut self, index: PropertyKeyIndex, len: u32, key: PropertyKey) { @@ -1490,32 +1504,36 @@ impl PropertyKeyArray { /// Push a copy of a PropertyKey storage into the PropertyKeyArray, copying /// the first len keys. + #[must_use] fn push_within<'a>( &mut self, key_index: PropertyKeyIndex<'a>, len: u32, - ) -> PropertyKeyIndex<'a> { + ) -> Result, TryReserveError> { + self.keys.try_reserve(1)?; let start = key_index.into_index(); - let end = start.wrapping_add(1); + let end = start.saturating_add(1); // TODO: We'd want to use split_at_spare_mut here to only copy len keys // instead of copying N keys and writing None into N - len. self.keys.extend_from_within(start..end); let last = &mut self.keys.last_mut().unwrap()[len as usize..]; last.fill(None); - PropertyKeyIndex::last_property_key_index(&self.keys) + Ok(PropertyKeyIndex::last_property_key_index(&self.keys)) } /// Push a copy of a PropertyKey storage into the PropertyKeyArray, copying /// the first len keys. + #[must_use] fn push_within_with_removal<'a>( &mut self, key_index: PropertyKeyIndex<'a>, len: u32, removal_index: usize, - ) -> PropertyKeyIndex<'a> { + ) -> Result, TryReserveError> { + self.keys.try_reserve(1)?; let len = len as usize; let start = key_index.into_index(); - let end = start.wrapping_add(1); + let end = start.saturating_add(1); // TODO: We'd want to use split_at_spare_mut here to only copy len keys // instead of copying N keys and writing None into N - len. self.keys.extend_from_within(start..end); @@ -1523,9 +1541,9 @@ impl PropertyKeyArray { debug_assert!(removal_index < last.len()); debug_assert!(last[removal_index].is_some()); debug_assert!(removal_index < len); - last.copy_within(removal_index.wrapping_add(1)..len, removal_index); + last.copy_within(removal_index.saturating_add(1)..len, removal_index); last[len.wrapping_sub(1)] = None; - PropertyKeyIndex::last_property_key_index(&self.keys) + Ok(PropertyKeyIndex::last_property_key_index(&self.keys)) } } @@ -1618,6 +1636,7 @@ impl IndexMut<&ElementsVector<'_>> for Agent { } impl ElementArrays { + #[must_use] fn push_values( &mut self, key: ElementArrayKey, @@ -1661,6 +1680,7 @@ impl ElementArrays { } } + #[must_use] fn reserve_elements( &mut self, elements_vector: &mut ElementsVector, @@ -1678,6 +1698,7 @@ impl ElementArrays { ) } + #[must_use] pub(crate) fn reserve_elements_raw( &mut self, index: &mut ElementIndex, @@ -1955,30 +1976,33 @@ impl ElementArrays { e2pow32.push(source, descriptors.cloned()) } }; - *cap = new_key; *index = new_index?; + *cap = new_key; Ok(()) } + #[must_use] pub(crate) fn reserve_keys_raw( &mut self, index: &mut PropertyKeyIndex, cap: &mut ElementArrayKey, old_len: u32, additional: u32, - ) { + ) -> Result<(), TryReserveError> { let new_len = old_len .checked_add(additional) .expect("Ridiculous amount of keys"); let new_cap = ElementArrayKey::from(new_len); if new_cap == *cap { - return; + return Ok(()); } let new_index = self.grow_keys_internal(*cap, *index, new_cap, old_len); + *index = new_index?; *cap = new_cap; - *index = new_index; + Ok(()) } + #[must_use] pub(crate) fn allocate_elements_with_length( &mut self, length: usize, @@ -1987,6 +2011,7 @@ impl ElementArrays { Self::allocate_elements_with_capacity(self, cap) } + #[must_use] pub(crate) fn allocate_elements_with_capacity( &mut self, cap: ElementArrayKey, @@ -1999,6 +2024,7 @@ impl ElementArrays { }) } + #[must_use] fn allocate_object_property_storage( &mut self, length: usize, @@ -2017,10 +2043,11 @@ impl ElementArrays { } /// Allocate an empty PropertyKey backing store with the given capacity. + #[must_use] pub(crate) fn allocate_keys_with_capacity( &mut self, capacity: usize, - ) -> (ElementArrayKey, PropertyKeyIndex<'static>) { + ) -> Result<(ElementArrayKey, PropertyKeyIndex<'static>), TryReserveError> { let Self { k2pow1, k2pow2, @@ -2040,31 +2067,32 @@ impl ElementArrays { ElementArrayKey::Empty | ElementArrayKey::EmptyIntrinsic => { PropertyKeyIndex::from_u32_index(0) } - ElementArrayKey::E1 => k2pow1.push(&[]), - ElementArrayKey::E2 => k2pow2.push(&[]), - ElementArrayKey::E3 => k2pow3.push(&[]), - ElementArrayKey::E4 => k2pow4.push(&[]), - ElementArrayKey::E6 => k2pow6.push(&[]), - ElementArrayKey::E8 => k2pow8.push(&[]), - ElementArrayKey::E10 => k2pow10.push(&[]), - ElementArrayKey::E12 => k2pow12.push(&[]), - ElementArrayKey::E16 => k2pow16.push(&[]), - ElementArrayKey::E24 => k2pow24.push(&[]), - ElementArrayKey::E32 => k2pow32.push(&[]), + ElementArrayKey::E1 => k2pow1.push(&[])?, + ElementArrayKey::E2 => k2pow2.push(&[])?, + ElementArrayKey::E3 => k2pow3.push(&[])?, + ElementArrayKey::E4 => k2pow4.push(&[])?, + ElementArrayKey::E6 => k2pow6.push(&[])?, + ElementArrayKey::E8 => k2pow8.push(&[])?, + ElementArrayKey::E10 => k2pow10.push(&[])?, + ElementArrayKey::E12 => k2pow12.push(&[])?, + ElementArrayKey::E16 => k2pow16.push(&[])?, + ElementArrayKey::E24 => k2pow24.push(&[])?, + ElementArrayKey::E32 => k2pow32.push(&[])?, }; - (key, index) + Ok((key, index)) } /// Allocate a new PropertyKey backing store with an added key. + #[must_use] pub(crate) fn copy_keys_with_addition<'a>( &mut self, cap: ElementArrayKey, index: PropertyKeyIndex<'a>, len: u32, key: PropertyKey<'a>, - ) -> (ElementArrayKey, PropertyKeyIndex<'a>) { + ) -> Result<(ElementArrayKey, PropertyKeyIndex<'a>), TryReserveError> { let new_len = len.checked_add(1).expect("Ridiculous amount of keys"); - let (new_cap, new_key) = self.copy_keys_with_capacity(new_len as usize, cap, index, len); + let (new_cap, new_key) = self.copy_keys_with_capacity(new_len as usize, cap, index, len)?; let Self { k2pow1, k2pow2, @@ -2115,7 +2143,7 @@ impl ElementArrays { k2pow32.get_uninit(new_key)[len as usize] = Some(key.unbind()); } } - (new_cap, new_key) + Ok((new_cap, new_key)) } /// Mutate a property key storage by removing a key at index. @@ -2181,13 +2209,14 @@ impl ElementArrays { /// /// Effectively, those objects would find that their object key-value pairs /// no longer match the expected values. + #[must_use] pub(crate) unsafe fn push_key( &mut self, cap: &mut ElementArrayKey, index: &mut PropertyKeyIndex, len: &mut u32, key: PropertyKey, - ) { + ) -> Result<(), TryReserveError> { let Self { k2pow1, k2pow2, @@ -2221,24 +2250,26 @@ impl ElementArrays { } } else { // We need to grow our backing store. - let (new_cap, new_index) = self.copy_keys_with_addition(*cap, *index, *len, key); + let (new_cap, new_index) = self.copy_keys_with_addition(*cap, *index, *len, key)?; *cap = new_cap; *index = new_index.unbind(); } *len += 1; + Ok(()) } + #[must_use] pub(crate) fn copy_keys_with_removal<'a>( &mut self, cap: ElementArrayKey, index: PropertyKeyIndex<'a>, len: u32, removal_index: usize, - ) -> (ElementArrayKey, PropertyKeyIndex<'a>) { + ) -> Result<(ElementArrayKey, PropertyKeyIndex<'a>), TryReserveError> { if len <= 1 { // Removing the last key. debug_assert_eq!(removal_index, 0); - return (ElementArrayKey::Empty, PropertyKeyIndex::from_u32_index(0)); + return Ok((ElementArrayKey::Empty, PropertyKeyIndex::from_u32_index(0))); } let Self { k2pow1, @@ -2393,21 +2424,22 @@ impl ElementArrays { ElementArrayKey::E32 => unreachable!(), } }; - (new_cap, new_index) + Ok((new_cap, new_index?)) } /// Allocate a new PropertyKey backing store with the given capacity, /// copying the first len values into the new allocation from the source /// backing store. + #[must_use] pub(crate) fn copy_keys_with_capacity<'a>( &mut self, capacity: usize, cap: ElementArrayKey, index: PropertyKeyIndex<'a>, len: u32, - ) -> (ElementArrayKey, PropertyKeyIndex<'a>) { + ) -> Result<(ElementArrayKey, PropertyKeyIndex<'a>), TryReserveError> { if capacity == 0 { - return (ElementArrayKey::Empty, PropertyKeyIndex::from_u32_index(0)); + return Ok((ElementArrayKey::Empty, PropertyKeyIndex::from_u32_index(0))); } let Self { k2pow1, @@ -2444,9 +2476,10 @@ impl ElementArrays { // Change in capacity. self.grow_keys_internal(cap, index, new_cap, len) }; - (new_cap, new_index) + Ok((new_cap, new_index?)) } + #[must_use] pub(crate) fn realloc_values_with_removal<'a>( &mut self, src_cap: ElementArrayKey, @@ -2454,11 +2487,11 @@ impl ElementArrays { dst_cap: ElementArrayKey, len: u32, removal_index: u32, - ) -> ElementIndex<'a> { + ) -> Result, TryReserveError> { if dst_cap.capacity() == 0 { // Removing the last key. debug_assert_eq!(removal_index, 0); - return ElementIndex::from_u32_index(0); + return Ok(ElementIndex::from_u32_index(0)); } if dst_cap == src_cap { @@ -2880,13 +2913,14 @@ impl ElementArrays { } /// Grow a keys storage to new capacity. + #[must_use] fn grow_keys_internal<'a>( &mut self, cap: ElementArrayKey, index: PropertyKeyIndex, new_cap: ElementArrayKey, len: u32, - ) -> PropertyKeyIndex<'a> { + ) -> Result, TryReserveError> { let Self { k2pow1, k2pow2, @@ -3038,6 +3072,7 @@ impl ElementArrays { } } + #[must_use] pub(crate) fn allocate_object_property_storage_from_entries_vec<'a>( &mut self, mut entries: Vec<( @@ -3065,6 +3100,7 @@ impl ElementArrays { self.allocate_object_property_storage(length, &values, descriptors) } + #[must_use] pub(crate) fn allocate_property_storage<'a>( &mut self, values: &[Option>], @@ -3074,6 +3110,7 @@ impl ElementArrays { self.allocate_object_property_storage(length, values, descriptors) } + #[must_use] pub(crate) fn allocate_object_property_storage_from_entries_slice<'a>( &mut self, entries: &[ObjectEntry<'a>], diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index a7e0b4f93..c4adbb7cf 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -2178,7 +2178,9 @@ fn test_heap_gc() { let (mut gc, mut scope) = unsafe { GcScope::create_root() }; let mut gc = GcScope::new(&mut gc, &mut scope); assert!(agent.heap.objects.is_empty()); - let obj = HeapRootData::Object(OrdinaryObject::create_object(&mut agent, None, &[])); + let obj = HeapRootData::Object( + OrdinaryObject::create_object(&mut agent, None, &[]).expect("Should perform GC here"), + ); agent.heap.globals.borrow_mut().push(obj); heap_gc(&mut agent, &mut [], gc.reborrow()); From 6c94fb8d7ad633a9551d9e3e6d6d1d5b18152f8d Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 21 Nov 2025 22:33:13 +0200 Subject: [PATCH 3/6] chore(test262): Update expectations --- tests/expectations.json | 67 ++++++++++++++++++++--------------------- tests/metrics.json | 6 ++-- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/tests/expectations.json b/tests/expectations.json index 909b99e96..ac1dab8c1 100644 --- a/tests/expectations.json +++ b/tests/expectations.json @@ -6300,11 +6300,11 @@ "language/expressions/await/await-in-global.js": "FAIL", "language/expressions/await/await-in-nested-function.js": "FAIL", "language/expressions/await/await-in-nested-generator.js": "FAIL", - "language/expressions/call/tco-call-args.js": "CRASH", - "language/expressions/call/tco-member-args.js": "CRASH", - "language/expressions/call/tco-non-eval-function-dynamic.js": "CRASH", - "language/expressions/call/tco-non-eval-function.js": "CRASH", - "language/expressions/call/tco-non-eval-global.js": "CRASH", + "language/expressions/call/tco-call-args.js": "FAIL", + "language/expressions/call/tco-member-args.js": "FAIL", + "language/expressions/call/tco-non-eval-function-dynamic.js": "FAIL", + "language/expressions/call/tco-non-eval-function.js": "FAIL", + "language/expressions/call/tco-non-eval-global.js": "FAIL", "language/expressions/call/tco-non-eval-with.js": "FAIL", "language/expressions/call/with-base-obj.js": "FAIL", "language/expressions/class/class-name-ident-await-escaped.js": "FAIL", @@ -6364,9 +6364,9 @@ "language/expressions/class/static-init-await-reference.js": "FAIL", "language/expressions/class/subclass-builtins/subclass-Promise.js": "FAIL", "language/expressions/class/subclass-builtins/subclass-WeakMap.js": "FAIL", - "language/expressions/coalesce/tco-pos-null.js": "CRASH", - "language/expressions/coalesce/tco-pos-undefined.js": "CRASH", - "language/expressions/comma/tco-final.js": "CRASH", + "language/expressions/coalesce/tco-pos-null.js": "FAIL", + "language/expressions/coalesce/tco-pos-undefined.js": "FAIL", + "language/expressions/comma/tco-final.js": "FAIL", "language/expressions/compound-assignment/S11.13.2_A5.10_T1.js": "FAIL", "language/expressions/compound-assignment/S11.13.2_A5.10_T2.js": "FAIL", "language/expressions/compound-assignment/S11.13.2_A5.10_T3.js": "FAIL", @@ -6411,8 +6411,8 @@ "language/expressions/compound-assignment/compound-assignment-operator-calls-putvalue-lref--v--6.js": "FAIL", "language/expressions/compound-assignment/compound-assignment-operator-calls-putvalue-lref--v--8.js": "FAIL", "language/expressions/compound-assignment/compound-assignment-operator-calls-putvalue-lref--v-.js": "FAIL", - "language/expressions/conditional/tco-cond.js": "CRASH", - "language/expressions/conditional/tco-pos.js": "CRASH", + "language/expressions/conditional/tco-cond.js": "FAIL", + "language/expressions/conditional/tco-pos.js": "FAIL", "language/expressions/delete/11.4.1-4.a-11.js": "FAIL", "language/expressions/delete/11.4.1-4.a-6.js": "FAIL", "language/expressions/delete/super-property-uninitialized-this.js": "FAIL", @@ -6586,8 +6586,8 @@ "language/expressions/less-than/bigint-and-bigint.js": "FAIL", "language/expressions/less-than/bigint-and-incomparable-string.js": "FAIL", "language/expressions/less-than/bigint-and-number-extremes.js": "FAIL", - "language/expressions/logical-and/tco-right.js": "CRASH", - "language/expressions/logical-or/tco-right.js": "CRASH", + "language/expressions/logical-and/tco-right.js": "FAIL", + "language/expressions/logical-or/tco-right.js": "FAIL", "language/expressions/multiplication/S11.5.1_A4_T7.js": "FAIL", "language/expressions/object/computed-property-name-topropertykey-before-value-evaluation.js": "FAIL", "language/expressions/object/fn-name-accessor-get.js": "FAIL", @@ -6630,9 +6630,9 @@ "language/expressions/super/prop-expr-uninitialized-this-putvalue.js": "FAIL", "language/expressions/super/realm.js": "FAIL", "language/expressions/tagged-template/cache-different-functions-same-site.js": "FAIL", - "language/expressions/tagged-template/tco-call.js": "CRASH", - "language/expressions/tagged-template/tco-member.js": "CRASH", - "language/expressions/tco-pos.js": "CRASH", + "language/expressions/tagged-template/tco-call.js": "FAIL", + "language/expressions/tagged-template/tco-member.js": "FAIL", + "language/expressions/tco-pos.js": "FAIL", "language/expressions/typeof/proxy.js": "FAIL", "language/expressions/yield/formal-parameters-after-reassignment-non-strict.js": "FAIL", "language/expressions/yield/from-with.js": "FAIL", @@ -6947,8 +6947,8 @@ "language/statements/async-function/evaluation-mapped-arguments.js": "FAIL", "language/statements/async-generator/yield-star-return-then-getter-ticks.js": "FAIL", "language/statements/await-using/throws-if-initializer-not-object.js": "CRASH", - "language/statements/block/tco-stmt-list.js": "CRASH", - "language/statements/block/tco-stmt.js": "CRASH", + "language/statements/block/tco-stmt-list.js": "FAIL", + "language/statements/block/tco-stmt.js": "FAIL", "language/statements/class/arguments/access.js": "FAIL", "language/statements/class/class-name-ident-await-escaped.js": "FAIL", "language/statements/class/class-name-ident-await.js": "FAIL", @@ -7038,15 +7038,15 @@ "language/statements/const/cptn-value.js": "FAIL", "language/statements/const/static-init-await-binding-valid.js": "FAIL", "language/statements/debugger/statement.js": "CRASH", - "language/statements/do-while/tco-body.js": "CRASH", + "language/statements/do-while/tco-body.js": "FAIL", "language/statements/for-await-of/async-from-sync-iterator-continuation-abrupt-completion-get-constructor.js": "FAIL", "language/statements/for-await-of/ticks-with-async-iter-resolved-promise-and-constructor-lookup-two.js": "FAIL", "language/statements/for-await-of/ticks-with-sync-iter-resolved-promise-and-constructor-lookup.js": "FAIL", "language/statements/for-of/arguments-mapped-aliasing.js": "FAIL", - "language/statements/for/tco-const-body.js": "CRASH", - "language/statements/for/tco-let-body.js": "CRASH", - "language/statements/for/tco-lhs-body.js": "CRASH", - "language/statements/for/tco-var-body.js": "CRASH", + "language/statements/for/tco-const-body.js": "FAIL", + "language/statements/for/tco-let-body.js": "FAIL", + "language/statements/for/tco-lhs-body.js": "FAIL", + "language/statements/for/tco-var-body.js": "FAIL", "language/statements/function/S13.2.1_A6_T3.js": "FAIL", "language/statements/function/S13.2.2_A17_T2.js": "FAIL", "language/statements/function/S13.2.2_A17_T3.js": "FAIL", @@ -7064,29 +7064,29 @@ "language/statements/function/S14_A5_T1.js": "FAIL", "language/statements/function/S14_A5_T2.js": "FAIL", "language/statements/function/static-init-await-binding-valid.js": "FAIL", - "language/statements/if/tco-else-body.js": "CRASH", - "language/statements/if/tco-if-body.js": "CRASH", - "language/statements/labeled/tco.js": "CRASH", + "language/statements/if/tco-else-body.js": "FAIL", + "language/statements/if/tco-if-body.js": "FAIL", + "language/statements/labeled/tco.js": "FAIL", "language/statements/labeled/value-await-non-module-escaped.js": "FAIL", "language/statements/labeled/value-await-non-module.js": "FAIL", "language/statements/let/cptn-value.js": "FAIL", "language/statements/let/static-init-await-binding-valid.js": "FAIL", - "language/statements/return/tco.js": "CRASH", - "language/statements/switch/tco-case-body-dflt.js": "CRASH", - "language/statements/switch/tco-case-body.js": "CRASH", - "language/statements/switch/tco-dftl-body.js": "CRASH", + "language/statements/return/tco.js": "FAIL", + "language/statements/switch/tco-case-body-dflt.js": "FAIL", + "language/statements/switch/tco-case-body.js": "FAIL", + "language/statements/switch/tco-dftl-body.js": "FAIL", "language/statements/try/S12.14_A14.js": "FAIL", "language/statements/try/static-init-await-binding-valid.js": "FAIL", - "language/statements/try/tco-catch-finally.js": "CRASH", - "language/statements/try/tco-catch.js": "CRASH", - "language/statements/try/tco-finally.js": "CRASH", + "language/statements/try/tco-catch-finally.js": "FAIL", + "language/statements/try/tco-catch.js": "FAIL", + "language/statements/try/tco-finally.js": "FAIL", "language/statements/using/throws-if-initializer-not-object.js": "CRASH", "language/statements/variable/binding-resolution.js": "FAIL", "language/statements/variable/cptn-value.js": "FAIL", "language/statements/variable/dstr/ary-ptrn-elem-id-static-init-await-valid.js": "FAIL", "language/statements/variable/dstr/obj-ptrn-elem-id-static-init-await-valid.js": "FAIL", "language/statements/variable/static-init-await-binding-valid.js": "FAIL", - "language/statements/while/tco-body.js": "CRASH", + "language/statements/while/tco-body.js": "FAIL", "language/statements/with/12.10-0-12.js": "FAIL", "language/statements/with/12.10-0-3.js": "FAIL", "language/statements/with/12.10-0-7.js": "FAIL", @@ -7692,7 +7692,6 @@ "staging/sm/extensions/getOwnPropertyNames-__proto__.js": "FAIL", "staging/sm/extensions/mutable-proto-special-form.js": "FAIL", "staging/sm/extensions/proxy-strict.js": "FAIL", - "staging/sm/extensions/recursion.js": "CRASH", "staging/sm/extensions/regress-469625-01.js": "FAIL", "staging/sm/extensions/regress-645160.js": "FAIL", "staging/sm/extensions/regress-650753.js": "FAIL", diff --git a/tests/metrics.json b/tests/metrics.json index 4a5cf37ec..7c7b2c4da 100644 --- a/tests/metrics.json +++ b/tests/metrics.json @@ -1,8 +1,8 @@ { "results": { - "crash": 107, - "fail": 7610, - "pass": 39637, + "crash": 73, + "fail": 7643, + "pass": 39638, "skip": 3325, "timeout": 17, "unresolved": 37 From 1fa04b9603210d5e474f391852e965528b55856a Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 21 Nov 2025 22:41:46 +0200 Subject: [PATCH 4/6] fmt --- nova_vm/src/ecmascript/builtins/regexp.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova_vm/src/ecmascript/builtins/regexp.rs b/nova_vm/src/ecmascript/builtins/regexp.rs index f492b24ca..e37f6750e 100644 --- a/nova_vm/src/ecmascript/builtins/regexp.rs +++ b/nova_vm/src/ecmascript/builtins/regexp.rs @@ -180,7 +180,8 @@ impl<'a> InternalSlots<'a> for RegExp<'a> { configurable: false, }, }], - ).expect("Should perform GC here"); + ) + .expect("Should perform GC here"); self.set_backing_object(agent, backing_object); backing_object } From 91ae3f0626c3c607070e0ca0aeef59590bcef305 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 21 Nov 2025 22:50:36 +0200 Subject: [PATCH 5/6] clippy --- .../src/ecmascript/builtins/ordinary/shape.rs | 1 - .../src/ecmascript/types/language/object.rs | 1 - nova_vm/src/heap/element_array.rs | 24 ------------------- 3 files changed, 26 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary/shape.rs b/nova_vm/src/ecmascript/builtins/ordinary/shape.rs index 32858f1f7..56f39779d 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary/shape.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary/shape.rs @@ -327,7 +327,6 @@ impl<'a> ObjectShape<'a> { /// /// > NOTE: This function will create a new Object Shape if an existing one /// > cannot be found. - #[must_use] pub(crate) fn get_child_shape( self, agent: &mut Agent, diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 744784fdf..67daa602f 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -364,7 +364,6 @@ impl<'a> OrdinaryObject<'a> { /// Turn an OrdinaryObject's Object Shape into an intrinsic. /// /// For objects with an intrinsic shape, this is a no-op. - #[must_use] pub(crate) fn make_intrinsic(self, agent: &mut Agent) -> Result<(), TryReserveError> { let shape = self.object_shape(agent); if shape.is_intrinsic(agent) { diff --git a/nova_vm/src/heap/element_array.rs b/nova_vm/src/heap/element_array.rs index 1cbe43480..09f75818f 100644 --- a/nova_vm/src/heap/element_array.rs +++ b/nova_vm/src/heap/element_array.rs @@ -314,7 +314,6 @@ impl<'gc> ElementsVector<'gc> { elements.reserve_elements(self, new_len) } - #[must_use] pub(crate) fn push( &mut self, elements: &mut ElementArrays, @@ -1249,7 +1248,6 @@ impl ElementArray { } } - #[must_use] fn push( &mut self, source: &[Option], @@ -1305,7 +1303,6 @@ impl ElementArray { Ok(index) } - #[must_use] fn push_with_removal( &mut self, source: ElementStorageRef, @@ -1411,7 +1408,6 @@ impl PropertyKeyArray { self.keys[index.into_index()].as_mut_slice() } - #[must_use] fn push( &mut self, source: &[PropertyKey], @@ -1443,7 +1439,6 @@ impl PropertyKeyArray { Ok(PropertyKeyIndex::last_property_key_index(&self.keys)) } - #[must_use] fn push_with_removal( &mut self, source: &[PropertyKey], @@ -1504,7 +1499,6 @@ impl PropertyKeyArray { /// Push a copy of a PropertyKey storage into the PropertyKeyArray, copying /// the first len keys. - #[must_use] fn push_within<'a>( &mut self, key_index: PropertyKeyIndex<'a>, @@ -1523,7 +1517,6 @@ impl PropertyKeyArray { /// Push a copy of a PropertyKey storage into the PropertyKeyArray, copying /// the first len keys. - #[must_use] fn push_within_with_removal<'a>( &mut self, key_index: PropertyKeyIndex<'a>, @@ -1636,7 +1629,6 @@ impl IndexMut<&ElementsVector<'_>> for Agent { } impl ElementArrays { - #[must_use] fn push_values( &mut self, key: ElementArrayKey, @@ -1680,7 +1672,6 @@ impl ElementArrays { } } - #[must_use] fn reserve_elements( &mut self, elements_vector: &mut ElementsVector, @@ -1698,7 +1689,6 @@ impl ElementArrays { ) } - #[must_use] pub(crate) fn reserve_elements_raw( &mut self, index: &mut ElementIndex, @@ -1981,7 +1971,6 @@ impl ElementArrays { Ok(()) } - #[must_use] pub(crate) fn reserve_keys_raw( &mut self, index: &mut PropertyKeyIndex, @@ -2002,7 +1991,6 @@ impl ElementArrays { Ok(()) } - #[must_use] pub(crate) fn allocate_elements_with_length( &mut self, length: usize, @@ -2011,7 +1999,6 @@ impl ElementArrays { Self::allocate_elements_with_capacity(self, cap) } - #[must_use] pub(crate) fn allocate_elements_with_capacity( &mut self, cap: ElementArrayKey, @@ -2024,7 +2011,6 @@ impl ElementArrays { }) } - #[must_use] fn allocate_object_property_storage( &mut self, length: usize, @@ -2043,7 +2029,6 @@ impl ElementArrays { } /// Allocate an empty PropertyKey backing store with the given capacity. - #[must_use] pub(crate) fn allocate_keys_with_capacity( &mut self, capacity: usize, @@ -2083,7 +2068,6 @@ impl ElementArrays { } /// Allocate a new PropertyKey backing store with an added key. - #[must_use] pub(crate) fn copy_keys_with_addition<'a>( &mut self, cap: ElementArrayKey, @@ -2209,7 +2193,6 @@ impl ElementArrays { /// /// Effectively, those objects would find that their object key-value pairs /// no longer match the expected values. - #[must_use] pub(crate) unsafe fn push_key( &mut self, cap: &mut ElementArrayKey, @@ -2258,7 +2241,6 @@ impl ElementArrays { Ok(()) } - #[must_use] pub(crate) fn copy_keys_with_removal<'a>( &mut self, cap: ElementArrayKey, @@ -2430,7 +2412,6 @@ impl ElementArrays { /// Allocate a new PropertyKey backing store with the given capacity, /// copying the first len values into the new allocation from the source /// backing store. - #[must_use] pub(crate) fn copy_keys_with_capacity<'a>( &mut self, capacity: usize, @@ -2479,7 +2460,6 @@ impl ElementArrays { Ok((new_cap, new_index?)) } - #[must_use] pub(crate) fn realloc_values_with_removal<'a>( &mut self, src_cap: ElementArrayKey, @@ -2913,7 +2893,6 @@ impl ElementArrays { } /// Grow a keys storage to new capacity. - #[must_use] fn grow_keys_internal<'a>( &mut self, cap: ElementArrayKey, @@ -3072,7 +3051,6 @@ impl ElementArrays { } } - #[must_use] pub(crate) fn allocate_object_property_storage_from_entries_vec<'a>( &mut self, mut entries: Vec<( @@ -3100,7 +3078,6 @@ impl ElementArrays { self.allocate_object_property_storage(length, &values, descriptors) } - #[must_use] pub(crate) fn allocate_property_storage<'a>( &mut self, values: &[Option>], @@ -3110,7 +3087,6 @@ impl ElementArrays { self.allocate_object_property_storage(length, values, descriptors) } - #[must_use] pub(crate) fn allocate_object_property_storage_from_entries_slice<'a>( &mut self, entries: &[ObjectEntry<'a>], From efc80f3d82f1b1f669f463de62610ac2d5d01b2a Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 21 Nov 2025 23:00:57 +0200 Subject: [PATCH 6/6] clippy --- nova_vm/src/engine/bytecode/bytecode_compiler.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler.rs b/nova_vm/src/engine/bytecode/bytecode_compiler.rs index 807980535..8eddf3268 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler.rs @@ -3692,7 +3692,8 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::TSEnum let (cap, index) = agent .heap .elements - .allocate_keys_with_capacity(properties_count); + .allocate_keys_with_capacity(properties_count) + .expect("Should perform GC here"); let cap = cap.make_intrinsic(); let keys_memory = agent.heap.elements.get_keys_uninit_raw(cap, index);