mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 20:15:17 +00:00
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:
parent
f82b1c5a2d
commit
5ea0aa5f08
Notes:
github-actions[bot]
2025-01-17 19:47:35 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/5ea0aa5f08b Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3290
12 changed files with 375 additions and 258 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
// 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)
|
||||
{
|
||||
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<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
|
||||
// 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)
|
||||
{
|
||||
auto& heap = function.heap();
|
||||
|
@ -432,9 +435,10 @@ GC::Ref<FunctionEnvironment> 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<String> 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<void> add_disposable_resource(VM& vm, Vector<DisposableResource>& 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<void> add_disposable_resource(VM& vm, DisposeCapability& dispose_capability, Value value, Environment::InitializeBindingHint hint, GC::Ptr<FunctionObject> method)
|
||||
{
|
||||
Optional<DisposableResource> 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<TypeError>(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<TypeError>(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<DisposableResource> 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<DisposableResource> create_disposable_resource(VM& vm, Value value, Environment::InitializeBindingHint hint, GC::Ptr<FunctionObject> 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<TypeError>(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<TypeError>(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<TypeError>(ErrorType::NoDisposeMethod, value.to_string_without_side_effects());
|
||||
// iii. If method is undefined, throw a TypeError exception.
|
||||
if (!method)
|
||||
return vm.throw_completion<TypeError>(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<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.
|
||||
VERIFY(hint == Environment::InitializeBindingHint::SyncDispose);
|
||||
GC::Ptr<FunctionObject> 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<FunctionObject> 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<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
|
||||
Completion dispose_resources(VM& vm, Vector<DisposableResource> 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<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
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <LibGC/RootVector.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Runtime/CanonicalIndex.h>
|
||||
#include <LibJS/Runtime/Environment.h>
|
||||
#include <LibJS/Runtime/FunctionObject.h>
|
||||
#include <LibJS/Runtime/GlobalObject.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_mapped_arguments_object(VM&, FunctionObject&, Vector<FunctionParameter> const&, ReadonlySpan<Value> arguments, Environment&);
|
||||
|
||||
struct DisposableResource {
|
||||
Value resource_value;
|
||||
GC::Ref<FunctionObject> 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<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);
|
||||
Completion dispose(VM& vm, Value, GC::Ref<FunctionObject> method);
|
||||
Completion dispose_resources(VM& vm, Vector<DisposableResource> const& disposable, Completion completion);
|
||||
Completion dispose_resources(VM& vm, GC::Ptr<DeclarativeEnvironment> disposable, Completion completion);
|
||||
Completion dispose(VM&, Value, Environment::InitializeBindingHint, GC::Ptr<FunctionObject> method);
|
||||
Completion dispose_resources(VM&, DisposeCapability&, Completion);
|
||||
|
||||
ThrowCompletionOr<Value> perform_import_call(VM&, Value specifier, Value options_value);
|
||||
|
||||
|
|
|
@ -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<Binding> 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<void> 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;
|
||||
|
|
|
@ -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<Value> get_binding_value_direct(VM&, Binding const&) const;
|
||||
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:
|
||||
DeclarativeEnvironment();
|
||||
explicit DeclarativeEnvironment(Environment* parent_environment);
|
||||
|
@ -125,7 +125,7 @@ protected:
|
|||
private:
|
||||
Vector<Binding> m_bindings;
|
||||
HashMap<DeprecatedFlyString, size_t> m_bindings_assoc;
|
||||
Vector<DisposableResource> m_disposable_resource_stack;
|
||||
DisposeCapability m_dispose_capability;
|
||||
|
||||
u64 m_environment_serial_number { 0 };
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -10,19 +11,16 @@ namespace JS {
|
|||
|
||||
GC_DEFINE_ALLOCATOR(DisposableStack);
|
||||
|
||||
DisposableStack::DisposableStack(Vector<DisposableResource> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -23,19 +24,19 @@ public:
|
|||
Disposed
|
||||
};
|
||||
|
||||
[[nodiscard]] DisposableState disposable_state() const { return m_state; }
|
||||
[[nodiscard]] Vector<DisposableResource> const& disposable_resource_stack() const { return m_disposable_resource_stack; }
|
||||
[[nodiscard]] Vector<DisposableResource>& 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<DisposableResource> stack, Object& prototype);
|
||||
DisposableStack(DisposeCapability, Object& prototype);
|
||||
|
||||
virtual void visit_edges(Visitor& visitor) override;
|
||||
|
||||
Vector<DisposableResource> m_disposable_resource_stack;
|
||||
DisposableState m_state { DisposableState::Pending };
|
||||
DisposableState m_disposable_state { DisposableState::Pending };
|
||||
DisposeCapability m_dispose_capability;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* 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<Value> DisposableStackConstructor::call()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
@ -37,16 +38,16 @@ ThrowCompletionOr<Value> DisposableStackConstructor::call()
|
|||
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)
|
||||
{
|
||||
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<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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/DisposableStack.h>
|
||||
#include <LibJS/Runtime/DisposableStackConstructor.h>
|
||||
#include <LibJS/Runtime/DisposableStackPrototype.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
|
@ -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<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
|
||||
// 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<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.
|
||||
// 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<Value> {
|
||||
// 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<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).
|
||||
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<ReferenceError>(ErrorType::DisposableStackAlreadyDisposed);
|
||||
|
||||
// 4. Let newDisposableStack be ? OrdinaryCreateFromConstructor(%DisposableStack%, "%DisposableStack.prototype%", « [[DisposableState]], [[DisposableResourceStack]] »).
|
||||
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()));
|
||||
|
||||
// 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<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.
|
||||
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<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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
/*
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/FinalizationRegistry.h>
|
||||
#include <LibJS/Runtime/DisposableStack.h>
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ public:
|
|||
enum class InitializeBindingHint {
|
||||
Normal,
|
||||
SyncDispose,
|
||||
AsyncDispose,
|
||||
};
|
||||
|
||||
virtual bool has_this_binding() const { return false; }
|
||||
|
|
|
@ -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
|
||||
// 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)
|
||||
{
|
||||
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) {
|
||||
// a. Assert: capability is not present.
|
||||
VERIFY(capability == nullptr);
|
||||
|
||||
// b. Push moduleContext onto the execution context stack; moduleContext is now the running execution 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.
|
||||
auto env = module_context->lexical_environment;
|
||||
VERIFY(is<DeclarativeEnvironment>(*env));
|
||||
auto& env = verify_cast<DeclarativeEnvironment>(*module_context->lexical_environment);
|
||||
|
||||
// e. Set result to DisposeResources(env, result).
|
||||
result = dispose_resources(vm, static_cast<DeclarativeEnvironment*>(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();
|
||||
|
|
Loading…
Add table
Reference in a new issue