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:
Andreas Kling 2021-07-02 21:02:56 +02:00
parent 8ed3315dec
commit 5ce9305c5f
Notes: sideshowbarker 2024-07-18 11:06:27 +09:00
2 changed files with 80 additions and 32 deletions

View file

@ -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

View file

@ -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 };