mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 08:58:11 +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
File diff suppressed because it is too large
Load diff
|
@ -24,10 +24,12 @@
|
|||
|
||||
namespace JS {
|
||||
|
||||
class VariableDeclaration;
|
||||
class Declaration;
|
||||
class ClassDeclaration;
|
||||
class FunctionDeclaration;
|
||||
class Identifier;
|
||||
class MemberExpression;
|
||||
class VariableDeclaration;
|
||||
|
||||
enum class FunctionKind {
|
||||
Generator,
|
||||
|
@ -137,6 +139,25 @@ private:
|
|||
NonnullRefPtr<Expression> m_expression;
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
class IteratorOrVoidFunction : public Function<IterationDecision(Args...)> {
|
||||
public:
|
||||
template<typename CallableType>
|
||||
IteratorOrVoidFunction(CallableType&& callable) requires(VoidFunction<CallableType, Args...>)
|
||||
: Function<IterationDecision(Args...)>([callable = forward<CallableType>(callable)](Args... args) {
|
||||
callable(forward<Args>(args)...);
|
||||
return IterationDecision::Continue;
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
template<typename CallableType>
|
||||
IteratorOrVoidFunction(CallableType&& callable) requires(IteratorFunction<CallableType, Args...>)
|
||||
: Function<IterationDecision(Args...)>(forward<CallableType>(callable))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class ScopeNode : public LabelableStatement {
|
||||
public:
|
||||
template<typename T, typename... Args>
|
||||
|
@ -152,16 +173,26 @@ public:
|
|||
}
|
||||
|
||||
NonnullRefPtrVector<Statement> const& children() const { return m_children; }
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
virtual void generate_bytecode(Bytecode::Generator&) const override;
|
||||
|
||||
void add_variables(NonnullRefPtrVector<VariableDeclaration>);
|
||||
void add_functions(NonnullRefPtrVector<FunctionDeclaration>);
|
||||
void add_hoisted_function(NonnullRefPtr<FunctionDeclaration>);
|
||||
NonnullRefPtrVector<VariableDeclaration> const& variables() const { return m_variables; }
|
||||
NonnullRefPtrVector<FunctionDeclaration> const& functions() const { return m_functions; }
|
||||
NonnullRefPtrVector<FunctionDeclaration> const& hoisted_functions() const { return m_hoisted_functions; }
|
||||
Value evaluate_statements(Interpreter& interpreter, GlobalObject& global_object) const;
|
||||
|
||||
void add_var_scoped_declaration(NonnullRefPtr<Declaration> variables);
|
||||
void add_lexical_declaration(NonnullRefPtr<Declaration> variables);
|
||||
void add_hoisted_function(NonnullRefPtr<FunctionDeclaration> declaration);
|
||||
|
||||
void for_each_lexically_scoped_declaration(IteratorOrVoidFunction<Declaration const&>&& callback) const;
|
||||
void for_each_lexically_declared_name(IteratorOrVoidFunction<FlyString const&>&& callback) const;
|
||||
|
||||
void for_each_var_declared_name(IteratorOrVoidFunction<FlyString const&>&& callback) const;
|
||||
|
||||
void for_each_var_function_declaration_in_reverse_order(IteratorOrVoidFunction<FunctionDeclaration const&>&& callback) const;
|
||||
void for_each_var_scoped_variable_declaration(IteratorOrVoidFunction<VariableDeclaration const&>&& callback) const;
|
||||
|
||||
void block_declaration_instantiation(GlobalObject& global_object, Environment* environment) const;
|
||||
|
||||
void for_each_function_hoistable_with_annexB_extension(IteratorOrVoidFunction<FunctionDeclaration&>&& callback) const;
|
||||
|
||||
protected:
|
||||
explicit ScopeNode(SourceRange source_range)
|
||||
|
@ -173,9 +204,10 @@ private:
|
|||
virtual bool is_scope_node() const final { return true; }
|
||||
|
||||
NonnullRefPtrVector<Statement> m_children;
|
||||
NonnullRefPtrVector<VariableDeclaration> m_variables;
|
||||
NonnullRefPtrVector<FunctionDeclaration> m_functions;
|
||||
NonnullRefPtrVector<FunctionDeclaration> m_hoisted_functions;
|
||||
NonnullRefPtrVector<Declaration> m_lexical_declarations;
|
||||
NonnullRefPtrVector<Declaration> m_var_declarations;
|
||||
|
||||
NonnullRefPtrVector<FunctionDeclaration> m_functions_hoistable_with_annexB_extension;
|
||||
};
|
||||
|
||||
class ImportStatement final : public Statement {
|
||||
|
@ -280,6 +312,8 @@ public:
|
|||
NonnullRefPtrVector<ImportStatement> const& imports() const { return m_imports; }
|
||||
NonnullRefPtrVector<ExportStatement> const& exports() const { return m_exports; }
|
||||
|
||||
ThrowCompletionOr<void> global_declaration_instantiation(Interpreter& interpreter, GlobalObject& global_object, GlobalEnvironment& global_environment) const;
|
||||
|
||||
private:
|
||||
virtual bool is_program() const override { return true; }
|
||||
|
||||
|
@ -296,6 +330,24 @@ public:
|
|||
: ScopeNode(source_range)
|
||||
{
|
||||
}
|
||||
Value execute(Interpreter& interpreter, GlobalObject& object) const override;
|
||||
};
|
||||
|
||||
class FunctionBody final : public ScopeNode {
|
||||
public:
|
||||
explicit FunctionBody(SourceRange source_range)
|
||||
: ScopeNode(source_range)
|
||||
{
|
||||
}
|
||||
|
||||
void set_strict_mode() { m_in_strict_mode = true; }
|
||||
|
||||
bool in_strict_mode() const { return m_in_strict_mode; }
|
||||
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
|
||||
private:
|
||||
bool m_in_strict_mode { false };
|
||||
};
|
||||
|
||||
class Expression : public ASTNode {
|
||||
|
@ -313,6 +365,15 @@ public:
|
|||
: Statement(source_range)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const = 0;
|
||||
|
||||
// 8.1.3 Static Semantics: IsConstantDeclaration, https://tc39.es/ecma262/#sec-static-semantics-isconstantdeclaration
|
||||
virtual bool is_constant_declaration() const { return false; }
|
||||
|
||||
virtual bool is_function_declaration() const { return false; }
|
||||
|
||||
virtual bool is_lexical_declaration() const { return false; }
|
||||
};
|
||||
|
||||
class ErrorDeclaration final : public Declaration {
|
||||
|
@ -322,6 +383,11 @@ public:
|
|||
{
|
||||
}
|
||||
Value execute(Interpreter&, GlobalObject&) const override { return {}; }
|
||||
|
||||
void for_each_bound_name(IteratorOrVoidFunction<FlyString const&>) const override
|
||||
{
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
};
|
||||
|
||||
struct BindingPattern : RefCounted<BindingPattern> {
|
||||
|
@ -346,6 +412,8 @@ struct BindingPattern : RefCounted<BindingPattern> {
|
|||
template<typename C>
|
||||
void for_each_bound_name(C&& callback) const;
|
||||
|
||||
bool contains_expression() const;
|
||||
|
||||
Vector<BindingEntry> entries;
|
||||
Kind kind { Kind::Object };
|
||||
};
|
||||
|
@ -412,6 +480,15 @@ public:
|
|||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
virtual void generate_bytecode(Bytecode::Generator&) const override;
|
||||
|
||||
void for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const override;
|
||||
|
||||
virtual bool is_function_declaration() const override { return true; }
|
||||
|
||||
void set_should_do_additional_annexB_steps() { m_is_hoisted = true; }
|
||||
|
||||
private:
|
||||
bool m_is_hoisted { false };
|
||||
};
|
||||
|
||||
class FunctionExpression final
|
||||
|
@ -429,25 +506,11 @@ public:
|
|||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
|
||||
void set_name_if_possible(FlyString new_name)
|
||||
{
|
||||
if (m_cannot_auto_rename)
|
||||
return;
|
||||
m_cannot_auto_rename = true;
|
||||
if (name().is_empty()) {
|
||||
set_name(move(new_name));
|
||||
m_is_auto_renamed = true;
|
||||
}
|
||||
}
|
||||
bool cannot_auto_rename() const { return m_cannot_auto_rename; }
|
||||
bool is_auto_renamed() const { return m_is_auto_renamed; }
|
||||
void set_cannot_auto_rename() { m_cannot_auto_rename = true; }
|
||||
|
||||
virtual void generate_bytecode(Bytecode::Generator&) const override;
|
||||
|
||||
private:
|
||||
bool m_cannot_auto_rename { false };
|
||||
bool m_is_auto_renamed { false };
|
||||
bool has_name() const { return !name().is_empty(); }
|
||||
|
||||
Value instantiate_ordinary_function_expression(Interpreter& interpreter, GlobalObject& global_object, FlyString given_name) const;
|
||||
};
|
||||
|
||||
class ErrorExpression final : public Expression {
|
||||
|
@ -614,7 +677,7 @@ private:
|
|||
|
||||
class ForInStatement final : public IterationStatement {
|
||||
public:
|
||||
ForInStatement(SourceRange source_range, NonnullRefPtr<ASTNode> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
|
||||
ForInStatement(SourceRange source_range, Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
|
||||
: IterationStatement(source_range)
|
||||
, m_lhs(move(lhs))
|
||||
, m_rhs(move(rhs))
|
||||
|
@ -622,7 +685,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
ASTNode const& lhs() const { return *m_lhs; }
|
||||
auto const& lhs() const { return m_lhs; }
|
||||
Expression const& rhs() const { return *m_rhs; }
|
||||
Statement const& body() const { return *m_body; }
|
||||
|
||||
|
@ -630,14 +693,14 @@ public:
|
|||
virtual void dump(int indent) const override;
|
||||
|
||||
private:
|
||||
NonnullRefPtr<ASTNode> m_lhs;
|
||||
Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> m_lhs;
|
||||
NonnullRefPtr<Expression> m_rhs;
|
||||
NonnullRefPtr<Statement> m_body;
|
||||
};
|
||||
|
||||
class ForOfStatement final : public IterationStatement {
|
||||
public:
|
||||
ForOfStatement(SourceRange source_range, NonnullRefPtr<ASTNode> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
|
||||
ForOfStatement(SourceRange source_range, Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
|
||||
: IterationStatement(source_range)
|
||||
, m_lhs(move(lhs))
|
||||
, m_rhs(move(rhs))
|
||||
|
@ -645,7 +708,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
ASTNode const& lhs() const { return *m_lhs; }
|
||||
auto const& lhs() const { return m_lhs; }
|
||||
Expression const& rhs() const { return *m_rhs; }
|
||||
Statement const& body() const { return *m_body; }
|
||||
|
||||
|
@ -653,7 +716,7 @@ public:
|
|||
virtual void dump(int indent) const override;
|
||||
|
||||
private:
|
||||
NonnullRefPtr<ASTNode> m_lhs;
|
||||
Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> m_lhs;
|
||||
NonnullRefPtr<Expression> m_rhs;
|
||||
NonnullRefPtr<Statement> m_body;
|
||||
};
|
||||
|
@ -1000,6 +1063,10 @@ public:
|
|||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
|
||||
bool has_name() const { return !m_name.is_empty(); }
|
||||
|
||||
ThrowCompletionOr<Value> class_definition_evaluation(Interpreter& interpreter, GlobalObject& global_object, FlyString const& binding_name = {}, FlyString const& class_name = {}) const;
|
||||
|
||||
private:
|
||||
String m_name;
|
||||
RefPtr<FunctionExpression> m_constructor;
|
||||
|
@ -1020,6 +1087,10 @@ public:
|
|||
virtual void dump(int indent) const override;
|
||||
virtual void generate_bytecode(Bytecode::Generator&) const override;
|
||||
|
||||
void for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const override;
|
||||
|
||||
virtual bool is_lexical_declaration() const override { return true; }
|
||||
|
||||
private:
|
||||
NonnullRefPtr<ClassExpression> m_class_expression;
|
||||
};
|
||||
|
@ -1239,6 +1310,12 @@ public:
|
|||
|
||||
NonnullRefPtrVector<VariableDeclarator> const& declarations() const { return m_declarations; }
|
||||
|
||||
virtual void for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const override;
|
||||
|
||||
virtual bool is_constant_declaration() const override { return m_declaration_kind == DeclarationKind::Const; };
|
||||
|
||||
virtual bool is_lexical_declaration() const override { return m_declaration_kind != DeclarationKind::Var; }
|
||||
|
||||
private:
|
||||
DeclarationKind m_declaration_kind;
|
||||
NonnullRefPtrVector<VariableDeclarator> m_declarations;
|
||||
|
@ -1550,32 +1627,28 @@ private:
|
|||
NonnullRefPtr<Expression> m_argument;
|
||||
};
|
||||
|
||||
class SwitchCase final : public ASTNode {
|
||||
class SwitchCase final : public ScopeNode {
|
||||
public:
|
||||
SwitchCase(SourceRange source_range, RefPtr<Expression> test, NonnullRefPtrVector<Statement> consequent)
|
||||
: ASTNode(source_range)
|
||||
SwitchCase(SourceRange source_range, RefPtr<Expression> test)
|
||||
: ScopeNode(source_range)
|
||||
, m_test(move(test))
|
||||
, m_consequent(move(consequent))
|
||||
{
|
||||
}
|
||||
|
||||
Expression const* test() const { return m_test; }
|
||||
NonnullRefPtrVector<Statement> const& consequent() const { return m_consequent; }
|
||||
|
||||
virtual void dump(int indent) const override;
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
|
||||
private:
|
||||
RefPtr<Expression> m_test;
|
||||
NonnullRefPtrVector<Statement> m_consequent;
|
||||
};
|
||||
|
||||
class SwitchStatement final : public LabelableStatement {
|
||||
class SwitchStatement final : public ScopeNode {
|
||||
public:
|
||||
SwitchStatement(SourceRange source_range, NonnullRefPtr<Expression> discriminant, NonnullRefPtrVector<SwitchCase> cases)
|
||||
: LabelableStatement(source_range)
|
||||
SwitchStatement(SourceRange source_range, NonnullRefPtr<Expression> discriminant)
|
||||
: ScopeNode(source_range)
|
||||
, m_discriminant(move(discriminant))
|
||||
, m_cases(move(cases))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1583,6 +1656,8 @@ public:
|
|||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void generate_bytecode(Bytecode::Generator&) const override;
|
||||
|
||||
void add_case(NonnullRefPtr<SwitchCase> switch_case) { m_cases.append(move(switch_case)); }
|
||||
|
||||
private:
|
||||
NonnullRefPtr<Expression> m_discriminant;
|
||||
NonnullRefPtrVector<SwitchCase> m_cases;
|
||||
|
|
|
@ -26,46 +26,7 @@ void ASTNode::generate_bytecode(Bytecode::Generator&) const
|
|||
|
||||
void ScopeNode::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
for (auto& function : functions()) {
|
||||
generator.emit<Bytecode::Op::NewFunction>(function);
|
||||
generator.emit<Bytecode::Op::SetVariable>(generator.intern_string(function.name()));
|
||||
}
|
||||
|
||||
HashMap<u32, Variable> scope_variables_with_declaration_kind;
|
||||
|
||||
bool is_program_node = is<Program>(*this);
|
||||
for (auto& declaration : variables()) {
|
||||
for (auto& declarator : declaration.declarations()) {
|
||||
if (is_program_node && declaration.declaration_kind() == DeclarationKind::Var) {
|
||||
declarator.target().visit(
|
||||
[&](const NonnullRefPtr<Identifier>& id) {
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
|
||||
generator.emit<Bytecode::Op::PutById>(Bytecode::Register::global_object(), generator.intern_string(id->string()));
|
||||
},
|
||||
[&](const NonnullRefPtr<BindingPattern>& binding) {
|
||||
binding->for_each_bound_name([&](const auto& name) {
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
|
||||
generator.emit<Bytecode::Op::PutById>(Bytecode::Register::global_object(), generator.intern_string(name));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
declarator.target().visit(
|
||||
[&](const NonnullRefPtr<Identifier>& id) {
|
||||
scope_variables_with_declaration_kind.set((size_t)generator.intern_string(id->string()).value(), { js_undefined(), declaration.declaration_kind() });
|
||||
},
|
||||
[&](const NonnullRefPtr<BindingPattern>& binding) {
|
||||
binding->for_each_bound_name([&](const auto& name) {
|
||||
scope_variables_with_declaration_kind.set((size_t)generator.intern_string(name).value(), { js_undefined(), declaration.declaration_kind() });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!scope_variables_with_declaration_kind.is_empty()) {
|
||||
generator.emit<Bytecode::Op::PushDeclarativeEnvironment>(move(scope_variables_with_declaration_kind));
|
||||
}
|
||||
|
||||
// FIXME: Register lexical and variable scope declarations
|
||||
for (auto& child : children()) {
|
||||
child.generate_bytecode(generator);
|
||||
if (generator.is_current_block_terminated())
|
||||
|
@ -1294,7 +1255,7 @@ void SwitchStatement::generate_bytecode(Bytecode::Generator& generator) const
|
|||
generator.switch_to_basic_block(*current_block);
|
||||
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
|
||||
for (auto& statement : switch_case.consequent()) {
|
||||
for (auto& statement : switch_case.children()) {
|
||||
statement.generate_bytecode(generator);
|
||||
}
|
||||
if (!generator.is_current_block_terminated()) {
|
||||
|
|
|
@ -217,12 +217,22 @@ void ConcatString::execute_impl(Bytecode::Interpreter& interpreter) const
|
|||
|
||||
void GetVariable::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
interpreter.accumulator() = interpreter.vm().get_variable(interpreter.current_executable().get_string(m_identifier), interpreter.global_object());
|
||||
auto& vm = interpreter.vm();
|
||||
auto reference = vm.resolve_binding(interpreter.current_executable().get_string(m_identifier));
|
||||
if (vm.exception())
|
||||
return;
|
||||
|
||||
interpreter.accumulator() = reference.get_value(interpreter.global_object());
|
||||
}
|
||||
|
||||
void SetVariable::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
interpreter.vm().set_variable(interpreter.current_executable().get_string(m_identifier), interpreter.accumulator(), interpreter.global_object());
|
||||
auto& vm = interpreter.vm();
|
||||
auto reference = vm.resolve_binding(interpreter.current_executable().get_string(m_identifier));
|
||||
if (vm.exception())
|
||||
return;
|
||||
|
||||
reference.put_value(interpreter.global_object(), interpreter.accumulator());
|
||||
}
|
||||
|
||||
void GetById::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
|
@ -390,10 +400,7 @@ void ContinuePendingUnwind::replace_references_impl(BasicBlock const& from, Basi
|
|||
|
||||
void PushDeclarativeEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
HashMap<FlyString, Variable> resolved_variables;
|
||||
for (auto& it : m_variables)
|
||||
resolved_variables.set(interpreter.current_executable().get_string(it.key), it.value);
|
||||
auto* environment = interpreter.vm().heap().allocate<DeclarativeEnvironment>(interpreter.global_object(), move(resolved_variables), interpreter.vm().lexical_environment());
|
||||
auto* environment = interpreter.vm().heap().allocate<DeclarativeEnvironment>(interpreter.global_object(), interpreter.vm().lexical_environment());
|
||||
interpreter.vm().running_execution_context().lexical_environment = environment;
|
||||
interpreter.vm().running_execution_context().variable_environment = environment;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <AK/ScopeGuard.h>
|
||||
#include <LibJS/AST.h>
|
||||
#include <LibJS/Interpreter.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
|
||||
#include <LibJS/Runtime/FunctionEnvironment.h>
|
||||
#include <LibJS/Runtime/GlobalEnvironment.h>
|
||||
|
@ -93,139 +94,4 @@ const Realm& Interpreter::realm() const
|
|||
return static_cast<const Realm&>(*m_realm.cell());
|
||||
}
|
||||
|
||||
void Interpreter::enter_scope(const ScopeNode& scope_node, ScopeType scope_type, GlobalObject& global_object)
|
||||
{
|
||||
ScopeGuard guard([&] {
|
||||
for (auto& declaration : scope_node.hoisted_functions()) {
|
||||
lexical_environment()->put_into_environment(declaration.name(), { js_undefined(), DeclarationKind::Var });
|
||||
}
|
||||
for (auto& declaration : scope_node.functions()) {
|
||||
auto* function = ECMAScriptFunctionObject::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), lexical_environment(), declaration.kind(), declaration.is_strict_mode());
|
||||
vm().set_variable(declaration.name(), function, global_object);
|
||||
}
|
||||
});
|
||||
|
||||
if (scope_type == ScopeType::Function) {
|
||||
push_scope({ scope_type, scope_node, false });
|
||||
for (auto& declaration : scope_node.functions())
|
||||
lexical_environment()->put_into_environment(declaration.name(), { js_undefined(), DeclarationKind::Var });
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<FlyString, Variable> scope_variables_with_declaration_kind;
|
||||
|
||||
if (!scope_node.variables().is_empty())
|
||||
scope_variables_with_declaration_kind.ensure_capacity(16);
|
||||
|
||||
bool is_program_node = is<Program>(scope_node);
|
||||
|
||||
for (auto& declaration : scope_node.variables()) {
|
||||
for (auto& declarator : declaration.declarations()) {
|
||||
if (is_program_node && declaration.declaration_kind() == DeclarationKind::Var) {
|
||||
declarator.target().visit(
|
||||
[&](const NonnullRefPtr<Identifier>& id) {
|
||||
global_object.define_direct_property(id->string(), js_undefined(), JS::Attribute::Writable | JS::Attribute::Enumerable);
|
||||
},
|
||||
[&](const NonnullRefPtr<BindingPattern>& binding) {
|
||||
binding->for_each_bound_name([&](const auto& name) {
|
||||
global_object.define_direct_property(name, js_undefined(), JS::Attribute::Writable | JS::Attribute::Enumerable);
|
||||
});
|
||||
});
|
||||
if (exception())
|
||||
return;
|
||||
} else {
|
||||
declarator.target().visit(
|
||||
[&](const NonnullRefPtr<Identifier>& id) {
|
||||
scope_variables_with_declaration_kind.set(id->string(), { js_undefined(), declaration.declaration_kind() });
|
||||
},
|
||||
[&](const NonnullRefPtr<BindingPattern>& binding) {
|
||||
binding->for_each_bound_name([&](const auto& name) {
|
||||
scope_variables_with_declaration_kind.set(name, { js_undefined(), declaration.declaration_kind() });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool pushed_environment = false;
|
||||
|
||||
if (!scope_variables_with_declaration_kind.is_empty()) {
|
||||
auto* environment = heap().allocate<DeclarativeEnvironment>(global_object, move(scope_variables_with_declaration_kind), lexical_environment());
|
||||
vm().running_execution_context().lexical_environment = environment;
|
||||
vm().running_execution_context().variable_environment = environment;
|
||||
pushed_environment = true;
|
||||
}
|
||||
|
||||
push_scope({ scope_type, scope_node, pushed_environment });
|
||||
}
|
||||
|
||||
void Interpreter::exit_scope(const ScopeNode& scope_node)
|
||||
{
|
||||
while (!m_scope_stack.is_empty()) {
|
||||
auto popped_scope = m_scope_stack.take_last();
|
||||
if (popped_scope.pushed_environment) {
|
||||
vm().running_execution_context().lexical_environment = vm().running_execution_context().lexical_environment->outer_environment();
|
||||
vm().running_execution_context().variable_environment = vm().running_execution_context().variable_environment->outer_environment();
|
||||
}
|
||||
if (popped_scope.scope_node.ptr() == &scope_node)
|
||||
break;
|
||||
}
|
||||
|
||||
// If we unwind all the way, just reset m_unwind_until so that future "return" doesn't break.
|
||||
if (m_scope_stack.is_empty())
|
||||
vm().stop_unwind();
|
||||
}
|
||||
|
||||
void Interpreter::push_scope(ScopeFrame frame)
|
||||
{
|
||||
m_scope_stack.append(move(frame));
|
||||
}
|
||||
|
||||
Value Interpreter::execute_statement(GlobalObject& global_object, const Statement& statement, ScopeType scope_type)
|
||||
{
|
||||
if (!is<ScopeNode>(statement))
|
||||
return statement.execute(*this, global_object);
|
||||
|
||||
auto& block = static_cast<const ScopeNode&>(statement);
|
||||
Vector<FlyString> const& labels = [&] {
|
||||
if (is<BlockStatement>(block)) {
|
||||
return static_cast<BlockStatement const&>(block).labels();
|
||||
} else {
|
||||
return Vector<FlyString>();
|
||||
}
|
||||
}();
|
||||
|
||||
enter_scope(block, scope_type, global_object);
|
||||
|
||||
Value last_value;
|
||||
for (auto& node : block.children()) {
|
||||
auto value = node.execute(*this, global_object);
|
||||
if (!value.is_empty())
|
||||
last_value = value;
|
||||
if (vm().should_unwind()) {
|
||||
if (!labels.is_empty() && vm().should_unwind_until(ScopeType::Breakable, labels))
|
||||
vm().stop_unwind();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (scope_type == ScopeType::Function) {
|
||||
bool did_return = vm().unwind_until() == ScopeType::Function;
|
||||
if (!did_return)
|
||||
last_value = js_undefined();
|
||||
}
|
||||
|
||||
if (vm().unwind_until() == scope_type)
|
||||
vm().stop_unwind();
|
||||
|
||||
exit_scope(block);
|
||||
|
||||
return last_value;
|
||||
}
|
||||
|
||||
FunctionEnvironment* Interpreter::current_function_environment()
|
||||
{
|
||||
return verify_cast<FunctionEnvironment>(vm().running_execution_context().lexical_environment);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -67,11 +67,6 @@ public:
|
|||
|
||||
Environment* lexical_environment() { return vm().lexical_environment(); }
|
||||
|
||||
FunctionEnvironment* current_function_environment();
|
||||
|
||||
void enter_scope(const ScopeNode&, ScopeType, GlobalObject&);
|
||||
void exit_scope(const ScopeNode&);
|
||||
|
||||
void push_ast_node(ExecutingASTNodeChain& chain_node)
|
||||
{
|
||||
chain_node.previous = m_ast_node_chain;
|
||||
|
@ -85,17 +80,10 @@ public:
|
|||
}
|
||||
|
||||
const ASTNode* current_node() const { return m_ast_node_chain ? &m_ast_node_chain->node : nullptr; }
|
||||
ExecutingASTNodeChain* executing_ast_node_chain() { return m_ast_node_chain; }
|
||||
const ExecutingASTNodeChain* executing_ast_node_chain() const { return m_ast_node_chain; }
|
||||
|
||||
Value execute_statement(GlobalObject&, const Statement&, ScopeType = ScopeType::Block);
|
||||
|
||||
private:
|
||||
explicit Interpreter(VM&);
|
||||
|
||||
void push_scope(ScopeFrame frame);
|
||||
|
||||
Vector<ScopeFrame> m_scope_stack;
|
||||
ExecutingASTNodeChain* m_ast_node_chain { nullptr };
|
||||
|
||||
NonnullRefPtr<VM> m_vm;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -33,6 +33,8 @@ struct FunctionNodeParseOptions {
|
|||
};
|
||||
};
|
||||
|
||||
class ScopePusher;
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
explicit Parser(Lexer lexer, Program::Type program_type = Program::Type::Script);
|
||||
|
@ -69,7 +71,7 @@ public:
|
|||
|
||||
NonnullRefPtr<Statement> parse_statement(AllowLabelledFunction allow_labelled_function = AllowLabelledFunction::No);
|
||||
NonnullRefPtr<BlockStatement> parse_block_statement();
|
||||
NonnullRefPtr<BlockStatement> parse_block_statement(bool& is_strict, bool function_with_non_simple_parameter_list = false);
|
||||
NonnullRefPtr<FunctionBody> parse_function_body(Vector<FunctionDeclaration::Parameter> const& parameters, FunctionKind function_kind);
|
||||
NonnullRefPtr<ReturnStatement> parse_return_statement();
|
||||
NonnullRefPtr<VariableDeclaration> parse_variable_declaration(bool for_loop_variable_declaration = false);
|
||||
NonnullRefPtr<Statement> parse_for_statement();
|
||||
|
@ -201,6 +203,9 @@ private:
|
|||
|
||||
bool match_invalid_escaped_keyword() const;
|
||||
|
||||
bool parse_directive(ScopeNode& body);
|
||||
void parse_statement_list(ScopeNode& output_node, AllowLabelledFunction allow_labelled_functions = AllowLabelledFunction::No);
|
||||
|
||||
struct RulePosition {
|
||||
AK_MAKE_NONCOPYABLE(RulePosition);
|
||||
AK_MAKE_NONMOVABLE(RulePosition);
|
||||
|
@ -229,37 +234,11 @@ private:
|
|||
|
||||
[[nodiscard]] RulePosition push_start() { return { *this, position() }; }
|
||||
|
||||
struct Scope : public RefCounted<Scope> {
|
||||
enum Type {
|
||||
Function,
|
||||
Block,
|
||||
};
|
||||
struct HoistableDeclaration {
|
||||
NonnullRefPtr<FunctionDeclaration> declaration;
|
||||
NonnullRefPtr<Scope> scope; // where it is actually declared
|
||||
};
|
||||
|
||||
Type type;
|
||||
RefPtr<Scope> parent;
|
||||
|
||||
NonnullRefPtrVector<FunctionDeclaration> function_declarations;
|
||||
Vector<HoistableDeclaration> hoisted_function_declarations;
|
||||
|
||||
HashTable<FlyString> lexical_declarations;
|
||||
|
||||
explicit Scope(Type, RefPtr<Scope>);
|
||||
RefPtr<Scope> get_current_function_scope();
|
||||
};
|
||||
|
||||
struct ParserState {
|
||||
Lexer lexer;
|
||||
Token current_token;
|
||||
Vector<Error> errors;
|
||||
Vector<NonnullRefPtrVector<VariableDeclaration>> var_scopes;
|
||||
Vector<NonnullRefPtrVector<VariableDeclaration>> let_scopes;
|
||||
RefPtr<Scope> current_scope;
|
||||
|
||||
Vector<Vector<FunctionNode::Parameter>&> function_parameters;
|
||||
ScopePusher* current_scope_pusher;
|
||||
|
||||
HashMap<StringView, Optional<Position>> labels_in_scope;
|
||||
bool strict_mode { false };
|
||||
|
|
|
@ -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,58 +108,129 @@ 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;
|
||||
for (auto& parameter : m_formal_parameters) {
|
||||
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 });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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* 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) {
|
||||
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);
|
||||
if (is<FunctionEnvironment>(m_environment))
|
||||
environment->set_new_target(static_cast<FunctionEnvironment*>(m_environment)->new_target());
|
||||
}
|
||||
|
||||
environment->set_new_target(new_target ? new_target : js_undefined());
|
||||
return environment;
|
||||
}
|
||||
|
||||
Value ECMAScriptFunctionObject::execute_function_body()
|
||||
// 10.2.11 FunctionDeclarationInstantiation ( func, argumentsList ), https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
|
||||
ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantiation(Interpreter* interpreter)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
Interpreter* ast_interpreter = nullptr;
|
||||
auto* bytecode_interpreter = Bytecode::Interpreter::current();
|
||||
auto& callee_context = vm.running_execution_context();
|
||||
|
||||
auto prepare_arguments = [&] {
|
||||
// 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(
|
||||
[&](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;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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(
|
||||
[&](const auto& param) {
|
||||
[&](auto const& param) {
|
||||
Value argument_value;
|
||||
if (parameter.is_rest) {
|
||||
auto* array = Array::create(global_object(), 0);
|
||||
|
@ -169,24 +241,136 @@ Value ECMAScriptFunctionObject::execute_function_body()
|
|||
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 (interpreter)
|
||||
argument_value = parameter.default_value->execute(*interpreter, global_object());
|
||||
if (vm.exception())
|
||||
return;
|
||||
} else {
|
||||
argument_value = js_undefined();
|
||||
}
|
||||
|
||||
vm.assign(param, argument_value, global_object(), true, vm.lexical_environment());
|
||||
});
|
||||
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();
|
||||
auto* bytecode_interpreter = Bytecode::Interpreter::current();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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;
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
auto value = expression.execute(interpreter(), global_object);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
return value;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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 (environment) {
|
||||
environment->initialize_binding(global_object, target, value);
|
||||
return {};
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
PropertyName name;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void VM::assign(const FlyString& target, Value value, GlobalObject& global_object, bool first_assignment, Environment* specific_scope)
|
||||
{
|
||||
set_variable(target, move(value), global_object, first_assignment, specific_scope);
|
||||
}
|
||||
|
||||
void VM::assign(const Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>>& target, Value value, GlobalObject& global_object, bool first_assignment, Environment* specific_scope)
|
||||
{
|
||||
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++) {
|
||||
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&) {
|
||||
// FIXME: We need to get the reference but bindings are broken so this doesn't work yet.
|
||||
[&](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 (exception())
|
||||
return;
|
||||
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);
|
||||
for (;;) {
|
||||
while (!iterator_done) {
|
||||
auto next_object = iterator_next(*iterator);
|
||||
if (!next_object)
|
||||
return;
|
||||
if (!next_object) {
|
||||
iterator_done = true;
|
||||
VERIFY(exception());
|
||||
return JS::throw_completion(exception()->value());
|
||||
}
|
||||
|
||||
auto done_property = next_object->get(names.done);
|
||||
if (exception())
|
||||
return;
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
if (done_property.to_boolean())
|
||||
if (done_property.to_boolean()) {
|
||||
iterator_done = true;
|
||||
break;
|
||||
}
|
||||
|
||||
auto next_value = next_object->get(names.value);
|
||||
if (exception())
|
||||
return;
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
array->indexed_properties().append(next_value);
|
||||
}
|
||||
value = array;
|
||||
} else if (iterator) {
|
||||
|
||||
} else if (!iterator_done) {
|
||||
auto next_object = iterator_next(*iterator);
|
||||
if (!next_object)
|
||||
return;
|
||||
if (!next_object) {
|
||||
iterator_done = true;
|
||||
VERIFY(exception());
|
||||
return JS::throw_completion(exception()->value());
|
||||
}
|
||||
|
||||
auto done_property = next_object->get(names.done);
|
||||
if (exception())
|
||||
return;
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
if (done_property.to_boolean()) {
|
||||
iterator = nullptr;
|
||||
iterator_done = true;
|
||||
value = js_undefined();
|
||||
} else {
|
||||
value = next_object->get(names.value);
|
||||
if (exception())
|
||||
return;
|
||||
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 (exception())
|
||||
return;
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
}
|
||||
|
||||
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&) {
|
||||
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 (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;
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
return {};
|
||||
}
|
||||
return global_object.get(name);
|
||||
}
|
||||
|
||||
// 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())
|
||||
if (exists)
|
||||
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 };
|
||||
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);
|
||||
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,7 +739,6 @@ 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);
|
||||
|
||||
if (auto* exception = this->exception())
|
||||
|
@ -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;
|
||||
|
|
|
@ -39,8 +39,9 @@ test("functions in objects", () => {
|
|||
expect(o.a.name).toBe("a");
|
||||
expect(o.b.name).toBe("a");
|
||||
|
||||
// Member expressions do not get named.
|
||||
o.c = function () {};
|
||||
expect(o.c.name).toBe("c");
|
||||
expect(o.c.name).toBe("");
|
||||
});
|
||||
|
||||
test("names of native functions", () => {
|
||||
|
@ -49,16 +50,25 @@ test("names of native functions", () => {
|
|||
expect(console.debug.name).toBe("debug");
|
||||
});
|
||||
|
||||
test("no invalid autonaming of anonymous functions", () => {
|
||||
describe("some anonymous functions get renamed", () => {
|
||||
test("direct assignment does name new function expression", () => {
|
||||
// prettier-ignore
|
||||
let f1 = (function () {});
|
||||
expect(f1.name).toBe("");
|
||||
let f2 = f1;
|
||||
expect(f2.name).toBe("");
|
||||
let f3;
|
||||
f3 = false || f2;
|
||||
expect(f3.name).toBe("");
|
||||
let f4 = false;
|
||||
f4 ||= function () {};
|
||||
expect(f1.name).toBe("f1");
|
||||
|
||||
let f2 = false;
|
||||
f2 ||= function () {};
|
||||
expect(f2.name).toBe("f2");
|
||||
});
|
||||
|
||||
test("assignment from variable does not name", () => {
|
||||
const f1 = function () {};
|
||||
let f3 = f1;
|
||||
expect(f3.name).toBe("f1");
|
||||
});
|
||||
|
||||
test("assignment via expression does not name", () => {
|
||||
let f4 = false || function () {};
|
||||
expect(f4.name).toBe("");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -51,5 +51,24 @@ test("use already-declared variable", () => {
|
|||
});
|
||||
|
||||
test("allow binding patterns", () => {
|
||||
expect(`for (let [a, b] in foo) {}`).toEval();
|
||||
const expected = [
|
||||
["1", "3", []],
|
||||
["s", undefined, []],
|
||||
["l", "n", ["g", "N", "a", "m", "e"]],
|
||||
];
|
||||
let counter = 0;
|
||||
|
||||
for (let [a, , b, ...c] in { 123: 1, sm: 2, longName: 3 }) {
|
||||
expect(a).toBe(expected[counter][0]);
|
||||
expect(b).toBe(expected[counter][1]);
|
||||
expect(c).toEqual(expected[counter][2]);
|
||||
counter++;
|
||||
}
|
||||
expect(counter).toBe(3);
|
||||
});
|
||||
|
||||
test("allow member expression as variable", () => {
|
||||
const f = {};
|
||||
for (f.a in "abc");
|
||||
expect(f.a).toBe("2");
|
||||
});
|
||||
|
|
|
@ -100,5 +100,20 @@ describe("errors", () => {
|
|||
});
|
||||
|
||||
test("allow binding patterns", () => {
|
||||
expect(`for (let [a, b] of foo) {}`).toEval();
|
||||
let counter = 0;
|
||||
for (let [a, b] of [
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 6],
|
||||
]) {
|
||||
expect(a + 1).toBe(b);
|
||||
counter++;
|
||||
}
|
||||
expect(counter).toBe(3);
|
||||
});
|
||||
|
||||
test("allow member expression as variable", () => {
|
||||
const f = {};
|
||||
for (f.a of "abc");
|
||||
expect(f.a).toBe("c");
|
||||
});
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
test("basic functionality", () => {
|
||||
let a = 5;
|
||||
expect(delete a).toBeTrue();
|
||||
var b = 6;
|
||||
c = 7;
|
||||
expect(delete a).toBeFalse();
|
||||
expect(a).toBe(5);
|
||||
|
||||
expect(delete b).toBeFalse();
|
||||
expect(b).toBe(6);
|
||||
|
||||
expect(delete c).toBeTrue();
|
||||
|
||||
expect(() => {
|
||||
a;
|
||||
}).toThrowWithMessage(ReferenceError, "'a' is not defined");
|
||||
c;
|
||||
}).toThrowWithMessage(ReferenceError, "'c' is not defined");
|
||||
});
|
||||
|
|
|
@ -4,15 +4,12 @@ test("basic functionality", () => {
|
|||
[true, false, "foo", 123].forEach(primitive => {
|
||||
expect(() => {
|
||||
primitive.foo = "bar";
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
`Cannot set property 'foo' of ${typeof primitive} '${primitive}'`
|
||||
);
|
||||
}).toThrowWithMessage(TypeError, `Cannot set property 'foo' of ${primitive}`);
|
||||
expect(() => {
|
||||
primitive[Symbol.hasInstance] = 123;
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
`Cannot set property 'Symbol(Symbol.hasInstance)' of ${typeof primitive} '${primitive}'`
|
||||
`Cannot set property 'Symbol(Symbol.hasInstance)' of ${primitive}`
|
||||
);
|
||||
});
|
||||
[null, undefined].forEach(primitive => {
|
||||
|
|
|
@ -58,6 +58,8 @@ describe("octal escapes", () => {
|
|||
expect("'use strict'; '\\123'").not.toEval();
|
||||
expect('"use strict"; "\\123"').not.toEval();
|
||||
// Special case, string literal precedes use strict directive
|
||||
expect("'\\123'; somethingElse; 'use strict'").not.toEval();
|
||||
expect("'\\123'; 'use strict'").not.toEval();
|
||||
// Because of the non string statement in the middle strict mode is not enabled.
|
||||
expect("'\\123'; somethingElse; 'use strict'").toEval();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -196,6 +196,11 @@ describe("evaluating", () => {
|
|||
expect(a).toBe(o[0]);
|
||||
expect(length).toBe(o[1].length);
|
||||
}
|
||||
{
|
||||
expect(() => {
|
||||
let [a, b, [...{ length }]] = o;
|
||||
}).toThrowWithMessage(TypeError, "ToObject on null or undefined");
|
||||
}
|
||||
});
|
||||
|
||||
test("patterns with default", () => {
|
||||
|
|
69
Userland/Libraries/LibJS/Tests/syntax/switch-as-statement.js
Normal file
69
Userland/Libraries/LibJS/Tests/syntax/switch-as-statement.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
describe("switch statement is a valid statement and gets executed", () => {
|
||||
test("switch statement in a block", () => {
|
||||
let hit = false;
|
||||
{
|
||||
switch (true) {
|
||||
case true:
|
||||
hit = true;
|
||||
}
|
||||
expect(hit).toBeTrue();
|
||||
}
|
||||
});
|
||||
|
||||
test("switch statement in an if statement when true", () => {
|
||||
let hit = false;
|
||||
var a = true;
|
||||
if (a)
|
||||
switch (true) {
|
||||
case true:
|
||||
hit = true;
|
||||
}
|
||||
else
|
||||
switch (true) {
|
||||
case true:
|
||||
expect().fail();
|
||||
}
|
||||
|
||||
expect(hit).toBeTrue();
|
||||
});
|
||||
|
||||
test("switch statement in an if statement when false", () => {
|
||||
let hit = false;
|
||||
var a = false;
|
||||
if (a)
|
||||
switch (a) {
|
||||
default:
|
||||
expect().fail();
|
||||
}
|
||||
else
|
||||
switch (a) {
|
||||
default:
|
||||
hit = true;
|
||||
}
|
||||
|
||||
expect(hit).toBeTrue();
|
||||
});
|
||||
|
||||
test("switch statement in an while statement", () => {
|
||||
var a = 0;
|
||||
var loops = 0;
|
||||
while (a < 1 && loops++ < 5)
|
||||
switch (a) {
|
||||
case 0:
|
||||
a = 1;
|
||||
}
|
||||
|
||||
expect(a).toBe(1);
|
||||
});
|
||||
|
||||
test("switch statement in an for statement", () => {
|
||||
var loops = 0;
|
||||
for (let a = 0; a < 1 && loops++ < 5; )
|
||||
switch (a) {
|
||||
case 0:
|
||||
a = 1;
|
||||
}
|
||||
|
||||
expect(loops).toBe(1);
|
||||
});
|
||||
});
|
|
@ -1332,14 +1332,21 @@ int main(int argc, char** argv)
|
|||
|
||||
switch (mode) {
|
||||
case CompleteProperty: {
|
||||
auto maybe_variable = vm->get_variable(variable_name, interpreter->global_object());
|
||||
if (maybe_variable.is_empty()) {
|
||||
maybe_variable = interpreter->global_object().get(FlyString(variable_name));
|
||||
if (maybe_variable.is_empty())
|
||||
Optional<JS::Value> maybe_value;
|
||||
auto maybe_variable = vm->resolve_binding(variable_name);
|
||||
if (vm->exception())
|
||||
break;
|
||||
if (!maybe_variable.is_unresolvable()) {
|
||||
maybe_value = maybe_variable.get_value(interpreter->global_object());
|
||||
if (vm->exception())
|
||||
break;
|
||||
} else {
|
||||
maybe_value = interpreter->global_object().get(FlyString(variable_name));
|
||||
if (maybe_value->is_empty())
|
||||
break;
|
||||
}
|
||||
|
||||
auto variable = maybe_variable;
|
||||
auto variable = *maybe_value;
|
||||
if (!variable.is_object())
|
||||
break;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue