ladybird/Userland/Libraries/LibJS/Runtime/PromiseCapability.cpp
Linus Groh f27bc56e5e LibJS: Capture promise capability in new_promise_capability() executor
This is how the spec suggests implementing this; we need to be slightly
more verbose as our PromiseCapability implementation cannot hold
arbitrary JS values.

Unfortunately it makes the error message slightly more ambiguous as we
no longer expose the non-function value to the outer scope (we could!),
but at least we don't UAF the stack allocated values anymore :^)
2022-10-02 23:02:27 +01:00

100 lines
4.4 KiB
C++

/*
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/PromiseCapability.h>
namespace JS {
NonnullGCPtr<PromiseCapability> PromiseCapability::create(VM& vm, GCPtr<Object> promise, GCPtr<FunctionObject> resolve, GCPtr<FunctionObject> reject)
{
return NonnullGCPtr { *vm.heap().allocate_without_realm<PromiseCapability>(promise, resolve, reject) };
}
PromiseCapability::PromiseCapability(GCPtr<Object> promise, GCPtr<FunctionObject> resolve, GCPtr<FunctionObject> reject)
: m_promise(promise)
, m_resolve(resolve)
, m_reject(reject)
{
}
void PromiseCapability::visit_edges(Cell::Visitor& visitor)
{
visitor.visit(m_promise);
visitor.visit(m_resolve);
visitor.visit(m_reject);
}
// 27.2.1.5 NewPromiseCapability ( C ), https://tc39.es/ecma262/#sec-newpromisecapability
ThrowCompletionOr<NonnullGCPtr<PromiseCapability>> new_promise_capability(VM& vm, Value constructor)
{
auto& realm = *vm.current_realm();
// 1. If IsConstructor(C) is false, throw a TypeError exception.
if (!constructor.is_constructor())
return vm.throw_completion<TypeError>(ErrorType::NotAConstructor, constructor.to_string_without_side_effects());
// 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1).
// 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }.
auto promise_capability = PromiseCapability::create(vm, nullptr, nullptr, nullptr);
// 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called:
// NOTE: Additionally to capturing the GC-allocated promise capability, we also create handles for
// the resolve and reject values to keep them alive within the closure, as it may outlive the former.
auto executor_closure = [&promise_capability, resolve_handle = make_handle({}), reject_handle = make_handle({})](auto& vm) mutable -> ThrowCompletionOr<Value> {
auto resolve = vm.argument(0);
auto reject = vm.argument(1);
// No idea what other engines say here.
// a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception.
if (!resolve_handle.is_null())
return vm.template throw_completion<TypeError>(ErrorType::GetCapabilitiesExecutorCalledMultipleTimes);
// b. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception.
if (!reject_handle.is_null())
return vm.template throw_completion<TypeError>(ErrorType::GetCapabilitiesExecutorCalledMultipleTimes);
// c. Set promiseCapability.[[Resolve]] to resolve.
resolve_handle = make_handle(resolve);
if (resolve.is_function())
promise_capability->set_resolve(resolve.as_function());
// d. Set promiseCapability.[[Reject]] to reject.
reject_handle = make_handle(reject);
if (reject.is_function())
promise_capability->set_reject(reject.as_function());
// e. Return undefined.
return js_undefined();
};
// 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »).
auto* executor = NativeFunction::create(realm, move(executor_closure), 2, "");
// 6. Let promise be ? Construct(C, « executor »).
auto* promise = TRY(construct(vm, constructor.as_function(), executor));
// 7. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
// NOTE: We only assign a value in the executor closure if it is a function.
if (promise_capability->resolve() == nullptr)
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "Promise capability resolve value");
// 8. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception.
// NOTE: We only assign a value in the executor closure if it is a function.
if (promise_capability->reject() == nullptr)
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "Promise capability reject value");
// 9. Set promiseCapability.[[Promise]] to promise.
promise_capability->set_promise(*promise);
// 10. Return promiseCapability.
return promise_capability;
}
}