mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 18:07:34 +00:00
LibJS: Make scoping follow the spec
Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
This commit is contained in:
parent
4428e494b0
commit
830ea0414c
44 changed files with 2622 additions and 1548 deletions
|
@ -19,6 +19,7 @@
|
|||
#include <LibJS/Runtime/BoundFunction.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/DeclarativeEnvironment.h>
|
||||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
|
||||
#include <LibJS/Runtime/ErrorTypes.h>
|
||||
#include <LibJS/Runtime/FunctionEnvironment.h>
|
||||
#include <LibJS/Runtime/FunctionObject.h>
|
||||
|
@ -423,6 +424,8 @@ ThrowCompletionOr<Value> perform_eval(Value x, GlobalObject& caller_realm, Calle
|
|||
return x;
|
||||
|
||||
auto& vm = caller_realm.vm();
|
||||
auto& eval_realm = vm.running_execution_context().realm;
|
||||
|
||||
auto& code_string = x.as_string();
|
||||
Parser parser { Lexer { code_string.string() } };
|
||||
auto program = parser.parse_program(strict_caller == CallerMode::Strict);
|
||||
|
@ -432,20 +435,249 @@ ThrowCompletionOr<Value> perform_eval(Value x, GlobalObject& caller_realm, Calle
|
|||
return vm.throw_completion<SyntaxError>(caller_realm, error.to_string());
|
||||
}
|
||||
|
||||
auto& interpreter = vm.interpreter();
|
||||
auto strict_eval = strict_caller == CallerMode::Strict;
|
||||
if (program->is_strict_mode())
|
||||
strict_eval = true;
|
||||
|
||||
auto& running_context = vm.running_execution_context();
|
||||
|
||||
Environment* lexical_environment;
|
||||
Environment* variable_environment;
|
||||
if (direct == EvalMode::Direct) {
|
||||
auto result = interpreter.execute_statement(caller_realm, program).value_or(js_undefined());
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
return result;
|
||||
lexical_environment = new_declarative_environment(*running_context.lexical_environment);
|
||||
variable_environment = running_context.variable_environment;
|
||||
} else {
|
||||
lexical_environment = new_declarative_environment(eval_realm->global_environment());
|
||||
variable_environment = &eval_realm->global_environment();
|
||||
}
|
||||
|
||||
TemporaryChange scope_change(vm.running_execution_context().lexical_environment, static_cast<Environment*>(&interpreter.realm().global_environment()));
|
||||
TemporaryChange scope_change_strict(vm.running_execution_context().is_strict_mode, strict_caller == CallerMode::Strict);
|
||||
auto result = interpreter.execute_statement(caller_realm, program).value_or(js_undefined());
|
||||
if (strict_eval)
|
||||
variable_environment = lexical_environment;
|
||||
|
||||
// 18. If runningContext is not already suspended, suspend runningContext.
|
||||
// FIXME: We don't have this concept yet.
|
||||
|
||||
ExecutionContext eval_context(vm.heap());
|
||||
eval_context.realm = eval_realm;
|
||||
eval_context.variable_environment = variable_environment;
|
||||
eval_context.lexical_environment = lexical_environment;
|
||||
vm.push_execution_context(eval_context, eval_realm->global_object());
|
||||
|
||||
ScopeGuard pop_guard = [&] {
|
||||
vm.pop_execution_context();
|
||||
};
|
||||
|
||||
TRY(eval_declaration_instantiation(vm, eval_realm->global_object(), program, variable_environment, lexical_environment, strict_eval));
|
||||
|
||||
auto& interpreter = vm.interpreter();
|
||||
TemporaryChange scope_change_strict(vm.running_execution_context().is_strict_mode, strict_eval);
|
||||
// Note: We specifically use evaluate_statements here since we don't want to use global_declaration_instantiation from Program::execute.
|
||||
auto eval_result = program->evaluate_statements(interpreter, caller_realm);
|
||||
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
return result;
|
||||
else
|
||||
return eval_result.value_or(js_undefined());
|
||||
}
|
||||
|
||||
// 19.2.1.3 EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict ), https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
|
||||
ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, GlobalObject& global_object, Program const& program, Environment* variable_environment, Environment* lexical_environment, bool strict)
|
||||
{
|
||||
// FIXME: I'm not sure if the global object is correct here. And this is quite a crucial spot!
|
||||
GlobalEnvironment* global_var_environment = variable_environment->is_global_environment() ? static_cast<GlobalEnvironment*>(variable_environment) : nullptr;
|
||||
|
||||
if (!strict) {
|
||||
if (global_var_environment) {
|
||||
program.for_each_var_declared_name([&](auto const& name) {
|
||||
if (global_var_environment->has_lexical_declaration(name)) {
|
||||
vm.throw_exception<SyntaxError>(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Var already declared lexically");
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
auto* this_environment = lexical_environment;
|
||||
while (this_environment != variable_environment) {
|
||||
if (!is<ObjectEnvironment>(*this_environment)) {
|
||||
program.for_each_var_declared_name([&](auto const& name) {
|
||||
if (this_environment->has_binding(name)) {
|
||||
vm.throw_exception<SyntaxError>(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Var already declared lexically");
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
// FIXME: NOTE: Annex B.3.4 defines alternate semantics for the above step.
|
||||
// In particular it only throw the syntax error if it is not an environment from a catchclause.
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
}
|
||||
|
||||
this_environment = this_environment->outer_environment();
|
||||
VERIFY(this_environment);
|
||||
}
|
||||
}
|
||||
|
||||
HashTable<FlyString> declared_function_names;
|
||||
Vector<FunctionDeclaration const&> functions_to_initialize;
|
||||
program.for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) {
|
||||
if (declared_function_names.set(function.name()) != AK::HashSetResult::InsertedNewEntry)
|
||||
return IterationDecision::Continue;
|
||||
|
||||
if (global_var_environment) {
|
||||
auto function_definable = global_var_environment->can_declare_global_function(function.name());
|
||||
if (vm.exception())
|
||||
return IterationDecision::Break;
|
||||
if (!function_definable) {
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Cannot define global function");
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
}
|
||||
functions_to_initialize.append(function);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
|
||||
if (!strict) {
|
||||
// The spec here uses 'declaredVarNames' but that has not been declared yet.
|
||||
HashTable<FlyString> hoisted_functions;
|
||||
program.for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) {
|
||||
auto& function_name = function_declaration.name();
|
||||
auto* this_environment = lexical_environment;
|
||||
|
||||
while (this_environment != variable_environment) {
|
||||
if (!is<ObjectEnvironment>(*this_environment) && this_environment->has_binding(function_name))
|
||||
return IterationDecision::Continue;
|
||||
|
||||
this_environment = this_environment->outer_environment();
|
||||
VERIFY(this_environment);
|
||||
}
|
||||
|
||||
if (global_var_environment) {
|
||||
if (global_var_environment->has_lexical_declaration(function_name))
|
||||
return IterationDecision::Continue;
|
||||
auto var_definable = global_var_environment->can_declare_global_var(function_name);
|
||||
if (vm.exception())
|
||||
return IterationDecision::Break;
|
||||
if (!var_definable)
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
|
||||
if (!declared_function_names.contains(function_name) && !hoisted_functions.contains(function_name)) {
|
||||
if (global_var_environment) {
|
||||
global_var_environment->create_global_var_binding(function_name, true);
|
||||
if (vm.exception())
|
||||
return IterationDecision::Break;
|
||||
} else {
|
||||
if (!variable_environment->has_binding(function_name)) {
|
||||
variable_environment->create_mutable_binding(global_object, function_name, true);
|
||||
variable_environment->initialize_binding(global_object, function_name, js_undefined());
|
||||
VERIFY(!vm.exception());
|
||||
}
|
||||
}
|
||||
|
||||
hoisted_functions.set(function_name);
|
||||
}
|
||||
|
||||
function_declaration.set_should_do_additional_annexB_steps();
|
||||
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
}
|
||||
|
||||
HashTable<FlyString> declared_var_names;
|
||||
|
||||
program.for_each_var_scoped_variable_declaration([&](VariableDeclaration const& declaration) {
|
||||
declaration.for_each_bound_name([&](auto const& name) {
|
||||
if (!declared_function_names.contains(name)) {
|
||||
if (global_var_environment) {
|
||||
auto variable_definable = global_var_environment->can_declare_global_var(name);
|
||||
if (vm.exception())
|
||||
return IterationDecision::Break;
|
||||
if (!variable_definable) {
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Cannot define global var");
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
}
|
||||
declared_var_names.set(name);
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
if (vm.exception())
|
||||
return IterationDecision::Break;
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
|
||||
// 14. NOTE: No abnormal terminations occur after this algorithm step unless varEnv is a global Environment Record and the global object is a Proxy exotic object.
|
||||
|
||||
program.for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
|
||||
declaration.for_each_bound_name([&](auto const& name) {
|
||||
if (declaration.is_constant_declaration())
|
||||
lexical_environment->create_immutable_binding(global_object, name, true);
|
||||
else
|
||||
lexical_environment->create_mutable_binding(global_object, name, false);
|
||||
if (vm.exception())
|
||||
return IterationDecision::Break;
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
if (vm.exception())
|
||||
return IterationDecision::Break;
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
|
||||
for (auto& declaration : functions_to_initialize) {
|
||||
auto* function = ECMAScriptFunctionObject::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), lexical_environment, declaration.kind(), declaration.is_strict_mode());
|
||||
if (global_var_environment) {
|
||||
global_var_environment->create_global_function_binding(declaration.name(), function, true);
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
} else {
|
||||
auto binding_exists = variable_environment->has_binding(declaration.name());
|
||||
|
||||
if (!binding_exists) {
|
||||
variable_environment->create_mutable_binding(global_object, declaration.name(), true);
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
variable_environment->initialize_binding(global_object, declaration.name(), function);
|
||||
} else {
|
||||
variable_environment->set_mutable_binding(global_object, declaration.name(), function, false);
|
||||
}
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& var_name : declared_var_names) {
|
||||
if (global_var_environment) {
|
||||
global_var_environment->create_global_var_binding(var_name, true);
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
} else {
|
||||
auto binding_exists = variable_environment->has_binding(var_name);
|
||||
|
||||
if (!binding_exists) {
|
||||
variable_environment->create_mutable_binding(global_object, var_name, true);
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
variable_environment->initialize_binding(global_object, var_name, js_undefined());
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// 10.4.4.6 CreateUnmappedArgumentsObject ( argumentsList ), https://tc39.es/ecma262/#sec-createunmappedargumentsobject
|
||||
|
@ -554,15 +786,11 @@ Object* create_mapped_arguments_object(GlobalObject& global_object, FunctionObje
|
|||
// 3. Perform map.[[DefineOwnProperty]](! ToString(𝔽(index)), PropertyDescriptor { [[Set]]: p, [[Get]]: g, [[Enumerable]]: false, [[Configurable]]: true }).
|
||||
object->parameter_map().define_native_accessor(
|
||||
String::number(index),
|
||||
[&environment, name](VM&, GlobalObject&) -> Value {
|
||||
auto variable = environment.get_from_environment(name);
|
||||
if (!variable.has_value())
|
||||
return {};
|
||||
return variable->value;
|
||||
[&environment, name](VM&, GlobalObject& global_object_getter) -> Value {
|
||||
return environment.get_binding_value(global_object_getter, name, false);
|
||||
},
|
||||
[&environment, name](VM& vm, GlobalObject&) {
|
||||
auto value = vm.argument(0);
|
||||
environment.put_into_environment(name, Variable { value, DeclarationKind::Var });
|
||||
[&environment, name](VM& vm, GlobalObject& global_object_setter) {
|
||||
environment.set_mutable_binding(global_object_setter, name, vm.argument(0), false);
|
||||
return js_undefined();
|
||||
},
|
||||
Attribute::Configurable);
|
||||
|
|
|
@ -42,6 +42,8 @@ enum class EvalMode {
|
|||
};
|
||||
ThrowCompletionOr<Value> perform_eval(Value, GlobalObject&, CallerMode, EvalMode);
|
||||
|
||||
ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, GlobalObject& global_object, Program const& program, Environment* variable_environment, Environment* lexical_environment, bool strict);
|
||||
|
||||
// 10.1.13 OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] ), https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor
|
||||
template<typename T, typename... Args>
|
||||
ThrowCompletionOr<T*> ordinary_create_from_constructor(GlobalObject& global_object, FunctionObject const& constructor, Object* (GlobalObject::*intrinsic_default_prototype)(), Args&&... args)
|
||||
|
|
|
@ -43,9 +43,9 @@ Value BoundFunction::construct(FunctionObject& new_target)
|
|||
return m_bound_target_function->construct(new_target);
|
||||
}
|
||||
|
||||
FunctionEnvironment* BoundFunction::create_environment(FunctionObject& function_being_invoked)
|
||||
FunctionEnvironment* BoundFunction::new_function_environment(Object* new_target)
|
||||
{
|
||||
return m_bound_target_function->create_environment(function_being_invoked);
|
||||
return m_bound_target_function->new_function_environment(new_target);
|
||||
}
|
||||
|
||||
void BoundFunction::visit_edges(Visitor& visitor)
|
||||
|
|
|
@ -20,7 +20,7 @@ public:
|
|||
|
||||
virtual Value call() override;
|
||||
virtual Value construct(FunctionObject& new_target) override;
|
||||
virtual FunctionEnvironment* create_environment(FunctionObject&) override;
|
||||
virtual FunctionEnvironment* new_function_environment(Object* new_target) override;
|
||||
virtual const FlyString& name() const override { return m_name; }
|
||||
virtual bool is_strict_mode() const override { return m_bound_target_function->is_strict_mode(); }
|
||||
virtual bool has_constructor() const override { return true; }
|
||||
|
|
|
@ -23,12 +23,6 @@ DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_scope)
|
|||
{
|
||||
}
|
||||
|
||||
DeclarativeEnvironment::DeclarativeEnvironment(HashMap<FlyString, Variable> variables, Environment* parent_scope)
|
||||
: Environment(parent_scope)
|
||||
, m_variables(move(variables))
|
||||
{
|
||||
}
|
||||
|
||||
DeclarativeEnvironment::~DeclarativeEnvironment()
|
||||
{
|
||||
}
|
||||
|
@ -36,28 +30,10 @@ DeclarativeEnvironment::~DeclarativeEnvironment()
|
|||
void DeclarativeEnvironment::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
for (auto& it : m_variables)
|
||||
visitor.visit(it.value.value);
|
||||
for (auto& it : m_bindings)
|
||||
visitor.visit(it.value.value);
|
||||
}
|
||||
|
||||
Optional<Variable> DeclarativeEnvironment::get_from_environment(FlyString const& name) const
|
||||
{
|
||||
return m_variables.get(name);
|
||||
}
|
||||
|
||||
bool DeclarativeEnvironment::put_into_environment(FlyString const& name, Variable variable)
|
||||
{
|
||||
m_variables.set(name, variable);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeclarativeEnvironment::delete_from_environment(FlyString const& name)
|
||||
{
|
||||
return m_variables.remove(name);
|
||||
}
|
||||
|
||||
// 9.1.1.1.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n
|
||||
bool DeclarativeEnvironment::has_binding(FlyString const& name) const
|
||||
{
|
||||
|
@ -156,4 +132,14 @@ bool DeclarativeEnvironment::delete_binding(GlobalObject&, FlyString const& name
|
|||
return true;
|
||||
}
|
||||
|
||||
void DeclarativeEnvironment::initialize_or_set_mutable_binding(Badge<ScopeNode>, GlobalObject& global_object, FlyString const& name, Value value)
|
||||
{
|
||||
auto it = m_bindings.find(name);
|
||||
VERIFY(it != m_bindings.end());
|
||||
if (!it->value.initialized)
|
||||
initialize_binding(global_object, name, value);
|
||||
else
|
||||
set_mutable_binding(global_object, name, value, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,16 +19,8 @@ class DeclarativeEnvironment : public Environment {
|
|||
public:
|
||||
DeclarativeEnvironment();
|
||||
explicit DeclarativeEnvironment(Environment* parent_scope);
|
||||
DeclarativeEnvironment(HashMap<FlyString, Variable> variables, Environment* parent_scope);
|
||||
virtual ~DeclarativeEnvironment() override;
|
||||
|
||||
// ^Environment
|
||||
virtual Optional<Variable> get_from_environment(FlyString const&) const override;
|
||||
virtual bool put_into_environment(FlyString const&, Variable) override;
|
||||
virtual bool delete_from_environment(FlyString const&) override;
|
||||
|
||||
HashMap<FlyString, Variable> const& variables() const { return m_variables; }
|
||||
|
||||
virtual bool has_binding(FlyString const& name) const override;
|
||||
virtual void create_mutable_binding(GlobalObject&, FlyString const& name, bool can_be_deleted) override;
|
||||
virtual void create_immutable_binding(GlobalObject&, FlyString const& name, bool strict) override;
|
||||
|
@ -37,14 +29,14 @@ public:
|
|||
virtual Value get_binding_value(GlobalObject&, FlyString const& name, bool strict) override;
|
||||
virtual bool delete_binding(GlobalObject&, FlyString const& name) override;
|
||||
|
||||
void initialize_or_set_mutable_binding(Badge<ScopeNode>, GlobalObject& global_object, FlyString const& name, Value value);
|
||||
|
||||
protected:
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
private:
|
||||
virtual bool is_declarative_environment() const override { return true; }
|
||||
|
||||
HashMap<FlyString, Variable> m_variables;
|
||||
|
||||
struct Binding {
|
||||
Value value;
|
||||
bool strict { false };
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <LibJS/Bytecode/Generator.h>
|
||||
#include <LibJS/Bytecode/Interpreter.h>
|
||||
#include <LibJS/Interpreter.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
|
||||
#include <LibJS/Runtime/Error.h>
|
||||
|
@ -107,86 +108,269 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor)
|
|||
}
|
||||
}
|
||||
|
||||
FunctionEnvironment* ECMAScriptFunctionObject::create_environment(FunctionObject& function_being_invoked)
|
||||
// 9.1.2.4 NewFunctionEnvironment ( F, newTarget ), https://tc39.es/ecma262/#sec-newfunctionenvironment
|
||||
FunctionEnvironment* ECMAScriptFunctionObject::new_function_environment(Object* new_target)
|
||||
{
|
||||
HashMap<FlyString, Variable> variables;
|
||||
auto* environment = heap().allocate<FunctionEnvironment>(global_object(), m_environment);
|
||||
environment->set_function_object(*this);
|
||||
if (this_mode() == ThisMode::Lexical) {
|
||||
environment->set_this_binding_status(FunctionEnvironment::ThisBindingStatus::Lexical);
|
||||
}
|
||||
|
||||
environment->set_new_target(new_target ? new_target : js_undefined());
|
||||
return environment;
|
||||
}
|
||||
|
||||
// 10.2.11 FunctionDeclarationInstantiation ( func, argumentsList ), https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
|
||||
ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantiation(Interpreter* interpreter)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
auto& callee_context = vm.running_execution_context();
|
||||
|
||||
// Needed to extract declarations and functions
|
||||
ScopeNode const* scope_body = nullptr;
|
||||
if (is<ScopeNode>(*m_ecmascript_code))
|
||||
scope_body = static_cast<ScopeNode const*>(m_ecmascript_code.ptr());
|
||||
|
||||
bool has_parameter_expressions = false;
|
||||
|
||||
// FIXME: Maybe compute has duplicates at parse time? (We need to anyway since it's an error in some cases)
|
||||
|
||||
bool has_duplicates = false;
|
||||
HashTable<FlyString> parameter_names;
|
||||
for (auto& parameter : m_formal_parameters) {
|
||||
if (parameter.default_value)
|
||||
has_parameter_expressions = true;
|
||||
|
||||
parameter.binding.visit(
|
||||
[&](const FlyString& name) { variables.set(name, { js_undefined(), DeclarationKind::Var }); },
|
||||
[&](const NonnullRefPtr<BindingPattern>& binding) {
|
||||
binding->for_each_bound_name([&](const auto& name) {
|
||||
variables.set(name, { js_undefined(), DeclarationKind::Var });
|
||||
[&](FlyString const& name) {
|
||||
if (parameter_names.set(name) != AK::HashSetResult::InsertedNewEntry)
|
||||
has_duplicates = true;
|
||||
},
|
||||
[&](NonnullRefPtr<BindingPattern> const& pattern) {
|
||||
if (pattern->contains_expression())
|
||||
has_parameter_expressions = true;
|
||||
|
||||
pattern->for_each_bound_name([&](auto& name) {
|
||||
if (parameter_names.set(name) != AK::HashSetResult::InsertedNewEntry)
|
||||
has_duplicates = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (is<ScopeNode>(ecmascript_code())) {
|
||||
for (auto& declaration : static_cast<const ScopeNode&>(ecmascript_code()).variables()) {
|
||||
for (auto& declarator : declaration.declarations()) {
|
||||
declarator.target().visit(
|
||||
[&](const NonnullRefPtr<Identifier>& id) {
|
||||
variables.set(id->string(), { js_undefined(), declaration.declaration_kind() });
|
||||
},
|
||||
[&](const NonnullRefPtr<BindingPattern>& binding) {
|
||||
binding->for_each_bound_name([&](const auto& name) {
|
||||
variables.set(name, { js_undefined(), declaration.declaration_kind() });
|
||||
});
|
||||
});
|
||||
}
|
||||
auto needs_argument_object = this_mode() != ThisMode::Lexical;
|
||||
if (parameter_names.contains(vm.names.arguments.as_string()))
|
||||
needs_argument_object = false;
|
||||
|
||||
HashTable<FlyString> function_names;
|
||||
Vector<FunctionDeclaration const&> functions_to_initialize;
|
||||
|
||||
if (scope_body) {
|
||||
scope_body->for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) {
|
||||
if (function_names.set(function.name()) == AK::HashSetResult::InsertedNewEntry)
|
||||
functions_to_initialize.append(function);
|
||||
});
|
||||
|
||||
auto arguments_name = vm.names.arguments.as_string();
|
||||
|
||||
if (!has_parameter_expressions && function_names.contains(arguments_name))
|
||||
needs_argument_object = false;
|
||||
|
||||
if (!has_parameter_expressions && needs_argument_object) {
|
||||
scope_body->for_each_lexically_declared_name([&](auto const& name) {
|
||||
if (name == arguments_name)
|
||||
needs_argument_object = false;
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
auto* environment = heap().allocate<FunctionEnvironment>(global_object(), m_environment, move(variables));
|
||||
environment->set_function_object(static_cast<ECMAScriptFunctionObject&>(function_being_invoked));
|
||||
if (m_is_arrow_function) {
|
||||
environment->set_this_binding_status(FunctionEnvironment::ThisBindingStatus::Lexical);
|
||||
if (is<FunctionEnvironment>(m_environment))
|
||||
environment->set_new_target(static_cast<FunctionEnvironment*>(m_environment)->new_target());
|
||||
Environment* environment;
|
||||
|
||||
if (is_strict_mode() || !has_parameter_expressions) {
|
||||
environment = callee_context.lexical_environment;
|
||||
} else {
|
||||
environment = new_declarative_environment(*callee_context.lexical_environment);
|
||||
VERIFY(callee_context.variable_environment == callee_context.lexical_environment);
|
||||
callee_context.lexical_environment = environment;
|
||||
}
|
||||
return environment;
|
||||
|
||||
for (auto const& parameter_name : parameter_names) {
|
||||
if (environment->has_binding(parameter_name))
|
||||
continue;
|
||||
|
||||
environment->create_mutable_binding(global_object(), parameter_name, false);
|
||||
if (has_duplicates)
|
||||
environment->initialize_binding(global_object(), parameter_name, js_undefined());
|
||||
VERIFY(!vm.exception());
|
||||
}
|
||||
|
||||
if (needs_argument_object) {
|
||||
Object* arguments_object;
|
||||
if (is_strict_mode() || !has_simple_parameter_list())
|
||||
arguments_object = create_unmapped_arguments_object(global_object(), vm.running_execution_context().arguments);
|
||||
else
|
||||
arguments_object = create_mapped_arguments_object(global_object(), *this, formal_parameters(), vm.running_execution_context().arguments, *environment);
|
||||
|
||||
if (is_strict_mode())
|
||||
environment->create_immutable_binding(global_object(), vm.names.arguments.as_string(), false);
|
||||
else
|
||||
environment->create_mutable_binding(global_object(), vm.names.arguments.as_string(), false);
|
||||
|
||||
environment->initialize_binding(global_object(), vm.names.arguments.as_string(), arguments_object);
|
||||
parameter_names.set(vm.names.arguments.as_string());
|
||||
}
|
||||
|
||||
// We now treat parameterBindings as parameterNames.
|
||||
|
||||
// The spec makes an iterator here to do IteratorBindingInitialization but we just do it manually
|
||||
auto& execution_context_arguments = vm.running_execution_context().arguments;
|
||||
|
||||
for (size_t i = 0; i < m_formal_parameters.size(); ++i) {
|
||||
auto& parameter = m_formal_parameters[i];
|
||||
parameter.binding.visit(
|
||||
[&](auto const& param) {
|
||||
Value argument_value;
|
||||
if (parameter.is_rest) {
|
||||
auto* array = Array::create(global_object(), 0);
|
||||
for (size_t rest_index = i; rest_index < execution_context_arguments.size(); ++rest_index)
|
||||
array->indexed_properties().append(execution_context_arguments[rest_index]);
|
||||
argument_value = move(array);
|
||||
} else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) {
|
||||
argument_value = execution_context_arguments[i];
|
||||
} else if (parameter.default_value) {
|
||||
// FIXME: Support default arguments in the bytecode world!
|
||||
if (interpreter)
|
||||
argument_value = parameter.default_value->execute(*interpreter, global_object());
|
||||
if (vm.exception())
|
||||
return;
|
||||
} else {
|
||||
argument_value = js_undefined();
|
||||
}
|
||||
|
||||
Environment* used_environment = has_duplicates ? nullptr : environment;
|
||||
|
||||
if constexpr (IsSame<FlyString const&, decltype(param)>) {
|
||||
Reference reference = vm.resolve_binding(param, used_environment);
|
||||
if (vm.exception())
|
||||
return;
|
||||
// Here the difference from hasDuplicates is important
|
||||
if (has_duplicates)
|
||||
reference.put_value(global_object(), argument_value);
|
||||
else
|
||||
reference.initialize_referenced_binding(global_object(), argument_value);
|
||||
} else if (IsSame<NonnullRefPtr<BindingPattern> const&, decltype(param)>) {
|
||||
// Here the difference from hasDuplicates is important
|
||||
auto result = vm.binding_initialization(param, argument_value, used_environment, global_object());
|
||||
if (result.is_error())
|
||||
return;
|
||||
}
|
||||
|
||||
if (vm.exception())
|
||||
return;
|
||||
});
|
||||
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
}
|
||||
|
||||
Environment* var_environment;
|
||||
|
||||
HashTable<FlyString> instantiated_var_names;
|
||||
|
||||
if (!has_parameter_expressions) {
|
||||
if (scope_body) {
|
||||
scope_body->for_each_var_declared_name([&](auto const& name) {
|
||||
if (!parameter_names.contains(name) && instantiated_var_names.set(name) == AK::HashSetResult::InsertedNewEntry) {
|
||||
environment->create_mutable_binding(global_object(), name, false);
|
||||
environment->initialize_binding(global_object(), name, js_undefined());
|
||||
}
|
||||
});
|
||||
}
|
||||
var_environment = environment;
|
||||
} else {
|
||||
var_environment = new_declarative_environment(*environment);
|
||||
callee_context.variable_environment = var_environment;
|
||||
|
||||
if (scope_body) {
|
||||
scope_body->for_each_var_declared_name([&](auto const& name) {
|
||||
if (instantiated_var_names.set(name) != AK::HashSetResult::InsertedNewEntry)
|
||||
return IterationDecision::Continue;
|
||||
var_environment->create_mutable_binding(global_object(), name, false);
|
||||
|
||||
Value initial_value;
|
||||
if (!parameter_names.contains(name) || function_names.contains(name))
|
||||
initial_value = js_undefined();
|
||||
else
|
||||
initial_value = environment->get_binding_value(global_object(), name, false);
|
||||
|
||||
var_environment->initialize_binding(global_object(), name, initial_value);
|
||||
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// B.3.2.1 Changes to FunctionDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation
|
||||
if (!m_strict && scope_body) {
|
||||
scope_body->for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) {
|
||||
auto& function_name = function_declaration.name();
|
||||
if (parameter_names.contains(function_name))
|
||||
return IterationDecision::Continue;
|
||||
// The spec says 'initializedBindings' here but that does not exist and it then adds it to 'instantiatedVarNames' so it probably means 'instantiatedVarNames'.
|
||||
if (!instantiated_var_names.contains(function_name) && function_name != vm.names.arguments.as_string()) {
|
||||
var_environment->create_mutable_binding(global_object(), function_name, false);
|
||||
VERIFY(!vm.exception());
|
||||
var_environment->initialize_binding(global_object(), function_name, js_undefined());
|
||||
instantiated_var_names.set(function_name);
|
||||
}
|
||||
|
||||
function_declaration.set_should_do_additional_annexB_steps();
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
Environment* lex_environment;
|
||||
|
||||
if (!is_strict_mode())
|
||||
lex_environment = new_declarative_environment(*var_environment);
|
||||
else
|
||||
lex_environment = var_environment;
|
||||
|
||||
callee_context.lexical_environment = lex_environment;
|
||||
|
||||
if (!scope_body)
|
||||
return {};
|
||||
|
||||
scope_body->for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
|
||||
declaration.for_each_bound_name([&](auto const& name) {
|
||||
if (declaration.is_constant_declaration())
|
||||
lex_environment->create_immutable_binding(global_object(), name, true);
|
||||
else
|
||||
lex_environment->create_mutable_binding(global_object(), name, false);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
});
|
||||
|
||||
VERIFY(!vm.exception());
|
||||
|
||||
for (auto& declaration : functions_to_initialize) {
|
||||
auto* function = ECMAScriptFunctionObject::create(global_object(), declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), lex_environment, declaration.kind(), declaration.is_strict_mode());
|
||||
var_environment->set_mutable_binding(global_object(), declaration.name(), function, false);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Value ECMAScriptFunctionObject::execute_function_body()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
Interpreter* ast_interpreter = nullptr;
|
||||
auto* bytecode_interpreter = Bytecode::Interpreter::current();
|
||||
|
||||
auto prepare_arguments = [&] {
|
||||
auto& execution_context_arguments = vm.running_execution_context().arguments;
|
||||
for (size_t i = 0; i < m_formal_parameters.size(); ++i) {
|
||||
auto& parameter = m_formal_parameters[i];
|
||||
parameter.binding.visit(
|
||||
[&](const auto& param) {
|
||||
Value argument_value;
|
||||
if (parameter.is_rest) {
|
||||
auto* array = Array::create(global_object(), 0);
|
||||
for (size_t rest_index = i; rest_index < execution_context_arguments.size(); ++rest_index)
|
||||
array->indexed_properties().append(execution_context_arguments[rest_index]);
|
||||
argument_value = move(array);
|
||||
} else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) {
|
||||
argument_value = execution_context_arguments[i];
|
||||
} else if (parameter.default_value) {
|
||||
// FIXME: Support default arguments in the bytecode world!
|
||||
if (!bytecode_interpreter)
|
||||
argument_value = parameter.default_value->execute(*ast_interpreter, global_object());
|
||||
if (vm.exception())
|
||||
return;
|
||||
} else {
|
||||
argument_value = js_undefined();
|
||||
}
|
||||
|
||||
vm.assign(param, argument_value, global_object(), true, vm.lexical_environment());
|
||||
});
|
||||
|
||||
if (vm.exception())
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if (bytecode_interpreter) {
|
||||
prepare_arguments();
|
||||
// FIXME: pass something to evaluate default arguments with
|
||||
TRY_OR_DISCARD(function_declaration_instantiation(nullptr));
|
||||
if (!m_bytecode_executable.has_value()) {
|
||||
m_bytecode_executable = Bytecode::Generator::generate(m_ecmascript_code, m_kind == FunctionKind::Generator);
|
||||
auto& passes = JS::Bytecode::Interpreter::optimization_pipeline();
|
||||
|
@ -206,7 +390,7 @@ Value ECMAScriptFunctionObject::execute_function_body()
|
|||
} else {
|
||||
VERIFY(m_kind != FunctionKind::Generator);
|
||||
OwnPtr<Interpreter> local_interpreter;
|
||||
ast_interpreter = vm.interpreter_if_exists();
|
||||
Interpreter* ast_interpreter = vm.interpreter_if_exists();
|
||||
|
||||
if (!ast_interpreter) {
|
||||
local_interpreter = Interpreter::create_with_existing_realm(*realm());
|
||||
|
@ -215,11 +399,9 @@ Value ECMAScriptFunctionObject::execute_function_body()
|
|||
|
||||
VM::InterpreterExecutionScope scope(*ast_interpreter);
|
||||
|
||||
prepare_arguments();
|
||||
if (vm.exception())
|
||||
return {};
|
||||
TRY_OR_DISCARD(function_declaration_instantiation(ast_interpreter));
|
||||
|
||||
return ast_interpreter->execute_statement(global_object(), m_ecmascript_code, ScopeType::Function);
|
||||
return m_ecmascript_code->execute(*ast_interpreter, global_object());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -78,9 +78,10 @@ protected:
|
|||
|
||||
private:
|
||||
virtual bool is_ecmascript_function_object() const override { return true; }
|
||||
virtual FunctionEnvironment* create_environment(FunctionObject&) override;
|
||||
virtual FunctionEnvironment* new_function_environment(Object* new_target) override;
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
ThrowCompletionOr<void> function_declaration_instantiation(Interpreter*);
|
||||
Value execute_function_body();
|
||||
|
||||
// Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects
|
||||
|
|
|
@ -27,10 +27,6 @@ public:
|
|||
|
||||
virtual void initialize(GlobalObject&) override;
|
||||
|
||||
virtual Optional<Variable> get_from_environment(FlyString const&) const = 0;
|
||||
virtual bool put_into_environment(FlyString const&, Variable) = 0;
|
||||
virtual bool delete_from_environment(FlyString const&) = 0;
|
||||
|
||||
virtual bool has_this_binding() const { return false; }
|
||||
virtual Value get_this_binding(GlobalObject&) const { return {}; }
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \
|
||||
M(Convert, "Cannot convert {} to {}") \
|
||||
M(DataViewOutOfRangeByteOffset, "Data view byte offset {} is out of range for buffer with length {}") \
|
||||
M(DerivedConstructorReturningInvalidValue, "Derived constructor return invalid value") \
|
||||
M(DescWriteNonWritable, "Cannot write to non-writable property '{}'") \
|
||||
M(DetachedArrayBuffer, "ArrayBuffer is detached") \
|
||||
M(DivisionByZero, "Division by zero") \
|
||||
|
@ -223,6 +224,7 @@
|
|||
M(BadArgCountAtLeastOne, "{}() needs at least one argument") \
|
||||
M(BadArgCountMany, "{}() needs {} arguments") \
|
||||
M(FixmeAddAnErrorString, "FIXME: Add a string for this error.") \
|
||||
M(FixmeAddAnErrorStringWithMessage, "FIXME: Add a real string for this error '{}'") \
|
||||
M(NotEnoughMemoryToAllocate, "Not enough memory to allocate {} bytes")
|
||||
|
||||
namespace JS {
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
namespace JS {
|
||||
|
||||
FunctionEnvironment::FunctionEnvironment(Environment* parent_scope, HashMap<FlyString, Variable> variables)
|
||||
: DeclarativeEnvironment(move(variables), parent_scope)
|
||||
FunctionEnvironment::FunctionEnvironment(Environment* parent_scope)
|
||||
: DeclarativeEnvironment(parent_scope)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ public:
|
|||
Uninitialized,
|
||||
};
|
||||
|
||||
FunctionEnvironment(Environment* parent_scope, HashMap<FlyString, Variable> variables);
|
||||
explicit FunctionEnvironment(Environment* parent_scope);
|
||||
virtual ~FunctionEnvironment() override;
|
||||
|
||||
// [[ThisValue]]
|
||||
|
|
|
@ -21,7 +21,7 @@ public:
|
|||
virtual Value call() = 0;
|
||||
virtual Value construct(FunctionObject& new_target) = 0;
|
||||
virtual const FlyString& name() const = 0;
|
||||
virtual FunctionEnvironment* create_environment(FunctionObject&) = 0;
|
||||
virtual FunctionEnvironment* new_function_environment(Object* new_target) = 0;
|
||||
|
||||
BoundFunction* bind(Value bound_this_value, Vector<Value> arguments);
|
||||
|
||||
|
|
|
@ -30,27 +30,9 @@ void GlobalEnvironment::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(m_declarative_record);
|
||||
}
|
||||
|
||||
Optional<Variable> GlobalEnvironment::get_from_environment(FlyString const& name) const
|
||||
{
|
||||
// FIXME: This should be a "composite" of the object record and the declarative record.
|
||||
return m_object_record->get_from_environment(name);
|
||||
}
|
||||
|
||||
bool GlobalEnvironment::put_into_environment(FlyString const& name, Variable variable)
|
||||
{
|
||||
// FIXME: This should be a "composite" of the object record and the declarative record.
|
||||
return m_object_record->put_into_environment(name, variable);
|
||||
}
|
||||
|
||||
bool GlobalEnvironment::delete_from_environment(FlyString const& name)
|
||||
{
|
||||
// FIXME: This should be a "composite" of the object record and the declarative record.
|
||||
return object_record().delete_from_environment(name);
|
||||
}
|
||||
|
||||
Value GlobalEnvironment::get_this_binding(GlobalObject&) const
|
||||
{
|
||||
return &global_object();
|
||||
return m_global_this_value;
|
||||
}
|
||||
|
||||
// 9.1.1.4.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-global-environment-records-hasbinding-n
|
||||
|
|
|
@ -16,9 +16,6 @@ class GlobalEnvironment final : public Environment {
|
|||
public:
|
||||
GlobalEnvironment(GlobalObject&, Object& this_value);
|
||||
|
||||
virtual Optional<Variable> get_from_environment(FlyString const&) const override;
|
||||
virtual bool put_into_environment(FlyString const&, Variable) override;
|
||||
virtual bool delete_from_environment(FlyString const&) override;
|
||||
virtual bool has_this_binding() const final { return true; }
|
||||
virtual Value get_this_binding(GlobalObject&) const final;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <LibJS/Interpreter.h>
|
||||
#include <LibJS/Runtime/FunctionEnvironment.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
@ -55,14 +56,22 @@ Value NativeFunction::construct(FunctionObject&)
|
|||
return {};
|
||||
}
|
||||
|
||||
FunctionEnvironment* NativeFunction::create_environment(FunctionObject&)
|
||||
FunctionEnvironment* NativeFunction::new_function_environment(Object* new_target)
|
||||
{
|
||||
return nullptr;
|
||||
// Simplified version of 9.1.2.4 NewFunctionEnvironment ( F, newTarget )
|
||||
Environment* parent_scope = nullptr;
|
||||
if (!vm().execution_context_stack().is_empty())
|
||||
parent_scope = vm().lexical_environment();
|
||||
|
||||
auto* environment = heap().allocate<FunctionEnvironment>(global_object(), parent_scope);
|
||||
environment->set_new_target(new_target ? new_target : js_undefined());
|
||||
|
||||
return environment;
|
||||
}
|
||||
|
||||
bool NativeFunction::is_strict_mode() const
|
||||
{
|
||||
return vm().in_strict_mode();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ protected:
|
|||
explicit NativeFunction(Object& prototype);
|
||||
|
||||
private:
|
||||
virtual FunctionEnvironment* create_environment(FunctionObject&) override final;
|
||||
virtual FunctionEnvironment* new_function_environment(Object* new_target) override final;
|
||||
virtual bool is_native_function() const final { return true; }
|
||||
|
||||
FlyString m_name;
|
||||
|
|
|
@ -469,6 +469,34 @@ MarkedValueList Object::enumerable_own_property_names(PropertyKind kind) const
|
|||
return properties;
|
||||
}
|
||||
|
||||
// 7.3.25 CopyDataProperties ( target, source, excludedItems ), https://tc39.es/ecma262/#sec-copydataproperties
|
||||
ThrowCompletionOr<Object*> Object::copy_data_properties(Value source, HashTable<PropertyName, PropertyNameTraits> const& seen_names, GlobalObject& global_object)
|
||||
{
|
||||
if (source.is_nullish())
|
||||
return this;
|
||||
|
||||
auto* from_object = source.to_object(global_object);
|
||||
VERIFY(from_object);
|
||||
|
||||
for (auto& next_key_value : TRY(from_object->internal_own_property_keys())) {
|
||||
auto next_key = PropertyName::from_value(global_object, next_key_value);
|
||||
if (seen_names.contains(next_key))
|
||||
continue;
|
||||
|
||||
auto desc = TRY(from_object->internal_get_own_property(next_key));
|
||||
|
||||
if (desc.has_value() && desc->attributes().is_enumerable()) {
|
||||
auto prop_value = from_object->get(next_key);
|
||||
if (auto* thrown_exception = vm().exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
create_data_property_or_throw(next_key, prop_value);
|
||||
if (auto* thrown_exception = vm().exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// 10.1 Ordinary Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
|
||||
|
||||
// 10.1.1 [[GetPrototypeOf]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
|
||||
|
|
|
@ -88,6 +88,7 @@ public:
|
|||
bool set_integrity_level(IntegrityLevel);
|
||||
bool test_integrity_level(IntegrityLevel) const;
|
||||
MarkedValueList enumerable_own_property_names(PropertyKind kind) const;
|
||||
ThrowCompletionOr<Object*> copy_data_properties(Value source, HashTable<PropertyName, PropertyNameTraits> const& seen_names, GlobalObject& global_object);
|
||||
|
||||
// 10.1 Ordinary Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
|
||||
|
||||
|
|
|
@ -24,24 +24,6 @@ void ObjectEnvironment::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(&m_binding_object);
|
||||
}
|
||||
|
||||
Optional<Variable> ObjectEnvironment::get_from_environment(FlyString const& name) const
|
||||
{
|
||||
if (!m_binding_object.storage_has(name))
|
||||
return {};
|
||||
auto value = m_binding_object.get(name);
|
||||
return Variable { value, DeclarationKind::Var };
|
||||
}
|
||||
|
||||
bool ObjectEnvironment::put_into_environment(FlyString const& name, Variable variable)
|
||||
{
|
||||
return m_binding_object.set(name, variable.value, Object::ShouldThrowExceptions::No);
|
||||
}
|
||||
|
||||
bool ObjectEnvironment::delete_from_environment(FlyString const& name)
|
||||
{
|
||||
return TRY_OR_DISCARD(m_binding_object.internal_delete(name));
|
||||
}
|
||||
|
||||
// 9.1.1.2.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n
|
||||
bool ObjectEnvironment::has_binding(FlyString const& name) const
|
||||
{
|
||||
|
|
|
@ -20,10 +20,6 @@ public:
|
|||
};
|
||||
ObjectEnvironment(Object& binding_object, IsWithEnvironment, Environment* outer_environment);
|
||||
|
||||
virtual Optional<Variable> get_from_environment(FlyString const&) const override;
|
||||
virtual bool put_into_environment(FlyString const&, Variable) override;
|
||||
virtual bool delete_from_environment(FlyString const&) override;
|
||||
|
||||
virtual bool has_binding(FlyString const& name) const override;
|
||||
virtual void create_mutable_binding(GlobalObject&, FlyString const& name, bool can_be_deleted) override;
|
||||
virtual void create_immutable_binding(GlobalObject&, FlyString const& name, bool strict) override;
|
||||
|
|
|
@ -891,10 +891,10 @@ const FlyString& ProxyObject::name() const
|
|||
return static_cast<FunctionObject&>(m_target).name();
|
||||
}
|
||||
|
||||
FunctionEnvironment* ProxyObject::create_environment(FunctionObject& function_being_invoked)
|
||||
FunctionEnvironment* ProxyObject::new_function_environment(Object* new_target)
|
||||
{
|
||||
VERIFY(is_function());
|
||||
return static_cast<FunctionObject&>(m_target).create_environment(function_being_invoked);
|
||||
return static_cast<FunctionObject&>(m_target).new_function_environment(new_target);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ public:
|
|||
virtual Value call() override;
|
||||
virtual Value construct(FunctionObject& new_target) override;
|
||||
virtual const FlyString& name() const override;
|
||||
virtual FunctionEnvironment* create_environment(FunctionObject&) override;
|
||||
virtual FunctionEnvironment* new_function_environment(Object* new_target) override;
|
||||
virtual bool has_constructor() const override { return true; }
|
||||
|
||||
const Object& target() const { return m_target; }
|
||||
|
|
|
@ -31,15 +31,6 @@ void Reference::put_value(GlobalObject& global_object, Value value)
|
|||
}
|
||||
|
||||
if (is_property_reference()) {
|
||||
// FIXME: This is an ad-hoc hack until we support proper variable bindings.
|
||||
if (!m_base_value.is_object() && vm.in_strict_mode()) {
|
||||
if (m_base_value.is_nullish())
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishSetProperty, m_name, m_base_value.to_string_without_side_effects());
|
||||
else
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::ReferencePrimitiveSetProperty, m_name, m_base_value.typeof(), m_base_value.to_string_without_side_effects());
|
||||
return;
|
||||
}
|
||||
|
||||
auto* base_obj = m_base_value.to_object(global_object);
|
||||
if (!base_obj)
|
||||
return;
|
||||
|
@ -56,29 +47,8 @@ void Reference::put_value(GlobalObject& global_object, Value value)
|
|||
}
|
||||
|
||||
VERIFY(m_base_type == BaseType::Environment);
|
||||
// FIXME: This entire following section is 100% not spec-compliant.
|
||||
auto existing_variable = m_base_environment->get_from_environment(m_name.as_string());
|
||||
Variable variable {
|
||||
.value = value,
|
||||
.declaration_kind = existing_variable.has_value() ? existing_variable->declaration_kind : DeclarationKind::Var
|
||||
};
|
||||
|
||||
// FIXME: This is a hack until we support proper variable bindings.
|
||||
if (variable.declaration_kind == DeclarationKind::Const) {
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::InvalidAssignToConst);
|
||||
return;
|
||||
}
|
||||
|
||||
bool succeeded = m_base_environment->put_into_environment(m_name.as_string(), variable);
|
||||
if (vm.exception())
|
||||
return;
|
||||
|
||||
if (!succeeded) {
|
||||
if (m_base_environment->has_binding(m_name.as_string())) {
|
||||
m_base_environment->set_mutable_binding(global_object, m_name.as_string(), value, is_strict());
|
||||
return;
|
||||
}
|
||||
}
|
||||
VERIFY(m_base_environment);
|
||||
m_base_environment->set_mutable_binding(global_object, m_name.as_string(), value, m_strict);
|
||||
}
|
||||
|
||||
void Reference::throw_reference_error(GlobalObject& global_object) const
|
||||
|
@ -91,7 +61,7 @@ void Reference::throw_reference_error(GlobalObject& global_object) const
|
|||
}
|
||||
|
||||
// 6.2.4.5 GetValue ( V ), https://tc39.es/ecma262/#sec-getvalue
|
||||
Value Reference::get_value(GlobalObject& global_object, bool throw_if_undefined) const
|
||||
Value Reference::get_value(GlobalObject& global_object) const
|
||||
{
|
||||
if (!is_valid_reference() || is_unresolvable()) {
|
||||
throw_reference_error(global_object);
|
||||
|
@ -106,19 +76,8 @@ Value Reference::get_value(GlobalObject& global_object, bool throw_if_undefined)
|
|||
}
|
||||
|
||||
VERIFY(m_base_type == BaseType::Environment);
|
||||
// FIXME: This entire section is 100% not spec-compliant.
|
||||
auto value = m_base_environment->get_from_environment(m_name.as_string());
|
||||
if (!value.has_value()) {
|
||||
if (m_base_environment->has_binding(m_name.as_string()))
|
||||
return m_base_environment->get_binding_value(global_object, m_name.as_string(), is_strict());
|
||||
if (!throw_if_undefined) {
|
||||
// FIXME: This is an ad-hoc hack for the `typeof` operator until we support proper variable bindings.
|
||||
return js_undefined();
|
||||
}
|
||||
throw_reference_error(global_object);
|
||||
return {};
|
||||
}
|
||||
return value->value;
|
||||
VERIFY(m_base_environment);
|
||||
return m_base_environment->get_binding_value(global_object, m_name.as_string(), m_strict);
|
||||
}
|
||||
|
||||
// 13.5.1.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation
|
||||
|
@ -177,11 +136,7 @@ bool Reference::delete_(GlobalObject& global_object)
|
|||
VERIFY(m_base_type == BaseType::Environment);
|
||||
|
||||
// c. Return ? base.DeleteBinding(ref.[[ReferencedName]]).
|
||||
if (m_base_environment->has_binding(m_name.as_string()))
|
||||
return m_base_environment->delete_binding(global_object, m_name.as_string());
|
||||
|
||||
// FIXME: This is ad-hoc, we should be calling DeleteBinding.
|
||||
return m_base_environment->delete_from_environment(m_name.as_string());
|
||||
return m_base_environment->delete_binding(global_object, m_name.as_string());
|
||||
}
|
||||
|
||||
String Reference::to_string() const
|
||||
|
|
|
@ -103,8 +103,15 @@ public:
|
|||
return m_base_type == BaseType::Environment;
|
||||
}
|
||||
|
||||
void initialize_referenced_binding(GlobalObject& global_object, Value value) const
|
||||
{
|
||||
VERIFY(!is_unresolvable());
|
||||
VERIFY(m_base_type == BaseType::Environment);
|
||||
m_base_environment->initialize_binding(global_object, m_name.as_string(), value);
|
||||
}
|
||||
|
||||
void put_value(GlobalObject&, Value);
|
||||
Value get_value(GlobalObject&, bool throw_if_undefined = true) const;
|
||||
Value get_value(GlobalObject&) const;
|
||||
bool delete_(GlobalObject&);
|
||||
|
||||
String to_string() const;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2021, David Tuin <david.tuin@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -16,7 +17,6 @@
|
|||
#include <LibJS/Runtime/Error.h>
|
||||
#include <LibJS/Runtime/FinalizationRegistry.h>
|
||||
#include <LibJS/Runtime/FunctionEnvironment.h>
|
||||
#include <LibJS/Runtime/GlobalEnvironment.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/IteratorOperations.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
|
@ -137,294 +137,310 @@ Symbol* VM::get_global_symbol(const String& description)
|
|||
return new_global_symbol;
|
||||
}
|
||||
|
||||
void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_object, bool first_assignment, Environment* specific_scope)
|
||||
ThrowCompletionOr<Value> VM::named_evaluation_if_anonymous_function(GlobalObject& global_object, ASTNode const& expression, FlyString const& name)
|
||||
{
|
||||
Optional<Variable> possible_match;
|
||||
if (!specific_scope && m_execution_context_stack.size()) {
|
||||
for (auto* environment = lexical_environment(); environment; environment = environment->outer_environment()) {
|
||||
possible_match = environment->get_from_environment(name);
|
||||
if (possible_match.has_value()) {
|
||||
specific_scope = environment;
|
||||
break;
|
||||
}
|
||||
// 8.3.3 Static Semantics: IsAnonymousFunctionDefinition ( expr ), https://tc39.es/ecma262/#sec-isanonymousfunctiondefinition
|
||||
// And 8.3.5 Runtime Semantics: NamedEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-namedevaluation
|
||||
if (is<FunctionExpression>(expression)) {
|
||||
auto& function = static_cast<FunctionExpression const&>(expression);
|
||||
if (!function.has_name()) {
|
||||
return function.instantiate_ordinary_function_expression(interpreter(), global_object, name);
|
||||
}
|
||||
} else if (is<ClassExpression>(expression)) {
|
||||
auto& class_expression = static_cast<ClassExpression const&>(expression);
|
||||
if (!class_expression.has_name()) {
|
||||
return TRY(class_expression.class_definition_evaluation(interpreter(), global_object, {}, name));
|
||||
}
|
||||
}
|
||||
|
||||
if (specific_scope && possible_match.has_value()) {
|
||||
if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) {
|
||||
throw_exception<TypeError>(global_object, ErrorType::InvalidAssignToConst);
|
||||
return;
|
||||
}
|
||||
|
||||
specific_scope->put_into_environment(name, { value, possible_match.value().declaration_kind });
|
||||
return;
|
||||
}
|
||||
|
||||
if (specific_scope) {
|
||||
specific_scope->put_into_environment(name, { value, DeclarationKind::Var });
|
||||
return;
|
||||
}
|
||||
|
||||
global_object.set(name, value, Object::ShouldThrowExceptions::Yes);
|
||||
auto value = expression.execute(interpreter(), global_object);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
return value;
|
||||
}
|
||||
|
||||
void VM::assign(const FlyString& target, Value value, GlobalObject& global_object, bool first_assignment, Environment* specific_scope)
|
||||
// 13.15.5.2 Runtime Semantics: DestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-destructuringassignmentevaluation
|
||||
ThrowCompletionOr<void> VM::destructuring_assignment_evaluation(NonnullRefPtr<BindingPattern> const& target, Value value, GlobalObject& global_object)
|
||||
{
|
||||
set_variable(target, move(value), global_object, first_assignment, specific_scope);
|
||||
// Note: DestructuringAssignmentEvaluation is just like BindingInitialization without an environment
|
||||
// And it allows member expressions. We thus trust the parser to disallow member expressions
|
||||
// in any non assignment binding and just call BindingInitialization with a nullptr environment
|
||||
return binding_initialization(target, value, nullptr, global_object);
|
||||
}
|
||||
|
||||
void VM::assign(const Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>>& target, Value value, GlobalObject& global_object, bool first_assignment, Environment* specific_scope)
|
||||
// 8.5.2 Runtime Semantics: BindingInitialization, https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization
|
||||
ThrowCompletionOr<void> VM::binding_initialization(FlyString const& target, Value value, Environment* environment, GlobalObject& global_object)
|
||||
{
|
||||
if (auto id_ptr = target.get_pointer<NonnullRefPtr<Identifier>>())
|
||||
return assign((*id_ptr)->string(), move(value), global_object, first_assignment, specific_scope);
|
||||
|
||||
assign(target.get<NonnullRefPtr<BindingPattern>>(), move(value), global_object, first_assignment, specific_scope);
|
||||
}
|
||||
|
||||
void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, GlobalObject& global_object, bool first_assignment, Environment* specific_scope)
|
||||
{
|
||||
auto& binding = *target;
|
||||
|
||||
switch (binding.kind) {
|
||||
case BindingPattern::Kind::Array: {
|
||||
auto iterator = get_iterator(global_object, value);
|
||||
if (!iterator)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < binding.entries.size(); i++) {
|
||||
if (exception())
|
||||
return;
|
||||
|
||||
auto& entry = binding.entries[i];
|
||||
|
||||
Optional<Reference> assignment_target;
|
||||
entry.alias.visit(
|
||||
[&](Empty) {},
|
||||
[&](NonnullRefPtr<Identifier> const&) {
|
||||
// FIXME: We need to get the reference but bindings are broken so this doesn't work yet.
|
||||
},
|
||||
[&](NonnullRefPtr<BindingPattern> const&) {},
|
||||
[&](NonnullRefPtr<MemberExpression> const& member_expression) {
|
||||
assignment_target = member_expression->to_reference(interpreter(), global_object);
|
||||
});
|
||||
|
||||
if (exception())
|
||||
return;
|
||||
|
||||
if (entry.is_rest) {
|
||||
VERIFY(i == binding.entries.size() - 1);
|
||||
|
||||
auto* array = Array::create(global_object, 0);
|
||||
for (;;) {
|
||||
auto next_object = iterator_next(*iterator);
|
||||
if (!next_object)
|
||||
return;
|
||||
|
||||
auto done_property = next_object->get(names.done);
|
||||
if (exception())
|
||||
return;
|
||||
|
||||
if (done_property.to_boolean())
|
||||
break;
|
||||
|
||||
auto next_value = next_object->get(names.value);
|
||||
if (exception())
|
||||
return;
|
||||
|
||||
array->indexed_properties().append(next_value);
|
||||
}
|
||||
value = array;
|
||||
} else if (iterator) {
|
||||
auto next_object = iterator_next(*iterator);
|
||||
if (!next_object)
|
||||
return;
|
||||
|
||||
auto done_property = next_object->get(names.done);
|
||||
if (exception())
|
||||
return;
|
||||
|
||||
if (done_property.to_boolean()) {
|
||||
iterator = nullptr;
|
||||
value = js_undefined();
|
||||
} else {
|
||||
value = next_object->get(names.value);
|
||||
if (exception())
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
value = js_undefined();
|
||||
}
|
||||
|
||||
if (value.is_undefined() && entry.initializer) {
|
||||
value = entry.initializer->execute(interpreter(), global_object);
|
||||
if (exception())
|
||||
return;
|
||||
}
|
||||
|
||||
entry.alias.visit(
|
||||
[&](Empty) {},
|
||||
[&](NonnullRefPtr<Identifier> const& identifier) {
|
||||
set_variable(identifier->string(), value, global_object, first_assignment, specific_scope);
|
||||
},
|
||||
[&](NonnullRefPtr<BindingPattern> const& pattern) {
|
||||
assign(pattern, value, global_object, first_assignment, specific_scope);
|
||||
},
|
||||
[&](NonnullRefPtr<MemberExpression> const&) {
|
||||
VERIFY(assignment_target.has_value());
|
||||
assignment_target->put_value(global_object, value);
|
||||
});
|
||||
|
||||
if (exception())
|
||||
return;
|
||||
|
||||
if (entry.is_rest)
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case BindingPattern::Kind::Object: {
|
||||
auto object = value.to_object(global_object);
|
||||
HashTable<PropertyName, PropertyNameTraits> seen_names;
|
||||
for (auto& property : binding.entries) {
|
||||
VERIFY(!property.is_elision());
|
||||
|
||||
PropertyName assignment_name;
|
||||
Optional<Reference> assignment_target;
|
||||
|
||||
JS::Value value_to_assign;
|
||||
if (property.is_rest) {
|
||||
if (auto identifier_ptr = property.name.get_pointer<NonnullRefPtr<Identifier>>())
|
||||
assignment_name = (*identifier_ptr)->string();
|
||||
property.alias.visit(
|
||||
[&](Empty) {},
|
||||
[&](NonnullRefPtr<Identifier> const&) {
|
||||
// FIXME: We need to get the reference but bindings are broken so this doesn't work yet.
|
||||
},
|
||||
[&](NonnullRefPtr<BindingPattern> const&) {},
|
||||
[&](NonnullRefPtr<MemberExpression> const& member_expression) {
|
||||
assignment_target = member_expression->to_reference(interpreter(), global_object);
|
||||
});
|
||||
|
||||
auto* rest_object = Object::create(global_object, global_object.object_prototype());
|
||||
for (auto& object_property : object->shape().property_table()) {
|
||||
if (!object_property.value.attributes.is_enumerable())
|
||||
continue;
|
||||
if (seen_names.contains(object_property.key.to_display_string()))
|
||||
continue;
|
||||
rest_object->set(object_property.key, object->get(object_property.key), Object::ShouldThrowExceptions::Yes);
|
||||
if (exception())
|
||||
return;
|
||||
}
|
||||
|
||||
value_to_assign = rest_object;
|
||||
} else {
|
||||
property.name.visit(
|
||||
[&](Empty) { VERIFY_NOT_REACHED(); },
|
||||
[&](NonnullRefPtr<Identifier> const& identifier) {
|
||||
assignment_name = identifier->string();
|
||||
},
|
||||
[&](NonnullRefPtr<Expression> const& expression) {
|
||||
auto result = expression->execute(interpreter(), global_object);
|
||||
if (exception())
|
||||
return;
|
||||
assignment_name = result.to_property_key(global_object);
|
||||
});
|
||||
property.alias.visit(
|
||||
[&](Empty) {},
|
||||
[&](NonnullRefPtr<Identifier> const&) {
|
||||
// FIXME: We need to get the reference but bindings are broken so this doesn't work yet.
|
||||
},
|
||||
[&](NonnullRefPtr<BindingPattern> const&) {},
|
||||
[&](NonnullRefPtr<MemberExpression> const& member_expression) {
|
||||
assignment_target = member_expression->to_reference(interpreter(), global_object);
|
||||
});
|
||||
|
||||
if (exception())
|
||||
break;
|
||||
|
||||
value_to_assign = object->get(assignment_name);
|
||||
}
|
||||
|
||||
seen_names.set(assignment_name);
|
||||
|
||||
if (value_to_assign.is_empty())
|
||||
value_to_assign = js_undefined();
|
||||
|
||||
if (value_to_assign.is_undefined() && property.initializer)
|
||||
value_to_assign = property.initializer->execute(interpreter(), global_object);
|
||||
|
||||
if (exception())
|
||||
break;
|
||||
|
||||
property.alias.visit(
|
||||
[&](Empty) {
|
||||
set_variable(assignment_name.to_string(), value_to_assign, global_object, first_assignment, specific_scope);
|
||||
},
|
||||
[&](NonnullRefPtr<Identifier> const& identifier) {
|
||||
VERIFY(!property.is_rest);
|
||||
set_variable(identifier->string(), value_to_assign, global_object, first_assignment, specific_scope);
|
||||
},
|
||||
[&](NonnullRefPtr<BindingPattern> const& pattern) {
|
||||
VERIFY(!property.is_rest);
|
||||
assign(pattern, value_to_assign, global_object, first_assignment, specific_scope);
|
||||
},
|
||||
[&](NonnullRefPtr<MemberExpression> const&) {
|
||||
VERIFY(assignment_target.has_value());
|
||||
assignment_target->put_value(global_object, value_to_assign);
|
||||
});
|
||||
|
||||
if (exception())
|
||||
return;
|
||||
|
||||
if (property.is_rest)
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Value VM::get_variable(const FlyString& name, GlobalObject& global_object)
|
||||
{
|
||||
if (!m_execution_context_stack.is_empty()) {
|
||||
auto& context = running_execution_context();
|
||||
if (name == names.arguments.as_string() && context.function) {
|
||||
// HACK: Special handling for the name "arguments":
|
||||
// If the name "arguments" is defined in the current scope, for example via
|
||||
// a function parameter, or by a local var declaration, we use that.
|
||||
// Otherwise, we return a lazily constructed Array with all the argument values.
|
||||
// FIXME: Do something much more spec-compliant.
|
||||
auto possible_match = lexical_environment()->get_from_environment(name);
|
||||
if (possible_match.has_value())
|
||||
return possible_match.value().value;
|
||||
if (!context.arguments_object) {
|
||||
if (context.function->is_strict_mode() || (is<ECMAScriptFunctionObject>(context.function) && !static_cast<ECMAScriptFunctionObject*>(context.function)->has_simple_parameter_list())) {
|
||||
context.arguments_object = create_unmapped_arguments_object(global_object, context.arguments.span());
|
||||
} else {
|
||||
context.arguments_object = create_mapped_arguments_object(global_object, *context.function, verify_cast<ECMAScriptFunctionObject>(context.function)->formal_parameters(), context.arguments.span(), *lexical_environment());
|
||||
}
|
||||
}
|
||||
return context.arguments_object;
|
||||
}
|
||||
|
||||
for (auto* environment = lexical_environment(); environment; environment = environment->outer_environment()) {
|
||||
auto possible_match = environment->get_from_environment(name);
|
||||
if (exception())
|
||||
return {};
|
||||
if (possible_match.has_value())
|
||||
return possible_match.value().value;
|
||||
if (environment->has_binding(name))
|
||||
return environment->get_binding_value(global_object, name, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!global_object.storage_has(name)) {
|
||||
if (m_underscore_is_last_value && name == "_")
|
||||
return m_last_value;
|
||||
if (environment) {
|
||||
environment->initialize_binding(global_object, target, value);
|
||||
return {};
|
||||
}
|
||||
return global_object.get(name);
|
||||
auto reference = resolve_binding(target);
|
||||
reference.put_value(global_object, value);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
return {};
|
||||
}
|
||||
|
||||
// 8.5.2 Runtime Semantics: BindingInitialization, https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization
|
||||
ThrowCompletionOr<void> VM::binding_initialization(NonnullRefPtr<BindingPattern> const& target, Value value, Environment* environment, GlobalObject& global_object)
|
||||
{
|
||||
if (target->kind == BindingPattern::Kind::Object) {
|
||||
TRY(require_object_coercible(global_object, value));
|
||||
TRY(property_binding_initialization(*target, value, environment, global_object));
|
||||
return {};
|
||||
} else {
|
||||
auto* iterator = get_iterator(global_object, value);
|
||||
if (!iterator) {
|
||||
VERIFY(exception());
|
||||
return JS::throw_completion(exception()->value());
|
||||
}
|
||||
|
||||
auto iterator_done = false;
|
||||
|
||||
auto result = iterator_binding_initialization(*target, iterator, iterator_done, environment, global_object);
|
||||
|
||||
if (!iterator_done) {
|
||||
// FIXME: Iterator close should take result and potentially return that. This logic should achieve the same until that is possible.
|
||||
iterator_close(*iterator);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// 13.15.5.3 Runtime Semantics: PropertyDestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-propertydestructuringassignmentevaluation
|
||||
// 14.3.3.1 Runtime Semantics: PropertyBindingInitialization, https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-propertybindinginitialization
|
||||
ThrowCompletionOr<void> VM::property_binding_initialization(BindingPattern const& binding, Value value, Environment* environment, GlobalObject& global_object)
|
||||
{
|
||||
auto* object = value.to_object(global_object);
|
||||
if (!object) {
|
||||
VERIFY(exception());
|
||||
return JS::throw_completion(exception()->value());
|
||||
}
|
||||
|
||||
HashTable<PropertyName, PropertyNameTraits> seen_names;
|
||||
for (auto& property : binding.entries) {
|
||||
|
||||
VERIFY(!property.is_elision());
|
||||
|
||||
if (property.is_rest) {
|
||||
Reference assignment_target;
|
||||
if (auto identifier_ptr = property.name.get_pointer<NonnullRefPtr<Identifier>>()) {
|
||||
assignment_target = resolve_binding((*identifier_ptr)->string(), environment);
|
||||
} else if (auto member_ptr = property.alias.get_pointer<NonnullRefPtr<MemberExpression>>()) {
|
||||
assignment_target = (*member_ptr)->to_reference(interpreter(), global_object);
|
||||
} else {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
auto* rest_object = Object::create(global_object, global_object.object_prototype());
|
||||
VERIFY(rest_object);
|
||||
|
||||
TRY(rest_object->copy_data_properties(object, seen_names, global_object));
|
||||
if (!environment)
|
||||
assignment_target.put_value(global_object, rest_object);
|
||||
else
|
||||
assignment_target.initialize_referenced_binding(global_object, rest_object);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
PropertyName name;
|
||||
|
||||
property.name.visit(
|
||||
[&](Empty) { VERIFY_NOT_REACHED(); },
|
||||
[&](NonnullRefPtr<Identifier> const& identifier) {
|
||||
name = identifier->string();
|
||||
},
|
||||
[&](NonnullRefPtr<Expression> const& expression) {
|
||||
auto result = expression->execute(interpreter(), global_object);
|
||||
if (exception())
|
||||
return;
|
||||
name = result.to_property_key(global_object);
|
||||
});
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
seen_names.set(name);
|
||||
|
||||
if (property.name.has<NonnullRefPtr<Identifier>>() && property.alias.has<Empty>()) {
|
||||
// FIXME: this branch and not taking this have a lot in common we might want to unify it more (like it was before).
|
||||
auto& identifier = *property.name.get<NonnullRefPtr<Identifier>>();
|
||||
auto reference = resolve_binding(identifier.string(), environment);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
auto value_to_assign = object->get(name);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
if (property.initializer && value_to_assign.is_undefined()) {
|
||||
value_to_assign = TRY(named_evaluation_if_anonymous_function(global_object, *property.initializer, identifier.string()));
|
||||
}
|
||||
|
||||
if (!environment)
|
||||
reference.put_value(global_object, value_to_assign);
|
||||
else
|
||||
reference.initialize_referenced_binding(global_object, value_to_assign);
|
||||
continue;
|
||||
}
|
||||
|
||||
Optional<Reference> reference_to_assign_to;
|
||||
|
||||
property.alias.visit(
|
||||
[&](Empty) {},
|
||||
[&](NonnullRefPtr<Identifier> const& identifier) {
|
||||
reference_to_assign_to = resolve_binding(identifier->string(), environment);
|
||||
},
|
||||
[&](NonnullRefPtr<BindingPattern> const&) {},
|
||||
[&](NonnullRefPtr<MemberExpression> const& member_expression) {
|
||||
reference_to_assign_to = member_expression->to_reference(interpreter(), global_object);
|
||||
});
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
auto value_to_assign = object->get(name);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
if (property.initializer && value_to_assign.is_undefined()) {
|
||||
if (auto* identifier_ptr = property.alias.get_pointer<NonnullRefPtr<Identifier>>())
|
||||
value_to_assign = TRY(named_evaluation_if_anonymous_function(global_object, *property.initializer, (*identifier_ptr)->string()));
|
||||
else
|
||||
value_to_assign = property.initializer->execute(interpreter(), global_object);
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
}
|
||||
|
||||
if (auto* binding_ptr = property.alias.get_pointer<NonnullRefPtr<BindingPattern>>()) {
|
||||
TRY(binding_initialization(*binding_ptr, value_to_assign, environment, global_object));
|
||||
} else {
|
||||
VERIFY(reference_to_assign_to.has_value());
|
||||
if (!environment)
|
||||
reference_to_assign_to->put_value(global_object, value_to_assign);
|
||||
else
|
||||
reference_to_assign_to->initialize_referenced_binding(global_object, value_to_assign);
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// 13.15.5.5 Runtime Semantics: IteratorDestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-iteratordestructuringassignmentevaluation
|
||||
// 8.5.3 Runtime Semantics: IteratorBindingInitialization, https://tc39.es/ecma262/#sec-runtime-semantics-iteratorbindinginitialization
|
||||
ThrowCompletionOr<void> VM::iterator_binding_initialization(BindingPattern const& binding, Object* iterator, bool& iterator_done, Environment* environment, GlobalObject& global_object)
|
||||
{
|
||||
// FIXME: this method is nearly identical to destructuring assignment!
|
||||
for (size_t i = 0; i < binding.entries.size(); i++) {
|
||||
auto& entry = binding.entries[i];
|
||||
Value value;
|
||||
|
||||
Optional<Reference> assignment_target;
|
||||
entry.alias.visit(
|
||||
[&](Empty) {},
|
||||
[&](NonnullRefPtr<Identifier> const& identifier) {
|
||||
assignment_target = resolve_binding(identifier->string(), environment);
|
||||
},
|
||||
[&](NonnullRefPtr<BindingPattern> const&) {},
|
||||
[&](NonnullRefPtr<MemberExpression> const& member_expression) {
|
||||
assignment_target = member_expression->to_reference(interpreter(), global_object);
|
||||
});
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
if (entry.is_rest) {
|
||||
VERIFY(i == binding.entries.size() - 1);
|
||||
|
||||
auto* array = Array::create(global_object, 0);
|
||||
while (!iterator_done) {
|
||||
auto next_object = iterator_next(*iterator);
|
||||
if (!next_object) {
|
||||
iterator_done = true;
|
||||
VERIFY(exception());
|
||||
return JS::throw_completion(exception()->value());
|
||||
}
|
||||
|
||||
auto done_property = next_object->get(names.done);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
if (done_property.to_boolean()) {
|
||||
iterator_done = true;
|
||||
break;
|
||||
}
|
||||
|
||||
auto next_value = next_object->get(names.value);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
array->indexed_properties().append(next_value);
|
||||
}
|
||||
value = array;
|
||||
|
||||
} else if (!iterator_done) {
|
||||
auto next_object = iterator_next(*iterator);
|
||||
if (!next_object) {
|
||||
iterator_done = true;
|
||||
VERIFY(exception());
|
||||
return JS::throw_completion(exception()->value());
|
||||
}
|
||||
|
||||
auto done_property = next_object->get(names.done);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
if (done_property.to_boolean()) {
|
||||
iterator_done = true;
|
||||
value = js_undefined();
|
||||
} else {
|
||||
value = next_object->get(names.value);
|
||||
if (auto* thrown_exception = exception()) {
|
||||
iterator_done = true;
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value = js_undefined();
|
||||
}
|
||||
|
||||
if (value.is_undefined() && entry.initializer) {
|
||||
VERIFY(!entry.is_rest);
|
||||
if (auto* identifier_ptr = entry.alias.get_pointer<NonnullRefPtr<Identifier>>())
|
||||
value = TRY(named_evaluation_if_anonymous_function(global_object, *entry.initializer, (*identifier_ptr)->string()));
|
||||
else
|
||||
value = entry.initializer->execute(interpreter(), global_object);
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
}
|
||||
|
||||
if (auto* binding_ptr = entry.alias.get_pointer<NonnullRefPtr<BindingPattern>>()) {
|
||||
TRY(binding_initialization(*binding_ptr, value, environment, global_object));
|
||||
} else if (!entry.alias.has<Empty>()) {
|
||||
VERIFY(assignment_target.has_value());
|
||||
if (!environment)
|
||||
assignment_target->put_value(global_object, value);
|
||||
else
|
||||
assignment_target->initialize_referenced_binding(global_object, value);
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference
|
||||
|
@ -435,23 +451,14 @@ Reference VM::get_identifier_reference(Environment* environment, FlyString name,
|
|||
// a. Return the Reference Record { [[Base]]: unresolvable, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }.
|
||||
return Reference { Reference::BaseType::Unresolvable, move(name), strict };
|
||||
}
|
||||
auto exists = environment->has_binding(name);
|
||||
if (exception())
|
||||
return {};
|
||||
|
||||
// FIXME: The remainder of this function is non-conforming.
|
||||
|
||||
for (; environment && environment->outer_environment(); environment = environment->outer_environment()) {
|
||||
auto possible_match = environment->get_from_environment(name);
|
||||
if (possible_match.has_value())
|
||||
return Reference { *environment, move(name), strict };
|
||||
if (environment->has_binding(name))
|
||||
return Reference { *environment, move(name), strict };
|
||||
}
|
||||
|
||||
auto& global_environment = interpreter().realm().global_environment();
|
||||
if (global_environment.has_binding(name) || !in_strict_mode()) {
|
||||
return Reference { global_environment, move(name), strict };
|
||||
}
|
||||
|
||||
return Reference { Reference::BaseType::Unresolvable, move(name), strict };
|
||||
if (exists)
|
||||
return Reference { *environment, move(name), strict };
|
||||
else
|
||||
return get_identifier_reference(environment->outer_environment(), move(name), strict);
|
||||
}
|
||||
|
||||
// 9.4.2 ResolveBinding ( name [ , env ] ), https://tc39.es/ecma262/#sec-resolvebinding
|
||||
|
@ -546,7 +553,9 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
|
|||
return {};
|
||||
}
|
||||
|
||||
auto* constructor_environment = callee_context.lexical_environment;
|
||||
auto result = function.construct(new_target);
|
||||
VERIFY(constructor_environment);
|
||||
|
||||
pop_execution_context();
|
||||
pop_guard.disarm();
|
||||
|
@ -557,8 +566,8 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
|
|||
if ((!is<ECMAScriptFunctionObject>(function) || static_cast<ECMAScriptFunctionObject&>(function).constructor_kind() == ECMAScriptFunctionObject::ConstructorKind::Base)
|
||||
&& is<ECMAScriptFunctionObject>(new_target) && static_cast<ECMAScriptFunctionObject&>(new_target).constructor_kind() == ECMAScriptFunctionObject::ConstructorKind::Derived
|
||||
&& result.is_object()) {
|
||||
if (auto* environment = callee_context.lexical_environment)
|
||||
verify_cast<FunctionEnvironment>(environment)->replace_this_binding(result);
|
||||
verify_cast<FunctionEnvironment>(constructor_environment)->replace_this_binding(result);
|
||||
|
||||
auto prototype = new_target.get(names.prototype);
|
||||
if (exception())
|
||||
return {};
|
||||
|
@ -573,9 +582,16 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
|
|||
if (result.is_object())
|
||||
return result;
|
||||
|
||||
if (auto* environment = callee_context.lexical_environment)
|
||||
return environment->get_this_binding(global_object);
|
||||
return this_argument;
|
||||
if (is<ECMAScriptFunctionObject>(function) && static_cast<ECMAScriptFunctionObject&>(function).constructor_kind() == ECMAScriptFunctionObject::ConstructorKind::Base)
|
||||
return this_argument;
|
||||
|
||||
if (!result.is_empty() && !result.is_undefined()) {
|
||||
throw_exception<TypeError>(global_object, ErrorType::DerivedConstructorReturningInvalidValue);
|
||||
return {};
|
||||
}
|
||||
|
||||
VERIFY(constructor_environment);
|
||||
return constructor_environment->get_this_binding(global_object);
|
||||
}
|
||||
|
||||
void VM::throw_exception(Exception& exception)
|
||||
|
@ -609,7 +625,7 @@ Value VM::get_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, [[maybe_unused]] Object* new_target)
|
||||
void VM::prepare_for_ordinary_call(FunctionObject& function, ExecutionContext& callee_context, Object* 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.
|
||||
|
@ -643,8 +659,7 @@ void VM::prepare_for_ordinary_call(FunctionObject& function, ExecutionContext& c
|
|||
// FIXME: Our execution context struct currently does not track this item.
|
||||
|
||||
// 7. 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);
|
||||
auto* local_environment = function.new_function_environment(new_target);
|
||||
|
||||
// 8. Set the LexicalEnvironment of calleeContext to localEnv.
|
||||
callee_context.lexical_environment = local_environment;
|
||||
|
@ -724,8 +739,7 @@ ThrowCompletionOr<Value> VM::call_internal(FunctionObject& function, Value this_
|
|||
callee_context.this_value = this_value;
|
||||
append_bound_and_passed_arguments(callee_context.arguments, {}, move(arguments));
|
||||
|
||||
if (callee_context.lexical_environment)
|
||||
ordinary_call_bind_this(function, callee_context, this_value);
|
||||
ordinary_call_bind_this(function, callee_context, this_value);
|
||||
|
||||
if (auto* exception = this->exception())
|
||||
return JS::throw_completion(exception->value());
|
||||
|
@ -810,19 +824,6 @@ void VM::dump_backtrace() const
|
|||
}
|
||||
}
|
||||
|
||||
void VM::dump_environment_chain() const
|
||||
{
|
||||
for (auto* environment = lexical_environment(); environment; environment = environment->outer_environment()) {
|
||||
dbgln("+> {} ({:p})", environment->class_name(), environment);
|
||||
if (is<DeclarativeEnvironment>(*environment)) {
|
||||
auto& declarative_environment = static_cast<DeclarativeEnvironment const&>(*environment);
|
||||
for (auto& variable : declarative_environment.variables()) {
|
||||
dbgln(" {}", variable.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VM::CustomData::~CustomData()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2021, David Tuin <david.tuin@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -38,12 +39,6 @@ enum class ScopeType {
|
|||
Continuable,
|
||||
};
|
||||
|
||||
struct ScopeFrame {
|
||||
ScopeType type;
|
||||
NonnullRefPtr<ScopeNode> scope_node;
|
||||
bool pushed_environment { false };
|
||||
};
|
||||
|
||||
class VM : public RefCounted<VM> {
|
||||
public:
|
||||
struct CustomData {
|
||||
|
@ -67,7 +62,6 @@ public:
|
|||
void clear_exception() { m_exception = nullptr; }
|
||||
|
||||
void dump_backtrace() const;
|
||||
void dump_environment_chain() const;
|
||||
|
||||
class InterpreterExecutionScope {
|
||||
public:
|
||||
|
@ -206,12 +200,6 @@ public:
|
|||
ScopeType unwind_until() const { return m_unwind_until; }
|
||||
FlyString unwind_until_label() const { return m_unwind_until_label; }
|
||||
|
||||
Value get_variable(const FlyString& name, GlobalObject&);
|
||||
void set_variable(const FlyString& name, Value, GlobalObject&, bool first_assignment = false, Environment* specific_scope = nullptr);
|
||||
void assign(const Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>>& target, Value, GlobalObject&, bool first_assignment = false, Environment* specific_scope = nullptr);
|
||||
void assign(const FlyString& target, Value, GlobalObject&, bool first_assignment = false, Environment* specific_scope = nullptr);
|
||||
void assign(const NonnullRefPtr<BindingPattern>& target, Value, GlobalObject&, bool first_assignment = false, Environment* specific_scope = nullptr);
|
||||
|
||||
Reference resolve_binding(FlyString const&, Environment* = nullptr);
|
||||
Reference get_identifier_reference(Environment*, FlyString, bool strict);
|
||||
|
||||
|
@ -285,6 +273,12 @@ public:
|
|||
|
||||
CustomData* custom_data() { return m_custom_data; }
|
||||
|
||||
ThrowCompletionOr<void> destructuring_assignment_evaluation(NonnullRefPtr<BindingPattern> const& target, Value value, GlobalObject& global_object);
|
||||
ThrowCompletionOr<void> binding_initialization(FlyString const& target, Value value, Environment* environment, GlobalObject& global_object);
|
||||
ThrowCompletionOr<void> binding_initialization(NonnullRefPtr<BindingPattern> const& target, Value value, Environment* environment, GlobalObject& global_object);
|
||||
|
||||
ThrowCompletionOr<Value> named_evaluation_if_anonymous_function(GlobalObject& global_object, ASTNode const& expression, FlyString const& name);
|
||||
|
||||
private:
|
||||
explicit VM(OwnPtr<CustomData>);
|
||||
|
||||
|
@ -293,6 +287,11 @@ private:
|
|||
[[nodiscard]] ThrowCompletionOr<Value> call_internal(FunctionObject&, Value this_value, Optional<MarkedValueList> arguments);
|
||||
void prepare_for_ordinary_call(FunctionObject&, ExecutionContext& callee_context, Object* new_target);
|
||||
|
||||
ThrowCompletionOr<Object*> copy_data_properties(Object& rest_object, Object const& source, HashTable<PropertyName, PropertyNameTraits> const& seen_names, GlobalObject& global_object);
|
||||
|
||||
ThrowCompletionOr<void> property_binding_initialization(BindingPattern const& binding, Value value, Environment* environment, GlobalObject& global_object);
|
||||
ThrowCompletionOr<void> iterator_binding_initialization(BindingPattern const& binding, Object* iterator, bool& iterator_done, Environment* environment, GlobalObject& global_object);
|
||||
|
||||
Exception* m_exception { nullptr };
|
||||
|
||||
Heap m_heap;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue