1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 16:57:35 +00:00

LibJS: Bring function environment records closer to the spec

This patch adds FunctionEnvironmentRecord as a subclass of the existing
DeclarativeEnvironmentRecord. Things that are specific to function
environment records move into there, simplifying the base.

Most of the abstract operations related to function environment records
are rewritten to match the spec exactly. I also had to implement
GetThisEnvironment() and GetSuperConstructor() to keep tests working
after the changes, so that's nice as well. :^)
This commit is contained in:
Andreas Kling 2021-06-22 13:30:48 +02:00
parent 6ed6434bab
commit aabd82d508
28 changed files with 228 additions and 159 deletions

View file

@ -19,6 +19,7 @@
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/BigInt.h> #include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/FunctionEnvironmentRecord.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h> #include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/MarkedValueList.h> #include <LibJS/Runtime/MarkedValueList.h>
@ -132,7 +133,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete
Object* this_value = nullptr; Object* this_value = nullptr;
if (is<SuperExpression>(member_expression.object())) { if (is<SuperExpression>(member_expression.object())) {
auto super_base = interpreter.current_declarative_environment_record()->get_super_base(); auto super_base = interpreter.current_function_environment_record()->get_super_base();
if (super_base.is_nullish()) { if (super_base.is_nullish()) {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, super_base.to_string_without_side_effects()); vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, super_base.to_string_without_side_effects());
return {}; return {};
@ -227,14 +228,7 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
if (result.is_object()) if (result.is_object())
new_object = &result.as_object(); new_object = &result.as_object();
} else if (is<SuperExpression>(*m_callee)) { } else if (is<SuperExpression>(*m_callee)) {
// FIXME: This is merely a band-aid to make super() inside catch {} work (which constructs auto* super_constructor = get_super_constructor(interpreter.vm());
// a new DeclarativeEnvironmentRecord without current function). Implement GetSuperConstructor()
// and subsequently GetThisEnvironment() instead.
auto* function_environment = interpreter.current_declarative_environment_record();
if (!function_environment->current_function())
function_environment = static_cast<DeclarativeEnvironmentRecord*>(function_environment->outer_environment());
auto* super_constructor = function_environment->current_function()->prototype();
// FIXME: Functions should track their constructor kind. // FIXME: Functions should track their constructor kind.
if (!super_constructor || !super_constructor->is_function()) { if (!super_constructor || !super_constructor->is_function()) {
vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, "Super constructor"); vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, "Super constructor");
@ -244,7 +238,9 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
if (vm.exception()) if (vm.exception())
return {}; return {};
function_environment->bind_this_value(global_object, result); auto& this_er = get_this_environment(interpreter.vm());
VERIFY(is<FunctionEnvironmentRecord>(this_er));
static_cast<FunctionEnvironmentRecord&>(this_er).bind_this_value(global_object, result);
} else { } else {
result = vm.call(function, this_value, move(arguments)); result = vm.call(function, this_value, move(arguments));
} }
@ -1355,7 +1351,7 @@ Value SpreadExpression::execute(Interpreter& interpreter, GlobalObject& global_o
Value ThisExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const Value ThisExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
{ {
InterpreterNodeScope node_scope { interpreter, *this }; InterpreterNodeScope node_scope { interpreter, *this };
return interpreter.vm().resolve_this_binding(global_object); return get_this_environment(interpreter.vm()).get_this_binding(global_object);
} }
void ThisExpression::dump(int indent) const void ThisExpression::dump(int indent) const

View file

@ -61,6 +61,7 @@ set(SOURCES
Runtime/FinalizationRegistryPrototype.cpp Runtime/FinalizationRegistryPrototype.cpp
Runtime/FunctionConstructor.cpp Runtime/FunctionConstructor.cpp
Runtime/Function.cpp Runtime/Function.cpp
Runtime/FunctionEnvironmentRecord.cpp
Runtime/FunctionPrototype.cpp Runtime/FunctionPrototype.cpp
Runtime/GeneratorFunctionConstructor.cpp Runtime/GeneratorFunctionConstructor.cpp
Runtime/GeneratorFunctionPrototype.cpp Runtime/GeneratorFunctionPrototype.cpp

View file

@ -127,6 +127,7 @@ class Error;
class ErrorType; class ErrorType;
class Exception; class Exception;
class Expression; class Expression;
class FunctionEnvironmentRecord;
class FunctionNode; class FunctionNode;
class GlobalObject; class GlobalObject;
class HandleImpl; class HandleImpl;

View file

@ -9,7 +9,7 @@
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <LibJS/AST.h> #include <LibJS/AST.h>
#include <LibJS/Interpreter.h> #include <LibJS/Interpreter.h>
#include <LibJS/Runtime/DeclarativeEnvironmentRecord.h> #include <LibJS/Runtime/FunctionEnvironmentRecord.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Reference.h> #include <LibJS/Runtime/Reference.h>
@ -193,10 +193,10 @@ Value Interpreter::execute_statement(GlobalObject& global_object, const Statemen
return last_value; return last_value;
} }
DeclarativeEnvironmentRecord* Interpreter::current_declarative_environment_record() FunctionEnvironmentRecord* Interpreter::current_function_environment_record()
{ {
VERIFY(is<DeclarativeEnvironmentRecord>(vm().call_frame().environment_record)); VERIFY(is<FunctionEnvironmentRecord>(vm().call_frame().environment_record));
return static_cast<DeclarativeEnvironmentRecord*>(vm().call_frame().environment_record); return static_cast<FunctionEnvironmentRecord*>(vm().call_frame().environment_record);
} }
} }

View file

@ -58,7 +58,7 @@ public:
EnvironmentRecord* current_environment_record() { return vm().current_environment_record(); } EnvironmentRecord* current_environment_record() { return vm().current_environment_record(); }
DeclarativeEnvironmentRecord* current_declarative_environment_record(); FunctionEnvironmentRecord* current_function_environment_record();
void enter_scope(const ScopeNode&, ScopeType, GlobalObject&); void enter_scope(const ScopeNode&, ScopeType, GlobalObject&);
void exit_scope(const ScopeNode&); void exit_scope(const ScopeNode&);

View file

@ -12,6 +12,7 @@
#include <LibJS/Runtime/DeclarativeEnvironmentRecord.h> #include <LibJS/Runtime/DeclarativeEnvironmentRecord.h>
#include <LibJS/Runtime/ErrorTypes.h> #include <LibJS/Runtime/ErrorTypes.h>
#include <LibJS/Runtime/Function.h> #include <LibJS/Runtime/Function.h>
#include <LibJS/Runtime/FunctionEnvironmentRecord.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/ObjectEnvironmentRecord.h> #include <LibJS/Runtime/ObjectEnvironmentRecord.h>
@ -176,4 +177,25 @@ ObjectEnvironmentRecord* new_object_environment(Object& object, bool is_with_env
return global_object.heap().allocate<ObjectEnvironmentRecord>(global_object, object, environment_record); return global_object.heap().allocate<ObjectEnvironmentRecord>(global_object, object, environment_record);
} }
// 9.4.3 GetThisEnvironment ( ), https://tc39.es/ecma262/#sec-getthisenvironment
EnvironmentRecord& get_this_environment(VM& vm)
{
// FIXME: Should be the *lexical* environment.
for (auto* env = vm.current_environment_record(); env; env = env->outer_environment()) {
if (env->has_this_binding())
return *env;
}
VERIFY_NOT_REACHED();
}
// 13.3.7.2 GetSuperConstructor ( ), https://tc39.es/ecma262/#sec-getsuperconstructor
Object* get_super_constructor(VM& vm)
{
auto& env = get_this_environment(vm);
VERIFY(is<FunctionEnvironmentRecord>(env));
auto& active_function = static_cast<FunctionEnvironmentRecord&>(env).function_object();
auto* super_constructor = active_function.prototype();
return super_constructor;
}
} }

View file

@ -15,6 +15,8 @@ namespace JS {
DeclarativeEnvironmentRecord* new_declarative_environment(EnvironmentRecord&); DeclarativeEnvironmentRecord* new_declarative_environment(EnvironmentRecord&);
ObjectEnvironmentRecord* new_object_environment(Object&, bool is_with_environment, EnvironmentRecord*); ObjectEnvironmentRecord* new_object_environment(Object&, bool is_with_environment, EnvironmentRecord*);
EnvironmentRecord& get_this_environment(VM&);
Object* get_super_constructor(VM&);
Value require_object_coercible(GlobalObject&, Value); Value require_object_coercible(GlobalObject&, Value);
Function* get_method(GlobalObject& global_object, Value, PropertyName const&); Function* get_method(GlobalObject& global_object, Value, PropertyName const&);
size_t length_of_array_like(GlobalObject&, Object const&); size_t length_of_array_like(GlobalObject&, Object const&);

View file

@ -44,7 +44,7 @@ Value BoundFunction::construct(Function& new_target)
return m_target_function->construct(new_target); return m_target_function->construct(new_target);
} }
DeclarativeEnvironmentRecord* BoundFunction::create_environment_record() FunctionEnvironmentRecord* BoundFunction::create_environment_record()
{ {
return m_target_function->create_environment_record(); return m_target_function->create_environment_record();
} }

View file

@ -22,7 +22,7 @@ public:
virtual Value construct(Function& new_target) override; virtual Value construct(Function& new_target) override;
virtual DeclarativeEnvironmentRecord* create_environment_record() override; virtual FunctionEnvironmentRecord* create_environment_record() override;
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;

View file

@ -49,9 +49,6 @@ DeclarativeEnvironmentRecord::~DeclarativeEnvironmentRecord()
void DeclarativeEnvironmentRecord::visit_edges(Visitor& visitor) void DeclarativeEnvironmentRecord::visit_edges(Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
visitor.visit(m_this_value);
visitor.visit(m_new_target);
visitor.visit(m_current_function);
for (auto& it : m_variables) for (auto& it : m_variables)
visitor.visit(it.value.value); visitor.visit(it.value.value);
} }
@ -71,51 +68,4 @@ bool DeclarativeEnvironmentRecord::delete_from_environment_record(FlyString cons
return m_variables.remove(name); return m_variables.remove(name);
} }
Value DeclarativeEnvironmentRecord::get_super_base()
{
if (m_environment_record_type != EnvironmentRecordType::Function)
return {};
VERIFY(m_current_function);
auto home_object = m_current_function->home_object();
if (!home_object.is_object())
return {};
return home_object.as_object().prototype();
}
bool DeclarativeEnvironmentRecord::has_this_binding() const
{
// More like "is_capable_of_having_a_this_binding".
switch (m_environment_record_type) {
case EnvironmentRecordType::Declarative:
case EnvironmentRecordType::Object:
return false;
case EnvironmentRecordType::Function:
return this_binding_status() != ThisBindingStatus::Lexical;
case EnvironmentRecordType::Module:
return true;
}
VERIFY_NOT_REACHED();
}
Value DeclarativeEnvironmentRecord::get_this_binding(GlobalObject& global_object) const
{
VERIFY(has_this_binding());
if (this_binding_status() == ThisBindingStatus::Uninitialized) {
vm().throw_exception<ReferenceError>(global_object, ErrorType::ThisHasNotBeenInitialized);
return {};
}
return m_this_value;
}
void DeclarativeEnvironmentRecord::bind_this_value(GlobalObject& global_object, Value this_value)
{
VERIFY(has_this_binding());
if (m_this_binding_status == ThisBindingStatus::Initialized) {
vm().throw_exception<ReferenceError>(global_object, ErrorType::ThisIsAlreadyInitialized);
return;
}
m_this_value = this_value;
m_this_binding_status = ThisBindingStatus::Initialized;
}
} }

View file

@ -13,16 +13,10 @@
namespace JS { namespace JS {
class DeclarativeEnvironmentRecord final : public EnvironmentRecord { class DeclarativeEnvironmentRecord : public EnvironmentRecord {
JS_OBJECT(DeclarativeEnvironmentRecord, EnvironmentRecord); JS_OBJECT(DeclarativeEnvironmentRecord, EnvironmentRecord);
public: public:
enum class ThisBindingStatus {
Lexical,
Initialized,
Uninitialized,
};
enum class EnvironmentRecordType { enum class EnvironmentRecordType {
Declarative, Declarative,
Function, Function,
@ -41,38 +35,19 @@ public:
virtual Optional<Variable> get_from_environment_record(FlyString const&) const override; virtual Optional<Variable> get_from_environment_record(FlyString const&) const override;
virtual void put_into_environment_record(FlyString const&, Variable) override; virtual void put_into_environment_record(FlyString const&, Variable) override;
virtual bool delete_from_environment_record(FlyString const&) override; virtual bool delete_from_environment_record(FlyString const&) override;
virtual bool has_this_binding() const override;
virtual Value get_this_binding(GlobalObject&) const override;
HashMap<FlyString, Variable> const& variables() const { return m_variables; } HashMap<FlyString, Variable> const& variables() const { return m_variables; }
Value get_super_base();
ThisBindingStatus this_binding_status() const { return m_this_binding_status; }
void bind_this_value(GlobalObject&, Value this_value);
// Not a standard operation.
void replace_this_binding(Value this_value) { m_this_value = this_value; }
Value new_target() const { return m_new_target; };
void set_new_target(Value new_target) { m_new_target = new_target; }
Function* current_function() const { return m_current_function; }
void set_current_function(Function& function) { m_current_function = &function; }
EnvironmentRecordType type() const { return m_environment_record_type; } EnvironmentRecordType type() const { return m_environment_record_type; }
protected:
virtual void visit_edges(Visitor&) override;
private: private:
virtual bool is_declarative_environment_record() const override { return true; } virtual bool is_declarative_environment_record() const override { return true; }
virtual void visit_edges(Visitor&) override;
EnvironmentRecordType m_environment_record_type : 8 { EnvironmentRecordType::Declarative }; EnvironmentRecordType m_environment_record_type : 8 { EnvironmentRecordType::Declarative };
ThisBindingStatus m_this_binding_status : 8 { ThisBindingStatus::Uninitialized };
HashMap<FlyString, Variable> m_variables; HashMap<FlyString, Variable> m_variables;
Value m_this_value;
Value m_new_target;
// Corresponds to [[FunctionObject]]
Function* m_current_function { nullptr };
}; };
template<> template<>

View file

@ -23,8 +23,8 @@ public:
virtual void put_into_environment_record(FlyString const&, Variable) = 0; virtual void put_into_environment_record(FlyString const&, Variable) = 0;
virtual bool delete_from_environment_record(FlyString const&) = 0; virtual bool delete_from_environment_record(FlyString const&) = 0;
virtual bool has_this_binding() const = 0; virtual bool has_this_binding() const { return false; }
virtual Value get_this_binding(GlobalObject&) const = 0; virtual Value get_this_binding(GlobalObject&) const { return {}; }
// [[OuterEnv]] // [[OuterEnv]]
EnvironmentRecord* outer_environment() { return m_outer_environment; } EnvironmentRecord* outer_environment() { return m_outer_environment; }

View file

@ -26,7 +26,7 @@ public:
virtual Value call() = 0; virtual Value call() = 0;
virtual Value construct(Function& new_target) = 0; virtual Value construct(Function& new_target) = 0;
virtual const FlyString& name() const = 0; virtual const FlyString& name() const = 0;
virtual DeclarativeEnvironmentRecord* create_environment_record() = 0; virtual FunctionEnvironmentRecord* create_environment_record() = 0;
BoundFunction* bind(Value bound_this_value, Vector<Value> arguments); BoundFunction* bind(Value bound_this_value, Vector<Value> arguments);
@ -42,6 +42,11 @@ public:
virtual bool is_strict_mode() const { return false; } virtual bool is_strict_mode() const { return false; }
// [[Environment]]
// The Environment Record that the function was closed over.
// Used as the outer environment when evaluating the code of the function.
virtual EnvironmentRecord* environment() { return nullptr; }
protected: protected:
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Function.h>
#include <LibJS/Runtime/FunctionEnvironmentRecord.h>
#include <LibJS/Runtime/GlobalObject.h>
namespace JS {
FunctionEnvironmentRecord::FunctionEnvironmentRecord(EnvironmentRecord* parent_scope, HashMap<FlyString, Variable> variables)
: DeclarativeEnvironmentRecord(variables, parent_scope, DeclarativeEnvironmentRecord::EnvironmentRecordType::Function)
{
}
FunctionEnvironmentRecord::~FunctionEnvironmentRecord()
{
}
void FunctionEnvironmentRecord::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_this_value);
visitor.visit(m_new_target);
visitor.visit(m_function_object);
}
// 9.1.1.3.5 GetSuperBase ( ), https://tc39.es/ecma262/#sec-getsuperbase
Value FunctionEnvironmentRecord::get_super_base() const
{
VERIFY(m_function_object);
auto home_object = m_function_object->home_object();
if (home_object.is_undefined())
return js_undefined();
return home_object.as_object().prototype();
}
// 9.1.1.3.2 HasThisBinding ( ), https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding
bool FunctionEnvironmentRecord::has_this_binding() const
{
if (this_binding_status() == ThisBindingStatus::Lexical)
return false;
return true;
}
// 9.1.1.3.3 HasSuperBinding ( ), https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding
bool FunctionEnvironmentRecord::has_super_binding() const
{
if (this_binding_status() == ThisBindingStatus::Lexical)
return false;
if (function_object().home_object().is_undefined())
return false;
return true;
}
// 9.1.1.3.4 GetThisBinding ( ), https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding
Value FunctionEnvironmentRecord::get_this_binding(GlobalObject& global_object) const
{
VERIFY(has_this_binding());
if (this_binding_status() == ThisBindingStatus::Uninitialized) {
vm().throw_exception<ReferenceError>(global_object, ErrorType::ThisHasNotBeenInitialized);
return {};
}
return m_this_value;
}
// 9.1.1.3.1 BindThisValue ( V ), https://tc39.es/ecma262/#sec-bindthisvalue
Value FunctionEnvironmentRecord::bind_this_value(GlobalObject& global_object, Value this_value)
{
VERIFY(has_this_binding());
if (this_binding_status() == ThisBindingStatus::Initialized) {
vm().throw_exception<ReferenceError>(global_object, ErrorType::ThisIsAlreadyInitialized);
return {};
}
m_this_value = this_value;
m_this_binding_status = ThisBindingStatus::Initialized;
return this_value;
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/DeclarativeEnvironmentRecord.h>
namespace JS {
class FunctionEnvironmentRecord final : public DeclarativeEnvironmentRecord {
JS_OBJECT(FunctionEnvironmentRecord, DeclarativeEnvironmentRecord);
public:
enum class ThisBindingStatus : u8 {
Lexical,
Initialized,
Uninitialized,
};
FunctionEnvironmentRecord(EnvironmentRecord* parent_scope, HashMap<FlyString, Variable> variables);
virtual ~FunctionEnvironmentRecord() override;
// [[ThisValue]]
Value this_value() const { return m_this_value; }
void set_this_value(Value value) { m_this_value = value; }
// Not a standard operation.
void replace_this_binding(Value this_value) { m_this_value = this_value; }
// [[ThisBindingStatus]]
ThisBindingStatus this_binding_status() const { return m_this_binding_status; }
void set_this_binding_status(ThisBindingStatus status) { m_this_binding_status = status; }
// [[FunctionObject]]
Function& function_object() { return *m_function_object; }
Function const& function_object() const { return *m_function_object; }
void set_function_object(Function& function) { m_function_object = &function; }
// [[NewTarget]]
Value new_target() const { return m_new_target; }
void set_new_target(Value new_target) { m_new_target = new_target; }
// Abstract operations
Value get_super_base() const;
bool has_super_binding() const;
virtual bool has_this_binding() const override;
virtual Value get_this_binding(GlobalObject&) const override;
Value bind_this_value(GlobalObject&, Value);
private:
virtual bool is_function_environment_record() const override { return true; }
virtual void visit_edges(Visitor&) override;
Value m_this_value;
ThisBindingStatus m_this_binding_status { ThisBindingStatus::Uninitialized };
Function* m_function_object { nullptr };
Value m_new_target;
};
template<>
inline bool Object::fast_is<FunctionEnvironmentRecord>() const { return is_function_environment_record(); }
}

View file

@ -346,16 +346,6 @@ bool GlobalObject::delete_from_environment_record(FlyString const& name)
return delete_property(name); return delete_property(name);
} }
bool GlobalObject::has_this_binding() const
{
return true;
}
Value GlobalObject::get_this_binding(GlobalObject&) const
{
return Value(this);
}
// 19.2.1 eval ( x ), https://tc39.es/ecma262/#sec-eval-x // 19.2.1 eval ( x ), https://tc39.es/ecma262/#sec-eval-x
JS_DEFINE_NATIVE_FUNCTION(GlobalObject::eval) JS_DEFINE_NATIVE_FUNCTION(GlobalObject::eval)
{ {

View file

@ -24,8 +24,8 @@ public:
virtual Optional<Variable> get_from_environment_record(FlyString const&) const override; virtual Optional<Variable> get_from_environment_record(FlyString const&) const override;
virtual void put_into_environment_record(FlyString const&, Variable) override; virtual void put_into_environment_record(FlyString const&, Variable) override;
virtual bool delete_from_environment_record(FlyString const&) override; virtual bool delete_from_environment_record(FlyString const&) override;
virtual bool has_this_binding() const override; virtual bool has_this_binding() const final { return true; }
virtual Value get_this_binding(GlobalObject&) const override; virtual Value get_this_binding(GlobalObject&) const final { return this; }
Console& console() { return *m_console; } Console& console() { return *m_console; }

View file

@ -47,7 +47,7 @@ Value NativeFunction::construct(Function&)
return {}; return {};
} }
DeclarativeEnvironmentRecord* NativeFunction::create_environment_record() FunctionEnvironmentRecord* NativeFunction::create_environment_record()
{ {
return nullptr; return nullptr;
} }

View file

@ -34,7 +34,7 @@ protected:
explicit NativeFunction(Object& prototype); explicit NativeFunction(Object& prototype);
private: private:
virtual DeclarativeEnvironmentRecord* create_environment_record() override final; virtual FunctionEnvironmentRecord* create_environment_record() override final;
virtual bool is_native_function() const final { return true; } virtual bool is_native_function() const final { return true; }
FlyString m_name; FlyString m_name;

View file

@ -109,6 +109,7 @@ public:
virtual bool is_proxy_object() const { return false; } virtual bool is_proxy_object() const { return false; }
virtual bool is_native_function() const { return false; } virtual bool is_native_function() const { return false; }
virtual bool is_declarative_environment_record() const { return false; } virtual bool is_declarative_environment_record() const { return false; }
virtual bool is_function_environment_record() const { return false; }
// B.3.7 The [[IsHTMLDDA]] Internal Slot, https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot // B.3.7 The [[IsHTMLDDA]] Internal Slot, https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot
virtual bool is_htmldda() const { return false; } virtual bool is_htmldda() const { return false; }

View file

@ -39,14 +39,4 @@ bool ObjectEnvironmentRecord::delete_from_environment_record(FlyString const& na
return m_object.delete_property(name); return m_object.delete_property(name);
} }
bool ObjectEnvironmentRecord::has_this_binding() const
{
return outer_environment()->has_this_binding();
}
Value ObjectEnvironmentRecord::get_this_binding(GlobalObject& global_object) const
{
return outer_environment()->get_this_binding(global_object);
}
} }

View file

@ -19,8 +19,6 @@ public:
virtual Optional<Variable> get_from_environment_record(FlyString const&) const override; virtual Optional<Variable> get_from_environment_record(FlyString const&) const override;
virtual void put_into_environment_record(FlyString const&, Variable) override; virtual void put_into_environment_record(FlyString const&, Variable) override;
virtual bool delete_from_environment_record(FlyString const&) override; virtual bool delete_from_environment_record(FlyString const&) override;
virtual bool has_this_binding() const override;
virtual Value get_this_binding(GlobalObject&) const override;
private: private:
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;

View file

@ -478,7 +478,7 @@ const FlyString& ProxyObject::name() const
return static_cast<Function&>(m_target).name(); return static_cast<Function&>(m_target).name();
} }
DeclarativeEnvironmentRecord* ProxyObject::create_environment_record() FunctionEnvironmentRecord* ProxyObject::create_environment_record()
{ {
VERIFY(is_function()); VERIFY(is_function());
return static_cast<Function&>(m_target).create_environment_record(); return static_cast<Function&>(m_target).create_environment_record();

View file

@ -22,7 +22,7 @@ public:
virtual Value call() override; virtual Value call() override;
virtual Value construct(Function& new_target) override; virtual Value construct(Function& new_target) override;
virtual const FlyString& name() const override; virtual const FlyString& name() const override;
virtual DeclarativeEnvironmentRecord* create_environment_record() override; virtual FunctionEnvironmentRecord* create_environment_record() override;
const Object& target() const { return m_target; } const Object& target() const { return m_target; }
const Object& handler() const { return m_handler; } const Object& handler() const { return m_handler; }

View file

@ -14,6 +14,7 @@
#include <LibJS/Interpreter.h> #include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/FunctionEnvironmentRecord.h>
#include <LibJS/Runtime/GeneratorObject.h> #include <LibJS/Runtime/GeneratorObject.h>
#include <LibJS/Runtime/GeneratorObjectPrototype.h> #include <LibJS/Runtime/GeneratorObjectPrototype.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
@ -93,7 +94,7 @@ void ScriptFunction::visit_edges(Visitor& visitor)
visitor.visit(m_parent_scope); visitor.visit(m_parent_scope);
} }
DeclarativeEnvironmentRecord* ScriptFunction::create_environment_record() FunctionEnvironmentRecord* ScriptFunction::create_environment_record()
{ {
HashMap<FlyString, Variable> variables; HashMap<FlyString, Variable> variables;
for (auto& parameter : m_parameters) { for (auto& parameter : m_parameters) {
@ -122,11 +123,11 @@ DeclarativeEnvironmentRecord* ScriptFunction::create_environment_record()
} }
} }
auto* environment = heap().allocate<DeclarativeEnvironmentRecord>(global_object(), move(variables), m_parent_scope, DeclarativeEnvironmentRecord::EnvironmentRecordType::Function); auto* environment = heap().allocate<FunctionEnvironmentRecord>(global_object(), m_parent_scope, variables);
environment->set_current_function(*this); environment->set_function_object(*this);
if (m_is_arrow_function) { if (m_is_arrow_function) {
if (is<DeclarativeEnvironmentRecord>(m_parent_scope)) if (is<FunctionEnvironmentRecord>(m_parent_scope))
environment->set_new_target(static_cast<DeclarativeEnvironmentRecord*>(m_parent_scope)->new_target()); environment->set_new_target(static_cast<FunctionEnvironmentRecord*>(m_parent_scope)->new_target());
} }
return environment; return environment;
} }

View file

@ -35,11 +35,13 @@ public:
auto& bytecode_executable() const { return m_bytecode_executable; } auto& bytecode_executable() const { return m_bytecode_executable; }
virtual EnvironmentRecord* environment() override { return m_parent_scope; }
protected: protected:
virtual bool is_strict_mode() const final { return m_is_strict; } virtual bool is_strict_mode() const final { return m_is_strict; }
private: private:
virtual DeclarativeEnvironmentRecord* create_environment_record() override; virtual FunctionEnvironmentRecord* create_environment_record() override;
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;
Value execute_function_body(); Value execute_function_body();

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org> * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org> * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
@ -9,9 +9,11 @@
#include <AK/ScopeGuard.h> #include <AK/ScopeGuard.h>
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <LibJS/Interpreter.h> #include <LibJS/Interpreter.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/FinalizationRegistry.h> #include <LibJS/Runtime/FinalizationRegistry.h>
#include <LibJS/Runtime/FunctionEnvironmentRecord.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h> #include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/NativeFunction.h> #include <LibJS/Runtime/NativeFunction.h>
@ -460,8 +462,8 @@ Value VM::construct(Function& function, Function& new_target, Optional<MarkedVal
// set the prototype on objects created by constructors that return an object (i.e. NativeFunction subclasses). // set the prototype on objects created by constructors that return an object (i.e. NativeFunction subclasses).
if (function.constructor_kind() == Function::ConstructorKind::Base && new_target.constructor_kind() == Function::ConstructorKind::Derived && result.is_object()) { if (function.constructor_kind() == Function::ConstructorKind::Base && new_target.constructor_kind() == Function::ConstructorKind::Derived && result.is_object()) {
if (environment) { if (environment) {
VERIFY(is<DeclarativeEnvironmentRecord>(current_environment_record())); VERIFY(is<FunctionEnvironmentRecord>(current_environment_record()));
static_cast<DeclarativeEnvironmentRecord*>(current_environment_record())->replace_this_binding(result); static_cast<FunctionEnvironmentRecord*>(current_environment_record())->replace_this_binding(result);
} }
auto prototype = new_target.get(names.prototype); auto prototype = new_target.get(names.prototype);
if (exception()) if (exception())
@ -500,25 +502,11 @@ String VM::join_arguments(size_t start_index) const
return joined_arguments.build(); return joined_arguments.build();
} }
Value VM::resolve_this_binding(GlobalObject& global_object) const Value VM::get_new_target()
{ {
return find_this_scope()->get_this_binding(global_object); auto& env = get_this_environment(*this);
} VERIFY(is<FunctionEnvironmentRecord>(env));
return static_cast<FunctionEnvironmentRecord&>(env).new_target();
const EnvironmentRecord* VM::find_this_scope() const
{
// We will always return because the Global environment will always be reached, which has a |this| binding.
for (auto* environment_record = current_environment_record(); environment_record; environment_record = environment_record->outer_environment()) {
if (environment_record->has_this_binding())
return environment_record;
}
VERIFY_NOT_REACHED();
}
Value VM::get_new_target() const
{
VERIFY(is<DeclarativeEnvironmentRecord>(find_this_scope()));
return static_cast<const DeclarativeEnvironmentRecord*>(find_this_scope())->new_target();
} }
Value VM::call_internal(Function& function, Value this_value, Optional<MarkedValueList> arguments) Value VM::call_internal(Function& function, Value this_value, Optional<MarkedValueList> arguments)
@ -540,7 +528,7 @@ Value VM::call_internal(Function& function, Value this_value, Optional<MarkedVal
call_frame.environment_record = environment; call_frame.environment_record = environment;
if (environment) { if (environment) {
VERIFY(environment->this_binding_status() == DeclarativeEnvironmentRecord::ThisBindingStatus::Uninitialized); VERIFY(environment->this_binding_status() == FunctionEnvironmentRecord::ThisBindingStatus::Uninitialized);
environment->bind_this_value(function.global_object(), call_frame.this_value); environment->bind_this_value(function.global_object(), call_frame.this_value);
} }

View file

@ -222,9 +222,7 @@ public:
String join_arguments(size_t start_index = 0) const; String join_arguments(size_t start_index = 0) const;
Value resolve_this_binding(GlobalObject&) const; Value get_new_target();
const EnvironmentRecord* find_this_scope() const;
Value get_new_target() const;
template<typename... Args> template<typename... Args>
[[nodiscard]] ALWAYS_INLINE Value call(Function& function, Value this_value, Args... args) [[nodiscard]] ALWAYS_INLINE Value call(Function& function, Value this_value, Args... args)