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:
Jack Karamanian 2020-06-08 13:31:21 -05:00 committed by Andreas Kling
parent a535d58cac
commit 7533fd8b02
Notes: sideshowbarker 2024-07-19 05:19:30 +09:00
18 changed files with 967 additions and 92 deletions

View File

@ -92,15 +92,28 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete
return { js_undefined(), m_callee->execute(interpreter, global_object) };
}
if (m_callee->is_super_expression()) {
// If we are calling super, |this| has not been initalized yet, and would not be meaningful to provide.
auto new_target = interpreter.get_new_target();
ASSERT(new_target.is_function());
return { js_undefined(), new_target };
}
if (m_callee->is_member_expression()) {
auto& member_expression = static_cast<const MemberExpression&>(*m_callee);
auto object_value = member_expression.object().execute(interpreter, global_object);
bool is_super_property_lookup = member_expression.object().is_super_expression();
auto lookup_target = is_super_property_lookup ? interpreter.current_environment()->get_super_base() : member_expression.object().execute(interpreter, global_object);
if (interpreter.exception())
return {};
auto* this_value = object_value.to_object(interpreter, global_object);
if (is_super_property_lookup && (lookup_target.is_null() || lookup_target.is_undefined())) {
interpreter.throw_exception<TypeError>(ErrorType::ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, lookup_target.to_string_without_side_effects());
return {};
}
auto* this_value = is_super_property_lookup ? &interpreter.this_value(global_object).as_object() : lookup_target.to_object(interpreter, global_object);
if (interpreter.exception())
return {};
auto callee = this_value->get(member_expression.computed_property_name(interpreter, global_object)).value_or(js_undefined());
auto callee = lookup_target.to_object(interpreter, global_object)->get(member_expression.computed_property_name(interpreter, global_object)).value_or(js_undefined());
return { this_value, callee };
}
return { &global_object, m_callee->execute(interpreter, global_object) };
@ -134,7 +147,6 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
auto& function = callee.as_function();
MarkedValueList arguments(interpreter.heap());
arguments.values().append(function.bound_arguments());
for (size_t i = 0; i < m_arguments.size(); ++i) {
auto value = m_arguments[i].value->execute(interpreter, global_object);
@ -163,31 +175,26 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
}
}
auto& call_frame = interpreter.push_call_frame();
call_frame.function_name = function.name();
call_frame.arguments = arguments.values();
call_frame.environment = function.create_environment();
Object* new_object = nullptr;
Value result;
if (is_new_expression()) {
new_object = Object::create_empty(interpreter, global_object);
auto prototype = function.get("prototype");
result = interpreter.construct(function, function, move(arguments), global_object);
if (result.is_object())
new_object = &result.as_object();
} else if (m_callee->is_super_expression()) {
auto* super_constructor = interpreter.current_environment()->current_function()->prototype();
// FIXME: Functions should track their constructor kind.
if (!super_constructor || !super_constructor->is_function())
return interpreter.throw_exception<TypeError>(ErrorType::NotAConstructor, "Super constructor");
result = interpreter.construct(static_cast<Function&>(*super_constructor), function, move(arguments), global_object);
if (interpreter.exception())
return {};
if (prototype.is_object()) {
new_object->set_prototype(&prototype.as_object());
if (interpreter.exception())
return {};
}
call_frame.this_value = new_object;
result = function.construct(interpreter);
} else {
call_frame.this_value = function.bound_this().value_or(this_value);
result = function.call(interpreter);
}
interpreter.pop_call_frame();
interpreter.current_environment()->bind_this_value(result);
} else {
result = interpreter.call(function, this_value, move(arguments));
}
if (interpreter.exception())
return {};
@ -658,6 +665,112 @@ Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
ASSERT_NOT_REACHED();
}
Value SuperExpression::execute(Interpreter&, GlobalObject&) const
{
// The semantics for SuperExpressions are handled in CallExpression::compute_this_and_callee()
ASSERT_NOT_REACHED();
}
Value ClassMethod::execute(Interpreter& interpreter, GlobalObject& global_object) const
{
return m_function->execute(interpreter, global_object);
}
Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
{
Value class_constructor_value = m_constructor->execute(interpreter, global_object);
if (interpreter.exception())
return {};
update_function_name(class_constructor_value, m_name);
ASSERT(class_constructor_value.is_function() && class_constructor_value.as_function().is_script_function());
ScriptFunction* class_constructor = static_cast<ScriptFunction*>(&class_constructor_value.as_function());
Value super_constructor = js_undefined();
if (!m_super_class.is_null()) {
super_constructor = m_super_class->execute(interpreter, global_object);
if (interpreter.exception())
return {};
if (!super_constructor.is_function() && !super_constructor.is_null())
return interpreter.throw_exception<TypeError>(ErrorType::ClassDoesNotExtendAConstructorOrNull, super_constructor.to_string_without_side_effects().characters());
class_constructor->set_constructor_kind(Function::ConstructorKind::Derived);
Object* prototype = Object::create_empty(interpreter, interpreter.global_object());
Object* super_constructor_prototype = nullptr;
if (!super_constructor.is_null()) {
super_constructor_prototype = &super_constructor.as_object().get("prototype").as_object();
if (interpreter.exception())
return {};
}
prototype->set_prototype(super_constructor_prototype);
prototype->define_property("constructor", class_constructor, 0);
if (interpreter.exception())
return {};
class_constructor->define_property("prototype", prototype, 0);
if (interpreter.exception())
return {};
class_constructor->set_prototype(super_constructor.is_null() ? global_object.function_prototype() : &super_constructor.as_object());
}
auto class_prototype = class_constructor->get("prototype");
if (interpreter.exception())
return {};
if (!class_prototype.is_object())
return interpreter.throw_exception<TypeError>(ErrorType::NotAnObject, "Class prototype");
for (const auto& method : m_methods) {
auto method_value = method.execute(interpreter, global_object);
if (interpreter.exception())
return {};
auto& method_function = method_value.as_function();
auto key = method.key().execute(interpreter, global_object);
if (interpreter.exception())
return {};
auto& target = method.is_static() ? *class_constructor : class_prototype.as_object();
method_function.set_home_object(&target);
auto property_name = key.to_string(interpreter);
if (method.kind() == ClassMethod::Kind::Method) {
target.define_property(property_name, method_value);
} else {
String accessor_name = [&] {
switch (method.kind()) {
case ClassMethod::Kind::Getter:
return String::format("get %s", property_name.characters());
case ClassMethod::Kind::Setter:
return String::format("set %s", property_name.characters());
default:
ASSERT_NOT_REACHED();
}
}();
update_function_name(method_value, accessor_name);
target.define_accessor(property_name, method_function, method.kind() == ClassMethod::Kind::Getter, Attribute::Configurable | Attribute::Enumerable);
}
if (interpreter.exception())
return {};
}
return class_constructor;
}
Value ClassDeclaration::execute(Interpreter& interpreter, GlobalObject& global_object) const
{
Value class_constructor = m_class_expression->execute(interpreter, global_object);
if (interpreter.exception())
return {};
interpreter.current_environment()->set(m_class_expression->name(), { class_constructor, DeclarationKind::Let });
return js_undefined();
}
static void print_indent(int indent)
{
for (int i = 0; i < indent * 2; ++i)
@ -833,12 +946,76 @@ void CallExpression::dump(int indent) const
argument.value->dump(indent + 1);
}
void ClassDeclaration::dump(int indent) const
{
ASTNode::dump(indent);
m_class_expression->dump(indent + 1);
}
void ClassExpression::dump(int indent) const
{
print_indent(indent);
printf("ClassExpression: \"%s\"\n", m_name.characters());
print_indent(indent);
printf("(Constructor)\n");
m_constructor->dump(indent + 1);
if (!m_super_class.is_null()) {
print_indent(indent);
printf("(Super Class)\n");
m_super_class->dump(indent + 1);
}
print_indent(indent);
printf("(Methods)\n");
for (auto& method : m_methods)
method.dump(indent + 1);
}
void ClassMethod::dump(int indent) const
{
ASTNode::dump(indent);
print_indent(indent);
printf("(Key)\n");
m_key->dump(indent + 1);
const char* kind_string = nullptr;
switch (m_kind) {
case Kind::Method:
kind_string = "Method";
break;
case Kind::Getter:
kind_string = "Getter";
break;
case Kind::Setter:
kind_string = "Setter";
break;
}
print_indent(indent);
printf("Kind: %s\n", kind_string);
print_indent(indent);
printf("Static: %s\n", m_is_static ? "true" : "false");
print_indent(indent);
printf("(Function)\n");
m_function->dump(indent + 1);
}
void StringLiteral::dump(int indent) const
{
print_indent(indent);
printf("StringLiteral \"%s\"\n", m_value.characters());
}
void SuperExpression::dump(int indent) const
{
print_indent(indent);
printf("super\n");
}
void NumericLiteral::dump(int indent) const
{
print_indent(indent);
@ -1006,9 +1183,9 @@ Value SpreadExpression::execute(Interpreter& interpreter, GlobalObject& global_o
return m_target->execute(interpreter, global_object);
}
Value ThisExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
Value ThisExpression::execute(Interpreter& interpreter, GlobalObject&) const
{
return interpreter.this_value(global_object);
return interpreter.resolve_this_binding();
}
void ThisExpression::dump(int indent) const
@ -1353,6 +1530,9 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o
if (interpreter.exception())
return {};
if (value.is_function() && property.is_method())
value.as_function().set_home_object(object);
String name = key;
if (property.type() == ObjectProperty::Type::Getter) {
name = String::format("get %s", key.characters());

View File

@ -63,6 +63,7 @@ public:
virtual bool is_variable_declaration() const { return false; }
virtual bool is_call_expression() const { return false; }
virtual bool is_new_expression() const { return false; }
virtual bool is_super_expression() const { return false; }
protected:
ASTNode() { }
@ -581,6 +582,8 @@ public:
{
}
StringView value() const { return m_value; }
virtual Value execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
@ -642,6 +645,87 @@ private:
FlyString m_string;
};
class ClassMethod final : public ASTNode {
public:
enum class Kind {
Method,
Getter,
Setter,
};
ClassMethod(NonnullRefPtr<Expression> key, NonnullRefPtr<FunctionExpression> function, Kind kind, bool is_static)
: m_key(move(key))
, m_function(move(function))
, m_kind(kind)
, m_is_static(is_static)
{
}
const Expression& key() const { return *m_key; }
Kind kind() const { return m_kind; }
bool is_static() const { return m_is_static; }
virtual Value execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
private:
virtual const char* class_name() const override { return "ClassMethod"; }
NonnullRefPtr<Expression> m_key;
NonnullRefPtr<FunctionExpression> m_function;
Kind m_kind;
bool m_is_static;
};
class SuperExpression final : public Expression {
public:
virtual Value execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
private:
virtual bool is_super_expression() const override { return true; }
virtual const char* class_name() const override { return "SuperExpression"; }
};
class ClassExpression final : public Expression {
public:
ClassExpression(String name, RefPtr<FunctionExpression> constructor, RefPtr<Expression> super_class, NonnullRefPtrVector<ClassMethod> methods)
: m_name(move(name))
, m_constructor(move(constructor))
, m_super_class(move(super_class))
, m_methods(move(methods))
{
}
StringView name() const { return m_name; }
virtual Value execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
private:
virtual const char* class_name() const override { return "ClassExpression"; }
String m_name;
RefPtr<FunctionExpression> m_constructor;
RefPtr<Expression> m_super_class;
NonnullRefPtrVector<ClassMethod> m_methods;
};
class ClassDeclaration final : public Declaration {
public:
ClassDeclaration(NonnullRefPtr<ClassExpression> class_expression)
: m_class_expression(move(class_expression))
{
}
virtual Value execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
private:
virtual const char* class_name() const override { return "ClassDeclaration"; }
NonnullRefPtr<ClassExpression> m_class_expression;
};
class SpreadExpression final : public Expression {
public:
explicit SpreadExpression(NonnullRefPtr<Expression> target)
@ -836,10 +920,11 @@ public:
Spread,
};
ObjectProperty(NonnullRefPtr<Expression> key, RefPtr<Expression> value, Type property_type)
ObjectProperty(NonnullRefPtr<Expression> key, RefPtr<Expression> value, Type property_type, bool is_method)
: m_key(move(key))
, m_value(move(value))
, m_property_type(property_type)
, m_is_method(is_method)
{
}
@ -851,6 +936,7 @@ public:
}
Type type() const { return m_property_type; }
bool is_method() const { return m_is_method; }
virtual void dump(int indent) const override;
virtual Value execute(Interpreter&, GlobalObject&) const override;
@ -861,6 +947,7 @@ private:
NonnullRefPtr<Expression> m_key;
RefPtr<Expression> m_value;
Type m_property_type;
bool m_is_method { false };
};
class ObjectExpression : public Expression {

View File

@ -59,7 +59,10 @@ Value Interpreter::run(GlobalObject& global_object, const Statement& statement,
CallFrame global_call_frame;
global_call_frame.this_value = &global_object;
global_call_frame.function_name = "(global execution context)";
global_call_frame.environment = heap().allocate<LexicalEnvironment>(global_object);
global_call_frame.environment = heap().allocate<LexicalEnvironment>(global_object, LexicalEnvironment::EnvironmentRecordType::Global);
global_call_frame.environment->bind_this_value(&global_object);
if (exception())
return {};
m_call_stack.append(move(global_call_frame));
}
}
@ -226,6 +229,10 @@ Value Interpreter::call(Function& function, Value this_value, Optional<MarkedVal
if (arguments.has_value())
call_frame.arguments.append(arguments.value().values());
call_frame.environment = function.create_environment();
ASSERT(call_frame.environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized);
call_frame.environment->bind_this_value(call_frame.this_value);
auto result = function.call(*this);
pop_call_frame();
return result;
@ -235,29 +242,59 @@ Value Interpreter::construct(Function& function, Function& new_target, Optional<
{
auto& call_frame = push_call_frame();
call_frame.function_name = function.name();
call_frame.arguments = function.bound_arguments();
if (arguments.has_value())
call_frame.arguments = arguments.value().values();
call_frame.arguments.append(arguments.value().values());
call_frame.environment = function.create_environment();
auto* new_object = Object::create_empty(*this, global_object);
auto prototype = new_target.get("prototype");
if (exception())
return {};
if (prototype.is_object()) {
new_object->set_prototype(&prototype.as_object());
current_environment()->set_new_target(&new_target);
Object* new_object = nullptr;
if (function.constructor_kind() == Function::ConstructorKind::Base) {
new_object = Object::create_empty(*this, global_object);
current_environment()->bind_this_value(new_object);
if (exception())
return {};
auto prototype = new_target.get("prototype");
if (exception())
return {};
if (prototype.is_object()) {
new_object->set_prototype(&prototype.as_object());
if (exception())
return {};
}
}
call_frame.this_value = new_object;
// If we are a Derived constructor, |this| has not been constructed before super is called.
Value this_value = function.constructor_kind() == Function::ConstructorKind::Base ? new_object : Value {};
call_frame.this_value = this_value;
auto result = function.construct(*this);
this_value = current_environment()->get_this_binding();
pop_call_frame();
// 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);
auto prototype = new_target.get("prototype");
if (exception())
return {};
if (prototype.is_object()) {
result.as_object().set_prototype(&prototype.as_object());
if (exception())
return {};
}
return result;
}
if (exception())
return {};
if (result.is_object())
return result;
return new_object;
return this_value;
}
Value Interpreter::throw_exception(Exception* exception)
@ -301,4 +338,24 @@ String Interpreter::join_arguments() const
return joined_arguments.build();
}
Value Interpreter::resolve_this_binding() const
{
return get_this_environment()->get_this_binding();
}
const LexicalEnvironment* Interpreter::get_this_environment() 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;
}
ASSERT_NOT_REACHED();
}
Value Interpreter::get_new_target() const
{
return get_this_environment()->new_target();
}
}

View File

@ -192,6 +192,10 @@ public:
String join_arguments() const;
Value resolve_this_binding() const;
const LexicalEnvironment* get_this_environment() const;
Value get_new_target() const;
private:
Interpreter();

View File

@ -244,6 +244,8 @@ NonnullRefPtr<Statement> Parser::parse_statement()
{
auto statement = [this]() -> NonnullRefPtr<Statement> {
switch (m_parser_state.m_current_token.type()) {
case TokenType::Class:
return parse_class_declaration();
case TokenType::Function: {
auto declaration = parse_function_node<FunctionDeclaration>();
m_parser_state.m_function_scopes.last().append(declaration);
@ -421,6 +423,136 @@ RefPtr<Statement> Parser::try_parse_labelled_statement()
return statement;
}
NonnullRefPtr<ClassDeclaration> Parser::parse_class_declaration()
{
return create_ast_node<ClassDeclaration>(parse_class_expression(true));
}
NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_name)
{
// Classes are always in strict mode.
TemporaryChange strict_mode_rollback(m_parser_state.m_strict_mode, true);
consume(TokenType::Class);
NonnullRefPtrVector<ClassMethod> methods;
RefPtr<Expression> super_class;
RefPtr<FunctionExpression> constructor;
String class_name = expect_class_name || match(TokenType::Identifier) ? consume(TokenType::Identifier).value().to_string() : "";
if (match(TokenType::Extends)) {
consume();
super_class = parse_primary_expression();
}
consume(TokenType::CurlyOpen);
while (!done() && !match(TokenType::CurlyClose)) {
RefPtr<Expression> property_key;
bool is_static = false;
bool is_constructor = false;
auto method_kind = ClassMethod::Kind::Method;
if (match(TokenType::Semicolon)) {
consume();
continue;
}
if (match_property_key()) {
StringView name;
if (match(TokenType::Identifier) && m_parser_state.m_current_token.value() == "static") {
consume();
is_static = true;
}
if (match(TokenType::Identifier)) {
auto identifier_name = m_parser_state.m_current_token.value();
if (identifier_name == "get") {
method_kind = ClassMethod::Kind::Getter;
consume();
} else if (identifier_name == "set") {
method_kind = ClassMethod::Kind::Setter;
consume();
}
}
if (match_property_key()) {
switch (m_parser_state.m_current_token.type()) {
case TokenType::Identifier:
name = consume().value();
property_key = create_ast_node<StringLiteral>(name);
break;
case TokenType::StringLiteral: {
auto string_literal = parse_string_literal(consume());
name = string_literal->value();
property_key = move(string_literal);
break;
}
default:
property_key = parse_property_key();
break;
}
} else {
expected("property key");
}
// Constructor may be a StringLiteral or an Identifier.
if (!is_static && name == "constructor") {
if (method_kind != ClassMethod::Kind::Method)
syntax_error("Class constructor may not be an accessor");
if (!constructor.is_null())
syntax_error("Classes may not have more than one constructor");
is_constructor = true;
}
}
if (match(TokenType::ParenOpen)) {
auto function = parse_function_node<FunctionExpression>(false, true, !super_class.is_null());
auto arg_count = function->parameters().size();
if (method_kind == ClassMethod::Kind::Getter && arg_count != 0) {
syntax_error("Class getter method must have no arguments");
} else if (method_kind == ClassMethod::Kind::Setter && arg_count != 1) {
syntax_error("Class setter method must have one argument");
}
if (is_constructor) {
constructor = move(function);
} else if (!property_key.is_null()) {
methods.append(create_ast_node<ClassMethod>(property_key.release_nonnull(), move(function), method_kind, is_static));
} else {
syntax_error("No key for class method");
}
} else {
expected("ParenOpen");
consume();
}
}
consume(TokenType::CurlyClose);
if (constructor.is_null()) {
auto constructor_body = create_ast_node<BlockStatement>();
if (!super_class.is_null()) {
// Set constructor to the result of parsing the source text
// constructor(... args){ super (...args);}
auto super_call = create_ast_node<CallExpression>(create_ast_node<SuperExpression>(), Vector { CallExpression::Argument { create_ast_node<Identifier>("args"), true } });
constructor_body->append(create_ast_node<ExpressionStatement>(move(super_call)));
constructor_body->add_variables(m_parser_state.m_var_scopes.last());
constructor = create_ast_node<FunctionExpression>(class_name, move(constructor_body), Vector { FunctionNode::Parameter { "args", nullptr, true } }, 0, NonnullRefPtrVector<VariableDeclaration>());
} else {
constructor = create_ast_node<FunctionExpression>(class_name, move(constructor_body), Vector<FunctionNode::Parameter> {}, 0, NonnullRefPtrVector<VariableDeclaration>());
}
}
return create_ast_node<ClassExpression>(move(class_name), move(constructor), move(super_class), move(methods));
}
NonnullRefPtr<Expression> Parser::parse_primary_expression()
{
if (match_unary_prefixed_expression())
@ -442,6 +574,13 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression()
case TokenType::This:
consume();
return create_ast_node<ThisExpression>();
case TokenType::Class:
return parse_class_expression(false);
case TokenType::Super:
consume();
if (!m_parser_state.m_allow_super_property_lookup)
syntax_error("'super' keyword unexpected here");
return create_ast_node<SuperExpression>();
case TokenType::Identifier: {
auto arrow_function_result = try_parse_arrow_function_expression(false);
if (!arrow_function_result.is_null()) {
@ -537,6 +676,27 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
}
}
NonnullRefPtr<Expression> Parser::parse_property_key()
{
if (match(TokenType::StringLiteral)) {
return parse_string_literal(consume());
} else if (match(TokenType::NumericLiteral)) {
return create_ast_node<StringLiteral>(consume(TokenType::NumericLiteral).value());
} else if (match(TokenType::BigIntLiteral)) {
auto value = consume(TokenType::BigIntLiteral).value();
return create_ast_node<StringLiteral>(value.substring_view(0, value.length() - 1));
} else if (match(TokenType::BracketOpen)) {
consume(TokenType::BracketOpen);
auto result = parse_expression(0);
consume(TokenType::BracketClose);
return result;
} else {
if (!match_identifier_name())
expected("IdentifierName");
return create_ast_node<StringLiteral>(consume().value());
}
}
NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
{
consume(TokenType::CurlyOpen);
@ -544,35 +704,6 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
NonnullRefPtrVector<ObjectProperty> properties;
ObjectProperty::Type property_type;
auto match_property_key = [&]() -> bool {
auto type = m_parser_state.m_current_token.type();
return match_identifier_name()
|| type == TokenType::BracketOpen
|| type == TokenType::StringLiteral
|| type == TokenType::NumericLiteral
|| type == TokenType::BigIntLiteral;
};
auto parse_property_key = [&]() -> NonnullRefPtr<Expression> {
if (match(TokenType::StringLiteral)) {
return parse_string_literal(consume());
} else if (match(TokenType::NumericLiteral)) {
return create_ast_node<StringLiteral>(consume(TokenType::NumericLiteral).value());
} else if (match(TokenType::BigIntLiteral)) {
auto value = consume(TokenType::BigIntLiteral).value();
return create_ast_node<StringLiteral>(value.substring_view(0, value.length() - 1));
} else if (match(TokenType::BracketOpen)) {
consume(TokenType::BracketOpen);
auto result = parse_expression(0);
consume(TokenType::BracketClose);
return result;
} else {
if (!match_identifier_name())
expected("IdentifierName");
return create_ast_node<StringLiteral>(consume().value());
}
};
auto skip_to_next_property = [&] {
while (!done() && !match(TokenType::Comma) && !match(TokenType::CurlyOpen))
consume();
@ -586,7 +717,7 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
if (match(TokenType::TripleDot)) {
consume();
property_name = parse_expression(4);
properties.append(create_ast_node<ObjectProperty>(*property_name, nullptr, ObjectProperty::Type::Spread));
properties.append(create_ast_node<ObjectProperty>(*property_name, nullptr, ObjectProperty::Type::Spread, false));
if (!match(TokenType::Comma))
break;
consume(TokenType::Comma);
@ -622,7 +753,7 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
if (match(TokenType::ParenOpen)) {
ASSERT(property_name);
auto function = parse_function_node<FunctionExpression>(false);
auto function = parse_function_node<FunctionExpression>(false, true);
auto arg_count = function->parameters().size();
if (property_type == ObjectProperty::Type::Getter && arg_count != 0) {
@ -642,7 +773,7 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
continue;
}
properties.append(create_ast_node<ObjectProperty>(*property_name, function, property_type));
properties.append(create_ast_node<ObjectProperty>(*property_name, function, property_type, true));
} else if (match(TokenType::Colon)) {
if (!property_name) {
syntax_error("Expected a property name");
@ -650,9 +781,9 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
continue;
}
consume();
properties.append(create_ast_node<ObjectProperty>(*property_name, parse_expression(2), property_type));
properties.append(create_ast_node<ObjectProperty>(*property_name, parse_expression(2), property_type, false));
} else if (property_name && property_value) {
properties.append(create_ast_node<ObjectProperty>(*property_name, *property_value, property_type));
properties.append(create_ast_node<ObjectProperty>(*property_name, *property_value, property_type, false));
} else {
syntax_error("Expected a property");
skip_to_next_property();
@ -976,6 +1107,9 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
NonnullRefPtr<CallExpression> Parser::parse_call_expression(NonnullRefPtr<Expression> lhs)
{
if (!m_parser_state.m_allow_super_constructor_call && lhs->is_super_expression())
syntax_error("'super' keyword unexpected here");
consume(TokenType::ParenOpen);
Vector<CallExpression::Argument> arguments;
@ -1085,8 +1219,11 @@ NonnullRefPtr<BlockStatement> Parser::parse_block_statement()
}
template<typename FunctionNodeType>
NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool check_for_function_and_name)
NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool check_for_function_and_name, bool allow_super_property_lookup, bool allow_super_constructor_call)
{
TemporaryChange super_property_access_rollback(m_parser_state.m_allow_super_property_lookup, allow_super_property_lookup);
TemporaryChange super_constructor_call_rollback(m_parser_state.m_allow_super_constructor_call, allow_super_constructor_call);
ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Function);
if (check_for_function_and_name)
@ -1465,6 +1602,7 @@ bool Parser::match_expression() const
|| type == TokenType::ParenOpen
|| type == TokenType::Function
|| type == TokenType::This
|| type == TokenType::Super
|| type == TokenType::RegexLiteral
|| match_unary_prefixed_expression();
}
@ -1563,6 +1701,16 @@ bool Parser::match_identifier_name() const
return m_parser_state.m_current_token.is_identifier_name();
}
bool Parser::match_property_key() const
{
auto type = m_parser_state.m_current_token.type();
return match_identifier_name()
|| type == TokenType::BracketOpen
|| type == TokenType::StringLiteral
|| type == TokenType::NumericLiteral
|| type == TokenType::BigIntLiteral;
}
bool Parser::done() const
{
return match(TokenType::Eof);

View File

@ -46,7 +46,7 @@ public:
NonnullRefPtr<Program> parse_program();
template<typename FunctionNodeType>
NonnullRefPtr<FunctionNodeType> parse_function_node(bool check_for_function_and_name = true);
NonnullRefPtr<FunctionNodeType> parse_function_node(bool check_for_function_and_name = true, bool allow_super_property_lookup = false, bool allow_super_constructor_call = false);
NonnullRefPtr<Statement> parse_statement();
NonnullRefPtr<BlockStatement> parse_block_statement();
@ -80,6 +80,9 @@ public:
NonnullRefPtr<NewExpression> parse_new_expression();
RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens);
RefPtr<Statement> try_parse_labelled_statement();
NonnullRefPtr<ClassDeclaration> parse_class_declaration();
NonnullRefPtr<ClassExpression> parse_class_expression(bool expect_class_name);
NonnullRefPtr<Expression> parse_property_key();
struct Error {
String message;
@ -126,6 +129,7 @@ private:
bool match_statement() const;
bool match_variable_declaration() const;
bool match_identifier_name() const;
bool match_property_key() const;
bool match(TokenType type) const;
bool done() const;
void expected(const char* what);
@ -151,6 +155,8 @@ private:
Vector<NonnullRefPtrVector<FunctionDeclaration>> m_function_scopes;
UseStrictDirectiveState m_use_strict_directive { UseStrictDirectiveState::None };
bool m_strict_mode { false };
bool m_allow_super_property_lookup { false };
bool m_allow_super_constructor_call { false };
explicit ParserState(Lexer);
};

View File

@ -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 {};
}

View File

@ -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 */ \

View File

@ -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;
};
}

View File

@ -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;
}
}

View File

@ -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 };
};
}

View File

@ -68,4 +68,9 @@ Value NativeFunction::construct(Interpreter&)
return {};
}
LexicalEnvironment* NativeFunction::create_environment()
{
return interpreter().heap().allocate<LexicalEnvironment>(global_object(), LexicalEnvironment::EnvironmentRecordType::Function);
}
}

View File

@ -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;

View File

@ -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 {};

View File

@ -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);
}

View File

@ -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 {};
}

View File

@ -0,0 +1,248 @@
load("test-common.js");
try {
class X {
constructor() {
this.x = 3;
}
getX() {
return 3;
}
init() {
this.y = 3;
}
}
assert(X.name === "X");
assert(X.length === 0);
class Y extends X {
init() {
super.init();
this.y += 3;
}
}
assert(new Y().getX() === 3);
assert(new Y().x === 3);
let x = new X();
assert(x.x === 3);
assert(x.getX() === 3);
let y = new Y();
assert(y.x === 3);
assert(y.y === undefined);
y.init();
assert(y.y === 6);
assert(y.hasOwnProperty("y"));
class Foo {
constructor(x) {
this.x = x;
}
}
assert(Foo.length === 1);
class Bar extends Foo {
constructor() {
super(5);
}
}
class Baz {
"constructor"() {
this.foo = 55;
this._bar = 33;
}
get bar() {
return this._bar;
}
set bar(value) {
this._bar = value;
}
["get" + "Foo"]() {
return this.foo;
}
static get staticFoo() {
assert(this === Baz);
return 11;
}
}
let barPropertyDescriptor = Object.getOwnPropertyDescriptor(Baz.prototype, "bar");
assert(barPropertyDescriptor.get.name === "get bar");
assert(barPropertyDescriptor.set.name === "set bar");
let baz = new Baz();
assert(baz.foo === 55);
assert(baz.bar === 33);
baz.bar = 22;
assert(baz.bar === 22);
assert(baz.getFoo() === 55);
assert(Baz.staticFoo === 11);
assert(new Bar().x === 5);
class ExtendsFunction extends function () { this.foo = 22; } { }
assert(new ExtendsFunction().foo === 22);
class ExtendsString extends String { }
assert(new ExtendsString() instanceof String);
assert(new ExtendsString() instanceof ExtendsString);
assert(new ExtendsString("abc").charAt(1) === "b");
class MyWeirdString extends ExtendsString {
charAt(i) {
return "#" + super.charAt(i);
}
}
assert(new MyWeirdString("abc").charAt(1) === "#b")
class ExtendsNull extends null { }
assertThrowsError(() => {
new ExtendsNull();
}, {
error: ReferenceError
});
assert(Object.getPrototypeOf(ExtendsNull.prototype) === null);
class ExtendsClassExpression extends class { constructor(x) { this.x = x; } } {
constructor(y) {
super(5);
this.y = 6;
}
}
let extendsClassExpression = new ExtendsClassExpression();
assert(extendsClassExpression.x === 5);
assert(extendsClassExpression.y === 6);
class InStrictMode {
constructor() {
assert(isStrictMode());
}
method() {
assert(isStrictMode());
}
}
let resultOfAnExpression = new (class {
constructor(x) {
this.x = x;
}
getX() {
return this.x + 10;
}
})(55);
assert(resultOfAnExpression.x === 55);
assert(resultOfAnExpression.getX() === 65);
let ClassExpression = class Foo { };
assert(ClassExpression.name === "Foo");
new InStrictMode().method();
assert(!isStrictMode());
assertIsSyntaxError(`
class GetterWithArgument {
get foo(bar) {
return 0;
}
}
`);
assertIsSyntaxError(`
class SetterWithNoArgumetns {
set foo() {
}
}
`);
assertIsSyntaxError(`
class SetterWithMoreThanOneArgument {
set foo(bar, baz) {
}
}
`);
assertIsSyntaxError(`
class FooBase {}
class IsASyntaxError extends FooBase {
bar() {
function f() { super.baz; }
}
}
`);
assertIsSyntaxError(`
class NoBaseSuper {
constructor() {
super();
}
}
`);
assertThrowsError(() => {
class BadExtends extends 3 { }
}, {
error: TypeError
});
assertThrowsError(() => {
class BadExtends extends undefined { }
}, {
error: TypeError
});
class SuperNotASyntaxError {
bar() {
() => { super.baz };
}
}
class SuperNoBasePropertyLookup {
constructor() {
super.foo;
}
}
assertThrowsError(() => {
class Base { }
class DerivedDoesntCallSuper extends Base {
constructor() {
this;
}
}
new DerivedDoesntCallSuper();
}, {
error: ReferenceError,
});
assertThrowsError(() => {
class Base { }
class CallsSuperTwice extends Base {
constructor() {
super();
super();
}
}
new CallsSuperTwice();
}, {
error: ReferenceError,
});
console.log("PASS");
} catch (e) {
console.log("FAIL: " + e);
}

View File

@ -70,6 +70,22 @@ try {
assert(a[2] === 3);
assert(o4.test === undefined);
var base = {
getNumber() {
return 10;
}
};
var derived = {
getNumber() {
return 20 + super.getNumber();
}
};
Object.setPrototypeOf(derived, base)
assert(derived.getNumber() === 30);
assertIsSyntaxError("({ foo: function() { super.bar; } })")
assertIsSyntaxError("({ get ...foo })");
assertIsSyntaxError("({ get... foo })");
assertIsSyntaxError("({ get foo })");