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();