Skip to content

Instantly share code, notes, and snippets.

@awesomekling
Created June 23, 2021 20:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save awesomekling/2c8910f58b5e23a7d364b1e4b0d0dbb5 to your computer and use it in GitHub Desktop.
Save awesomekling/2c8910f58b5e23a7d364b1e4b0d0dbb5 to your computer and use it in GitHub Desktop.
diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp
index f85376baa..0382f1fba 100644
--- a/Userland/Libraries/LibJS/AST.cpp
+++ b/Userland/Libraries/LibJS/AST.cpp
@@ -63,6 +63,67 @@ String ASTNode::class_name() const
return demangle(typeid(*this).name()).substring(4);
}
+// 13.3.8.1 Runtime Semantics: ArgumentListEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-argumentlistevaluation
+Optional<MarkedValueList> argument_list_evaluation(Interpreter& interpreter, GlobalObject& global_object, Vector<CallExpression::Argument> const& arguments)
+{
+ // FIXME: This is somewhat ad-hoc.
+
+ auto& vm = global_object.vm();
+ MarkedValueList arg_list(vm.heap());
+ arg_list.ensure_capacity(arguments.size());
+
+ for (auto& argument : arguments) {
+ auto value = argument.value->execute(interpreter, global_object);
+ if (vm.exception())
+ return {};
+ if (argument.is_spread) {
+ get_iterator_values(global_object, value, [&](Value iterator_value) {
+ if (vm.exception())
+ return IterationDecision::Break;
+ arg_list.append(iterator_value);
+ return IterationDecision::Continue;
+ });
+ if (vm.exception())
+ return {};
+ } else {
+ arg_list.append(value);
+ }
+ }
+ return arg_list;
+}
+
+// 13.3.6.2 EvaluateCall ( func, ref, arguments, tailPosition ), https://tc39.es/ecma262/#sec-evaluatecall
+Value evaluate_call(Interpreter& interpreter, GlobalObject& global_object, Value function_value, Reference* reference, Vector<CallExpression::Argument> const& arguments)
+{
+ auto& vm = interpreter.vm();
+
+ Value this_value;
+ if (reference) {
+ if (reference->is_property_reference()) {
+ this_value = reference->get_this_value();
+ } else {
+ this_value = reference->base_environment().with_base_object();
+ }
+ } else {
+ this_value = js_undefined();
+ }
+
+ auto arg_list = argument_list_evaluation(interpreter, global_object, arguments);
+ if (!arg_list.has_value())
+ return {};
+
+ if (!function_value.is_object()) {
+ interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::NotAnObject, reference ? reference->name() : String("Value"));
+ return {};
+ }
+ if (!function_value.as_object().is_function()) {
+ interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::NotAFunction, reference ? reference->name() : String("Value"));
+ return {};
+ }
+
+ return vm.call(function_value.as_function(), this_value, arg_list.release_value());
+}
+
static void update_function_name(Value value, FlyString const& name)
{
if (!value.is_function())
@@ -81,18 +142,6 @@ static String get_function_name(GlobalObject& global_object, Value value)
return value.to_string(global_object);
}
-Value ScopeNode::execute(Interpreter& interpreter, GlobalObject& global_object) const
-{
- InterpreterNodeScope node_scope { interpreter, *this };
- return interpreter.execute_statement(global_object, *this);
-}
-
-Value Program::execute(Interpreter& interpreter, GlobalObject& global_object) const
-{
- InterpreterNodeScope node_scope { interpreter, *this };
- return interpreter.execute_statement(global_object, *this, ScopeType::Block);
-}
-
Value FunctionDeclaration::execute(Interpreter& interpreter, GlobalObject&) const
{
InterpreterNodeScope node_scope { interpreter, *this };
@@ -141,7 +190,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete
auto property_name = member_expression.computed_property_name(interpreter, global_object);
if (!property_name.is_valid())
return {};
- auto reference = Reference(super_base, property_name);
+ auto reference = Reference { super_base, property_name, super_base };
callee = reference.get(global_object);
if (vm.exception())
return {};
@@ -173,6 +222,15 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
{
InterpreterNodeScope node_scope { interpreter, *this };
auto& vm = interpreter.vm();
+
+ auto reference = m_callee->to_reference(interpreter, global_object);
+ auto func = reference.get(global_object);
+
+ if (vm.exception())
+ return {};
+
+ return evaluate_call(interpreter, global_object, func, &reference, m_arguments);
+#if 0
auto [this_value, callee] = compute_this_and_callee(interpreter, global_object);
if (vm.exception())
return {};
@@ -259,6 +317,7 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
return new_object;
}
return result;
+#endif
}
Value YieldExpression::execute(Interpreter&, GlobalObject&) const
@@ -286,10 +345,10 @@ Value IfStatement::execute(Interpreter& interpreter, GlobalObject& global_object
return {};
if (predicate_result.to_boolean())
- return interpreter.execute_statement(global_object, *m_consequent);
+ return m_consequent->execute(interpreter, global_object);
if (m_alternate)
- return interpreter.execute_statement(global_object, *m_alternate);
+ return m_alternate->execute(interpreter, global_object);
return js_undefined();
}
@@ -310,7 +369,7 @@ Value WithStatement::execute(Interpreter& interpreter, GlobalObject& global_obje
auto* object_environment_record = new_object_environment(*object, true, interpreter.vm().call_frame().lexical_environment);
TemporaryChange<EnvironmentRecord*> scope_change(interpreter.vm().call_frame().lexical_environment, object_environment_record);
- return interpreter.execute_statement(global_object, m_body).value_or(js_undefined());
+ return m_body->execute(interpreter, global_object).value_or(js_undefined());
}
Value WhileStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
@@ -324,7 +383,7 @@ Value WhileStatement::execute(Interpreter& interpreter, GlobalObject& global_obj
return {};
if (!test_result.to_boolean())
break;
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value);
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value);
if (interpreter.exception())
return {};
if (interpreter.vm().should_unwind()) {
@@ -350,7 +409,7 @@ Value DoWhileStatement::execute(Interpreter& interpreter, GlobalObject& global_o
for (;;) {
if (interpreter.exception())
return {};
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value);
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value);
if (interpreter.exception())
return {};
if (interpreter.vm().should_unwind()) {
@@ -406,7 +465,7 @@ Value ForStatement::execute(Interpreter& interpreter, GlobalObject& global_objec
return {};
if (!test_result.to_boolean())
break;
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value);
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value);
if (interpreter.exception())
return {};
if (interpreter.vm().should_unwind()) {
@@ -427,7 +486,7 @@ Value ForStatement::execute(Interpreter& interpreter, GlobalObject& global_objec
}
} else {
while (true) {
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value);
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value);
if (interpreter.exception())
return {};
if (interpreter.vm().should_unwind()) {
@@ -496,10 +555,10 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj
while (object) {
auto property_names = object->get_enumerable_own_property_names(Object::PropertyKind::Key);
for (auto& value : property_names) {
- interpreter.vm().assign(target, value, global_object, has_declaration);
+ interpreter.vm().assign(target, value, global_object);
if (interpreter.exception())
return {};
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value);
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value);
if (interpreter.exception())
return {};
if (interpreter.vm().should_unwind()) {
@@ -541,8 +600,8 @@ Value ForOfStatement::execute(Interpreter& interpreter, GlobalObject& global_obj
return {};
get_iterator_values(global_object, rhs_result, [&](Value value) {
- interpreter.vm().assign(target, value, global_object, has_declaration);
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value);
+ interpreter.vm().assign(target, value, global_object);
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value);
if (interpreter.exception())
return IterationDecision::Break;
if (interpreter.vm().should_unwind()) {
@@ -668,9 +727,9 @@ Reference Expression::to_reference(Interpreter&, GlobalObject&) const
return {};
}
-Reference Identifier::to_reference(Interpreter& interpreter, GlobalObject&) const
+Reference Identifier::to_reference(Interpreter& interpreter, GlobalObject& global_object) const
{
- return interpreter.vm().get_reference(string());
+ return interpreter.vm().resolve_binding(global_object, string());
}
Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject& global_object) const
@@ -681,7 +740,10 @@ Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject&
auto property_name = computed_property_name(interpreter, global_object);
if (!property_name.is_valid())
return {};
- return { object_value, property_name };
+ require_object_coercible(global_object, object_value);
+ if (interpreter.exception())
+ return {};
+ return Reference { object_value, property_name, object_value };
}
Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
@@ -699,13 +761,12 @@ Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
Value lhs_result;
if (m_op == UnaryOp::Typeof && is<Identifier>(*m_lhs)) {
auto reference = m_lhs->to_reference(interpreter, global_object);
- if (interpreter.exception()) {
+ if (interpreter.exception())
return {};
- }
- // FIXME: standard recommends checking with is_unresolvable but it ALWAYS return false here
- if (reference.is_local_variable() || reference.is_global_variable()) {
- const auto& name = reference.name();
- lhs_result = interpreter.vm().get_variable(name.to_string(), global_object).value_or(js_undefined());
+ if (reference.is_unresolvable()) {
+ lhs_result = js_undefined();
+ } else {
+ lhs_result = reference.get(global_object);
if (interpreter.exception())
return {};
}
@@ -1318,13 +1379,10 @@ Value Identifier::execute(Interpreter& interpreter, GlobalObject& global_object)
{
InterpreterNodeScope node_scope { interpreter, *this };
- auto value = interpreter.vm().get_variable(string(), global_object);
- if (value.is_empty()) {
- if (!interpreter.exception())
- interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, string());
+ auto reference = interpreter.vm().resolve_binding(global_object, string());
+ if (interpreter.exception())
return {};
- }
- return value;
+ return reference.get(global_object);
}
void Identifier::dump(int indent) const
@@ -1461,11 +1519,6 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob
return {};
}
- if (reference.is_unresolvable()) {
- interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::InvalidLeftHandAssignment);
- return {};
- }
-
reference.put(global_object, rhs_result);
if (interpreter.exception())
return {};
@@ -1600,20 +1653,32 @@ Value VariableDeclaration::execute(Interpreter& interpreter, GlobalObject& globa
{
InterpreterNodeScope node_scope { interpreter, *this };
+ auto& vm = interpreter.vm();
+
for (auto& declarator : m_declarations) {
if (auto* init = declarator.init()) {
- auto initializer_result = init->execute(interpreter, global_object);
- if (interpreter.exception())
- return {};
declarator.target().visit(
[&](NonnullRefPtr<Identifier> const& id) {
- auto variable_name = id->string();
+ auto binding_id = id->string();
+
+ auto lhs = vm.resolve_binding(global_object, binding_id);
+ if (vm.exception())
+ return;
+
+ auto rhs = init->execute(interpreter, global_object);
+ if (vm.exception())
+ return;
+
if (is<ClassExpression>(*init))
- update_function_name(initializer_result, variable_name);
- interpreter.vm().set_variable(variable_name, initializer_result, global_object, true);
+ update_function_name(rhs, binding_id);
+
+ lhs.put(global_object, rhs);
},
[&](NonnullRefPtr<BindingPattern> const& pattern) {
- interpreter.vm().assign(pattern, initializer_result, global_object, true);
+ auto initializer_result = init->execute(interpreter, global_object);
+ if (vm.exception())
+ return;
+ vm.assign(pattern, initializer_result, global_object);
});
}
}
@@ -2042,16 +2107,24 @@ Value TryStatement::execute(Interpreter& interpreter, GlobalObject& global_objec
{
InterpreterNodeScope node_scope { interpreter, *this };
+ auto& vm = interpreter.vm();
+
auto result = interpreter.execute_statement(global_object, m_block, ScopeType::Try);
if (auto* exception = interpreter.exception()) {
if (m_handler) {
- interpreter.vm().clear_exception();
+ vm.clear_exception();
+
+ auto* old_environment = vm.lexical_environment();
+ auto* catch_environment = new_declarative_environment(*old_environment);
+
+ catch_environment->create_mutable_binding(global_object, m_handler->parameter(), false);
+ TemporaryChange change(vm.call_frame().lexical_environment, static_cast<EnvironmentRecord*>(catch_environment));
+
+ initialize_bound_name(global_object, m_handler->parameter(), exception->value(), catch_environment);
+ if (vm.exception())
+ return {};
- HashMap<FlyString, Variable> parameters;
- parameters.set(m_handler->parameter(), Variable { exception->value(), DeclarationKind::Var });
- auto* catch_scope = interpreter.heap().allocate<DeclarativeEnvironmentRecord>(global_object, move(parameters), interpreter.vm().call_frame().lexical_environment);
- TemporaryChange<EnvironmentRecord*> scope_change(interpreter.vm().call_frame().lexical_environment, catch_scope);
- result = interpreter.execute_statement(global_object, m_handler->body());
+ result = m_handler->body().execute(interpreter, global_object);
}
}
@@ -2265,4 +2338,35 @@ void ScopeNode::add_functions(NonnullRefPtrVector<FunctionDeclaration> functions
m_functions.extend(move(functions));
}
+Value Program::execute(Interpreter&, GlobalObject&) const
+{
+ // NOTE: This is implemented by Interpreter::run() at the moment.
+ VERIFY_NOT_REACHED();
+}
+
+// 14.2.2 RS: Evaluation, https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation
+Value BlockStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
+{
+ if (children().is_empty()) {
+ // Block : { }
+ return {};
+ }
+
+ auto& vm = interpreter.vm();
+
+ // Block : { StatementList }
+ auto* old_env = vm.call_frame().lexical_environment;
+ auto* block_env = new_declarative_environment(*old_env);
+ if (interpreter.exception())
+ return {};
+ block_declaration_instantiation(*this, *block_env);
+
+ vm.call_frame().lexical_environment = block_env;
+
+ auto block_value = interpreter.evaluate_statement_list(global_object, *this, children(), ScopeType::Block);
+
+ vm.call_frame().lexical_environment = old_env;
+ return block_value;
+}
+
}
diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h
index 457a20e8c..6c19917aa 100644
--- a/Userland/Libraries/LibJS/AST.h
+++ b/Userland/Libraries/LibJS/AST.h
@@ -139,7 +139,6 @@ 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;
@@ -186,6 +185,8 @@ public:
: ScopeNode(move(source_range))
{
}
+
+ Value execute(Interpreter&, GlobalObject&) const override;
};
class Expression : public ASTNode {
diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp
index aedbe5028..cd120dd75 100644
--- a/Userland/Libraries/LibJS/Bytecode/Op.cpp
+++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp
@@ -16,6 +16,7 @@
#include <LibJS/Runtime/EnvironmentRecord.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h>
+#include <LibJS/Runtime/Reference.h>
#include <LibJS/Runtime/RegExpObject.h>
#include <LibJS/Runtime/ScriptFunction.h>
#include <LibJS/Runtime/Value.h>
@@ -215,12 +216,18 @@ 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 reference = interpreter.vm().resolve_binding(interpreter.global_object(), interpreter.current_executable().get_string(m_identifier));
+ if (interpreter.vm().exception())
+ return;
+ interpreter.accumulator() = reference.get(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 reference = interpreter.vm().resolve_binding(interpreter.global_object(), interpreter.current_executable().get_string(m_identifier));
+ if (interpreter.vm().exception())
+ return;
+ reference.put(interpreter.global_object(), interpreter.accumulator());
}
void GetById::execute_impl(Bytecode::Interpreter& interpreter) const
diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h
index 5e1ba35db..fa5f41b1f 100644
--- a/Userland/Libraries/LibJS/Forward.h
+++ b/Userland/Libraries/LibJS/Forward.h
@@ -127,6 +127,7 @@ class Error;
class ErrorType;
class Exception;
class Expression;
+class FunctionDeclaration;
class FunctionEnvironmentRecord;
class FunctionNode;
class GlobalEnvironmentRecord;
@@ -140,6 +141,7 @@ class NativeFunction;
class NativeProperty;
class ObjectEnvironmentRecord;
class PrimitiveString;
+class Program;
class PromiseReaction;
class PromiseReactionJob;
class PromiseResolveThenableJob;
diff --git a/Userland/Libraries/LibJS/Interpreter.cpp b/Userland/Libraries/LibJS/Interpreter.cpp
index b70671b78..5479f7ef2 100644
--- a/Userland/Libraries/LibJS/Interpreter.cpp
+++ b/Userland/Libraries/LibJS/Interpreter.cpp
@@ -9,6 +9,7 @@
#include <AK/StringBuilder.h>
#include <LibJS/AST.h>
#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/FunctionEnvironmentRecord.h>
#include <LibJS/Runtime/GlobalEnvironmentRecord.h>
#include <LibJS/Runtime/GlobalObject.h>
@@ -39,6 +40,9 @@ Interpreter::~Interpreter()
void Interpreter::run(GlobalObject& global_object, const Program& program)
{
+ // FIXME: This is an ad-hoc implementation of 16.1.6 and should be rewritten per the specification.
+ // 16.1.6 ScriptEvaluation ( scriptRecord ), https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation
+
auto& vm = this->vm();
VERIFY(!vm.exception());
@@ -56,8 +60,11 @@ void Interpreter::run(GlobalObject& global_object, const Program& program)
VERIFY(!vm.exception());
global_call_frame.is_strict_mode = program.is_strict_mode();
vm.push_call_frame(global_call_frame, global_object);
+
+ global_declaration_instantiation(program, global_object.environment_record());
+
VERIFY(!vm.exception());
- auto value = program.execute(*this, global_object);
+ auto value = evaluate_statement_list(global_object, program, program.children(), ScopeType::Block);
vm.set_last_value(Badge<Interpreter> {}, value.value_or(js_undefined()));
vm.pop_call_frame();
@@ -164,21 +171,15 @@ 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)
+Value Interpreter::evaluate_statement_list(GlobalObject& global_object, ScopeNode const& scope_node, NonnullRefPtrVector<Statement> const& statement_list, ScopeType scope_type)
{
- if (!is<ScopeNode>(statement))
- return statement.execute(*this, global_object);
-
- auto& block = static_cast<const ScopeNode&>(statement);
- enter_scope(block, scope_type, global_object);
-
Value last_value;
- for (auto& node : block.children()) {
- auto value = node.execute(*this, global_object);
+ for (auto& statement : statement_list) {
+ auto value = statement.execute(*this, global_object);
if (!value.is_empty())
last_value = value;
if (vm().should_unwind()) {
- if (!block.label().is_null() && vm().should_unwind_until(ScopeType::Breakable, block.label()))
+ if (!scope_node.label().is_null() && vm().should_unwind_until(ScopeType::Breakable, scope_node.label()))
vm().stop_unwind();
break;
}
@@ -193,11 +194,23 @@ Value Interpreter::execute_statement(GlobalObject& global_object, const Statemen
if (vm().unwind_until() == scope_type)
vm().stop_unwind();
- exit_scope(block);
-
return last_value;
}
+Value Interpreter::execute_statement(GlobalObject& global_object, const Statement& statement, ScopeType scope_type)
+{
+ if (!is<ScopeNode>(statement))
+ return statement.execute(*this, global_object);
+
+ auto& scope_node = static_cast<const ScopeNode&>(statement);
+ enter_scope(scope_node, scope_type, global_object);
+
+ auto value = evaluate_statement_list(global_object, scope_node, scope_node.children(), scope_type);
+
+ exit_scope(scope_node);
+ return value;
+}
+
FunctionEnvironmentRecord* Interpreter::current_function_environment_record()
{
VERIFY(is<FunctionEnvironmentRecord>(vm().call_frame().lexical_environment));
diff --git a/Userland/Libraries/LibJS/Interpreter.h b/Userland/Libraries/LibJS/Interpreter.h
index a342fc8b1..8db143fe0 100644
--- a/Userland/Libraries/LibJS/Interpreter.h
+++ b/Userland/Libraries/LibJS/Interpreter.h
@@ -80,6 +80,7 @@ public:
const ExecutingASTNodeChain* executing_ast_node_chain() const { return m_ast_node_chain; }
Value execute_statement(GlobalObject&, const Statement&, ScopeType = ScopeType::Block);
+ Value evaluate_statement_list(GlobalObject&, ScopeNode const&, NonnullRefPtrVector<Statement> const&, ScopeType);
private:
explicit Interpreter(VM&);
diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp
index 1865212e0..50bf6a58a 100644
--- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp
+++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp
@@ -8,6 +8,7 @@
#include <AK/Function.h>
#include <AK/Result.h>
#include <AK/TemporaryChange.h>
+#include <LibJS/AST.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/AbstractOperations.h>
@@ -22,6 +23,8 @@
#include <LibJS/Runtime/ObjectEnvironmentRecord.h>
#include <LibJS/Runtime/PropertyName.h>
#include <LibJS/Runtime/ProxyObject.h>
+#include <LibJS/Runtime/Reference.h>
+#include <LibJS/Runtime/ScriptFunction.h>
namespace JS {
@@ -178,7 +181,7 @@ ObjectEnvironmentRecord* new_object_environment(Object& object, bool is_with_env
if (!is_with_environment) {
TODO();
}
- return global_object.heap().allocate<ObjectEnvironmentRecord>(global_object, object, environment_record);
+ return global_object.heap().allocate<ObjectEnvironmentRecord>(global_object, object, is_with_environment, environment_record);
}
// 9.4.3 GetThisEnvironment ( ), https://tc39.es/ecma262/#sec-getthisenvironment
@@ -228,4 +231,177 @@ Value perform_eval(Value x, GlobalObject& caller_realm, CallerMode strict_caller
return interpreter.execute_statement(caller_realm, program).value_or(js_undefined());
}
+// 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference
+Reference get_identifier_reference(GlobalObject& global_object, EnvironmentRecord* environment_record, FlyString const& name, bool strict)
+{
+ if (!environment_record) {
+ // FIXME: "Return the Reference Record { [[Base]]: unresolvable, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }."
+ // (The FIXME here is to return this exact reference as specified instead of just an empty Reference {})
+ return Reference { Reference::BaseType::Unresolvable, name, strict };
+ }
+
+ bool exists = environment_record->has_binding(name);
+ if (exists)
+ return Reference { *environment_record, name, strict };
+
+ return get_identifier_reference(global_object, environment_record->outer_environment(), name, strict);
+}
+
+// 14.2.3 BlockDeclarationInstantiation ( code, environment ), https://tc39.es/ecma262/#sec-blockdeclarationinstantiation
+void block_declaration_instantiation(ScopeNode const& code, DeclarativeEnvironmentRecord& environment)
+{
+ auto& global_object = environment.global_object();
+ auto& vm = global_object.vm();
+
+ for (auto& declaration : code.variables()) {
+ bool is_constant_declaration = declaration.declaration_kind() == DeclarationKind::Const;
+ auto create_binding = [&](auto& name) {
+ if (is_constant_declaration) {
+ environment.create_immutable_binding(global_object, name, true);
+ } else {
+ // FIXME: "NOTE: This step is replaced in section B.3.3.6."
+ environment.create_mutable_binding(global_object, name, false);
+ }
+ };
+ for (auto& declarator : declaration.declarations()) {
+ declarator.target().visit(
+ [&](NonnullRefPtr<Identifier> const& id) {
+ create_binding(id->string());
+ },
+ [&](NonnullRefPtr<BindingPattern> const& binding) {
+ binding->for_each_bound_name([&](const auto& name) {
+ create_binding(name);
+ });
+ });
+ }
+ if (vm.exception())
+ return;
+ }
+
+ for (auto& declaration : code.functions()) {
+ auto& function_name = declaration.name();
+ auto* function_object = instantiate_function_object(declaration, environment);
+ if (vm.exception())
+ return;
+
+ // FIXME: "NOTE: This step is replaced in section B.3.3.6."
+ environment.initialize_binding(global_object, function_name, function_object);
+ if (vm.exception())
+ return;
+ }
+}
+
+// 15.2.4 Runtime Semantics: InstantiateOrdinaryFunctionObject, https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionobject
+Function* instantiate_ordinary_function_object(FunctionDeclaration const& declaration, EnvironmentRecord& scope)
+{
+ // FIXME: This is very ad-hoc.
+ auto& global_object = scope.global_object();
+ auto& vm = global_object.vm();
+ return ScriptFunction::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), vm.lexical_environment(), declaration.kind(), declaration.is_strict_mode() || vm.in_strict_mode(), declaration.is_arrow_function());
+}
+
+// 8.5.1 Runtime Semantics: InstantiateFunctionObject, https://tc39.es/ecma262/#sec-runtime-semantics-instantiatefunctionobject
+Function* instantiate_function_object(FunctionDeclaration const& declaration, EnvironmentRecord& scope)
+{
+ // FIXME: Support the other function kinds.
+ return instantiate_ordinary_function_object(declaration, scope);
+}
+
+// 16.1.7 GlobalDeclarationInstantiation ( script, env ), https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
+void global_declaration_instantiation(Program const& script, GlobalEnvironmentRecord& environment)
+{
+ auto& global_object = environment.global_object();
+ auto& vm = global_object.vm();
+
+ // FIXME: We want to iterate lexNames and varNames separately here, but they are not separate..
+
+ HashTable<FlyString> declared_function_names;
+ NonnullRefPtrVector<FunctionDeclaration> functions_to_initialize;
+
+ for (auto& declaration : script.functions()) {
+ if (!declared_function_names.contains(declaration.name())) {
+ if (!environment.can_declare_global_function(declaration.name())) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString);
+ return;
+ }
+
+ declared_function_names.set(declaration.name());
+ functions_to_initialize.prepend(declaration);
+ }
+ }
+
+ HashTable<FlyString> declared_var_names;
+ for (auto& declaration : script.variables()) {
+ auto do_var_name = [&](auto& name) {
+ if (!declared_var_names.contains(name)) {
+ if (!environment.can_declare_global_var(name)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString);
+ return;
+ }
+ declared_var_names.set(name);
+ }
+ };
+ for (auto& declarator : declaration.declarations()) {
+ declarator.target().visit(
+ [&](NonnullRefPtr<Identifier> const& id) { do_var_name(id->string()); },
+ [&](NonnullRefPtr<BindingPattern> const& binding) { binding->for_each_bound_name([&](auto& name) { do_var_name(name); }); });
+ if (vm.exception())
+ return;
+ }
+ }
+
+ for (auto& declaration : script.variables()) {
+ bool is_constant_declaration = declaration.declaration_kind() == DeclarationKind::Const;
+ auto create_binding = [&](auto& name) {
+ if (is_constant_declaration) {
+ environment.create_immutable_binding(global_object, name, true);
+ } else {
+ environment.create_mutable_binding(global_object, name, false);
+ }
+ };
+ for (auto& declarator : declaration.declarations()) {
+ declarator.target().visit(
+ [&](NonnullRefPtr<Identifier> const& id) {
+ create_binding(id->string());
+ },
+ [&](NonnullRefPtr<BindingPattern> const& binding) {
+ binding->for_each_bound_name([&](const auto& name) {
+ create_binding(name);
+ });
+ });
+ }
+ if (vm.exception())
+ return;
+ }
+
+ for (auto& declaration : functions_to_initialize) {
+ auto& function_name = declaration.name();
+ auto* function_object = instantiate_function_object(declaration, environment);
+ if (vm.exception())
+ return;
+
+ environment.create_global_function_binding(function_name, function_object, false);
+ if (vm.exception())
+ return;
+ }
+
+ for (auto& name : declared_var_names) {
+ environment.create_global_var_binding(name, false);
+ if (vm.exception())
+ return;
+ }
+}
+
+// 8.5.2.1 InitializeBoundName ( name, value, environment ), https://tc39.es/ecma262/#sec-initializeboundname
+void initialize_bound_name(GlobalObject& global_object, FlyString const& name, Value value, EnvironmentRecord* environment)
+{
+ if (environment) {
+ environment->initialize_binding(global_object, name, value);
+ return;
+ }
+
+ auto lhs = global_object.vm().resolve_binding(global_object, name);
+ lhs.put(global_object, value);
+}
+
}
diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h
index 1e00d90cb..502f7a8c6 100644
--- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h
+++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h
@@ -24,6 +24,12 @@ MarkedValueList create_list_from_array_like(GlobalObject&, Value, AK::Function<R
Function* species_constructor(GlobalObject&, Object const&, Function& default_constructor);
GlobalObject* get_function_realm(GlobalObject&, Function const&);
Object* get_prototype_from_constructor(GlobalObject&, Function const& constructor, Object* (GlobalObject::*intrinsic_default_prototype)());
+Reference get_identifier_reference(GlobalObject&, EnvironmentRecord*, FlyString const&, bool strict);
+void block_declaration_instantiation(ScopeNode const&, DeclarativeEnvironmentRecord&);
+Function* instantiate_function_object(FunctionDeclaration const&, EnvironmentRecord&);
+Function* instantiate_ordinary_function_object(FunctionDeclaration const&, EnvironmentRecord&);
+void global_declaration_instantiation(Program const&, GlobalEnvironmentRecord&);
+void initialize_bound_name(GlobalObject&, FlyString const& name, Value, EnvironmentRecord*);
enum class CallerMode {
Strict,
diff --git a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.cpp b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.cpp
index 54a48871c..39cc38ba5 100644
--- a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.cpp
+++ b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.cpp
@@ -65,11 +65,6 @@ void DeclarativeEnvironmentRecord::put_into_environment_record(FlyString const&
m_variables.set(name, variable);
}
-bool DeclarativeEnvironmentRecord::delete_from_environment_record(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 DeclarativeEnvironmentRecord::has_binding(FlyString const& name) const
{
diff --git a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.h b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.h
index e82a1b334..5c29f86e8 100644
--- a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.h
+++ b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.h
@@ -42,7 +42,6 @@ public:
// ^EnvironmentRecord
virtual Optional<Variable> get_from_environment_record(FlyString const&) const override;
virtual void put_into_environment_record(FlyString const&, Variable) override;
- virtual bool delete_from_environment_record(FlyString const&) override;
HashMap<FlyString, Variable> const& variables() const { return m_variables; }
diff --git a/Userland/Libraries/LibJS/Runtime/EnvironmentRecord.h b/Userland/Libraries/LibJS/Runtime/EnvironmentRecord.h
index f4a13278b..2be94c6ca 100644
--- a/Userland/Libraries/LibJS/Runtime/EnvironmentRecord.h
+++ b/Userland/Libraries/LibJS/Runtime/EnvironmentRecord.h
@@ -29,11 +29,12 @@ public:
virtual Optional<Variable> get_from_environment_record(FlyString const&) const = 0;
virtual void put_into_environment_record(FlyString const&, Variable) = 0;
- virtual bool delete_from_environment_record(FlyString const&) = 0;
virtual bool has_this_binding() const { return false; }
virtual Value get_this_binding(GlobalObject&) const { return {}; }
+ virtual Object* with_base_object() const { return nullptr; }
+
virtual bool has_binding([[maybe_unused]] FlyString const& name) const { return false; }
virtual void create_mutable_binding(GlobalObject&, [[maybe_unused]] FlyString const& name, [[maybe_unused]] bool can_be_deleted) { }
virtual void create_immutable_binding(GlobalObject&, [[maybe_unused]] FlyString const& name, [[maybe_unused]] bool strict) { }
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.cpp b/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.cpp
index ee866636e..bbae11dbf 100644
--- a/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.cpp
+++ b/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.cpp
@@ -14,9 +14,8 @@ namespace JS {
GlobalEnvironmentRecord::GlobalEnvironmentRecord(GlobalObject& global_object)
: EnvironmentRecord(nullptr)
- , m_global_object(global_object)
{
- m_object_record = global_object.heap().allocate<ObjectEnvironmentRecord>(global_object, global_object, nullptr);
+ m_object_record = global_object.heap().allocate<ObjectEnvironmentRecord>(global_object, global_object, false, nullptr);
m_declarative_record = global_object.heap().allocate<DeclarativeEnvironmentRecord>(global_object);
}
@@ -39,20 +38,14 @@ void GlobalEnvironmentRecord::put_into_environment_record(FlyString const& name,
m_object_record->put_into_environment_record(name, variable);
}
-bool GlobalEnvironmentRecord::delete_from_environment_record(FlyString const& name)
-{
- // FIXME: This should be a "composite" of the object record and the declarative record.
- return object_record().delete_from_environment_record(name);
-}
-
Value GlobalEnvironmentRecord::get_this_binding(GlobalObject&) const
{
- return &m_global_object;
+ return &global_object();
}
Value GlobalEnvironmentRecord::global_this_value() const
{
- return &m_global_object;
+ return &global_object();
}
// 9.1.1.4.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-global-environment-records-hasbinding-n
@@ -144,7 +137,7 @@ bool GlobalEnvironmentRecord::has_lexical_declaration(FlyString const& name) con
// 9.1.1.4.14 HasRestrictedGlobalProperty ( N ), https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty
bool GlobalEnvironmentRecord::has_restricted_global_property(FlyString const& name) const
{
- auto existing_prop = m_global_object.get_own_property_descriptor(name);
+ auto existing_prop = global_object().get_own_property_descriptor(name);
if (!existing_prop.has_value() || existing_prop.value().value.is_undefined())
return false;
if (existing_prop.value().attributes.is_configurable())
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.h b/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.h
index 093a1ba57..ed968a33d 100644
--- a/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.h
+++ b/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.h
@@ -18,7 +18,6 @@ public:
virtual Optional<Variable> get_from_environment_record(FlyString const&) const override;
virtual void put_into_environment_record(FlyString const&, Variable) override;
- virtual bool delete_from_environment_record(FlyString const&) override;
virtual bool has_this_binding() const final { return true; }
virtual Value get_this_binding(GlobalObject&) const final;
@@ -50,8 +49,6 @@ private:
virtual bool is_global_environment_record() const override { return true; }
virtual void visit_edges(Visitor&) override;
- GlobalObject& m_global_object;
-
ObjectEnvironmentRecord* m_object_record { nullptr };
DeclarativeEnvironmentRecord* m_declarative_record { nullptr };
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
index 521734060..e06db34dc 100644
--- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
@@ -99,7 +99,7 @@ void GlobalObject::initialize_global_object()
m_object_prototype = heap().allocate_without_global_object<ObjectPrototype>(*this);
m_function_prototype = heap().allocate_without_global_object<FunctionPrototype>(*this);
- m_environment_record = heap().allocate_without_global_object<GlobalEnvironmentRecord>(*this);
+ m_environment_record = heap().allocate<GlobalEnvironmentRecord>(*this, *this);
m_new_object_shape = vm.heap().allocate_without_global_object<Shape>(*this);
m_new_object_shape->set_prototype_without_transition(m_object_prototype);
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.cpp b/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.cpp
index 2be830ea7..b50e5cc43 100644
--- a/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.cpp
@@ -10,9 +10,10 @@
namespace JS {
-ObjectEnvironmentRecord::ObjectEnvironmentRecord(Object& object, EnvironmentRecord* parent_scope)
+ObjectEnvironmentRecord::ObjectEnvironmentRecord(Object& object, bool is_with_environment, EnvironmentRecord* parent_scope)
: EnvironmentRecord(parent_scope)
, m_object(object)
+ , m_with_environment(is_with_environment)
{
}
@@ -35,11 +36,6 @@ void ObjectEnvironmentRecord::put_into_environment_record(FlyString const& name,
m_object.put(name, variable.value);
}
-bool ObjectEnvironmentRecord::delete_from_environment_record(FlyString const& name)
-{
- return m_object.delete_property(name);
-}
-
// 9.1.1.2.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n
bool ObjectEnvironmentRecord::has_binding(FlyString const& name) const
{
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.h b/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.h
index f9780f0fb..2deebbc7c 100644
--- a/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.h
+++ b/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.h
@@ -14,11 +14,10 @@ class ObjectEnvironmentRecord : public EnvironmentRecord {
JS_ENVIRONMENT_RECORD(ObjectEnvironmentRecord, EnvironmentRecord);
public:
- ObjectEnvironmentRecord(Object&, EnvironmentRecord* parent_scope);
+ ObjectEnvironmentRecord(Object&, bool is_with_environment, EnvironmentRecord* parent_scope);
virtual Optional<Variable> get_from_environment_record(FlyString const&) const override;
virtual void put_into_environment_record(FlyString const&, Variable) override;
- virtual bool delete_from_environment_record(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;
@@ -28,12 +27,23 @@ public:
virtual Value get_binding_value(GlobalObject&, FlyString const& name, bool strict) override;
virtual bool delete_binding(GlobalObject&, FlyString const& name) override;
+ // 9.1.1.2.10 WithBaseObject ( ), https://tc39.es/ecma262/#sec-object-environment-records-withbaseobject
+ virtual Object* with_base_object() const override
+ {
+ if (is_with_environment())
+ return &m_object;
+ return nullptr;
+ }
+
+ bool is_with_environment() const { return m_with_environment; }
+
Object& object() { return m_object; }
private:
virtual void visit_edges(Visitor&) override;
Object& m_object;
+ bool m_with_environment { false };
};
}
diff --git a/Userland/Libraries/LibJS/Runtime/Reference.cpp b/Userland/Libraries/LibJS/Runtime/Reference.cpp
index 2bc6b466f..1f4a4ce17 100644
--- a/Userland/Libraries/LibJS/Runtime/Reference.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Reference.cpp
@@ -1,9 +1,10 @@
/*
- * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include <LibJS/AST.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Reference.h>
@@ -15,39 +16,28 @@ void Reference::put(GlobalObject& global_object, Value value)
auto& vm = global_object.vm();
if (is_unresolvable()) {
- throw_reference_error(global_object);
- return;
- }
-
- if (is_local_variable() || is_global_variable()) {
- if (is_local_variable())
- vm.set_variable(m_name.to_string(), value, global_object);
- else
- global_object.put(m_name, value);
- return;
- }
-
- auto base = this->base();
-
- if (!base.is_object() && vm.in_strict_mode()) {
- if (base.is_nullish())
- vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishSetProperty, m_name.to_value(vm).to_string_without_side_effects(), base.to_string_without_side_effects());
- else
- vm.throw_exception<TypeError>(global_object, ErrorType::ReferencePrimitiveSetProperty, m_name.to_value(vm).to_string_without_side_effects(), base.typeof(), base.to_string_without_side_effects());
+ if (m_strict) {
+ throw_reference_error(global_object);
+ return;
+ }
+ global_object.put(m_name, value);
return;
}
- if (base.is_nullish()) {
- // This will always fail the to_object() call below, let's throw the TypeError ourselves with a nice message instead.
- vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishSetProperty, m_name.to_value(vm).to_string_without_side_effects(), base.to_string_without_side_effects());
+ if (is_property_reference()) {
+ auto* base_obj = m_base_value.to_object(global_object);
+ if (!base_obj)
+ return;
+ bool succeeded = base_obj->put(m_name, value);
+ if (!succeeded && m_strict) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishSetProperty, m_name.to_value(vm).to_string_without_side_effects(), m_base_value.to_string_without_side_effects());
+ return;
+ }
return;
}
- auto* object = base.to_object(global_object);
- if (!object)
- return;
-
- object->put(m_name, value);
+ VERIFY(m_base_type == BaseType::EnvironmentRecord);
+ m_base_environment_record->set_mutable_binding(global_object, m_name.as_string(), value, m_strict);
}
void Reference::throw_reference_error(GlobalObject& global_object)
@@ -61,69 +51,46 @@ void Reference::throw_reference_error(GlobalObject& global_object)
Value Reference::get(GlobalObject& global_object)
{
- auto& vm = global_object.vm();
-
if (is_unresolvable()) {
throw_reference_error(global_object);
return {};
}
- if (is_local_variable() || is_global_variable()) {
- Value value;
- if (is_local_variable())
- value = vm.get_variable(m_name.to_string(), global_object);
- else
- value = global_object.get(m_name);
- if (vm.exception())
+ if (is_property_reference()) {
+ auto* base_obj = m_base_value.to_object(global_object);
+ if (!base_obj)
return {};
- if (value.is_empty()) {
- throw_reference_error(global_object);
- return {};
- }
- return value;
- }
-
- auto base = this->base();
-
- if (base.is_nullish()) {
- // This will always fail the to_object() call below, let's throw the TypeError ourselves with a nice message instead.
- vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishGetProperty, m_name.to_value(vm).to_string_without_side_effects(), base.to_string_without_side_effects());
- return {};
+ return base_obj->get(m_name).value_or(js_undefined());
}
- auto* object = base.to_object(global_object);
- if (!object)
- return {};
-
- return object->get(m_name).value_or(js_undefined());
+ VERIFY(m_base_type == BaseType::EnvironmentRecord);
+ return m_base_environment_record->get_binding_value(global_object, m_name.as_string(), m_strict);
}
+// 13.5.1.2 RS: Evaluation (UnaryExpression : delete UnaryExpression), https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation
bool Reference::delete_(GlobalObject& global_object)
{
- if (is_unresolvable())
+ if (is_unresolvable()) {
+ VERIFY(!m_strict);
return true;
-
- auto& vm = global_object.vm();
-
- if (is_local_variable() || is_global_variable()) {
- if (is_local_variable())
- return vm.delete_variable(m_name.to_string());
- else
- return global_object.delete_property(m_name);
}
- auto base = this->base();
+ auto& vm = global_object.vm();
- if (base.is_nullish()) {
- // This will always fail the to_object() call below, let's throw the TypeError ourselves with a nice message instead.
- vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishDeleteProperty, m_name.to_value(vm).to_string_without_side_effects(), base.to_string_without_side_effects());
- return false;
+ if (is_property_reference()) {
+ auto* base_obj = m_base_value.to_object(global_object);
+ if (!base_obj)
+ return false;
+ bool succeeded = base_obj->delete_property(m_name);
+ if (!succeeded && m_strict) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishDeleteProperty, m_name.to_value(vm).to_string_without_side_effects(), m_base_value.to_string_without_side_effects());
+ return false;
+ }
+ return succeeded;
}
- auto* object = base.to_object(global_object);
- VERIFY(object);
-
- return object->delete_property(m_name);
+ VERIFY(m_base_type == BaseType::EnvironmentRecord);
+ return m_base_environment_record->delete_binding(global_object, m_name.as_string());
}
}
diff --git a/Userland/Libraries/LibJS/Runtime/Reference.h b/Userland/Libraries/LibJS/Runtime/Reference.h
index 864d7855c..7ce40b957 100644
--- a/Userland/Libraries/LibJS/Runtime/Reference.h
+++ b/Userland/Libraries/LibJS/Runtime/Reference.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -7,6 +7,7 @@
#pragma once
#include <AK/String.h>
+#include <LibJS/Runtime/EnvironmentRecord.h>
#include <LibJS/Runtime/PropertyName.h>
#include <LibJS/Runtime/Value.h>
@@ -14,55 +15,84 @@ namespace JS {
class Reference {
public:
+ enum class BaseType : u8 {
+ Unresolvable,
+ Value,
+ EnvironmentRecord,
+ };
+
Reference() { }
- Reference(Value base, const PropertyName& name, bool strict = false)
- : m_base(base)
+ Reference(BaseType type, PropertyName const& name, bool strict)
+ : m_base_type(type)
, m_name(name)
, m_strict(strict)
{
}
- enum LocalVariableTag { LocalVariable };
- Reference(LocalVariableTag, const FlyString& name, bool strict = false)
- : m_base(js_null())
+ Reference(Value base, PropertyName const& name, Value this_value, bool strict = false)
+ : m_base_type(BaseType::Value)
+ , m_base_value(base)
, m_name(name)
+ , m_this_value(this_value)
, m_strict(strict)
- , m_local_variable(true)
{
+ if (base.is_nullish()) {
+ m_base_value = {};
+ m_name = {};
+ }
}
- enum GlobalVariableTag { GlobalVariable };
- Reference(GlobalVariableTag, const FlyString& name, bool strict = false)
- : m_base(js_null())
- , m_name(name)
+ Reference(EnvironmentRecord& base, FlyString const& referenced_name, bool strict = false)
+ : m_base_type(BaseType::EnvironmentRecord)
+ , m_base_environment_record(&base)
+ , m_name(referenced_name)
, m_strict(strict)
- , m_global_variable(true)
{
}
- Value base() const { return m_base; }
- const PropertyName& name() const { return m_name; }
- bool is_strict() const { return m_strict; }
+ Value base() const
+ {
+ VERIFY(m_base_type == BaseType::Value);
+ return m_base_value;
+ }
- bool is_unresolvable() const { return m_base.is_empty(); }
- bool is_property() const
+ EnvironmentRecord& base_environment() const
{
- return m_base.is_object() || has_primitive_base();
+ VERIFY(m_base_type == BaseType::EnvironmentRecord);
+ return *m_base_environment_record;
}
- bool has_primitive_base() const
+ PropertyName const& name() const { return m_name; }
+ bool is_strict() const { return m_strict; }
+
+ // 6.2.4.2 IsUnresolvableReference ( V ), https://tc39.es/ecma262/#sec-isunresolvablereference
+ bool is_unresolvable() const { return m_base_type == BaseType::Unresolvable; }
+
+ // 6.2.4.1 IsPropertyReference ( V ), https://tc39.es/ecma262/#sec-ispropertyreference
+ bool is_property_reference() const
{
- return m_base.is_boolean() || m_base.is_string() || m_base.is_number();
+ if (is_unresolvable())
+ return false;
+ if (m_base_type == BaseType::EnvironmentRecord)
+ return false;
+ if (m_base_value.is_boolean() || m_base_value.is_string() || m_base_value.is_symbol() || m_base_value.is_bigint() || m_base_value.is_number() || m_base_value.is_object())
+ return true;
+ return false;
}
- bool is_local_variable() const
+ // 6.2.4.7 GetThisValue ( V ), https://tc39.es/ecma262/#sec-getthisvalue
+ Value get_this_value() const
{
- return m_local_variable;
+ VERIFY(is_property_reference());
+ if (is_super_reference())
+ return m_this_value;
+ return m_base_value;
}
- bool is_global_variable() const
+ // 6.2.4.3 IsSuperReference ( V ), https://tc39.es/ecma262/#sec-issuperreference
+ bool is_super_reference() const
{
- return m_global_variable;
+ return !m_this_value.is_empty();
}
void put(GlobalObject&, Value);
@@ -72,11 +102,14 @@ public:
private:
void throw_reference_error(GlobalObject&);
- Value m_base;
+ BaseType m_base_type { BaseType::Unresolvable };
+ union {
+ Value m_base_value;
+ EnvironmentRecord* m_base_environment_record;
+ };
PropertyName m_name;
+ Value m_this_value;
bool m_strict { false };
- bool m_local_variable { false };
- bool m_global_variable { false };
};
}
diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp
index 9f657e8ad..d020dfd35 100644
--- a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp
@@ -166,7 +166,7 @@ Value ScriptFunction::execute_function_body()
if (i >= call_frame_args.size())
call_frame_args.resize(i + 1);
call_frame_args[i] = argument_value;
- vm.assign(param, argument_value, global_object(), true, vm.lexical_environment());
+ vm.assign(param, argument_value, global_object());
});
if (vm.exception())
diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp
index e805bcba5..583109919 100644
--- a/Userland/Libraries/LibJS/Runtime/VM.cpp
+++ b/Userland/Libraries/LibJS/Runtime/VM.cpp
@@ -11,7 +11,6 @@
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
-#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/FinalizationRegistry.h>
#include <LibJS/Runtime/FunctionEnvironmentRecord.h>
#include <LibJS/Runtime/GlobalEnvironmentRecord.h>
@@ -131,74 +130,26 @@ 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, EnvironmentRecord* specific_scope)
+void VM::set_variable(FlyString const& name, Value value, GlobalObject& global_object)
{
- Optional<Variable> possible_match;
- if (!specific_scope && m_call_stack.size()) {
- for (auto* environment_record = lexical_environment(); environment_record; environment_record = environment_record->outer_environment()) {
- possible_match = environment_record->get_from_environment_record(name);
- if (possible_match.has_value()) {
- specific_scope = environment_record;
- 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;
- }
-
- specific_scope->put_into_environment_record(name, { value, possible_match.value().declaration_kind });
- return;
- }
-
- if (specific_scope) {
- specific_scope->put_into_environment_record(name, { value, DeclarationKind::Var });
- return;
- }
-
- global_object.put(name, value);
-}
-
-bool VM::delete_variable(FlyString const& name)
-{
- EnvironmentRecord* specific_scope = nullptr;
- Optional<Variable> possible_match;
- if (!m_call_stack.is_empty()) {
- for (auto* environment_record = lexical_environment(); environment_record; environment_record = environment_record->outer_environment()) {
- possible_match = environment_record->get_from_environment_record(name);
- if (possible_match.has_value()) {
- specific_scope = environment_record;
- break;
- }
- }
- }
-
- if (!possible_match.has_value())
- return false;
- if (possible_match.value().declaration_kind == DeclarationKind::Const)
- return false;
-
- VERIFY(specific_scope);
- return specific_scope->delete_from_environment_record(name);
+ auto reference = resolve_binding(global_object, name);
+ return reference.put(global_object, value);
}
-void VM::assign(const FlyString& target, Value value, GlobalObject& global_object, bool first_assignment, EnvironmentRecord* specific_scope)
+void VM::assign(FlyString const& target, Value value, GlobalObject& global_object)
{
- set_variable(target, move(value), global_object, first_assignment, specific_scope);
+ set_variable(target, value, global_object);
}
-void VM::assign(const Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>>& target, Value value, GlobalObject& global_object, bool first_assignment, EnvironmentRecord* specific_scope)
+void VM::assign(Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>> const& target, Value value, GlobalObject& global_object)
{
if (auto id_ptr = target.get_pointer<NonnullRefPtr<Identifier>>())
- return assign((*id_ptr)->string(), move(value), global_object, first_assignment, specific_scope);
+ return assign((*id_ptr)->string(), value, global_object);
- assign(target.get<NonnullRefPtr<BindingPattern>>(), move(value), global_object, first_assignment, specific_scope);
+ assign(target.get<NonnullRefPtr<BindingPattern>>(), value, global_object);
}
-void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, GlobalObject& global_object, bool first_assignment, EnvironmentRecord* specific_scope)
+void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, GlobalObject& global_object)
{
auto& binding = *target;
@@ -267,10 +218,10 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global
entry.alias.visit(
[&](Empty) {},
[&](NonnullRefPtr<Identifier> const& identifier) {
- set_variable(identifier->string(), value, global_object, first_assignment, specific_scope);
+ set_variable(identifier->string(), value, global_object);
},
[&](NonnullRefPtr<BindingPattern> const& pattern) {
- assign(pattern, value, global_object, first_assignment, specific_scope);
+ assign(pattern, value, global_object);
});
if (entry.is_rest)
@@ -335,15 +286,15 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global
property.alias.visit(
[&](Empty) {
- set_variable(assignment_name.to_string(), value_to_assign, global_object, first_assignment, specific_scope);
+ set_variable(assignment_name.to_string(), value_to_assign, global_object);
},
[&](NonnullRefPtr<Identifier> const& identifier) {
VERIFY(!property.is_rest);
- set_variable(identifier->string(), value_to_assign, global_object, first_assignment, specific_scope);
+ set_variable(identifier->string(), value_to_assign, global_object);
},
[&](NonnullRefPtr<BindingPattern> const& pattern) {
VERIFY(!property.is_rest);
- assign(pattern, value_to_assign, global_object, first_assignment, specific_scope);
+ assign(pattern, value_to_assign, global_object);
});
if (property.is_rest)
@@ -356,6 +307,9 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global
Value VM::get_variable(const FlyString& name, GlobalObject& global_object)
{
+ if (m_call_stack.is_empty())
+ return global_object.get(name);
+
if (!m_call_stack.is_empty()) {
if (name == names.arguments.as_string() && !call_frame().callee.is_empty()) {
// HACK: Special handling for the name "arguments":
@@ -375,29 +329,25 @@ Value VM::get_variable(const FlyString& name, GlobalObject& global_object)
}
return call_frame().arguments_object;
}
+ }
- for (auto* environment_record = lexical_environment(); environment_record; environment_record = environment_record->outer_environment()) {
- auto possible_match = environment_record->get_from_environment_record(name);
- if (exception())
- return {};
- if (possible_match.has_value())
- return possible_match.value().value;
- }
+ auto reference = resolve_binding(global_object, name);
+ if (reference.is_unresolvable()) {
+ if (m_underscore_is_last_value && name == "_"sv)
+ return m_last_value;
}
- auto value = global_object.get(name);
- if (m_underscore_is_last_value && name == "_" && value.is_empty())
- return m_last_value;
- return value;
+ return reference.get(global_object);
}
-Reference VM::get_reference(const FlyString& name)
+Reference VM::resolve_binding(GlobalObject& global_object, FlyString const& name, EnvironmentRecord* environment)
{
- for (auto* environment_record = lexical_environment(); environment_record && environment_record->outer_environment(); environment_record = environment_record->outer_environment()) {
- auto possible_match = environment_record->get_from_environment_record(name);
- if (possible_match.has_value())
- return { Reference::LocalVariable, name };
- }
- return { Reference::GlobalVariable, name };
+ if (!environment)
+ environment = lexical_environment();
+
+ // FIXME: 3. If the code matching the syntactic production that is being evaluated is contained in strict mode code, let strict be true; else let strict be false.
+ bool strict = in_strict_mode();
+
+ return get_identifier_reference(global_object, environment, name, strict);
}
Value VM::construct(Function& function, Function& new_target, Optional<MarkedValueList> arguments)
diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h
index 44c893825..b4d4ec5b1 100644
--- a/Userland/Libraries/LibJS/Runtime/VM.h
+++ b/Userland/Libraries/LibJS/Runtime/VM.h
@@ -195,14 +195,13 @@ 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, EnvironmentRecord* specific_scope = nullptr);
- bool delete_variable(FlyString const& name);
- void assign(const Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>>& target, Value, GlobalObject&, bool first_assignment = false, EnvironmentRecord* specific_scope = nullptr);
- void assign(const FlyString& target, Value, GlobalObject&, bool first_assignment = false, EnvironmentRecord* specific_scope = nullptr);
- void assign(const NonnullRefPtr<BindingPattern>& target, Value, GlobalObject&, bool first_assignment = false, EnvironmentRecord* specific_scope = nullptr);
-
- Reference get_reference(const FlyString& name);
+ Value get_variable(FlyString const& name, GlobalObject&);
+ void set_variable(FlyString const& name, Value, GlobalObject&);
+ void assign(Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>> const& target, Value, GlobalObject&);
+ void assign(FlyString const& target, Value, GlobalObject&);
+ void assign(NonnullRefPtr<BindingPattern> const& target, Value, GlobalObject&);
+
+ Reference resolve_binding(GlobalObject&, FlyString const& name, EnvironmentRecord* = nullptr);
template<typename T, typename... Args>
void throw_exception(GlobalObject& global_object, Args&&... args)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment