mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 05:37:35 +00:00
LibJS: Initial class implementation; allow super expressions in object
literal methods; add EnvrionmentRecord fields and methods to LexicalEnvironment Adding EnvrionmentRecord's fields and methods lets us throw an exception when |this| is not initialized, which occurs when the super constructor in a derived class has not yet been called, or when |this| has already been initialized (the super constructor was already called).
This commit is contained in:
parent
a535d58cac
commit
7533fd8b02
18 changed files with 967 additions and 92 deletions
|
@ -74,7 +74,7 @@ Value BigIntConstructor::call(Interpreter& interpreter)
|
|||
|
||||
Value BigIntConstructor::construct(Interpreter& interpreter)
|
||||
{
|
||||
interpreter.throw_exception<TypeError>(ErrorType::NotACtor, "BigInt");
|
||||
interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, "BigInt");
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
M(BigIntBadOperatorOtherType, "Cannot use %s operator with BigInt and other type") \
|
||||
M(BigIntIntArgument, "BigInt argument must be an integer") \
|
||||
M(BigIntInvalidValue, "Invalid value for BigInt: %s") \
|
||||
M(ClassDoesNotExtendAConstructorOrNull, "Class extends value %s is not a constructor or null") \
|
||||
M(Convert, "Cannot convert %s to %s") \
|
||||
M(ConvertUndefinedToObject, "Cannot convert undefined to object") \
|
||||
M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '%s'") \
|
||||
|
@ -51,7 +52,7 @@
|
|||
M(JsonCircular, "Cannot stringify circular object") \
|
||||
M(JsonMalformed, "Malformed JSON string") \
|
||||
M(NotA, "Not a %s object") \
|
||||
M(NotACtor, "%s is not a constructor") \
|
||||
M(NotAConstructor, "%s is not a constructor") \
|
||||
M(NotAFunction, "%s is not a function") \
|
||||
M(NotAFunctionNoParam, "Not a function") \
|
||||
M(NotAn, "Not an %s object") \
|
||||
|
@ -63,6 +64,8 @@
|
|||
M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false") \
|
||||
M(ObjectSetPrototypeOfTwoArgs, "Object.setPrototypeOf requires at least two arguments") \
|
||||
M(ObjectPreventExtensionsReturnedFalse, "Object's [[PreventExtensions]] method returned false") \
|
||||
M(ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, \
|
||||
"Object prototype must not be %s on a super property access") \
|
||||
M(ObjectPrototypeWrongType, "Prototype must be an object or null") \
|
||||
M(ProxyCallWithNew, "Proxy must be called with the 'new' operator") \
|
||||
M(ProxyConstructorBadType, "Expected %s argument of Proxy constructor to be object, got %s") \
|
||||
|
@ -138,6 +141,8 @@
|
|||
M(ReflectBadDescriptorArgument, "Descriptor argument is not an object") \
|
||||
M(StringRawCannotConvert, "Cannot convert property 'raw' to object from %s") \
|
||||
M(StringRepeatCountMustBe, "repeat count must be a %s number") \
|
||||
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
|
||||
M(ThisIsAlreadyInitialized, "|this| is already initialized") \
|
||||
M(ToObjectNullOrUndef, "ToObject on null or undefined") \
|
||||
M(UnknownIdentifier, "'%s' is not defined") \
|
||||
/* LibWeb bindings */ \
|
||||
|
|
|
@ -35,6 +35,11 @@ class Function : public Object {
|
|||
JS_OBJECT(Function, Object);
|
||||
|
||||
public:
|
||||
enum class ConstructorKind {
|
||||
Base,
|
||||
Derived,
|
||||
};
|
||||
|
||||
virtual ~Function();
|
||||
virtual void initialize(Interpreter&, GlobalObject&) override { }
|
||||
|
||||
|
@ -49,15 +54,15 @@ public:
|
|||
|
||||
BoundFunction* bind(Value bound_this_value, Vector<Value> arguments);
|
||||
|
||||
Value bound_this() const
|
||||
{
|
||||
return m_bound_this;
|
||||
}
|
||||
Value bound_this() const { return m_bound_this; }
|
||||
|
||||
const Vector<Value>& bound_arguments() const
|
||||
{
|
||||
return m_bound_arguments;
|
||||
}
|
||||
const Vector<Value>& bound_arguments() const { return m_bound_arguments; }
|
||||
|
||||
Value home_object() const { return m_home_object; }
|
||||
void set_home_object(Value home_object) { m_home_object = home_object; }
|
||||
|
||||
ConstructorKind constructor_kind() const { return m_constructor_kind; };
|
||||
void set_constructor_kind(ConstructorKind constructor_kind) { m_constructor_kind = constructor_kind; }
|
||||
|
||||
protected:
|
||||
explicit Function(Object& prototype);
|
||||
|
@ -67,6 +72,8 @@ private:
|
|||
virtual bool is_function() const final { return true; }
|
||||
Value m_bound_this;
|
||||
Vector<Value> m_bound_arguments;
|
||||
Value m_home_object;
|
||||
ConstructorKind m_constructor_kind = ConstructorKind::Base;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,11 @@
|
|||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibJS/Interpreter.h>
|
||||
#include <LibJS/Runtime/Error.h>
|
||||
#include <LibJS/Runtime/Function.h>
|
||||
#include <LibJS/Runtime/LexicalEnvironment.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
@ -32,12 +36,24 @@ LexicalEnvironment::LexicalEnvironment()
|
|||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::LexicalEnvironment(EnvironmentRecordType environment_record_type)
|
||||
: m_environment_record_type(environment_record_type)
|
||||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent)
|
||||
: m_parent(parent)
|
||||
, m_variables(move(variables))
|
||||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent, EnvironmentRecordType environment_record_type)
|
||||
: m_parent(parent)
|
||||
, m_variables(move(variables))
|
||||
, m_environment_record_type(environment_record_type)
|
||||
{
|
||||
}
|
||||
|
||||
LexicalEnvironment::~LexicalEnvironment()
|
||||
{
|
||||
}
|
||||
|
@ -46,6 +62,10 @@ void LexicalEnvironment::visit_children(Visitor& visitor)
|
|||
{
|
||||
Cell::visit_children(visitor);
|
||||
visitor.visit(m_parent);
|
||||
visitor.visit(m_this_value);
|
||||
visitor.visit(m_home_object);
|
||||
visitor.visit(m_new_target);
|
||||
visitor.visit(m_current_function);
|
||||
for (auto& it : m_variables)
|
||||
visitor.visit(it.value.value);
|
||||
}
|
||||
|
@ -60,4 +80,53 @@ void LexicalEnvironment::set(const FlyString& name, Variable variable)
|
|||
m_variables.set(name, variable);
|
||||
}
|
||||
|
||||
bool LexicalEnvironment::has_super_binding() const
|
||||
{
|
||||
return m_environment_record_type == EnvironmentRecordType::Function && this_binding_status() != ThisBindingStatus::Lexical && m_home_object.is_object();
|
||||
}
|
||||
|
||||
Value LexicalEnvironment::get_super_base()
|
||||
{
|
||||
ASSERT(has_super_binding());
|
||||
if (m_home_object.is_object())
|
||||
return m_home_object.as_object().prototype();
|
||||
return {};
|
||||
}
|
||||
|
||||
bool LexicalEnvironment::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:
|
||||
case EnvironmentRecordType::Global:
|
||||
return true;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
Value LexicalEnvironment::get_this_binding() const
|
||||
{
|
||||
ASSERT(has_this_binding());
|
||||
if (this_binding_status() == ThisBindingStatus::Uninitialized)
|
||||
return interpreter().throw_exception<ReferenceError>(ErrorType::ThisHasNotBeenInitialized);
|
||||
|
||||
return m_this_value;
|
||||
}
|
||||
|
||||
void LexicalEnvironment::bind_this_value(Value this_value)
|
||||
{
|
||||
ASSERT(has_this_binding());
|
||||
if (m_this_binding_status == ThisBindingStatus::Initialized) {
|
||||
interpreter().throw_exception<ReferenceError>(ErrorType::ThisIsAlreadyInitialized);
|
||||
return;
|
||||
}
|
||||
m_this_value = this_value;
|
||||
m_this_binding_status = ThisBindingStatus::Initialized;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,11 +40,27 @@ struct Variable {
|
|||
|
||||
class LexicalEnvironment final : public Cell {
|
||||
public:
|
||||
enum class ThisBindingStatus {
|
||||
Lexical,
|
||||
Initialized,
|
||||
Uninitialized,
|
||||
};
|
||||
|
||||
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);
|
||||
virtual ~LexicalEnvironment() override;
|
||||
|
||||
LexicalEnvironment* parent() { return m_parent; }
|
||||
LexicalEnvironment* parent() const { return m_parent; }
|
||||
|
||||
Optional<Variable> get(const FlyString&) const;
|
||||
void set(const FlyString&, Variable);
|
||||
|
@ -53,12 +69,37 @@ public:
|
|||
|
||||
const HashMap<FlyString, Variable>& variables() const { return m_variables; }
|
||||
|
||||
void set_home_object(Value object) { m_home_object = object; }
|
||||
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() const;
|
||||
void bind_this_value(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; }
|
||||
|
||||
private:
|
||||
virtual const char* class_name() const override { return "LexicalEnvironment"; }
|
||||
virtual void visit_children(Visitor&) override;
|
||||
|
||||
LexicalEnvironment* m_parent { nullptr };
|
||||
HashMap<FlyString, Variable> m_variables;
|
||||
EnvironmentRecordType m_environment_record_type = EnvironmentRecordType::Declarative;
|
||||
ThisBindingStatus m_this_binding_status = ThisBindingStatus::Uninitialized;
|
||||
Value m_home_object;
|
||||
Value m_this_value;
|
||||
Value m_new_target;
|
||||
// Corresponds to [[FunctionObject]]
|
||||
Function* m_current_function { nullptr };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -68,4 +68,9 @@ Value NativeFunction::construct(Interpreter&)
|
|||
return {};
|
||||
}
|
||||
|
||||
LexicalEnvironment* NativeFunction::create_environment()
|
||||
{
|
||||
return interpreter().heap().allocate<LexicalEnvironment>(global_object(), LexicalEnvironment::EnvironmentRecordType::Function);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ protected:
|
|||
|
||||
private:
|
||||
virtual bool is_native_function() const override { return true; }
|
||||
virtual LexicalEnvironment* create_environment() override final { return nullptr; }
|
||||
virtual LexicalEnvironment* create_environment() override final;
|
||||
|
||||
FlyString m_name;
|
||||
AK::Function<Value(Interpreter&, GlobalObject&)> m_native_function;
|
||||
|
|
|
@ -437,7 +437,7 @@ bool Object::define_accessor(PropertyName property_name, Function& getter_or_set
|
|||
accessor = &existing_property.as_accessor();
|
||||
}
|
||||
if (!accessor) {
|
||||
accessor = Accessor::create(interpreter(), nullptr, nullptr);
|
||||
accessor = Accessor::create(interpreter(), global_object(), nullptr, nullptr);
|
||||
bool definition_success = define_property(property_name, accessor, attributes, throw_exceptions);
|
||||
if (interpreter().exception())
|
||||
return {};
|
||||
|
|
|
@ -66,8 +66,8 @@ ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& nam
|
|||
void ScriptFunction::initialize(Interpreter& interpreter, GlobalObject& global_object)
|
||||
{
|
||||
Function::initialize(interpreter, global_object);
|
||||
if (!is_arrow_function) {
|
||||
Object* prototype = Object::create_empty(interpreter(), interpreter().global_object());
|
||||
if (!m_is_arrow_function) {
|
||||
Object* prototype = Object::create_empty(interpreter, global_object);
|
||||
prototype->define_property("constructor", this, Attribute::Writable | Attribute::Configurable);
|
||||
define_property("prototype", prototype, 0);
|
||||
}
|
||||
|
@ -99,9 +99,11 @@ LexicalEnvironment* ScriptFunction::create_environment()
|
|||
}
|
||||
}
|
||||
}
|
||||
if (variables.is_empty())
|
||||
return m_parent_environment;
|
||||
return heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_environment);
|
||||
|
||||
auto* environment = heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_environment, LexicalEnvironment::EnvironmentRecordType::Function);
|
||||
environment->set_home_object(home_object());
|
||||
environment->set_current_function(*this);
|
||||
return environment;
|
||||
}
|
||||
|
||||
Value ScriptFunction::call(Interpreter& interpreter)
|
||||
|
@ -134,7 +136,7 @@ Value ScriptFunction::call(Interpreter& interpreter)
|
|||
Value ScriptFunction::construct(Interpreter& interpreter)
|
||||
{
|
||||
if (m_is_arrow_function)
|
||||
return interpreter.throw_exception<TypeError>(ErrorType::NotACtor, m_name.characters());
|
||||
return interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, m_name.characters());
|
||||
return call(interpreter);
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ Value SymbolConstructor::call(Interpreter& interpreter)
|
|||
|
||||
Value SymbolConstructor::construct(Interpreter& interpreter)
|
||||
{
|
||||
interpreter.throw_exception<TypeError>(ErrorType::NotACtor, "Symbol");
|
||||
interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, "Symbol");
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue