mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-30 00:31:14 +00:00
LibJS: Implement the PrepareForOrdinaryCall abstract operation
This is used by VM::call_internal() and VM::construct() which roughly map to function objects' [[Call]] and [[Construct]] slots in the spec. Reorganizing this code revealed something weird: NativeFunction gets its strictness by checking VM::in_strict_mode(). In other words, it inherits the strict flag from the caller context. This is quite weird, but many test-js tests rely on it, so let's preserve it until we can think of something nicer.
This commit is contained in:
parent
8ed3315dec
commit
5ce9305c5f
Notes:
sideshowbarker
2024-07-18 11:06:27 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/5ce9305c5fe
|
@ -417,29 +417,26 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
|
||||||
}
|
}
|
||||||
|
|
||||||
ExecutionContext callee_context;
|
ExecutionContext callee_context;
|
||||||
callee_context.function = &function;
|
prepare_for_ordinary_call(function, callee_context, &new_target);
|
||||||
if (auto* interpreter = interpreter_if_exists())
|
|
||||||
callee_context.current_node = interpreter->current_node();
|
|
||||||
callee_context.is_strict_mode = function.is_strict_mode();
|
|
||||||
|
|
||||||
push_execution_context(callee_context, global_object);
|
|
||||||
if (exception())
|
if (exception())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
ArmedScopeGuard pop_guard = [&] {
|
ArmedScopeGuard pop_guard = [&] {
|
||||||
pop_execution_context();
|
pop_execution_context();
|
||||||
};
|
};
|
||||||
|
|
||||||
callee_context.function_name = function.name();
|
if (auto* interpreter = interpreter_if_exists())
|
||||||
|
callee_context.current_node = interpreter->current_node();
|
||||||
|
|
||||||
callee_context.arguments = function.bound_arguments();
|
callee_context.arguments = function.bound_arguments();
|
||||||
if (arguments.has_value())
|
if (arguments.has_value())
|
||||||
callee_context.arguments.extend(arguments.value().values());
|
callee_context.arguments.extend(arguments.value().values());
|
||||||
auto* environment = function.create_environment(function);
|
|
||||||
callee_context.lexical_environment = environment;
|
if (auto* environment = callee_context.lexical_environment) {
|
||||||
callee_context.variable_environment = environment;
|
auto& function_environment = verify_cast<FunctionEnvironment>(*environment);
|
||||||
if (environment) {
|
function_environment.set_new_target(&new_target);
|
||||||
environment->set_new_target(&new_target);
|
|
||||||
if (!this_argument.is_empty()) {
|
if (!this_argument.is_empty()) {
|
||||||
environment->bind_this_value(global_object, this_argument);
|
function_environment.bind_this_value(global_object, this_argument);
|
||||||
if (exception())
|
if (exception())
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -450,7 +447,7 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
|
||||||
auto result = function.construct(new_target);
|
auto result = function.construct(new_target);
|
||||||
|
|
||||||
Value this_value = this_argument;
|
Value this_value = this_argument;
|
||||||
if (environment)
|
if (auto* environment = callee_context.lexical_environment)
|
||||||
this_value = environment->get_this_binding(global_object);
|
this_value = environment->get_this_binding(global_object);
|
||||||
pop_execution_context();
|
pop_execution_context();
|
||||||
pop_guard.disarm();
|
pop_guard.disarm();
|
||||||
|
@ -458,9 +455,8 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
|
||||||
// If we are constructing an instance of a derived class,
|
// If we are constructing an instance of a derived class,
|
||||||
// set the prototype on objects created by constructors that return an object (i.e. NativeFunction subclasses).
|
// set the prototype on objects created by constructors that return an object (i.e. NativeFunction subclasses).
|
||||||
if (function.constructor_kind() == FunctionObject::ConstructorKind::Base && new_target.constructor_kind() == FunctionObject::ConstructorKind::Derived && result.is_object()) {
|
if (function.constructor_kind() == FunctionObject::ConstructorKind::Base && new_target.constructor_kind() == FunctionObject::ConstructorKind::Derived && result.is_object()) {
|
||||||
if (environment) {
|
if (auto* environment = callee_context.lexical_environment)
|
||||||
verify_cast<FunctionEnvironment>(lexical_environment())->replace_this_binding(result);
|
verify_cast<FunctionEnvironment>(environment)->replace_this_binding(result);
|
||||||
}
|
|
||||||
auto prototype = new_target.get(names.prototype);
|
auto prototype = new_target.get(names.prototype);
|
||||||
if (exception())
|
if (exception())
|
||||||
return {};
|
return {};
|
||||||
|
@ -511,39 +507,90 @@ Value VM::get_new_target()
|
||||||
return verify_cast<FunctionEnvironment>(env).new_target();
|
return verify_cast<FunctionEnvironment>(env).new_target();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 10.2.1.1 PrepareForOrdinaryCall ( F, newTarget ), https://tc39.es/ecma262/#sec-prepareforordinarycall
|
||||||
|
void VM::prepare_for_ordinary_call(FunctionObject& function, ExecutionContext& callee_context, Value new_target)
|
||||||
|
{
|
||||||
|
// NOTE: This is a LibJS specific hack for NativeFunction to inherit the strictness of its caller.
|
||||||
|
// FIXME: I feel like we should be able to get rid of this.
|
||||||
|
if (is<NativeFunction>(function))
|
||||||
|
callee_context.is_strict_mode = in_strict_mode();
|
||||||
|
else
|
||||||
|
callee_context.is_strict_mode = function.is_strict_mode();
|
||||||
|
|
||||||
|
// 1. Assert: Type(newTarget) is Undefined or Object.
|
||||||
|
VERIFY(new_target.is_undefined() || new_target.is_object());
|
||||||
|
|
||||||
|
// 2. Let callerContext be the running execution context.
|
||||||
|
// 3. Let calleeContext be a new ECMAScript code execution context.
|
||||||
|
|
||||||
|
// NOTE: In the specification, PrepareForOrdinaryCall "returns" a new callee execution context.
|
||||||
|
// To avoid heap allocations, we put our ExecutionContext objects on the C++ stack instead.
|
||||||
|
// Whoever calls us should put an ExecutionContext on their stack and pass that as the `callee_context`.
|
||||||
|
|
||||||
|
// 4. Set the Function of calleeContext to F.
|
||||||
|
callee_context.function = &function;
|
||||||
|
callee_context.function_name = function.name();
|
||||||
|
|
||||||
|
// 5. Let calleeRealm be F.[[Realm]].
|
||||||
|
// 6. Set the Realm of calleeContext to calleeRealm.
|
||||||
|
// 7. Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]].
|
||||||
|
// FIXME: Our execution context struct currently does not track these items.
|
||||||
|
|
||||||
|
// 8. Let localEnv be NewFunctionEnvironment(F, newTarget).
|
||||||
|
// FIXME: This should call NewFunctionEnvironment instead of the ad-hoc FunctionObject::create_environment()
|
||||||
|
auto* local_environment = function.create_environment(function);
|
||||||
|
|
||||||
|
// 9. Set the LexicalEnvironment of calleeContext to localEnv.
|
||||||
|
callee_context.lexical_environment = local_environment;
|
||||||
|
|
||||||
|
// 10. Set the VariableEnvironment of calleeContext to localEnv.
|
||||||
|
callee_context.variable_environment = local_environment;
|
||||||
|
|
||||||
|
// 11. Set the PrivateEnvironment of calleeContext to F.[[PrivateEnvironment]].
|
||||||
|
// FIXME: We currently don't support private environments.
|
||||||
|
|
||||||
|
// 12. If callerContext is not already suspended, suspend callerContext.
|
||||||
|
// FIXME: We don't have this concept yet.
|
||||||
|
|
||||||
|
// 13. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
|
||||||
|
push_execution_context(callee_context, function.global_object());
|
||||||
|
|
||||||
|
// 14. NOTE: Any exception objects produced after this point are associated with calleeRealm.
|
||||||
|
// 15. Return calleeContext. (See NOTE above about how contexts are allocated on the C++ stack.)
|
||||||
|
}
|
||||||
|
|
||||||
Value VM::call_internal(FunctionObject& function, Value this_value, Optional<MarkedValueList> arguments)
|
Value VM::call_internal(FunctionObject& function, Value this_value, Optional<MarkedValueList> arguments)
|
||||||
{
|
{
|
||||||
VERIFY(!exception());
|
VERIFY(!exception());
|
||||||
VERIFY(!this_value.is_empty());
|
VERIFY(!this_value.is_empty());
|
||||||
|
|
||||||
ExecutionContext callee_context;
|
ExecutionContext callee_context;
|
||||||
callee_context.function = &function;
|
prepare_for_ordinary_call(function, callee_context, js_undefined());
|
||||||
|
if (exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
ScopeGuard pop_guard = [&] {
|
||||||
|
pop_execution_context();
|
||||||
|
};
|
||||||
|
|
||||||
if (auto* interpreter = interpreter_if_exists())
|
if (auto* interpreter = interpreter_if_exists())
|
||||||
callee_context.current_node = interpreter->current_node();
|
callee_context.current_node = interpreter->current_node();
|
||||||
callee_context.is_strict_mode = function.is_strict_mode();
|
|
||||||
callee_context.function_name = function.name();
|
|
||||||
callee_context.this_value = function.bound_this().value_or(this_value);
|
callee_context.this_value = function.bound_this().value_or(this_value);
|
||||||
callee_context.arguments = function.bound_arguments();
|
callee_context.arguments = function.bound_arguments();
|
||||||
if (arguments.has_value())
|
if (arguments.has_value())
|
||||||
callee_context.arguments.extend(arguments.value().values());
|
callee_context.arguments.extend(arguments.value().values());
|
||||||
auto* environment = function.create_environment(function);
|
|
||||||
callee_context.lexical_environment = environment;
|
|
||||||
callee_context.variable_environment = environment;
|
|
||||||
|
|
||||||
if (environment) {
|
if (auto* environment = callee_context.lexical_environment) {
|
||||||
VERIFY(environment->this_binding_status() == FunctionEnvironment::ThisBindingStatus::Uninitialized);
|
auto& function_environment = verify_cast<FunctionEnvironment>(*environment);
|
||||||
environment->bind_this_value(function.global_object(), callee_context.this_value);
|
VERIFY(function_environment.this_binding_status() == FunctionEnvironment::ThisBindingStatus::Uninitialized);
|
||||||
|
function_environment.bind_this_value(function.global_object(), callee_context.this_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception())
|
if (exception())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
push_execution_context(callee_context, function.global_object());
|
return function.call();
|
||||||
if (exception())
|
|
||||||
return {};
|
|
||||||
auto result = function.call();
|
|
||||||
pop_execution_context();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VM::in_strict_mode() const
|
bool VM::in_strict_mode() const
|
||||||
|
|
|
@ -260,6 +260,7 @@ private:
|
||||||
VM();
|
VM();
|
||||||
|
|
||||||
[[nodiscard]] Value call_internal(FunctionObject&, Value this_value, Optional<MarkedValueList> arguments);
|
[[nodiscard]] Value call_internal(FunctionObject&, Value this_value, Optional<MarkedValueList> arguments);
|
||||||
|
void prepare_for_ordinary_call(FunctionObject&, ExecutionContext& callee_context, Value new_target);
|
||||||
|
|
||||||
Exception* m_exception { nullptr };
|
Exception* m_exception { nullptr };
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue