mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
LibJS: Add a scope object abstraction
Both GlobalObject and LexicalEnvironment now inherit from ScopeObject, and the VM's call frames point to a ScopeObject chain rather than just a LexicalEnvironment chain. This gives us much more flexibility to implement things like "with", and also unifies some of the code paths that previously required special handling of the global object. There's a bunch of more cleanup that can be done in the wake of this change, and there might be some oversights in the handling of the "super" keyword, but this generally seems like a good architectural improvement. :^)
This commit is contained in:
parent
e1bbc7c075
commit
c3fe9b4df8
Notes:
sideshowbarker
2024-07-19 01:13:02 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/c3fe9b4df8c
16 changed files with 241 additions and 92 deletions
|
@ -101,7 +101,7 @@ Value FunctionDeclaration::execute(Interpreter&, GlobalObject&) const
|
|||
|
||||
Value FunctionExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||
{
|
||||
return ScriptFunction::create(global_object, name(), body(), parameters(), function_length(), interpreter.current_environment(), is_strict_mode() || interpreter.vm().in_strict_mode(), m_is_arrow_function);
|
||||
return ScriptFunction::create(global_object, name(), body(), parameters(), function_length(), interpreter.current_scope(), is_strict_mode() || interpreter.vm().in_strict_mode(), m_is_arrow_function);
|
||||
}
|
||||
|
||||
Value ExpressionStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||
|
@ -128,7 +128,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete
|
|||
if (m_callee->is_member_expression()) {
|
||||
auto& member_expression = static_cast<const MemberExpression&>(*m_callee);
|
||||
bool is_super_property_lookup = member_expression.object().is_super_expression();
|
||||
auto lookup_target = is_super_property_lookup ? vm.current_environment()->get_super_base() : member_expression.object().execute(interpreter, global_object);
|
||||
auto lookup_target = is_super_property_lookup ? interpreter.current_environment()->get_super_base() : member_expression.object().execute(interpreter, global_object);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
if (is_super_property_lookup && lookup_target.is_nullish()) {
|
||||
|
@ -205,7 +205,7 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
|
|||
if (result.is_object())
|
||||
new_object = &result.as_object();
|
||||
} else if (m_callee->is_super_expression()) {
|
||||
auto* super_constructor = vm.current_environment()->current_function()->prototype();
|
||||
auto* super_constructor = interpreter.current_environment()->current_function()->prototype();
|
||||
// FIXME: Functions should track their constructor kind.
|
||||
if (!super_constructor || !super_constructor->is_function()) {
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, "Super constructor");
|
||||
|
@ -215,7 +215,7 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
|
|||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
vm.current_environment()->bind_this_value(global_object, result);
|
||||
interpreter.current_environment()->bind_this_value(global_object, result);
|
||||
} else {
|
||||
result = vm.call(function, this_value, move(arguments));
|
||||
}
|
||||
|
@ -801,7 +801,7 @@ Value ClassDeclaration::execute(Interpreter& interpreter, GlobalObject& global_o
|
|||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
interpreter.current_environment()->set(global_object, m_class_expression->name(), { class_constructor, DeclarationKind::Let });
|
||||
interpreter.current_scope()->put_to_scope(m_class_expression->name(), { class_constructor, DeclarationKind::Let });
|
||||
|
||||
return js_undefined();
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ set(SOURCES
|
|||
Runtime/RegExpConstructor.cpp
|
||||
Runtime/RegExpObject.cpp
|
||||
Runtime/RegExpPrototype.cpp
|
||||
Runtime/ScopeObject.cpp
|
||||
Runtime/ScriptFunction.cpp
|
||||
Runtime/Shape.cpp
|
||||
Runtime/StringConstructor.cpp
|
||||
|
|
|
@ -124,6 +124,7 @@ class NativeProperty;
|
|||
class PrimitiveString;
|
||||
class Reference;
|
||||
class ScopeNode;
|
||||
class ScopeObject;
|
||||
class Shape;
|
||||
class Statement;
|
||||
class Symbol;
|
||||
|
|
|
@ -72,8 +72,7 @@ Value Interpreter::run(GlobalObject& global_object, const Program& program)
|
|||
global_call_frame.this_value = &global_object;
|
||||
static FlyString global_execution_context_name = "(global execution context)";
|
||||
global_call_frame.function_name = global_execution_context_name;
|
||||
global_call_frame.environment = heap().allocate<LexicalEnvironment>(global_object, LexicalEnvironment::EnvironmentRecordType::Global);
|
||||
global_call_frame.environment->bind_this_value(global_object, &global_object);
|
||||
global_call_frame.scope = &global_object;
|
||||
ASSERT(!vm.exception());
|
||||
global_call_frame.is_strict_mode = program.is_strict_mode();
|
||||
vm.push_call_frame(global_call_frame, global_object);
|
||||
|
@ -96,7 +95,7 @@ const GlobalObject& Interpreter::global_object() const
|
|||
void Interpreter::enter_scope(const ScopeNode& scope_node, ArgumentVector arguments, ScopeType scope_type, GlobalObject& global_object)
|
||||
{
|
||||
for (auto& declaration : scope_node.functions()) {
|
||||
auto* function = ScriptFunction::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), current_environment(), declaration.is_strict_mode());
|
||||
auto* function = ScriptFunction::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), current_scope(), declaration.is_strict_mode());
|
||||
vm().set_variable(declaration.name(), function, global_object);
|
||||
}
|
||||
|
||||
|
@ -127,8 +126,8 @@ void Interpreter::enter_scope(const ScopeNode& scope_node, ArgumentVector argume
|
|||
bool pushed_lexical_environment = false;
|
||||
|
||||
if (!scope_variables_with_declaration_kind.is_empty()) {
|
||||
auto* block_lexical_environment = heap().allocate<LexicalEnvironment>(global_object, move(scope_variables_with_declaration_kind), current_environment());
|
||||
vm().call_frame().environment = block_lexical_environment;
|
||||
auto* block_lexical_environment = heap().allocate<LexicalEnvironment>(global_object, move(scope_variables_with_declaration_kind), current_scope());
|
||||
vm().call_frame().scope = block_lexical_environment;
|
||||
pushed_lexical_environment = true;
|
||||
}
|
||||
|
||||
|
@ -140,7 +139,7 @@ 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().call_frame().environment = vm().call_frame().environment->parent();
|
||||
vm().call_frame().scope = vm().call_frame().scope->parent();
|
||||
if (popped_scope.scope_node.ptr() == &scope_node)
|
||||
break;
|
||||
}
|
||||
|
@ -185,4 +184,10 @@ Value Interpreter::execute_statement(GlobalObject& global_object, const Statemen
|
|||
return did_return ? vm().last_value() : js_undefined();
|
||||
}
|
||||
|
||||
LexicalEnvironment* Interpreter::current_environment()
|
||||
{
|
||||
ASSERT(vm().call_frame().scope->is_lexical_environment());
|
||||
return static_cast<LexicalEnvironment*>(vm().call_frame().scope);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -71,7 +71,8 @@ public:
|
|||
Heap& heap() { return vm().heap(); }
|
||||
Exception* exception() { return vm().exception(); }
|
||||
|
||||
LexicalEnvironment* current_environment() { return vm().current_environment(); }
|
||||
ScopeObject* current_scope() { return vm().current_scope(); }
|
||||
LexicalEnvironment* current_environment();
|
||||
|
||||
void enter_scope(const ScopeNode&, ArgumentVector, ScopeType, GlobalObject&);
|
||||
void exit_scope(const ScopeNode&);
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
namespace JS {
|
||||
|
||||
GlobalObject::GlobalObject()
|
||||
: Object(GlobalObjectTag::Tag)
|
||||
: ScopeObject(GlobalObjectTag::Tag)
|
||||
, m_console(make<Console>(*this))
|
||||
{
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ GlobalObject::~GlobalObject()
|
|||
|
||||
void GlobalObject::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Object::visit_edges(visitor);
|
||||
Base::visit_edges(visitor);
|
||||
|
||||
visitor.visit(m_empty_object_shape);
|
||||
visitor.visit(m_new_object_shape);
|
||||
|
@ -205,4 +205,27 @@ JS_DEFINE_NATIVE_FUNCTION(GlobalObject::parse_float)
|
|||
return js_nan();
|
||||
}
|
||||
|
||||
Optional<Variable> GlobalObject::get_from_scope(const FlyString& name) const
|
||||
{
|
||||
auto value = get(name);
|
||||
if (value.is_empty())
|
||||
return {};
|
||||
return Variable { value, DeclarationKind::Var };
|
||||
}
|
||||
|
||||
void GlobalObject::put_to_scope(const FlyString& name, Variable variable)
|
||||
{
|
||||
put(name, variable.value);
|
||||
}
|
||||
|
||||
bool GlobalObject::has_this_binding() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Value GlobalObject::get_this_binding(GlobalObject&) const
|
||||
{
|
||||
return Value(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,13 +27,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibJS/Heap/Heap.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibJS/Runtime/ScopeObject.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
class GlobalObject : public Object {
|
||||
JS_OBJECT(GlobalObject, Object);
|
||||
class GlobalObject : public ScopeObject {
|
||||
JS_OBJECT(GlobalObject, ScopeObject);
|
||||
|
||||
public:
|
||||
explicit GlobalObject();
|
||||
|
@ -41,6 +41,11 @@ public:
|
|||
|
||||
virtual ~GlobalObject() override;
|
||||
|
||||
virtual Optional<Variable> get_from_scope(const FlyString&) const override;
|
||||
virtual void put_to_scope(const FlyString&, Variable) override;
|
||||
virtual bool has_this_binding() const override;
|
||||
virtual Value get_this_binding(GlobalObject&) const override;
|
||||
|
||||
Console& console() { return *m_console; }
|
||||
|
||||
Shape* empty_object_shape() { return m_empty_object_shape; }
|
||||
|
@ -66,6 +71,8 @@ protected:
|
|||
void add_constructor(const FlyString& property_name, ConstructorType*&, Object& prototype);
|
||||
|
||||
private:
|
||||
virtual bool is_global_object() const final { return true; }
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(gc);
|
||||
JS_DECLARE_NATIVE_FUNCTION(is_nan);
|
||||
JS_DECLARE_NATIVE_FUNCTION(is_finite);
|
||||
|
|
|
@ -34,23 +34,25 @@
|
|||
namespace JS {
|
||||
|
||||
LexicalEnvironment::LexicalEnvironment()
|
||||
: ScopeObject(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::LexicalEnvironment(EnvironmentRecordType environment_record_type)
|
||||
: m_environment_record_type(environment_record_type)
|
||||
: ScopeObject(nullptr)
|
||||
, m_environment_record_type(environment_record_type)
|
||||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent)
|
||||
: m_parent(parent)
|
||||
LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope)
|
||||
: ScopeObject(parent_scope)
|
||||
, m_variables(move(variables))
|
||||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent, EnvironmentRecordType environment_record_type)
|
||||
: m_environment_record_type(environment_record_type)
|
||||
, m_parent(parent)
|
||||
LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope, EnvironmentRecordType environment_record_type)
|
||||
: ScopeObject(parent_scope)
|
||||
, m_environment_record_type(environment_record_type)
|
||||
, m_variables(move(variables))
|
||||
{
|
||||
}
|
||||
|
@ -62,7 +64,6 @@ LexicalEnvironment::~LexicalEnvironment()
|
|||
void LexicalEnvironment::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Cell::visit_edges(visitor);
|
||||
visitor.visit(m_parent);
|
||||
visitor.visit(m_this_value);
|
||||
visitor.visit(m_home_object);
|
||||
visitor.visit(m_new_target);
|
||||
|
@ -71,18 +72,14 @@ void LexicalEnvironment::visit_edges(Visitor& visitor)
|
|||
visitor.visit(it.value.value);
|
||||
}
|
||||
|
||||
Optional<Variable> LexicalEnvironment::get(const FlyString& name) const
|
||||
Optional<Variable> LexicalEnvironment::get_from_scope(const FlyString& name) const
|
||||
{
|
||||
ASSERT(type() != EnvironmentRecordType::Global);
|
||||
return m_variables.get(name);
|
||||
}
|
||||
|
||||
void LexicalEnvironment::set(GlobalObject& global_object, const FlyString& name, Variable variable)
|
||||
void LexicalEnvironment::put_to_scope(const FlyString& name, Variable variable)
|
||||
{
|
||||
if (type() == EnvironmentRecordType::Global)
|
||||
global_object.put(name, variable.value);
|
||||
else
|
||||
m_variables.set(name, variable);
|
||||
m_variables.set(name, variable);
|
||||
}
|
||||
|
||||
bool LexicalEnvironment::has_super_binding() const
|
||||
|
@ -108,7 +105,6 @@ bool LexicalEnvironment::has_this_binding() const
|
|||
case EnvironmentRecordType::Function:
|
||||
return this_binding_status() != ThisBindingStatus::Lexical;
|
||||
case EnvironmentRecordType::Module:
|
||||
case EnvironmentRecordType::Global:
|
||||
return true;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
|
|
|
@ -28,17 +28,14 @@
|
|||
|
||||
#include <AK/FlyString.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <LibJS/Runtime/Cell.h>
|
||||
#include <LibJS/Runtime/ScopeObject.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
struct Variable {
|
||||
Value value;
|
||||
DeclarationKind declaration_kind;
|
||||
};
|
||||
class LexicalEnvironment final : public ScopeObject {
|
||||
JS_OBJECT(LexicalEnvironment, ScopeObject);
|
||||
|
||||
class LexicalEnvironment final : public Cell {
|
||||
public:
|
||||
enum class ThisBindingStatus {
|
||||
Lexical,
|
||||
|
@ -49,21 +46,21 @@ public:
|
|||
enum class EnvironmentRecordType {
|
||||
Declarative,
|
||||
Function,
|
||||
Global,
|
||||
Object,
|
||||
Module,
|
||||
};
|
||||
|
||||
LexicalEnvironment();
|
||||
LexicalEnvironment(EnvironmentRecordType);
|
||||
LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent);
|
||||
LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent, EnvironmentRecordType);
|
||||
LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope);
|
||||
LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope, EnvironmentRecordType);
|
||||
virtual ~LexicalEnvironment() override;
|
||||
|
||||
LexicalEnvironment* parent() const { return m_parent; }
|
||||
|
||||
Optional<Variable> get(const FlyString&) const;
|
||||
void set(GlobalObject&, const FlyString&, Variable);
|
||||
// ^ScopeObject
|
||||
virtual Optional<Variable> get_from_scope(const FlyString&) const override;
|
||||
virtual void put_to_scope(const FlyString&, Variable) override;
|
||||
virtual bool has_this_binding() const override;
|
||||
virtual Value get_this_binding(GlobalObject&) const override;
|
||||
|
||||
void clear();
|
||||
|
||||
|
@ -73,9 +70,7 @@ public:
|
|||
bool has_super_binding() const;
|
||||
Value get_super_base();
|
||||
|
||||
bool has_this_binding() const;
|
||||
ThisBindingStatus this_binding_status() const { return m_this_binding_status; }
|
||||
Value get_this_binding(GlobalObject&) const;
|
||||
void bind_this_value(GlobalObject&, Value this_value);
|
||||
|
||||
// Not a standard operation.
|
||||
|
@ -90,12 +85,11 @@ public:
|
|||
EnvironmentRecordType type() const { return m_environment_record_type; }
|
||||
|
||||
private:
|
||||
virtual const char* class_name() const override { return "LexicalEnvironment"; }
|
||||
virtual bool is_lexical_environment() const final { return true; }
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
EnvironmentRecordType m_environment_record_type : 8 { EnvironmentRecordType::Declarative };
|
||||
ThisBindingStatus m_this_binding_status : 8 { ThisBindingStatus::Uninitialized };
|
||||
LexicalEnvironment* m_parent { nullptr };
|
||||
HashMap<FlyString, Variable> m_variables;
|
||||
Value m_home_object;
|
||||
Value m_this_value;
|
||||
|
|
|
@ -127,6 +127,8 @@ public:
|
|||
virtual bool is_bigint_object() const { return false; }
|
||||
virtual bool is_string_iterator_object() const { return false; }
|
||||
virtual bool is_array_iterator_object() const { return false; }
|
||||
virtual bool is_lexical_environment() const { return false; }
|
||||
virtual bool is_global_object() const { return false; }
|
||||
|
||||
virtual const char* class_name() const override { return "Object"; }
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
|
49
Libraries/LibJS/Runtime/ScopeObject.cpp
Normal file
49
Libraries/LibJS/Runtime/ScopeObject.cpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/ScopeObject.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
ScopeObject::ScopeObject(ScopeObject* parent)
|
||||
: Object(vm().scope_object_shape())
|
||||
, m_parent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ScopeObject::ScopeObject(GlobalObjectTag tag)
|
||||
: Object(tag)
|
||||
{
|
||||
}
|
||||
|
||||
void ScopeObject::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_parent);
|
||||
}
|
||||
|
||||
}
|
60
Libraries/LibJS/Runtime/ScopeObject.h
Normal file
60
Libraries/LibJS/Runtime/ScopeObject.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
struct Variable {
|
||||
Value value;
|
||||
DeclarationKind declaration_kind;
|
||||
};
|
||||
|
||||
class ScopeObject : public Object {
|
||||
JS_OBJECT(ScopeObject, Object);
|
||||
|
||||
public:
|
||||
virtual Optional<Variable> get_from_scope(const FlyString&) const = 0;
|
||||
virtual void put_to_scope(const FlyString&, Variable) = 0;
|
||||
virtual bool has_this_binding() const = 0;
|
||||
virtual Value get_this_binding(GlobalObject&) const = 0;
|
||||
|
||||
ScopeObject* parent() { return m_parent; }
|
||||
const ScopeObject* parent() const { return m_parent; }
|
||||
|
||||
protected:
|
||||
explicit ScopeObject(ScopeObject* parent);
|
||||
explicit ScopeObject(GlobalObjectTag);
|
||||
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
private:
|
||||
ScopeObject* m_parent { nullptr };
|
||||
};
|
||||
|
||||
}
|
|
@ -47,17 +47,17 @@ static ScriptFunction* typed_this(VM& vm, GlobalObject& global_object)
|
|||
return static_cast<ScriptFunction*>(this_object);
|
||||
}
|
||||
|
||||
ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, LexicalEnvironment* parent_environment, bool is_strict, bool is_arrow_function)
|
||||
ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function)
|
||||
{
|
||||
return global_object.heap().allocate<ScriptFunction>(global_object, global_object, name, body, move(parameters), m_function_length, parent_environment, *global_object.function_prototype(), is_strict, is_arrow_function);
|
||||
return global_object.heap().allocate<ScriptFunction>(global_object, global_object, name, body, move(parameters), m_function_length, parent_scope, *global_object.function_prototype(), is_strict, is_arrow_function);
|
||||
}
|
||||
|
||||
ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, LexicalEnvironment* parent_environment, Object& prototype, bool is_strict, bool is_arrow_function)
|
||||
ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function)
|
||||
: Function(prototype, is_arrow_function ? vm().this_value(global_object) : Value(), {})
|
||||
, m_name(name)
|
||||
, m_body(body)
|
||||
, m_parameters(move(parameters))
|
||||
, m_parent_environment(parent_environment)
|
||||
, m_parent_scope(parent_scope)
|
||||
, m_function_length(m_function_length)
|
||||
, m_is_strict(is_strict)
|
||||
, m_is_arrow_function(is_arrow_function)
|
||||
|
@ -84,7 +84,7 @@ ScriptFunction::~ScriptFunction()
|
|||
void ScriptFunction::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Function::visit_edges(visitor);
|
||||
visitor.visit(m_parent_environment);
|
||||
visitor.visit(m_parent_scope);
|
||||
}
|
||||
|
||||
LexicalEnvironment* ScriptFunction::create_environment()
|
||||
|
@ -102,11 +102,13 @@ LexicalEnvironment* ScriptFunction::create_environment()
|
|||
}
|
||||
}
|
||||
|
||||
auto* environment = heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_environment, LexicalEnvironment::EnvironmentRecordType::Function);
|
||||
auto* environment = heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_scope, LexicalEnvironment::EnvironmentRecordType::Function);
|
||||
environment->set_home_object(home_object());
|
||||
environment->set_current_function(*this);
|
||||
if (m_is_arrow_function)
|
||||
environment->set_new_target(m_parent_environment->new_target());
|
||||
if (m_is_arrow_function) {
|
||||
if (m_parent_scope->is_lexical_environment())
|
||||
environment->set_new_target(static_cast<LexicalEnvironment*>(m_parent_scope)->new_target());
|
||||
}
|
||||
return environment;
|
||||
}
|
||||
|
||||
|
@ -144,7 +146,7 @@ Value ScriptFunction::execute_function_body()
|
|||
argument_value = js_undefined();
|
||||
}
|
||||
arguments.append({ parameter.name, argument_value });
|
||||
vm.current_environment()->set(global_object(), parameter.name, { argument_value, DeclarationKind::Var });
|
||||
vm.current_scope()->put_to_scope(parameter.name, { argument_value, DeclarationKind::Var });
|
||||
}
|
||||
|
||||
return interpreter->execute_statement(global_object(), m_body, move(arguments), ScopeType::Function);
|
||||
|
|
|
@ -35,9 +35,9 @@ class ScriptFunction final : public Function {
|
|||
JS_OBJECT(ScriptFunction, Function);
|
||||
|
||||
public:
|
||||
static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, LexicalEnvironment* parent_environment, bool is_strict, bool is_arrow_function = false);
|
||||
static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function = false);
|
||||
|
||||
ScriptFunction(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, LexicalEnvironment* parent_environment, Object& prototype, bool is_strict, bool is_arrow_function = false);
|
||||
ScriptFunction(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function = false);
|
||||
virtual void initialize(GlobalObject&) override;
|
||||
virtual ~ScriptFunction();
|
||||
|
||||
|
@ -68,7 +68,7 @@ private:
|
|||
FlyString m_name;
|
||||
NonnullRefPtr<Statement> m_body;
|
||||
const Vector<FunctionNode::Parameter> m_parameters;
|
||||
LexicalEnvironment* m_parent_environment { nullptr };
|
||||
ScopeObject* m_parent_scope { nullptr };
|
||||
i32 m_function_length { 0 };
|
||||
bool m_is_strict { false };
|
||||
bool m_is_arrow_function { false };
|
||||
|
|
|
@ -51,6 +51,8 @@ VM::VM()
|
|||
m_single_ascii_character_strings[i] = m_heap.allocate_without_global_object<PrimitiveString>(String::format("%c", i));
|
||||
}
|
||||
|
||||
m_scope_object_shape = m_heap.allocate_without_global_object<Shape>(Shape::ShapeWithoutGlobalObjectTag::Tag);
|
||||
|
||||
#define __JS_ENUMERATE(SymbolName, snake_name) \
|
||||
m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false);
|
||||
JS_ENUMERATE_WELL_KNOWN_SYMBOLS
|
||||
|
@ -103,6 +105,8 @@ void VM::gather_roots(HashTable<Cell*>& roots)
|
|||
for (auto* string : m_single_ascii_character_strings)
|
||||
roots.set(string);
|
||||
|
||||
roots.set(m_scope_object_shape);
|
||||
|
||||
if (m_exception)
|
||||
roots.set(m_exception);
|
||||
|
||||
|
@ -116,7 +120,7 @@ void VM::gather_roots(HashTable<Cell*>& roots)
|
|||
if (argument.is_cell())
|
||||
roots.set(argument.as_cell());
|
||||
}
|
||||
roots.set(call_frame->environment);
|
||||
roots.set(call_frame->scope);
|
||||
}
|
||||
|
||||
#define __JS_ENUMERATE(SymbolName, snake_name) \
|
||||
|
@ -142,17 +146,15 @@ Symbol* VM::get_global_symbol(const String& description)
|
|||
void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_object, bool first_assignment)
|
||||
{
|
||||
if (m_call_stack.size()) {
|
||||
for (auto* environment = current_environment(); environment; environment = environment->parent()) {
|
||||
if (environment->type() == LexicalEnvironment::EnvironmentRecordType::Global)
|
||||
break;
|
||||
auto possible_match = environment->get(name);
|
||||
for (auto* scope = current_scope(); scope; scope = scope->parent()) {
|
||||
auto possible_match = scope->get_from_scope(name);
|
||||
if (possible_match.has_value()) {
|
||||
if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) {
|
||||
throw_exception<TypeError>(global_object, ErrorType::InvalidAssignToConst);
|
||||
return;
|
||||
}
|
||||
|
||||
environment->set(global_object, name, { value, possible_match.value().declaration_kind });
|
||||
scope->put_to_scope(name, { value, possible_match.value().declaration_kind });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -164,10 +166,8 @@ void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_o
|
|||
Value VM::get_variable(const FlyString& name, GlobalObject& global_object)
|
||||
{
|
||||
if (m_call_stack.size()) {
|
||||
for (auto* environment = current_environment(); environment; environment = environment->parent()) {
|
||||
if (environment->type() == LexicalEnvironment::EnvironmentRecordType::Global)
|
||||
break;
|
||||
auto possible_match = environment->get(name);
|
||||
for (auto* scope = current_scope(); scope; scope = scope->parent()) {
|
||||
auto possible_match = scope->get_from_scope(name);
|
||||
if (possible_match.has_value())
|
||||
return possible_match.value().value;
|
||||
}
|
||||
|
@ -181,10 +181,10 @@ Value VM::get_variable(const FlyString& name, GlobalObject& global_object)
|
|||
Reference VM::get_reference(const FlyString& name)
|
||||
{
|
||||
if (m_call_stack.size()) {
|
||||
for (auto* environment = current_environment(); environment; environment = environment->parent()) {
|
||||
if (environment->type() == LexicalEnvironment::EnvironmentRecordType::Global)
|
||||
for (auto* scope = current_scope(); scope; scope = scope->parent()) {
|
||||
if (scope->is_global_object())
|
||||
break;
|
||||
auto possible_match = environment->get(name);
|
||||
auto possible_match = scope->get_from_scope(name);
|
||||
if (possible_match.has_value())
|
||||
return { Reference::LocalVariable, name };
|
||||
}
|
||||
|
@ -208,13 +208,14 @@ Value VM::construct(Function& function, Function& new_target, Optional<MarkedVal
|
|||
call_frame.arguments = function.bound_arguments();
|
||||
if (arguments.has_value())
|
||||
call_frame.arguments.append(arguments.value().values());
|
||||
call_frame.environment = function.create_environment();
|
||||
call_frame.environment->set_new_target(&new_target);
|
||||
auto* environment = function.create_environment();
|
||||
call_frame.scope = environment;
|
||||
environment->set_new_target(&new_target);
|
||||
|
||||
Object* new_object = nullptr;
|
||||
if (function.constructor_kind() == Function::ConstructorKind::Base) {
|
||||
new_object = Object::create_empty(global_object);
|
||||
call_frame.environment->bind_this_value(global_object, new_object);
|
||||
environment->bind_this_value(global_object, new_object);
|
||||
if (exception())
|
||||
return {};
|
||||
auto prototype = new_target.get(names.prototype);
|
||||
|
@ -232,14 +233,15 @@ Value VM::construct(Function& function, Function& new_target, Optional<MarkedVal
|
|||
call_frame.this_value = this_value;
|
||||
auto result = function.construct(new_target);
|
||||
|
||||
this_value = call_frame.environment->get_this_binding(global_object);
|
||||
this_value = call_frame.scope->get_this_binding(global_object);
|
||||
pop_call_frame();
|
||||
call_frame_popper.disarm();
|
||||
|
||||
// If we are constructing an instance of a derived class,
|
||||
// set the prototype on objects created by constructors that return an object (i.e. NativeFunction subclasses).
|
||||
if (function.constructor_kind() == Function::ConstructorKind::Base && new_target.constructor_kind() == Function::ConstructorKind::Derived && result.is_object()) {
|
||||
current_environment()->replace_this_binding(result);
|
||||
ASSERT(current_scope()->is_lexical_environment());
|
||||
static_cast<LexicalEnvironment*>(current_scope())->replace_this_binding(result);
|
||||
auto prototype = new_target.get(names.prototype);
|
||||
if (exception())
|
||||
return {};
|
||||
|
@ -292,22 +294,23 @@ String VM::join_arguments() const
|
|||
|
||||
Value VM::resolve_this_binding(GlobalObject& global_object) const
|
||||
{
|
||||
return get_this_environment()->get_this_binding(global_object);
|
||||
return find_this_scope()->get_this_binding(global_object);
|
||||
}
|
||||
|
||||
const LexicalEnvironment* VM::get_this_environment() const
|
||||
const ScopeObject* VM::find_this_scope() const
|
||||
{
|
||||
// We will always return because the Global environment will always be reached, which has a |this| binding.
|
||||
for (const LexicalEnvironment* environment = current_environment(); environment; environment = environment->parent()) {
|
||||
if (environment->has_this_binding())
|
||||
return environment;
|
||||
for (auto* scope = current_scope(); scope; scope = scope->parent()) {
|
||||
if (scope->has_this_binding())
|
||||
return scope;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
Value VM::get_new_target() const
|
||||
{
|
||||
return get_this_environment()->new_target();
|
||||
ASSERT(find_this_scope()->is_lexical_environment());
|
||||
return static_cast<const LexicalEnvironment*>(find_this_scope())->new_target();
|
||||
}
|
||||
|
||||
Value VM::call_internal(Function& function, Value this_value, Optional<MarkedValueList> arguments)
|
||||
|
@ -321,10 +324,11 @@ Value VM::call_internal(Function& function, Value this_value, Optional<MarkedVal
|
|||
call_frame.arguments = function.bound_arguments();
|
||||
if (arguments.has_value())
|
||||
call_frame.arguments.append(move(arguments.release_value().values()));
|
||||
call_frame.environment = function.create_environment();
|
||||
auto* environment = function.create_environment();
|
||||
call_frame.scope = environment;
|
||||
|
||||
ASSERT(call_frame.environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized);
|
||||
call_frame.environment->bind_this_value(function.global_object(), call_frame.this_value);
|
||||
ASSERT(environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized);
|
||||
environment->bind_this_value(function.global_object(), call_frame.this_value);
|
||||
if (exception())
|
||||
return {};
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ struct CallFrame {
|
|||
FlyString function_name;
|
||||
Value this_value;
|
||||
Vector<Value> arguments;
|
||||
LexicalEnvironment* environment { nullptr };
|
||||
ScopeObject* scope { nullptr };
|
||||
bool is_strict_mode { false };
|
||||
};
|
||||
|
||||
|
@ -134,8 +134,8 @@ public:
|
|||
const Vector<CallFrame*>& call_stack() const { return m_call_stack; }
|
||||
Vector<CallFrame*>& call_stack() { return m_call_stack; }
|
||||
|
||||
const LexicalEnvironment* current_environment() const { return call_frame().environment; }
|
||||
LexicalEnvironment* current_environment() { return call_frame().environment; }
|
||||
const ScopeObject* current_scope() const { return call_frame().scope; }
|
||||
ScopeObject* current_scope() { return call_frame().scope; }
|
||||
|
||||
bool in_strict_mode() const;
|
||||
|
||||
|
@ -222,7 +222,7 @@ public:
|
|||
String join_arguments() const;
|
||||
|
||||
Value resolve_this_binding(GlobalObject&) const;
|
||||
const LexicalEnvironment* get_this_environment() const;
|
||||
const ScopeObject* find_this_scope() const;
|
||||
Value get_new_target() const;
|
||||
|
||||
template<typename... Args>
|
||||
|
@ -242,6 +242,8 @@ public:
|
|||
|
||||
CommonPropertyNames names;
|
||||
|
||||
Shape& scope_object_shape() { return *m_scope_object_shape; }
|
||||
|
||||
private:
|
||||
VM();
|
||||
|
||||
|
@ -271,6 +273,8 @@ private:
|
|||
Symbol* m_well_known_symbol_##snake_name { nullptr };
|
||||
JS_ENUMERATE_WELL_KNOWN_SYMBOLS
|
||||
#undef __JS_ENUMERATE
|
||||
|
||||
Shape* m_scope_object_shape { nullptr };
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
Loading…
Reference in a new issue