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.
This commit is contained in:
Timothy Flynn 2025-01-16 16:51:26 -05:00 committed by Andreas Kling
commit 5ea0aa5f08
Notes: github-actions[bot] 2025-01-17 19:47:35 +00:00
12 changed files with 375 additions and 258 deletions

View file

@ -122,6 +122,7 @@
JS_ENUMERATE_TYPED_ARRAYS JS_ENUMERATE_TYPED_ARRAYS
#define JS_ENUMERATE_WELL_KNOWN_SYMBOLS \ #define JS_ENUMERATE_WELL_KNOWN_SYMBOLS \
__JS_ENUMERATE(asyncDispose, async_dispose) \
__JS_ENUMERATE(asyncIterator, async_iterator) \ __JS_ENUMERATE(asyncIterator, async_iterator) \
__JS_ENUMERATE(dispose, dispose) \ __JS_ENUMERATE(dispose, dispose) \
__JS_ENUMERATE(hasInstance, has_instance) \ __JS_ENUMERATE(hasInstance, has_instance) \
@ -163,6 +164,8 @@ class Completion;
class Console; class Console;
class CyclicModule; class CyclicModule;
class DeclarativeEnvironment; class DeclarativeEnvironment;
struct DisposeCapability;
struct DisposableResource;
class ECMAScriptFunctionObject; class ECMAScriptFunctionObject;
class Environment; class Environment;
class Error; class Error;

View file

@ -387,13 +387,15 @@ ThrowCompletionOr<Object*> get_prototype_from_constructor(VM& vm, FunctionObject
} }
// 9.1.2.2 NewDeclarativeEnvironment ( E ), https://tc39.es/ecma262/#sec-newdeclarativeenvironment // 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<DeclarativeEnvironment> new_declarative_environment(Environment& environment) GC::Ref<DeclarativeEnvironment> new_declarative_environment(Environment& environment)
{ {
auto& heap = environment.heap(); auto& heap = environment.heap();
// 1. Let env be a new Declarative Environment Record containing no bindings. // 1. Let env be a new Declarative Environment Record containing no bindings.
// 2. Set env.[[OuterEnv]] to E. // 2. Set env.[[OuterEnv]] to E.
// 3. Return env. // 3. Set env.[[DisposeCapability]] to NewDisposeCapability().
// 4. Return env.
return heap.allocate<DeclarativeEnvironment>(&environment); return heap.allocate<DeclarativeEnvironment>(&environment);
} }
@ -411,6 +413,7 @@ GC::Ref<ObjectEnvironment> new_object_environment(Object& object, bool is_with_e
} }
// 9.1.2.4 NewFunctionEnvironment ( F, newTarget ), https://tc39.es/ecma262/#sec-newfunctionenvironment // 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<FunctionEnvironment> new_function_environment(ECMAScriptFunctionObject& function, Object* new_target) GC::Ref<FunctionEnvironment> new_function_environment(ECMAScriptFunctionObject& function, Object* new_target)
{ {
auto& heap = function.heap(); auto& heap = function.heap();
@ -432,9 +435,10 @@ GC::Ref<FunctionEnvironment> new_function_environment(ECMAScriptFunctionObject&
env->set_new_target(new_target ?: js_undefined()); env->set_new_target(new_target ?: js_undefined());
// 6. Set env.[[OuterEnv]] to F.[[Environment]]. // 6. Set env.[[OuterEnv]] to F.[[Environment]].
// 7. Set env.[[DisposeCapability]] to NewDisposeCapability().
// NOTE: Done in step 1 via the FunctionEnvironment constructor. // NOTE: Done in step 1 via the FunctionEnvironment constructor.
// 7. Return env. // 8. Return env.
return env; return env;
} }
@ -1403,159 +1407,278 @@ ThrowCompletionOr<String> get_substitution(VM& vm, Utf16View const& matched, Utf
return MUST(Utf16View { result }.to_utf8(Utf16View::AllowInvalidCodeUnits::Yes)); 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 void DisposeCapability::visit_edges(GC::Cell::Visitor& visitor) const
ThrowCompletionOr<void> add_disposable_resource(VM& vm, Vector<DisposableResource>& disposable, Value value, Environment::InitializeBindingHint hint, FunctionObject* method)
{ {
// NOTE: For now only sync is a valid hint for (auto const& disposable_resource : disposable_resource_stack)
VERIFY(hint == Environment::InitializeBindingHint::SyncDispose); 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<void> add_disposable_resource(VM& vm, DisposeCapability& dispose_capability, Value value, Environment::InitializeBindingHint hint, GC::Ptr<FunctionObject> method)
{
Optional<DisposableResource> resource; Optional<DisposableResource> resource;
// 1. If method is not present then, // 1. If method is not present then,
if (!method) { if (!method) {
// a. If V is null or undefined, return NormalCompletion(empty). // a. If V is either null or undefined and hint is sync-dispose, then
if (value.is_nullish()) if (value.is_nullish() && hint == Environment::InitializeBindingHint::SyncDispose) {
// i. Return unused.
return {}; return {};
}
// b. If Type(V) is not Object, throw a TypeError exception. // b. NOTE: When V is either null or undefined and hint is async-dispose, we record that the resource was evaluated
if (!value.is_object()) // to ensure we will still perform an Await when resources are later disposed.
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, value.to_string_without_side_effects());
// c. Let resource be ? CreateDisposableResource(V, hint). // c. Let resource be ? CreateDisposableResource(V, hint).
resource = TRY(create_disposable_resource(vm, value, hint)); resource = TRY(create_disposable_resource(vm, value, hint));
} }
// 2. Else, // 2. Else,
else { else {
// a. If V is null or undefined, then // a. Assert: V is undefined.
if (value.is_nullish()) { VERIFY(value.is_undefined());
// 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<TypeError>(ErrorType::NotAnObject, value.to_string_without_side_effects());
// ii. Let resource be ? CreateDisposableResource(V, hint, method). // b. Let resource be ? CreateDisposableResource(undefined, hint, method).
resource = TRY(create_disposable_resource(vm, value, hint, method)); resource = TRY(create_disposable_resource(vm, js_undefined(), hint, method));
}
} }
// 3. Append resource to disposable.[[DisposableResourceStack]]. // 3. Append resource to disposeCapability.[[DisposableResourceStack]].
VERIFY(resource.has_value()); dispose_capability.disposable_resource_stack.append(resource.release_value());
disposable.append(resource.release_value());
// 4. Return NormalCompletion(empty). // 4. Return unused.
return {}; return {};
} }
// 2.1.3 CreateDisposableResource ( V, hint [ , method ] ), https://tc39.es/proposal-explicit-resource-management/#sec-createdisposableresource // 2.1.5 CreateDisposableResource ( V, hint [ , method ] ), https://tc39.es/proposal-explicit-resource-management/#sec-createdisposableresource
ThrowCompletionOr<DisposableResource> create_disposable_resource(VM& vm, Value value, Environment::InitializeBindingHint hint, FunctionObject* method) ThrowCompletionOr<DisposableResource> create_disposable_resource(VM& vm, Value value, Environment::InitializeBindingHint hint, GC::Ptr<FunctionObject> method)
{ {
// 1. If method is not present, then // 1. If method is not present, then
if (!method) { if (!method) {
// a. If V is undefined, throw a TypeError exception. // a. If V is either null or undefined, then
if (value.is_undefined()) if (value.is_nullish()) {
return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "value"); // 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<TypeError>(ErrorType::NotAnObject, value);
// b. Set method to ? GetDisposeMethod(V, hint). // ii. Set method to ? GetDisposeMethod(V, hint).
method = TRY(get_dispose_method(vm, value, hint)); method = TRY(get_dispose_method(vm, value, hint));
// c. If method is undefined, throw a TypeError exception. // iii. If method is undefined, throw a TypeError exception.
if (!method) if (!method)
return vm.throw_completion<TypeError>(ErrorType::NoDisposeMethod, value.to_string_without_side_effects()); return vm.throw_completion<TypeError>(ErrorType::NoDisposeMethod, value);
}
} }
// 2. Else, // 2. Else,
// a. If IsCallable(method) is false, throw a TypeError exception. else {
// NOTE: This is guaranteed to never occur from the type. // a. If IsCallable(method) is false, throw a TypeError exception.
VERIFY(method); // NOTE: This is guaranteed to never occur due to its type.
}
// 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }. // 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 { return DisposableResource {
value, .resource_value = value.is_object() ? GC::Ptr { value.as_object() } : nullptr,
*method .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<GC::Ptr<FunctionObject>> get_dispose_method(VM& vm, Value value, Environment::InitializeBindingHint hint) ThrowCompletionOr<GC::Ptr<FunctionObject>> get_dispose_method(VM& vm, Value value, Environment::InitializeBindingHint hint)
{ {
// NOTE: We only have sync dispose for now which means we ignore step 1. GC::Ptr<FunctionObject> method;
VERIFY(hint == Environment::InitializeBindingHint::SyncDispose);
// 2. Else, // 1. If hint is async-dispose, then
// a. Let method be ? GetMethod(V, @@dispose). if (hint == Environment::InitializeBindingHint::AsyncDispose) {
return TRY(value.get_method(vm, vm.well_known_symbol_dispose())); // 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 // b. If method is undefined, then
Completion dispose(VM& vm, Value value, GC::Ref<FunctionObject> method) if (!method) {
{ // i. Set method to ? GetMethod(V, @@dispose).
// 1. Let result be ? Call(method, V). method = TRY(value.get_method(vm, vm.well_known_symbol_dispose()));
[[maybe_unused]] auto result = TRY(call(vm, *method, value));
// NOTE: Hint can only be sync-dispose so we ignore step 2. // ii. If method is not undefined, then
// 2. If hint is async-dispose and result is not undefined, then if (method) {
// a. Perform ? Await(result). auto& realm = *vm.current_realm();
// 3. Return undefined. // 1. Let closure be a new Abstract Closure with no parameters that captures method and performs the following steps when called:
return js_undefined(); auto closure = [&realm, method](VM& vm) -> ThrowCompletionOr<Value> {
} // 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 // b. Let promiseCapability be ! NewPromiseCapability(%Promise%).
Completion dispose_resources(VM& vm, Vector<DisposableResource> const& disposable, Completion completion) auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor()));
{
// 1. If disposable is not undefined, then
// NOTE: At this point disposable is always defined.
// a. For each resource of disposable.[[DisposableResourceStack]], in reverse list order, do // c. Let result be Completion(Call(method, O)).
for (auto const& resource : disposable.in_reverse()) { // d. IfAbruptRejectPromise(result, promiseCapability).
// i. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]). TRY_OR_REJECT(vm, promise_capability, call(vm, method, object));
auto result = dispose(vm, resource.resource_value, resource.dispose_method);
// ii. If result.[[Type]] is throw, then // e. Perform ? Call(promiseCapability.[[Resolve]], undefined, « undefined »).
if (result.is_error()) { TRY(call(vm, *promise_capability->resolve(), js_undefined(), js_undefined()));
// 1. If completion.[[Type]] is throw, then
if (completion.is_error()) {
// a. Set result to result.[[Value]].
// b. Let suppressed be completion.[[Value]]. // f. Return promiseCapability.[[Promise]].
auto suppressed = completion.value().value(); return promise_capability->promise();
};
// c. Let error be a newly created SuppressedError object. // 2. NOTE: This function is not observable to user code. It is used to ensure that a Promise returned
auto error = SuppressedError::create(*vm.current_realm()); // 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 }). // 3. Return CreateBuiltinFunction(closure, 0, "", « »).
MUST(error->define_property_or_throw(vm.names.error, { .value = result.value(), .writable = true, .enumerable = true, .configurable = true })); return NativeFunction::create(realm, move(closure), 0, "");
// 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;
} }
} }
} }
// 2. Else,
else {
// a. Let method be ? GetMethod(V, @@dispose).
method = TRY(value.get_method(vm, vm.well_known_symbol_dispose()));
}
// 2. Return completion. // 3. Return method.
return completion; return method;
} }
Completion dispose_resources(VM& vm, GC::Ptr<DeclarativeEnvironment> 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<FunctionObject> method)
{ {
// 1. If disposable is not undefined, then Value result;
if (disposable)
return dispose_resources(vm, disposable->disposable_resource_stack(), completion);
// 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; return completion;
} }

View file

@ -12,6 +12,7 @@
#include <LibGC/RootVector.h> #include <LibGC/RootVector.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
#include <LibJS/Runtime/CanonicalIndex.h> #include <LibJS/Runtime/CanonicalIndex.h>
#include <LibJS/Runtime/Environment.h>
#include <LibJS/Runtime/FunctionObject.h> #include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Iterator.h> #include <LibJS/Runtime/Iterator.h>
@ -44,16 +45,28 @@ ThrowCompletionOr<Object*> get_prototype_from_constructor(VM&, FunctionObject co
Object* create_unmapped_arguments_object(VM&, ReadonlySpan<Value> arguments); Object* create_unmapped_arguments_object(VM&, ReadonlySpan<Value> arguments);
Object* create_mapped_arguments_object(VM&, FunctionObject&, Vector<FunctionParameter> const&, ReadonlySpan<Value> arguments, Environment&); Object* create_mapped_arguments_object(VM&, FunctionObject&, Vector<FunctionParameter> const&, ReadonlySpan<Value> arguments, Environment&);
struct DisposableResource { // 2.1.1 DisposeCapability Records, https://tc39.es/proposal-explicit-resource-management/#sec-disposecapability-records
Value resource_value; struct DisposeCapability {
GC::Ref<FunctionObject> dispose_method; void visit_edges(GC::Cell::Visitor&) const;
Vector<DisposableResource> disposable_resource_stack; // [[DisposableResourceStack]]
}; };
ThrowCompletionOr<void> add_disposable_resource(VM&, Vector<DisposableResource>& disposable, Value, Environment::InitializeBindingHint, FunctionObject* = nullptr);
ThrowCompletionOr<DisposableResource> 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<Object> resource_value; // [[ResourceValue]]
Environment::InitializeBindingHint hint; // [[Hint]]
GC::Ptr<FunctionObject> dispose_method; // [[DisposeMethod]]
};
DisposeCapability new_dispose_capability();
ThrowCompletionOr<void> add_disposable_resource(VM&, DisposeCapability&, Value, Environment::InitializeBindingHint, GC::Ptr<FunctionObject> = {});
ThrowCompletionOr<DisposableResource> create_disposable_resource(VM&, Value, Environment::InitializeBindingHint, GC::Ptr<FunctionObject> = {});
ThrowCompletionOr<GC::Ptr<FunctionObject>> get_dispose_method(VM&, Value, Environment::InitializeBindingHint); ThrowCompletionOr<GC::Ptr<FunctionObject>> get_dispose_method(VM&, Value, Environment::InitializeBindingHint);
Completion dispose(VM& vm, Value, GC::Ref<FunctionObject> method); Completion dispose(VM&, Value, Environment::InitializeBindingHint, GC::Ptr<FunctionObject> method);
Completion dispose_resources(VM& vm, Vector<DisposableResource> const& disposable, Completion completion); Completion dispose_resources(VM&, DisposeCapability&, Completion);
Completion dispose_resources(VM& vm, GC::Ptr<DeclarativeEnvironment> disposable, Completion completion);
ThrowCompletionOr<Value> perform_import_call(VM&, Value specifier, Value options_value); ThrowCompletionOr<Value> perform_import_call(VM&, Value specifier, Value options_value);

View file

@ -25,30 +25,30 @@ DeclarativeEnvironment* DeclarativeEnvironment::create_for_per_iteration_binding
DeclarativeEnvironment::DeclarativeEnvironment() DeclarativeEnvironment::DeclarativeEnvironment()
: Environment(nullptr, IsDeclarative::Yes) : Environment(nullptr, IsDeclarative::Yes)
, m_dispose_capability(new_dispose_capability())
{ {
} }
DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_environment) DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_environment)
: Environment(parent_environment, IsDeclarative::Yes) : Environment(parent_environment, IsDeclarative::Yes)
, m_dispose_capability(new_dispose_capability())
{ {
} }
DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_environment, ReadonlySpan<Binding> bindings) DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_environment, ReadonlySpan<Binding> bindings)
: Environment(parent_environment, IsDeclarative::Yes) : Environment(parent_environment, IsDeclarative::Yes)
, m_bindings(bindings) , m_bindings(bindings)
, m_dispose_capability(new_dispose_capability())
{ {
} }
void DeclarativeEnvironment::visit_edges(Visitor& visitor) void DeclarativeEnvironment::visit_edges(Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
m_dispose_capability.visit_edges(visitor);
for (auto& binding : m_bindings) for (auto& binding : m_bindings)
visitor.visit(binding.value); 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 // 9.1.1.1.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n
@ -122,9 +122,9 @@ ThrowCompletionOr<void> DeclarativeEnvironment::initialize_binding_direct(VM& vm
// 1. Assert: envRec must have an uninitialized binding for N. // 1. Assert: envRec must have an uninitialized binding for N.
VERIFY(binding.initialized == false); 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) 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. // 3. Set the bound value for N in envRec to V.
binding.value = value; binding.value = value;

View file

@ -69,13 +69,13 @@ public:
[[nodiscard]] u64 environment_serial_number() const { return m_environment_serial_number; } [[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: private:
ThrowCompletionOr<Value> get_binding_value_direct(VM&, Binding const&) const; ThrowCompletionOr<Value> get_binding_value_direct(VM&, Binding const&) const;
ThrowCompletionOr<void> set_mutable_binding_direct(VM&, Binding&, Value, bool strict); ThrowCompletionOr<void> set_mutable_binding_direct(VM&, Binding&, Value, bool strict);
friend Completion dispose_resources(VM&, GC::Ptr<DeclarativeEnvironment>, Completion);
Vector<DisposableResource> const& disposable_resource_stack() const { return m_disposable_resource_stack; }
protected: protected:
DeclarativeEnvironment(); DeclarativeEnvironment();
explicit DeclarativeEnvironment(Environment* parent_environment); explicit DeclarativeEnvironment(Environment* parent_environment);
@ -125,7 +125,7 @@ protected:
private: private:
Vector<Binding> m_bindings; Vector<Binding> m_bindings;
HashMap<DeprecatedFlyString, size_t> m_bindings_assoc; HashMap<DeprecatedFlyString, size_t> m_bindings_assoc;
Vector<DisposableResource> m_disposable_resource_stack; DisposeCapability m_dispose_capability;
u64 m_environment_serial_number { 0 }; u64 m_environment_serial_number { 0 };
}; };

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2022, David Tuin <davidot@serenityos.org> * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -10,19 +11,16 @@ namespace JS {
GC_DEFINE_ALLOCATOR(DisposableStack); GC_DEFINE_ALLOCATOR(DisposableStack);
DisposableStack::DisposableStack(Vector<DisposableResource> stack, Object& prototype) DisposableStack::DisposableStack(DisposeCapability dispose_capability, Object& prototype)
: Object(ConstructWithPrototypeTag::Tag, prototype) : Object(ConstructWithPrototypeTag::Tag, prototype)
, m_disposable_resource_stack(move(stack)) , m_dispose_capability(move(dispose_capability))
{ {
} }
void DisposableStack::visit_edges(Cell::Visitor& visitor) void DisposableStack::visit_edges(Cell::Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
for (auto& resource : m_disposable_resource_stack) { m_dispose_capability.visit_edges(visitor);
visitor.visit(resource.resource_value);
visitor.visit(resource.dispose_method);
}
} }
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2022, David Tuin <davidot@serenityos.org> * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -23,19 +24,19 @@ public:
Disposed Disposed
}; };
[[nodiscard]] DisposableState disposable_state() const { return m_state; } [[nodiscard]] DisposableState disposable_state() const { return m_disposable_state; }
[[nodiscard]] Vector<DisposableResource> const& disposable_resource_stack() const { return m_disposable_resource_stack; } void set_disposed() { m_disposable_state = DisposableState::Disposed; }
[[nodiscard]] Vector<DisposableResource>& disposable_resource_stack() { return m_disposable_resource_stack; }
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: private:
DisposableStack(Vector<DisposableResource> stack, Object& prototype); DisposableStack(DisposeCapability, Object& prototype);
virtual void visit_edges(Visitor& visitor) override; virtual void visit_edges(Visitor& visitor) override;
Vector<DisposableResource> m_disposable_resource_stack; DisposableState m_disposable_state { DisposableState::Pending };
DisposableState m_state { DisposableState::Pending }; DisposeCapability m_dispose_capability;
}; };
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2022, David Tuin <davidot@serenityos.org> * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -22,13 +23,13 @@ void DisposableStackConstructor::initialize(Realm& realm)
auto& vm = this->vm(); auto& vm = this->vm();
Base::initialize(realm); 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.prototype, realm.intrinsics().disposable_stack_prototype(), 0);
define_direct_property(vm.names.length, Value(0), Attribute::Configurable); 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<Value> DisposableStackConstructor::call() ThrowCompletionOr<Value> DisposableStackConstructor::call()
{ {
auto& vm = this->vm(); auto& vm = this->vm();
@ -37,16 +38,16 @@ ThrowCompletionOr<Value> DisposableStackConstructor::call()
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, vm.names.DisposableStack); return vm.throw_completion<TypeError>(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<GC::Ref<Object>> DisposableStackConstructor::construct(FunctionObject& new_target) ThrowCompletionOr<GC::Ref<Object>> DisposableStackConstructor::construct(FunctionObject& new_target)
{ {
auto& vm = this->vm(); 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. // 3. Set disposableStack.[[DisposableState]] to pending.
// 4. Set disposableStack.[[DisposableResourceStack]] to a new empty List. // 4. Set disposableStack.[[DisposeCapability]] to NewDisposeCapability().
// 5. Return disposableStack. // 5. Return disposableStack.
return TRY(ordinary_create_from_constructor<DisposableStack>(vm, new_target, &Intrinsics::disposable_stack_prototype, Vector<DisposableResource> {})); return TRY(ordinary_create_from_constructor<DisposableStack>(vm, new_target, &Intrinsics::disposable_stack_prototype, new_dispose_capability()));
} }
} }

View file

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2022, David Tuin <davidot@serenityos.org> * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/DisposableStack.h>
#include <LibJS/Runtime/DisposableStackConstructor.h> #include <LibJS/Runtime/DisposableStackConstructor.h>
#include <LibJS/Runtime/DisposableStackPrototype.h> #include <LibJS/Runtime/DisposableStackPrototype.h>
#include <LibJS/Runtime/NativeFunction.h> #include <LibJS/Runtime/NativeFunction.h>
@ -21,96 +21,26 @@ DisposableStackPrototype::DisposableStackPrototype(Realm& realm)
void DisposableStackPrototype::initialize(Realm& realm) void DisposableStackPrototype::initialize(Realm& realm)
{ {
auto& vm = this->vm();
Base::initialize(realm); Base::initialize(realm);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_accessor(realm, vm.names.disposed, disposed_getter, {}, attr); auto& vm = this->vm();
define_native_function(realm, vm.names.dispose, dispose, 0, attr);
define_native_function(realm, vm.names.use, use, 1, attr); u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.adopt, adopt, 2, attr); 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.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.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); 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); 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 // 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::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<ReferenceError>(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<TypeError>(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<TypeError>(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
JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::adopt) JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::adopt)
{ {
auto& realm = *vm.current_realm(); 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. // 4. If IsCallable(onDispose) is false, throw a TypeError exception.
if (!on_dispose.is_function()) if (!on_dispose.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, on_dispose.to_string_without_side_effects()); return vm.throw_completion<TypeError>(ErrorType::NotAFunction, on_dispose);
// 5. Let F be a new built-in function object as defined in 11.3.3.4.1. // 5. Let closure be a new Abstract Closure with no parameters that captures value and onDispose and performs the following steps when called:
// 6. Set F.[[Argument]] to value. auto closure = [value, on_dispose](VM& vm) mutable -> ThrowCompletionOr<Value> {
// 7. Set F.[[OnDisposeCallback]] to onDispose. // a. Return ? Call(onDispose, undefined, « value »).
// 11.3.3.4.1 DisposableStack Adopt Callback Functions, https://tc39.es/proposal-explicit-resource-management/#sec-disposablestack-adopt-callback-functions return TRY(call(vm, on_dispose.as_function(), js_undefined(), value));
// 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());
// 3. Return Call(F.[[OnDisposeCallback]], undefined, « F.[[Argument]] »). // 6. Let F be CreateBuiltinFunction(closure, 0, "", « »).
return call(vm, callback.value(), js_undefined(), argument.value()); auto function = NativeFunction::create(realm, move(closure), 0, "");
},
0, "");
// 8. Perform ? AddDisposableResource(disposableStack, undefined, sync-dispose, F). // 7. Perform ? AddDisposableResource(disposableStack.[[DisposeCapability]], undefined, sync-dispose, F).
TRY(add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, function)); TRY(add_disposable_resource(vm, disposable_stack->dispose_capability(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, function));
// 9. Return value. // 8. Return value.
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) JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::defer)
{ {
auto on_dispose = vm.argument(0); 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. // 4. If IsCallable(onDispose) is false, throw a TypeError exception.
if (!on_dispose.is_function()) if (!on_dispose.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, on_dispose.to_string_without_side_effects()); return vm.throw_completion<TypeError>(ErrorType::NotAFunction, on_dispose);
// 5. Perform ? AddDisposableResource(disposableStack, undefined, sync-dispose, onDispose). // 5. Perform ? AddDisposableResource(disposableStack.[[DisposeCapability]], undefined, sync-dispose, onDispose).
TRY(add_disposable_resource(vm, disposable_stack->disposable_resource_stack(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, &on_dispose.as_function())); TRY(add_disposable_resource(vm, disposable_stack->dispose_capability(), js_undefined(), Environment::InitializeBindingHint::SyncDispose, on_dispose.as_function()));
// 6. Return undefined. // 6. Return undefined.
return js_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_) JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::move_)
{ {
auto& realm = *vm.current_realm();
// 1. Let disposableStack be the this value. // 1. Let disposableStack be the this value.
// 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]). // 2. Perform ? RequireInternalSlot(disposableStack, [[DisposableState]]).
auto disposable_stack = TRY(typed_this_object(vm)); 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) if (disposable_stack->disposable_state() == DisposableStack::DisposableState::Disposed)
return vm.throw_completion<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed); return vm.throw_completion<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed);
// 4. Let newDisposableStack be ? OrdinaryCreateFromConstructor(%DisposableStack%, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposableResourceStack]] »). // 4. Let newDisposableStack be ? OrdinaryCreateFromConstructor(%DisposableStack%, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposeCapability]] »).
auto new_disposable_stack = TRY(ordinary_create_from_constructor<DisposableStack>(vm, *vm.current_realm()->intrinsics().disposable_stack_constructor(), &Intrinsics::disposable_stack_prototype, disposable_stack->disposable_resource_stack()));
// 5. Set newDisposableStack.[[DisposableState]] to pending. // 5. Set newDisposableStack.[[DisposableState]] to pending.
// 6. Set newDisposableStack.[[DisposableResourceStack]] to disposableStack.[[DisposableResourceStack]]. // 6. Set newDisposableStack.[[DisposeCapability]] to disposableStack.[[DisposeCapability]].
// NOTE: Already done in the constructor auto new_disposable_stack = TRY(ordinary_create_from_constructor<DisposableStack>(vm, realm.intrinsics().disposable_stack_constructor(), &Intrinsics::disposable_stack_prototype, move(disposable_stack->dispose_capability())));
// 7. Set disposableStack.[[DisposableResourceStack]] to a new empty List. // 7. Set disposableStack.[[DisposeCapability]] to NewDisposeCapability().
disposable_stack->disposable_resource_stack().clear(); disposable_stack->dispose_capability() = new_dispose_capability();
// 8. Set disposableStack.[[DisposableState]] to disposed. // 8. Set disposableStack.[[DisposableState]] to disposed.
disposable_stack->set_disposed(); disposable_stack->set_disposed();
@ -206,4 +161,24 @@ JS_DEFINE_NATIVE_FUNCTION(DisposableStackPrototype::move_)
return new_disposable_stack; 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<ReferenceError>(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;
}
} }

View file

@ -1,12 +1,13 @@
/* /*
* Copyright (c) 2022, David Tuin <davidot@serenityos.org> * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#pragma once #pragma once
#include <LibJS/Runtime/FinalizationRegistry.h> #include <LibJS/Runtime/DisposableStack.h>
#include <LibJS/Runtime/PrototypeObject.h> #include <LibJS/Runtime/PrototypeObject.h>
namespace JS { namespace JS {
@ -22,12 +23,12 @@ public:
private: private:
explicit DisposableStackPrototype(Realm&); 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(adopt);
JS_DECLARE_NATIVE_FUNCTION(defer); 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(move_);
JS_DECLARE_NATIVE_FUNCTION(use);
}; };
} }

View file

@ -26,6 +26,7 @@ public:
enum class InitializeBindingHint { enum class InitializeBindingHint {
Normal, Normal,
SyncDispose, SyncDispose,
AsyncDispose,
}; };
virtual bool has_this_binding() const { return false; } virtual bool has_this_binding() const { return false; }

View file

@ -671,6 +671,7 @@ ThrowCompletionOr<ResolvedBinding> SourceTextModule::resolve_export(VM& vm, Depr
} }
// 16.2.1.6.5 ExecuteModule ( [ capability ] ), https://tc39.es/ecma262/#sec-source-text-module-record-execute-module // 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<void> SourceTextModule::execute_module(VM& vm, GC::Ptr<PromiseCapability> capability) ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GC::Ptr<PromiseCapability> capability)
{ {
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] SourceTextModule::execute_module({}, PromiseCapability @ {})", filename(), capability.ptr()); dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] SourceTextModule::execute_module({}, PromiseCapability @ {})", filename(), capability.ptr());
@ -708,6 +709,7 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GC::Ptr<Promise
if (!m_has_top_level_await) { if (!m_has_top_level_await) {
// a. Assert: capability is not present. // a. Assert: capability is not present.
VERIFY(capability == nullptr); VERIFY(capability == nullptr);
// b. Push moduleContext onto the execution context stack; moduleContext is now the running execution context. // b. Push moduleContext onto the execution context stack; moduleContext is now the running execution context.
TRY(vm.push_execution_context(*module_context, {})); TRY(vm.push_execution_context(*module_context, {}));
@ -730,11 +732,10 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GC::Ptr<Promise
} }
// d. Let env be moduleContext's LexicalEnvironment. // d. Let env be moduleContext's LexicalEnvironment.
auto env = module_context->lexical_environment; auto& env = verify_cast<DeclarativeEnvironment>(*module_context->lexical_environment);
VERIFY(is<DeclarativeEnvironment>(*env));
// e. Set result to DisposeResources(env, result). // e. Set result to Completion(DisposeResources(env.[[DisposeCapability]], result)).
result = dispose_resources(vm, static_cast<DeclarativeEnvironment*>(env.ptr()), result); result = dispose_resources(vm, env.dispose_capability(), result);
// f. Suspend moduleContext and remove it from the execution context stack. // f. Suspend moduleContext and remove it from the execution context stack.
vm.pop_execution_context(); vm.pop_execution_context();