From 5ea0aa5f08ba5cd50f89ec03d9d3ea94d0e3d117 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 16 Jan 2025 16:51:26 -0500 Subject: [PATCH] LibJS: Bring the explicit resource management implementation up to date While we don't yet have a working `using` implementation with our byte code, we can still keep our DisposableStack implementation up to date. The changes brought in here are all editorial, and set us up to start an AsyncDisposableStack implementation. --- Libraries/LibJS/Forward.h | 3 + .../LibJS/Runtime/AbstractOperations.cpp | 325 ++++++++++++------ Libraries/LibJS/Runtime/AbstractOperations.h | 29 +- .../LibJS/Runtime/DeclarativeEnvironment.cpp | 14 +- .../LibJS/Runtime/DeclarativeEnvironment.h | 8 +- Libraries/LibJS/Runtime/DisposableStack.cpp | 10 +- Libraries/LibJS/Runtime/DisposableStack.h | 15 +- .../Runtime/DisposableStackConstructor.cpp | 13 +- .../Runtime/DisposableStackPrototype.cpp | 197 +++++------ .../LibJS/Runtime/DisposableStackPrototype.h | 9 +- Libraries/LibJS/Runtime/Environment.h | 1 + Libraries/LibJS/SourceTextModule.cpp | 9 +- 12 files changed, 375 insertions(+), 258 deletions(-) diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index bddf503fd33..c60b526cdd8 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -122,6 +122,7 @@ JS_ENUMERATE_TYPED_ARRAYS #define JS_ENUMERATE_WELL_KNOWN_SYMBOLS \ + __JS_ENUMERATE(asyncDispose, async_dispose) \ __JS_ENUMERATE(asyncIterator, async_iterator) \ __JS_ENUMERATE(dispose, dispose) \ __JS_ENUMERATE(hasInstance, has_instance) \ @@ -163,6 +164,8 @@ class Completion; class Console; class CyclicModule; class DeclarativeEnvironment; +struct DisposeCapability; +struct DisposableResource; class ECMAScriptFunctionObject; class Environment; class Error; diff --git a/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Libraries/LibJS/Runtime/AbstractOperations.cpp index e41acc15d9c..cf0d2eb9dc5 100644 --- a/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -387,13 +387,15 @@ ThrowCompletionOr get_prototype_from_constructor(VM& vm, FunctionObject } // 9.1.2.2 NewDeclarativeEnvironment ( E ), https://tc39.es/ecma262/#sec-newdeclarativeenvironment +// 4.1.2.1 NewDeclarativeEnvironment ( E ), https://tc39.es/proposal-explicit-resource-management/#sec-declarative-environment-records-initializebinding-n-v GC::Ref new_declarative_environment(Environment& environment) { auto& heap = environment.heap(); // 1. Let env be a new Declarative Environment Record containing no bindings. // 2. Set env.[[OuterEnv]] to E. - // 3. Return env. + // 3. Set env.[[DisposeCapability]] to NewDisposeCapability(). + // 4. Return env. return heap.allocate(&environment); } @@ -411,6 +413,7 @@ GC::Ref new_object_environment(Object& object, bool is_with_e } // 9.1.2.4 NewFunctionEnvironment ( F, newTarget ), https://tc39.es/ecma262/#sec-newfunctionenvironment +// 4.1.2.2 NewFunctionEnvironment ( F, newTarget ), https://tc39.es/proposal-explicit-resource-management/#sec-newfunctionenvironment GC::Ref new_function_environment(ECMAScriptFunctionObject& function, Object* new_target) { auto& heap = function.heap(); @@ -432,9 +435,10 @@ GC::Ref new_function_environment(ECMAScriptFunctionObject& env->set_new_target(new_target ?: js_undefined()); // 6. Set env.[[OuterEnv]] to F.[[Environment]]. + // 7. Set env.[[DisposeCapability]] to NewDisposeCapability(). // NOTE: Done in step 1 via the FunctionEnvironment constructor. - // 7. Return env. + // 8. Return env. return env; } @@ -1403,159 +1407,278 @@ ThrowCompletionOr get_substitution(VM& vm, Utf16View const& matched, Utf return MUST(Utf16View { result }.to_utf8(Utf16View::AllowInvalidCodeUnits::Yes)); } -// 2.1.2 AddDisposableResource ( disposable, V, hint [ , method ] ), https://tc39.es/proposal-explicit-resource-management/#sec-adddisposableresource-disposable-v-hint-disposemethod -ThrowCompletionOr add_disposable_resource(VM& vm, Vector& disposable, Value value, Environment::InitializeBindingHint hint, FunctionObject* method) +void DisposeCapability::visit_edges(GC::Cell::Visitor& visitor) const { - // NOTE: For now only sync is a valid hint - VERIFY(hint == Environment::InitializeBindingHint::SyncDispose); + for (auto const& disposable_resource : disposable_resource_stack) + disposable_resource.visit_edges(visitor); +} +void DisposableResource::visit_edges(GC::Cell::Visitor& visitor) const +{ + visitor.visit(resource_value); + visitor.visit(dispose_method); +} + +// 2.1.3 NewDisposeCapability ( ), https://tc39.es/proposal-explicit-resource-management/#sec-newdisposecapability +DisposeCapability new_dispose_capability() +{ + // 1. Let stack be a new empty List. + // 2. Return the DisposeCapability Record { [[DisposableResourceStack]]: stack }. + return DisposeCapability {}; +} + +// 2.1.4 AddDisposableResource ( disposeCapability, V, hint [ , method ] ), https://tc39.es/proposal-explicit-resource-management/#sec-adddisposableresource-disposable-v-hint-disposemethod +ThrowCompletionOr add_disposable_resource(VM& vm, DisposeCapability& dispose_capability, Value value, Environment::InitializeBindingHint hint, GC::Ptr method) +{ Optional resource; // 1. If method is not present then, if (!method) { - // a. If V is null or undefined, return NormalCompletion(empty). - if (value.is_nullish()) + // a. If V is either null or undefined and hint is sync-dispose, then + if (value.is_nullish() && hint == Environment::InitializeBindingHint::SyncDispose) { + // i. Return unused. return {}; + } - // b. If Type(V) is not Object, throw a TypeError exception. - if (!value.is_object()) - return vm.throw_completion(ErrorType::NotAnObject, value.to_string_without_side_effects()); + // b. NOTE: When V is either null or undefined and hint is async-dispose, we record that the resource was evaluated + // to ensure we will still perform an Await when resources are later disposed. // c. Let resource be ? CreateDisposableResource(V, hint). resource = TRY(create_disposable_resource(vm, value, hint)); } // 2. Else, else { - // a. If V is null or undefined, then - if (value.is_nullish()) { - // i. Let resource be ? CreateDisposableResource(undefined, hint, method). - resource = TRY(create_disposable_resource(vm, js_undefined(), hint, method)); - } - // b. Else, - else { - // i. If Type(V) is not Object, throw a TypeError exception. - if (!value.is_object()) - return vm.throw_completion(ErrorType::NotAnObject, value.to_string_without_side_effects()); + // a. Assert: V is undefined. + VERIFY(value.is_undefined()); - // ii. Let resource be ? CreateDisposableResource(V, hint, method). - resource = TRY(create_disposable_resource(vm, value, hint, method)); - } + // b. Let resource be ? CreateDisposableResource(undefined, hint, method). + resource = TRY(create_disposable_resource(vm, js_undefined(), hint, method)); } - // 3. Append resource to disposable.[[DisposableResourceStack]]. - VERIFY(resource.has_value()); - disposable.append(resource.release_value()); + // 3. Append resource to disposeCapability.[[DisposableResourceStack]]. + dispose_capability.disposable_resource_stack.append(resource.release_value()); - // 4. Return NormalCompletion(empty). + // 4. Return unused. return {}; } -// 2.1.3 CreateDisposableResource ( V, hint [ , method ] ), https://tc39.es/proposal-explicit-resource-management/#sec-createdisposableresource -ThrowCompletionOr create_disposable_resource(VM& vm, Value value, Environment::InitializeBindingHint hint, FunctionObject* method) +// 2.1.5 CreateDisposableResource ( V, hint [ , method ] ), https://tc39.es/proposal-explicit-resource-management/#sec-createdisposableresource +ThrowCompletionOr create_disposable_resource(VM& vm, Value value, Environment::InitializeBindingHint hint, GC::Ptr method) { // 1. If method is not present, then if (!method) { - // a. If V is undefined, throw a TypeError exception. - if (value.is_undefined()) - return vm.throw_completion(ErrorType::IsUndefined, "value"); + // a. If V is either null or undefined, then + if (value.is_nullish()) { + // i. Set V to undefined. + // ii. Set method to undefined. + } + // b. Else, + else { + // i. If V is not an Object, throw a TypeError exception. + if (!value.is_object()) + return vm.throw_completion(ErrorType::NotAnObject, value); - // b. Set method to ? GetDisposeMethod(V, hint). - method = TRY(get_dispose_method(vm, value, hint)); + // ii. Set method to ? GetDisposeMethod(V, hint). + method = TRY(get_dispose_method(vm, value, hint)); - // c. If method is undefined, throw a TypeError exception. - if (!method) - return vm.throw_completion(ErrorType::NoDisposeMethod, value.to_string_without_side_effects()); + // iii. If method is undefined, throw a TypeError exception. + if (!method) + return vm.throw_completion(ErrorType::NoDisposeMethod, value); + } } // 2. Else, - // a. If IsCallable(method) is false, throw a TypeError exception. - // NOTE: This is guaranteed to never occur from the type. - VERIFY(method); + else { + // a. If IsCallable(method) is false, throw a TypeError exception. + // NOTE: This is guaranteed to never occur due to its type. + } // 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. - // NOTE: Since we only support sync dispose we don't store the hint for now. - VERIFY(hint == Environment::InitializeBindingHint::SyncDispose); return DisposableResource { - value, - *method + .resource_value = value.is_object() ? GC::Ptr { value.as_object() } : nullptr, + .hint = hint, + .dispose_method = method, }; } -// 2.1.4 GetDisposeMethod ( V, hint ), https://tc39.es/proposal-explicit-resource-management/#sec-getdisposemethod +// 2.1.6 GetDisposeMethod ( V, hint ), https://tc39.es/proposal-explicit-resource-management/#sec-getdisposemethod ThrowCompletionOr> get_dispose_method(VM& vm, Value value, Environment::InitializeBindingHint hint) { - // NOTE: We only have sync dispose for now which means we ignore step 1. - VERIFY(hint == Environment::InitializeBindingHint::SyncDispose); + GC::Ptr method; - // 2. Else, - // a. Let method be ? GetMethod(V, @@dispose). - return TRY(value.get_method(vm, vm.well_known_symbol_dispose())); -} + // 1. If hint is async-dispose, then + if (hint == Environment::InitializeBindingHint::AsyncDispose) { + // a. Let method be ? GetMethod(V, @@asyncDispose). + method = TRY(value.get_method(vm, vm.well_known_symbol_async_dispose())); -// 2.1.5 Dispose ( V, hint, method ), https://tc39.es/proposal-explicit-resource-management/#sec-dispose -Completion dispose(VM& vm, Value value, GC::Ref method) -{ - // 1. Let result be ? Call(method, V). - [[maybe_unused]] auto result = TRY(call(vm, *method, value)); + // b. If method is undefined, then + if (!method) { + // i. Set method to ? GetMethod(V, @@dispose). + method = TRY(value.get_method(vm, vm.well_known_symbol_dispose())); - // NOTE: Hint can only be sync-dispose so we ignore step 2. - // 2. If hint is async-dispose and result is not undefined, then - // a. Perform ? Await(result). + // ii. If method is not undefined, then + if (method) { + auto& realm = *vm.current_realm(); - // 3. Return undefined. - return js_undefined(); -} + // 1. Let closure be a new Abstract Closure with no parameters that captures method and performs the following steps when called: + auto closure = [&realm, method](VM& vm) -> ThrowCompletionOr { + // a. Let O be the this value. + auto object = vm.this_value(); -// 2.1.6 DisposeResources ( disposable, completion ), https://tc39.es/proposal-explicit-resource-management/#sec-disposeresources-disposable-completion-errors -Completion dispose_resources(VM& vm, Vector const& disposable, Completion completion) -{ - // 1. If disposable is not undefined, then - // NOTE: At this point disposable is always defined. + // b. Let promiseCapability be ! NewPromiseCapability(%Promise%). + auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor())); - // a. For each resource of disposable.[[DisposableResourceStack]], in reverse list order, do - for (auto const& resource : disposable.in_reverse()) { - // i. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). - auto result = dispose(vm, resource.resource_value, resource.dispose_method); + // c. Let result be Completion(Call(method, O)). + // d. IfAbruptRejectPromise(result, promiseCapability). + TRY_OR_REJECT(vm, promise_capability, call(vm, method, object)); - // ii. If result.[[Type]] is throw, then - if (result.is_error()) { - // 1. If completion.[[Type]] is throw, then - if (completion.is_error()) { - // a. Set result to result.[[Value]]. + // e. Perform ? Call(promiseCapability.[[Resolve]], undefined, « undefined »). + TRY(call(vm, *promise_capability->resolve(), js_undefined(), js_undefined())); - // b. Let suppressed be completion.[[Value]]. - auto suppressed = completion.value().value(); + // f. Return promiseCapability.[[Promise]]. + return promise_capability->promise(); + }; - // c. Let error be a newly created SuppressedError object. - auto error = SuppressedError::create(*vm.current_realm()); + // 2. NOTE: This function is not observable to user code. It is used to ensure that a Promise returned + // from a synchronous @@dispose method will not be awaited and that any exception thrown will not be + // thrown synchronously. - // d. Perform ! DefinePropertyOrThrow(error, "error", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: result }). - MUST(error->define_property_or_throw(vm.names.error, { .value = result.value(), .writable = true, .enumerable = true, .configurable = true })); - - // e. Perform ! DefinePropertyOrThrow(error, "suppressed", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: suppressed }). - MUST(error->define_property_or_throw(vm.names.suppressed, { .value = suppressed, .writable = true, .enumerable = false, .configurable = true })); - - // f. Set completion to ThrowCompletion(error). - completion = throw_completion(error); - } - // 2. Else, - else { - // a. Set completion to result. - completion = result; + // 3. Return CreateBuiltinFunction(closure, 0, "", « »). + return NativeFunction::create(realm, move(closure), 0, ""); } } } + // 2. Else, + else { + // a. Let method be ? GetMethod(V, @@dispose). + method = TRY(value.get_method(vm, vm.well_known_symbol_dispose())); + } - // 2. Return completion. - return completion; + // 3. Return method. + return method; } -Completion dispose_resources(VM& vm, GC::Ptr disposable, Completion completion) +// 2.1.7 Dispose ( V, hint, method ), https://tc39.es/proposal-explicit-resource-management/#sec-dispose +Completion dispose(VM& vm, Value value, Environment::InitializeBindingHint hint, GC::Ptr method) { - // 1. If disposable is not undefined, then - if (disposable) - return dispose_resources(vm, disposable->disposable_resource_stack(), completion); + Value result; - // 2. Return completion. + // 1. If method is undefined, let result be undefined. + if (!method) { + result = js_undefined(); + } + // 2. Else, let result be ? Call(method, V). + else { + result = TRY(call(vm, *method, value)); + } + + // 3. If hint is async-dispose, then + if (hint == Environment::InitializeBindingHint::AsyncDispose) { + // a. Perform ? Await(result). + TRY(await(vm, result)); + } + + // 4. Return undefined. + return js_undefined(); +} + +// 2.1.8 DisposeResources ( disposeCapability, completion ), https://tc39.es/proposal-explicit-resource-management/#sec-disposeresources +Completion dispose_resources(VM& vm, DisposeCapability& dispose_capability, Completion completion) +{ + // 1. Let needsAwait be false. + bool needs_await = false; + + // 2. Let hasAwaited be false. + bool has_awaited = false; + + // 3. For each element resource of disposeCapability.[[DisposableResourceStack]], in reverse list order, do + for (auto const& resource : dispose_capability.disposable_resource_stack.in_reverse()) { + // a. Let value be resource.[[ResourceValue]]. + auto value = resource.resource_value; + + // b. Let hint be resource.[[Hint]]. + auto hint = resource.hint; + + // c. Let method be resource.[[DisposeMethod]]. + auto method = resource.dispose_method; + + // d. If hint is sync-dispose and needsAwait is true and hasAwaited is false, then + if (hint == Environment::InitializeBindingHint::SyncDispose && needs_await && !has_awaited) { + // i. Perform ! Await(undefined). + MUST(await(vm, js_undefined())); + + // ii. Set needsAwait to false. + needs_await = false; + } + + // e. If method is not undefined, then + if (method) { + // i. Let result be Completion(Call(method, value)). + auto result = call(vm, *method, value); + + // ii. If result is a normal completion and hint is async-dispose, then + if (!result.is_throw_completion() && hint == Environment::InitializeBindingHint::AsyncDispose) { + // 1. Set result to Completion(Await(result.[[Value]])). + result = await(vm, result.value()); + + // 2. Set hasAwaited to true. + has_awaited = true; + } + // iii. If result is a throw completion, then + else if (result.is_throw_completion()) { + // 1. If completion is a throw completion, then + if (completion.type() == Completion::Type::Throw) { + // a. Set result to result.[[Value]]. + auto result_value = result.error().value().value(); + + // b. Let suppressed be completion.[[Value]]. + auto suppressed = completion.value().value(); + + // c. Let error be a newly created SuppressedError object. + auto error = SuppressedError::create(*vm.current_realm()); + + // d. Perform CreateNonEnumerableDataPropertyOrThrow(error, "error", result). + error->create_non_enumerable_data_property_or_throw(vm.names.error, result_value); + + // e. Perform CreateNonEnumerableDataPropertyOrThrow(error, "suppressed", suppressed). + error->create_non_enumerable_data_property_or_throw(vm.names.suppressed, suppressed); + + // f. Set completion to ThrowCompletion(error). + completion = throw_completion(error); + } + // 2. Else, + else { + // a. Set completion to result. + completion = result.release_error(); + } + } + } + // f. Else, + else { + // i. Assert: hint is async-dispose. + VERIFY(hint == Environment::InitializeBindingHint::AsyncDispose); + + // ii. Set needsAwait to true. + needs_await = true; + + // iii. NOTE: This can only indicate a case where either null or undefined was the initialized value of an + // await using declaration. + } + } + + // 4. If needsAwait is true and hasAwaited is false, then + if (needs_await && !has_awaited) { + // a. Perform ! Await(undefined). + MUST(await(vm, js_undefined())); + } + + // 5. NOTE: After disposeCapability has been disposed, it will never be used again. The contents of + // disposeCapability.[[DisposableResourceStack]] can be discarded in implementations, such as by garbage + // collection, at this point. + + // 6. Set disposeCapability.[[DisposableResourceStack]] to a new empty List. + dispose_capability.disposable_resource_stack.clear(); + + // 7. Return completion. return completion; } diff --git a/Libraries/LibJS/Runtime/AbstractOperations.h b/Libraries/LibJS/Runtime/AbstractOperations.h index 793a0cf6dc9..7149902dbd9 100644 --- a/Libraries/LibJS/Runtime/AbstractOperations.h +++ b/Libraries/LibJS/Runtime/AbstractOperations.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -44,16 +45,28 @@ ThrowCompletionOr get_prototype_from_constructor(VM&, FunctionObject co Object* create_unmapped_arguments_object(VM&, ReadonlySpan arguments); Object* create_mapped_arguments_object(VM&, FunctionObject&, Vector const&, ReadonlySpan arguments, Environment&); -struct DisposableResource { - Value resource_value; - GC::Ref dispose_method; +// 2.1.1 DisposeCapability Records, https://tc39.es/proposal-explicit-resource-management/#sec-disposecapability-records +struct DisposeCapability { + void visit_edges(GC::Cell::Visitor&) const; + + Vector disposable_resource_stack; // [[DisposableResourceStack]] }; -ThrowCompletionOr add_disposable_resource(VM&, Vector& disposable, Value, Environment::InitializeBindingHint, FunctionObject* = nullptr); -ThrowCompletionOr create_disposable_resource(VM&, Value, Environment::InitializeBindingHint, FunctionObject* method = nullptr); + +// 2.1.2 DisposableResource Records, https://tc39.es/proposal-explicit-resource-management/#sec-disposableresource-records +struct DisposableResource { + void visit_edges(GC::Cell::Visitor&) const; + + GC::Ptr resource_value; // [[ResourceValue]] + Environment::InitializeBindingHint hint; // [[Hint]] + GC::Ptr dispose_method; // [[DisposeMethod]] +}; + +DisposeCapability new_dispose_capability(); +ThrowCompletionOr add_disposable_resource(VM&, DisposeCapability&, Value, Environment::InitializeBindingHint, GC::Ptr = {}); +ThrowCompletionOr create_disposable_resource(VM&, Value, Environment::InitializeBindingHint, GC::Ptr = {}); ThrowCompletionOr> get_dispose_method(VM&, Value, Environment::InitializeBindingHint); -Completion dispose(VM& vm, Value, GC::Ref method); -Completion dispose_resources(VM& vm, Vector const& disposable, Completion completion); -Completion dispose_resources(VM& vm, GC::Ptr disposable, Completion completion); +Completion dispose(VM&, Value, Environment::InitializeBindingHint, GC::Ptr method); +Completion dispose_resources(VM&, DisposeCapability&, Completion); ThrowCompletionOr perform_import_call(VM&, Value specifier, Value options_value); diff --git a/Libraries/LibJS/Runtime/DeclarativeEnvironment.cpp b/Libraries/LibJS/Runtime/DeclarativeEnvironment.cpp index 6bc3b33561a..4a7b0ceb01c 100644 --- a/Libraries/LibJS/Runtime/DeclarativeEnvironment.cpp +++ b/Libraries/LibJS/Runtime/DeclarativeEnvironment.cpp @@ -25,30 +25,30 @@ DeclarativeEnvironment* DeclarativeEnvironment::create_for_per_iteration_binding DeclarativeEnvironment::DeclarativeEnvironment() : Environment(nullptr, IsDeclarative::Yes) + , m_dispose_capability(new_dispose_capability()) { } DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_environment) : Environment(parent_environment, IsDeclarative::Yes) + , m_dispose_capability(new_dispose_capability()) { } DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_environment, ReadonlySpan bindings) : Environment(parent_environment, IsDeclarative::Yes) , m_bindings(bindings) + , m_dispose_capability(new_dispose_capability()) { } void DeclarativeEnvironment::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); + m_dispose_capability.visit_edges(visitor); + for (auto& binding : m_bindings) visitor.visit(binding.value); - - for (auto& disposable : m_disposable_resource_stack) { - visitor.visit(disposable.resource_value); - visitor.visit(disposable.dispose_method); - } } // 9.1.1.1.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n @@ -122,9 +122,9 @@ ThrowCompletionOr DeclarativeEnvironment::initialize_binding_direct(VM& vm // 1. Assert: envRec must have an uninitialized binding for N. VERIFY(binding.initialized == false); - // 2. If hint is not normal, perform ? AddDisposableResource(envRec, V, hint). + // 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). if (hint != Environment::InitializeBindingHint::Normal) - TRY(add_disposable_resource(vm, m_disposable_resource_stack, value, hint)); + TRY(add_disposable_resource(vm, m_dispose_capability, value, hint)); // 3. Set the bound value for N in envRec to V. binding.value = value; diff --git a/Libraries/LibJS/Runtime/DeclarativeEnvironment.h b/Libraries/LibJS/Runtime/DeclarativeEnvironment.h index 2a9e1dcbff8..96adb668931 100644 --- a/Libraries/LibJS/Runtime/DeclarativeEnvironment.h +++ b/Libraries/LibJS/Runtime/DeclarativeEnvironment.h @@ -69,13 +69,13 @@ public: [[nodiscard]] u64 environment_serial_number() const { return m_environment_serial_number; } + DisposeCapability const& dispose_capability() const { return m_dispose_capability; } + DisposeCapability& dispose_capability() { return m_dispose_capability; } + private: ThrowCompletionOr get_binding_value_direct(VM&, Binding const&) const; ThrowCompletionOr set_mutable_binding_direct(VM&, Binding&, Value, bool strict); - friend Completion dispose_resources(VM&, GC::Ptr, Completion); - Vector const& disposable_resource_stack() const { return m_disposable_resource_stack; } - protected: DeclarativeEnvironment(); explicit DeclarativeEnvironment(Environment* parent_environment); @@ -125,7 +125,7 @@ protected: private: Vector m_bindings; HashMap m_bindings_assoc; - Vector m_disposable_resource_stack; + DisposeCapability m_dispose_capability; u64 m_environment_serial_number { 0 }; }; diff --git a/Libraries/LibJS/Runtime/DisposableStack.cpp b/Libraries/LibJS/Runtime/DisposableStack.cpp index bfca0571d22..277dcbd30c3 100644 --- a/Libraries/LibJS/Runtime/DisposableStack.cpp +++ b/Libraries/LibJS/Runtime/DisposableStack.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, David Tuin + * Copyright (c) 2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,19 +11,16 @@ namespace JS { GC_DEFINE_ALLOCATOR(DisposableStack); -DisposableStack::DisposableStack(Vector stack, Object& prototype) +DisposableStack::DisposableStack(DisposeCapability dispose_capability, Object& prototype) : Object(ConstructWithPrototypeTag::Tag, prototype) - , m_disposable_resource_stack(move(stack)) + , m_dispose_capability(move(dispose_capability)) { } void DisposableStack::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); - for (auto& resource : m_disposable_resource_stack) { - visitor.visit(resource.resource_value); - visitor.visit(resource.dispose_method); - } + m_dispose_capability.visit_edges(visitor); } } diff --git a/Libraries/LibJS/Runtime/DisposableStack.h b/Libraries/LibJS/Runtime/DisposableStack.h index d0e2d8d6151..89c9c064a64 100644 --- a/Libraries/LibJS/Runtime/DisposableStack.h +++ b/Libraries/LibJS/Runtime/DisposableStack.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, David Tuin + * Copyright (c) 2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -23,19 +24,19 @@ public: Disposed }; - [[nodiscard]] DisposableState disposable_state() const { return m_state; } - [[nodiscard]] Vector const& disposable_resource_stack() const { return m_disposable_resource_stack; } - [[nodiscard]] Vector& disposable_resource_stack() { return m_disposable_resource_stack; } + [[nodiscard]] DisposableState disposable_state() const { return m_disposable_state; } + void set_disposed() { m_disposable_state = DisposableState::Disposed; } - void set_disposed() { m_state = DisposableState::Disposed; } + [[nodiscard]] DisposeCapability const& dispose_capability() const { return m_dispose_capability; } + [[nodiscard]] DisposeCapability& dispose_capability() { return m_dispose_capability; } private: - DisposableStack(Vector stack, Object& prototype); + DisposableStack(DisposeCapability, Object& prototype); virtual void visit_edges(Visitor& visitor) override; - Vector m_disposable_resource_stack; - DisposableState m_state { DisposableState::Pending }; + DisposableState m_disposable_state { DisposableState::Pending }; + DisposeCapability m_dispose_capability; }; } diff --git a/Libraries/LibJS/Runtime/DisposableStackConstructor.cpp b/Libraries/LibJS/Runtime/DisposableStackConstructor.cpp index 7d811b5f3f2..0511e6082db 100644 --- a/Libraries/LibJS/Runtime/DisposableStackConstructor.cpp +++ b/Libraries/LibJS/Runtime/DisposableStackConstructor.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, David Tuin + * Copyright (c) 2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -22,13 +23,13 @@ void DisposableStackConstructor::initialize(Realm& realm) auto& vm = this->vm(); Base::initialize(realm); - // 26.2.2.1 DisposableStack.prototype, https://tc39.es/ecma262/#sec-finalization-registry.prototype + // 12.3.2.1 DisposableStack.prototype, https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype define_direct_property(vm.names.prototype, realm.intrinsics().disposable_stack_prototype(), 0); define_direct_property(vm.names.length, Value(0), Attribute::Configurable); } -// 11.3.1.1 DisposableStack ( ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack +// 12.3.1.1 DisposableStack ( ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack ThrowCompletionOr DisposableStackConstructor::call() { auto& vm = this->vm(); @@ -37,16 +38,16 @@ ThrowCompletionOr DisposableStackConstructor::call() return vm.throw_completion(ErrorType::ConstructorWithoutNew, vm.names.DisposableStack); } -// 11.3.1.1 DisposableStack ( ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack +// 12.3.1.1 DisposableStack ( ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack ThrowCompletionOr> DisposableStackConstructor::construct(FunctionObject& new_target) { auto& vm = this->vm(); - // 2. Let disposableStack be ? OrdinaryCreateFromConstructor(NewTarget, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposableResourceStack]] »). + // 2. Let disposableStack be ? OrdinaryCreateFromConstructor(NewTarget, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposeCapability]] »). // 3. Set disposableStack.[[DisposableState]] to pending. - // 4. Set disposableStack.[[DisposableResourceStack]] to a new empty List. + // 4. Set disposableStack.[[DisposeCapability]] to NewDisposeCapability(). // 5. Return disposableStack. - return TRY(ordinary_create_from_constructor(vm, new_target, &Intrinsics::disposable_stack_prototype, Vector {})); + return TRY(ordinary_create_from_constructor(vm, new_target, &Intrinsics::disposable_stack_prototype, new_dispose_capability())); } } diff --git a/Libraries/LibJS/Runtime/DisposableStackPrototype.cpp b/Libraries/LibJS/Runtime/DisposableStackPrototype.cpp index 7fd01bab3ee..d44f951a9e0 100644 --- a/Libraries/LibJS/Runtime/DisposableStackPrototype.cpp +++ b/Libraries/LibJS/Runtime/DisposableStackPrototype.cpp @@ -1,11 +1,11 @@ /* * Copyright (c) 2022, David Tuin + * Copyright (c) 2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include -#include #include #include #include @@ -21,96 +21,26 @@ DisposableStackPrototype::DisposableStackPrototype(Realm& realm) void DisposableStackPrototype::initialize(Realm& realm) { - auto& vm = this->vm(); Base::initialize(realm); - u8 attr = Attribute::Writable | Attribute::Configurable; - define_native_accessor(realm, vm.names.disposed, disposed_getter, {}, attr); - define_native_function(realm, vm.names.dispose, dispose, 0, attr); - define_native_function(realm, vm.names.use, use, 1, attr); + auto& vm = this->vm(); + + u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function(realm, vm.names.adopt, adopt, 2, attr); define_native_function(realm, vm.names.defer, defer, 1, attr); + define_native_function(realm, vm.names.dispose, dispose, 0, attr); + define_native_accessor(realm, vm.names.disposed, disposed_getter, {}, attr); define_native_function(realm, vm.names.move, move_, 0, attr); + define_native_function(realm, vm.names.use, use, 1, attr); - // 11.3.3.7 DisposableStack.prototype [ @@dispose ] (), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype-@@dispose + // 12.3.3.7 DisposableStack.prototype [ @@dispose ] (), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype-@@dispose define_direct_property(vm.well_known_symbol_dispose(), get_without_side_effects(vm.names.dispose), attr); - // 11.3.3.8 DisposableStack.prototype [ @@toStringTag ], https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype-@@toStringTag + // 12.3.3.8 DisposableStack.prototype [ @@toStringTag ], https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype-@@toStringTag define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, vm.names.DisposableStack.as_string()), Attribute::Configurable); } -// 11.3.3.1 get DisposableStack.prototype.disposed, https://tc39.es/proposal-explicit-resource-management/#sec-get-disposablestack.prototype.disposed -JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::disposed_getter) -{ - // 1. Let disposableStack be the this value. - // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). - auto disposable_stack = TRY(typed_this_object(vm)); - - // 3. If disposableStack.[[DisposableState]] is disposed, return true. - if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) - return Value(true); - - // 4. Otherwise, return false. - return Value(false); -} - -// 11.3.3.2 DisposableStack.prototype.dispose (), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.dispose -JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::dispose) -{ - // 1. Let disposableStack be the this value. - // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). - auto disposable_stack = TRY(typed_this_object(vm)); - - // 3. If disposableStack.[[DisposableState]] is disposed, return undefined. - if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) - return js_undefined(); - - // 4. Set disposableStack.[[DisposableState]] to disposed. - disposable_stack->set_disposed(); - - // 5. Return DisposeResources(disposableStack, NormalCompletion(undefined)). - return *TRY(dispose_resources(vm, disposable_stack->disposable_resource_stack(), Completion { js_undefined() })); -} - -// 11.3.3.3 DisposableStack.prototype.use( value ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.use -JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::use) -{ - auto value = vm.argument(0); - - // 1. Let disposableStack be the this value. - // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). - auto disposable_stack = TRY(typed_this_object(vm)); - - // 3. If disposableStack.[[DisposableState]] is disposed, throw a ReferenceError exception. - if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) - return vm.throw_completion(ErrorType::DisposableStackAlreadyDisposed); - - // 4. If value is neither null nor undefined, then - if (!value.is_nullish()) { - // a. If Type(value) is not Object, throw a TypeError exception. - if (!value.is_object()) - return vm.throw_completion(ErrorType::NotAnObject, value.to_string_without_side_effects()); - - // FIXME: This should be TRY in the spec - // b. Let method be GetDisposeMethod(value, sync-dispose). - auto method = TRY(get_dispose_method(vm, value, Environment::InitializeBindingHint::SyncDispose)); - - // c. If method is undefined, then - if (!method.ptr()) { - // i. Throw a TypeError exception. - return vm.throw_completion(ErrorType::NoDisposeMethod, value.to_string_without_side_effects()); - } - // d. Else, - // i. Perform ? AddDisposableResource(disposableStack, value, sync-dispose, method). - // FIXME: Fairly sure this can't fail, see https://github.com/tc39/proposal-explicit-resource-management/pull/142 - MUST(add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), value, Environment::InitializeBindingHint::SyncDispose, method)); - } - - // 5. Return value. - return value; -} - -// 11.3.3.4 DisposableStack.prototype.adopt( value, onDispose ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.adopt +// 12.3.3.1 DisposableStack.prototype.adopt( value, onDispose ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.adopt JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::adopt) { auto& realm = *vm.current_realm(); @@ -128,33 +58,25 @@ JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::adopt) // 4. If IsCallable(onDispose) is false, throw a TypeError exception. if (!on_dispose.is_function()) - return vm.throw_completion(ErrorType::NotAFunction, on_dispose.to_string_without_side_effects()); + return vm.throw_completion(ErrorType::NotAFunction, on_dispose); - // 5. Let F be a new built-in function object as defined in 11.3.3.4.1. - // 6. Set F.[[Argument]] to value. - // 7. Set F.[[OnDisposeCallback]] to onDispose. - // 11.3.3.4.1 DisposableStack Adopt Callback Functions, https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack-adopt-callback-functions - // A DisposableStack adopt callback function is an anonymous built-in function object that has [[Argument]] and [[OnDisposeCallback]] internal slots. - auto function = NativeFunction::create( - realm, [argument = make_root(value), callback = make_root(on_dispose)](VM& vm) { - // When a DisposableStack adopt callback function is called, the following steps are taken: - // 1. Let F be the active function object. - // 2. Assert: IsCallable(F.[[OnDisposeCallback]]) is true. - VERIFY(callback.value().is_function()); + // 5. Let closure be a new Abstract Closure with no parameters that captures value and onDispose and performs the following steps when called: + auto closure = [value, on_dispose](VM& vm) mutable -> ThrowCompletionOr { + // a. Return ? Call(onDispose, undefined, « value »). + return TRY(call(vm, on_dispose.as_function(), js_undefined(), value)); + }; - // 3. Return Call(F.[[OnDisposeCallback]], undefined, « F.[[Argument]] »). - return call(vm, callback.value(), js_undefined(), argument.value()); - }, - 0, ""); + // 6. Let F be CreateBuiltinFunction(closure, 0, "", « »). + auto function = NativeFunction::create(realm, move(closure), 0, ""); - // 8. Perform ? AddDisposableResource(disposableStack, undefined, sync-dispose, F). - TRY(add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, function)); + // 7. Perform ? AddDisposableResource(disposableStack.[[DisposeCapability]], undefined, sync-dispose, F). + TRY(add_disposable_resource(vm, disposable_stack->dispose_capability(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, function)); - // 9. Return value. + // 8. Return value. return value; } -// 11.3.3.5 DisposableStack.prototype.defer( onDispose ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.defer +// 12.3.3.2 DisposableStack.prototype.defer( onDispose ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.defer JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::defer) { auto on_dispose = vm.argument(0); @@ -169,18 +91,53 @@ JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::defer) // 4. If IsCallable(onDispose) is false, throw a TypeError exception. if (!on_dispose.is_function()) - return vm.throw_completion(ErrorType::NotAFunction, on_dispose.to_string_without_side_effects()); + return vm.throw_completion(ErrorType::NotAFunction, on_dispose); - // 5. Perform ? AddDisposableResource(disposableStack, undefined, sync-dispose, onDispose). - TRY(add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, &on_dispose.as_function())); + // 5. Perform ? AddDisposableResource(disposableStack.[[DisposeCapability]], undefined, sync-dispose, onDispose). + TRY(add_disposable_resource(vm, disposable_stack->dispose_capability(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, on_dispose.as_function())); // 6. Return undefined. return js_undefined(); } -// 11.3.3.6 DisposableStack.prototype.move(), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.move +// 12.3.3.3 DisposableStack.prototype.dispose (), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.dispose +JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::dispose) +{ + // 1. Let disposableStack be the this value. + // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). + auto disposable_stack = TRY(typed_this_object(vm)); + + // 3. If disposableStack.[[DisposableState]] is disposed, return undefined. + if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) + return js_undefined(); + + // 4. Set disposableStack.[[DisposableState]] to disposed. + disposable_stack->set_disposed(); + + // 5. Return DisposeResources(disposableStack.[[DisposeCapability]], NormalCompletion(undefined)). + return *TRY(dispose_resources(vm, disposable_stack->dispose_capability(), normal_completion(js_undefined()))); +} + +// 12.3.3.4 get DisposableStack.prototype.disposed, https://tc39.es/proposal-explicit-resource-management/#sec-get-disposablestack.prototype.disposed +JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::disposed_getter) +{ + // 1. Let disposableStack be the this value. + // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). + auto disposable_stack = TRY(typed_this_object(vm)); + + // 3. If disposableStack.[[DisposableState]] is disposed, return true. + if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) + return true; + + // 4. Otherwise, return false. + return false; +} + +// 12.3.3.5 DisposableStack.prototype.move(), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.move JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::move_) { + auto& realm = *vm.current_realm(); + // 1. Let disposableStack be the this value. // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). auto disposable_stack = TRY(typed_this_object(vm)); @@ -189,15 +146,13 @@ JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::move_) if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) return vm.throw_completion(ErrorType::DisposableStackAlreadyDisposed); - // 4. Let newDisposableStack be ? OrdinaryCreateFromConstructor(%DisposableStack%, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposableResourceStack]] »). - auto new_disposable_stack = TRY(ordinary_create_from_constructor(vm, *vm.current_realm()->intrinsics().disposable_stack_constructor(), &Intrinsics::disposable_stack_prototype, disposable_stack->disposable_resource_stack())); - + // 4. Let newDisposableStack be ? OrdinaryCreateFromConstructor(%DisposableStack%, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposeCapability]] »). // 5. Set newDisposableStack.[[DisposableState]] to pending. - // 6. Set newDisposableStack.[[DisposableResourceStack]] to disposableStack.[[DisposableResourceStack]]. - // NOTE: Already done in the constructor + // 6. Set newDisposableStack.[[DisposeCapability]] to disposableStack.[[DisposeCapability]]. + auto new_disposable_stack = TRY(ordinary_create_from_constructor(vm, realm.intrinsics().disposable_stack_constructor(), &Intrinsics::disposable_stack_prototype, move(disposable_stack->dispose_capability()))); - // 7. Set disposableStack.[[DisposableResourceStack]] to a new empty List. - disposable_stack->disposable_resource_stack().clear(); + // 7. Set disposableStack.[[DisposeCapability]] to NewDisposeCapability(). + disposable_stack->dispose_capability() = new_dispose_capability(); // 8. Set disposableStack.[[DisposableState]] to disposed. disposable_stack->set_disposed(); @@ -206,4 +161,24 @@ JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::move_) return new_disposable_stack; } +// 12.3.3.6 DisposableStack.prototype.use( value ), https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack.prototype.use +JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::use) +{ + auto value = vm.argument(0); + + // 1. Let disposableStack be the this value. + // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). + auto disposable_stack = TRY(typed_this_object(vm)); + + // 3. If disposableStack.[[DisposableState]] is disposed, throw a ReferenceError exception. + if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed) + return vm.throw_completion(ErrorType::DisposableStackAlreadyDisposed); + + // 4. Perform ? AddDisposableResource(disposableStack.[[DisposeCapability]], value, sync-dispose). + TRY(add_disposable_resource(vm, disposable_stack->dispose_capability(), value, Environment::InitializeBindingHint::SyncDispose)); + + // 5. Return value. + return value; +} + } diff --git a/Libraries/LibJS/Runtime/DisposableStackPrototype.h b/Libraries/LibJS/Runtime/DisposableStackPrototype.h index a5741564572..3995ab7d218 100644 --- a/Libraries/LibJS/Runtime/DisposableStackPrototype.h +++ b/Libraries/LibJS/Runtime/DisposableStackPrototype.h @@ -1,12 +1,13 @@ /* * Copyright (c) 2022, David Tuin + * Copyright (c) 2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once -#include +#include #include namespace JS { @@ -22,12 +23,12 @@ public: private: explicit DisposableStackPrototype(Realm&); - JS_DECLARE_NATIVE_FUNCTION(disposed_getter); - JS_DECLARE_NATIVE_FUNCTION(dispose); - JS_DECLARE_NATIVE_FUNCTION(use); JS_DECLARE_NATIVE_FUNCTION(adopt); JS_DECLARE_NATIVE_FUNCTION(defer); + JS_DECLARE_NATIVE_FUNCTION(dispose); + JS_DECLARE_NATIVE_FUNCTION(disposed_getter); JS_DECLARE_NATIVE_FUNCTION(move_); + JS_DECLARE_NATIVE_FUNCTION(use); }; } diff --git a/Libraries/LibJS/Runtime/Environment.h b/Libraries/LibJS/Runtime/Environment.h index 53e93aafa29..5af506897b8 100644 --- a/Libraries/LibJS/Runtime/Environment.h +++ b/Libraries/LibJS/Runtime/Environment.h @@ -26,6 +26,7 @@ public: enum class InitializeBindingHint { Normal, SyncDispose, + AsyncDispose, }; virtual bool has_this_binding() const { return false; } diff --git a/Libraries/LibJS/SourceTextModule.cpp b/Libraries/LibJS/SourceTextModule.cpp index ea85d92f2b8..def620cf4fe 100644 --- a/Libraries/LibJS/SourceTextModule.cpp +++ b/Libraries/LibJS/SourceTextModule.cpp @@ -671,6 +671,7 @@ ThrowCompletionOr SourceTextModule::resolve_export(VM& vm, Depr } // 16.2.1.6.5 ExecuteModule ( [ capability ] ), https://tc39.es/ecma262/#sec-source-text-module-record-execute-module +// 9.1.1.1.2 ExecuteModule ( [ capability ] ), https://tc39.es/proposal-explicit-resource-management/#sec-source-text-module-record-execute-module ThrowCompletionOr SourceTextModule::execute_module(VM& vm, GC::Ptr capability) { dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] SourceTextModule::execute_module({}, PromiseCapability @ {})", filename(), capability.ptr()); @@ -708,6 +709,7 @@ ThrowCompletionOr SourceTextModule::execute_module(VM& vm, GC::Ptr SourceTextModule::execute_module(VM& vm, GC::Ptrlexical_environment; - VERIFY(is(*env)); + auto& env = verify_cast(*module_context->lexical_environment); - // e. Set result to DisposeResources(env, result). - result = dispose_resources(vm, static_cast(env.ptr()), result); + // e. Set result to Completion(DisposeResources(env.[[DisposeCapability]], result)). + result = dispose_resources(vm, env.dispose_capability(), result); // f. Suspend moduleContext and remove it from the execution context stack. vm.pop_execution_context();