1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 04:08:11 +00:00

LibJS: Support array holes, encoded as empty JS::Value

This patch adds a new kind of JS::Value, the empty value.
It's what you get when you do JSValue() (or most commonly, {} in C++.)

An empty Value signifies the absence of a value, and should never be
visible to JavaScript itself. As of right now, it's used for array
holes and as a return value when an exception has been thrown and we
just want to unwind.

This patch is a bit of a mess as I had to fix a whole bunch of code
that was relying on JSValue() being undefined, etc.
This commit is contained in:
Andreas Kling 2020-04-06 20:24:45 +02:00
parent 5495f06af5
commit bdffc9e7fb
9 changed files with 102 additions and 35 deletions

View file

@ -49,7 +49,7 @@ Value FunctionDeclaration::execute(Interpreter& interpreter) const
{ {
auto* function = interpreter.heap().allocate<ScriptFunction>(body(), parameters()); auto* function = interpreter.heap().allocate<ScriptFunction>(body(), parameters());
interpreter.set_variable(name(), function); interpreter.set_variable(name(), function);
return {}; return js_undefined();
} }
Value FunctionExpression::execute(Interpreter& interpreter) const Value FunctionExpression::execute(Interpreter& interpreter) const
@ -66,7 +66,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete
{ {
if (is_new_expression()) { if (is_new_expression()) {
// Computing |this| is irrelevant for "new" expression. // Computing |this| is irrelevant for "new" expression.
return { {}, m_callee->execute(interpreter) }; return { js_undefined(), m_callee->execute(interpreter) };
} }
if (m_callee->is_member_expression()) { if (m_callee->is_member_expression()) {
@ -77,7 +77,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete
auto* this_value = object_value.to_object(interpreter.heap()); auto* this_value = object_value.to_object(interpreter.heap());
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
auto callee = this_value->get(member_expression.computed_property_name(interpreter)).value_or({}); auto callee = this_value->get(member_expression.computed_property_name(interpreter)).value_or(js_undefined());
return { this_value, callee }; return { this_value, callee };
} }
return { &interpreter.global_object(), m_callee->execute(interpreter) }; return { &interpreter.global_object(), m_callee->execute(interpreter) };
@ -89,6 +89,8 @@ Value CallExpression::execute(Interpreter& interpreter) const
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
ASSERT(!callee.is_empty());
if (is_new_expression()) { if (is_new_expression()) {
if (!callee.is_object() if (!callee.is_object()
|| !callee.as_object().is_function() || !callee.as_object().is_function()
@ -130,6 +132,9 @@ Value CallExpression::execute(Interpreter& interpreter) const
result = function.call(interpreter); result = function.call(interpreter);
} }
if (interpreter.exception())
return {};
interpreter.pop_call_frame(); interpreter.pop_call_frame();
if (is_new_expression()) { if (is_new_expression()) {
@ -161,7 +166,7 @@ Value IfStatement::execute(Interpreter& interpreter) const
if (m_alternate) if (m_alternate)
return interpreter.run(*m_alternate); return interpreter.run(*m_alternate);
return {}; return js_undefined();
} }
Value WhileStatement::execute(Interpreter& interpreter) const Value WhileStatement::execute(Interpreter& interpreter) const
@ -228,7 +233,7 @@ Value ForStatement::execute(Interpreter& interpreter) const
interpreter.stop_unwind(); interpreter.stop_unwind();
break; break;
} else { } else {
return {}; return js_undefined();
} }
} }
if (m_update) { if (m_update) {
@ -249,7 +254,7 @@ Value ForStatement::execute(Interpreter& interpreter) const
interpreter.stop_unwind(); interpreter.stop_unwind();
break; break;
} else { } else {
return {}; return js_undefined();
} }
} }
if (m_update) { if (m_update) {
@ -352,6 +357,8 @@ Value LogicalExpression::execute(Interpreter& interpreter) const
Value UnaryExpression::execute(Interpreter& interpreter) const Value UnaryExpression::execute(Interpreter& interpreter) const
{ {
auto lhs_result = m_lhs->execute(interpreter); auto lhs_result = m_lhs->execute(interpreter);
if (interpreter.exception())
return {};
switch (m_op) { switch (m_op) {
case UnaryOp::BitwiseNot: case UnaryOp::BitwiseNot:
return bitwise_not(lhs_result); return bitwise_not(lhs_result);
@ -363,6 +370,9 @@ Value UnaryExpression::execute(Interpreter& interpreter) const
return unary_minus(lhs_result); return unary_minus(lhs_result);
case UnaryOp::Typeof: case UnaryOp::Typeof:
switch (lhs_result.type()) { switch (lhs_result.type()) {
case Value::Type::Empty:
ASSERT_NOT_REACHED();
return {};
case Value::Type::Undefined: case Value::Type::Undefined:
return js_string(interpreter, "undefined"); return js_string(interpreter, "undefined");
case Value::Type::Null: case Value::Type::Null:
@ -528,7 +538,8 @@ void UnaryExpression::dump(int indent) const
void CallExpression::dump(int indent) const void CallExpression::dump(int indent) const
{ {
ASTNode::dump(indent); print_indent(indent);
printf("CallExpression %s\n", is_new_expression() ? "[new]" : "");
m_callee->dump(indent + 1); m_callee->dump(indent + 1);
for (auto& argument : m_arguments) for (auto& argument : m_arguments)
argument.dump(indent + 1); argument.dump(indent + 1);
@ -655,20 +666,33 @@ Value AssignmentExpression::execute(Interpreter& interpreter) const
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
Value lhs_result;
switch (m_op) { switch (m_op) {
case AssignmentOp::Assignment: case AssignmentOp::Assignment:
break; break;
case AssignmentOp::AdditionAssignment: case AssignmentOp::AdditionAssignment:
rhs_result = add(m_lhs->execute(interpreter), rhs_result); lhs_result = m_lhs->execute(interpreter);
if (interpreter.exception())
return {};
rhs_result = add(lhs_result, rhs_result);
break; break;
case AssignmentOp::SubtractionAssignment: case AssignmentOp::SubtractionAssignment:
rhs_result = sub(m_lhs->execute(interpreter), rhs_result); lhs_result = m_lhs->execute(interpreter);
if (interpreter.exception())
return {};
rhs_result = sub(lhs_result, rhs_result);
break; break;
case AssignmentOp::MultiplicationAssignment: case AssignmentOp::MultiplicationAssignment:
rhs_result = mul(m_lhs->execute(interpreter), rhs_result); lhs_result = m_lhs->execute(interpreter);
if (interpreter.exception())
return {};
rhs_result = mul(lhs_result, rhs_result);
break; break;
case AssignmentOp::DivisionAssignment: case AssignmentOp::DivisionAssignment:
rhs_result = div(m_lhs->execute(interpreter), rhs_result); lhs_result = m_lhs->execute(interpreter);
if (interpreter.exception())
return {};
rhs_result = div(lhs_result, rhs_result);
break; break;
} }
if (interpreter.exception()) if (interpreter.exception())
@ -678,7 +702,10 @@ Value AssignmentExpression::execute(Interpreter& interpreter) const
auto name = static_cast<const Identifier&>(*m_lhs).string(); auto name = static_cast<const Identifier&>(*m_lhs).string();
interpreter.set_variable(name, rhs_result); interpreter.set_variable(name, rhs_result);
} else if (m_lhs->is_member_expression()) { } else if (m_lhs->is_member_expression()) {
if (auto* object = static_cast<const MemberExpression&>(*m_lhs).object().execute(interpreter).to_object(interpreter.heap())) { auto object_value = static_cast<const MemberExpression&>(*m_lhs).object().execute(interpreter);
if (interpreter.exception())
return {};
if (auto* object = object_value.to_object(interpreter.heap())) {
auto property_name = static_cast<const MemberExpression&>(*m_lhs).computed_property_name(interpreter); auto property_name = static_cast<const MemberExpression&>(*m_lhs).computed_property_name(interpreter);
object->put(property_name, rhs_result); object->put(property_name, rhs_result);
} }
@ -779,7 +806,7 @@ Value VariableDeclaration::execute(Interpreter& interpreter) const
interpreter.set_variable(declarator.id().string(), initalizer_result, true); interpreter.set_variable(declarator.id().string(), initalizer_result, true);
} }
} }
return {}; return js_undefined();
} }
Value VariableDeclarator::execute(Interpreter&) const Value VariableDeclarator::execute(Interpreter&) const
@ -862,6 +889,9 @@ PropertyName MemberExpression::computed_property_name(Interpreter& interpreter)
return PropertyName(static_cast<const Identifier&>(*m_property).string()); return PropertyName(static_cast<const Identifier&>(*m_property).string());
} }
auto index = m_property->execute(interpreter); auto index = m_property->execute(interpreter);
if (interpreter.exception())
return {};
ASSERT(!index.is_empty());
// FIXME: What about non-integer numbers tho. // FIXME: What about non-integer numbers tho.
if (index.is_number()) if (index.is_number())
return PropertyName(index.to_i32()); return PropertyName(index.to_i32());
@ -870,11 +900,17 @@ PropertyName MemberExpression::computed_property_name(Interpreter& interpreter)
Value MemberExpression::execute(Interpreter& interpreter) const Value MemberExpression::execute(Interpreter& interpreter) const
{ {
auto* object_result = m_object->execute(interpreter).to_object(interpreter.heap()); auto object_value = m_object->execute(interpreter);
if (interpreter.exception())
return {};
auto* object_result = object_value.to_object(interpreter.heap());
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
auto result = object_result->get(computed_property_name(interpreter)); auto result = object_result->get(computed_property_name(interpreter));
return result.value_or({}); if (result.has_value()) {
ASSERT(!result.value().is_empty());
}
return result.value_or(js_undefined());
} }
Value StringLiteral::execute(Interpreter& interpreter) const Value StringLiteral::execute(Interpreter& interpreter) const
@ -967,7 +1003,7 @@ Value TryStatement::execute(Interpreter& interpreter) const
if (m_finalizer) if (m_finalizer)
m_finalizer->execute(interpreter); m_finalizer->execute(interpreter);
return {}; return js_undefined();
} }
Value CatchClause::execute(Interpreter&) const Value CatchClause::execute(Interpreter&) const
@ -1017,7 +1053,7 @@ Value SwitchStatement::execute(Interpreter& interpreter) const
} }
} }
return {}; return js_undefined();
} }
Value SwitchCase::execute(Interpreter& interpreter) const Value SwitchCase::execute(Interpreter& interpreter) const
@ -1029,13 +1065,13 @@ Value SwitchCase::execute(Interpreter& interpreter) const
Value BreakStatement::execute(Interpreter& interpreter) const Value BreakStatement::execute(Interpreter& interpreter) const
{ {
interpreter.unwind(ScopeType::Breakable); interpreter.unwind(ScopeType::Breakable);
return {}; return js_undefined();
} }
Value ContinueStatement::execute(Interpreter& interpreter) const Value ContinueStatement::execute(Interpreter& interpreter) const
{ {
interpreter.unwind(ScopeType::Continuable); interpreter.unwind(ScopeType::Continuable);
return {}; return js_undefined();
} }
void SwitchStatement::dump(int indent) const void SwitchStatement::dump(int indent) const

View file

@ -69,7 +69,7 @@ Value Interpreter::run(const Statement& statement, ArgumentVector arguments, Sco
auto& block = static_cast<const ScopeNode&>(statement); auto& block = static_cast<const ScopeNode&>(statement);
enter_scope(block, move(arguments), scope_type); enter_scope(block, move(arguments), scope_type);
m_last_value = {}; m_last_value = js_undefined();
for (auto& node : block.children()) { for (auto& node : block.children()) {
m_last_value = node.execute(*this); m_last_value = node.execute(*this);
if (m_unwind_until != ScopeType::None) if (m_unwind_until != ScopeType::None)

View file

@ -77,6 +77,7 @@ Optional<Value> Object::get_own_property(const Object& this_object, const FlyStr
return {}; return {};
auto value_here = m_storage[metadata.value().offset]; auto value_here = m_storage[metadata.value().offset];
ASSERT(!value_here.is_empty());
if (value_here.is_object() && value_here.as_object().is_native_property()) { if (value_here.is_object() && value_here.as_object().is_native_property()) {
auto& native_property = static_cast<const NativeProperty&>(value_here.as_object()); auto& native_property = static_cast<const NativeProperty&>(value_here.as_object());
auto& interpreter = const_cast<Object*>(this)->interpreter(); auto& interpreter = const_cast<Object*>(this)->interpreter();
@ -126,8 +127,12 @@ Optional<Value> Object::get_by_index(i32 property_index) const
const Object* object = this; const Object* object = this;
while (object) { while (object) {
if (static_cast<size_t>(property_index) < object->m_elements.size()) if (static_cast<size_t>(property_index) < object->m_elements.size()) {
return object->m_elements[property_index]; auto value = object->m_elements[property_index];
if (value.is_empty())
return {};
return value;
}
object = object->prototype(); object = object->prototype();
} }
return {}; return {};
@ -159,6 +164,7 @@ Optional<Value> Object::get(PropertyName property_name) const
void Object::put_by_index(i32 property_index, Value value) void Object::put_by_index(i32 property_index, Value value)
{ {
ASSERT(!value.is_empty());
if (property_index < 0) if (property_index < 0)
return put(String::number(property_index), value); return put(String::number(property_index), value);
// FIXME: Implement some kind of sparse storage for arrays with huge indices. // FIXME: Implement some kind of sparse storage for arrays with huge indices.
@ -169,6 +175,7 @@ void Object::put_by_index(i32 property_index, Value value)
void Object::put(const FlyString& property_name, Value value) void Object::put(const FlyString& property_name, Value value)
{ {
ASSERT(!value.is_empty());
bool ok; bool ok;
i32 property_index = property_name.to_int(ok); i32 property_index = property_name.to_int(ok);
if (ok && property_index >= 0) if (ok && property_index >= 0)
@ -231,8 +238,11 @@ bool Object::has_own_property(const FlyString& property_name) const
{ {
bool ok; bool ok;
i32 property_index = property_name.to_int(ok); i32 property_index = property_name.to_int(ok);
if (ok && property_index >= 0) if (ok && property_index >= 0) {
return static_cast<size_t>(property_index) < m_elements.size(); if (static_cast<size_t>(property_index) >= m_elements.size())
return false;
return !m_elements[property_index].is_empty();
}
return shape().lookup(property_name).has_value(); return shape().lookup(property_name).has_value();
} }

View file

@ -64,8 +64,10 @@ Value ObjectConstructor::get_own_property_names(Interpreter& interpreter)
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
auto* result = interpreter.heap().allocate<Array>(); auto* result = interpreter.heap().allocate<Array>();
for (size_t i = 0; i < object->elements().size(); ++i) for (size_t i = 0; i < object->elements().size(); ++i) {
result->push(js_string(interpreter, String::number(i))); if (!object->elements()[i].is_empty())
result->push(js_string(interpreter, String::number(i)));
}
for (auto& it : object->shape().property_table()) for (auto& it : object->shape().property_table())
result->push(js_string(interpreter, it.key)); result->push(js_string(interpreter, it.key));

View file

@ -33,10 +33,13 @@ namespace JS {
class PropertyName { class PropertyName {
public: public:
enum class Type { enum class Type {
Invalid,
Number, Number,
String, String,
}; };
PropertyName() {}
explicit PropertyName(i32 index) explicit PropertyName(i32 index)
: m_type(Type::Number) : m_type(Type::Number)
, m_number(index) , m_number(index)
@ -50,6 +53,7 @@ public:
{ {
} }
bool is_valid() const { return m_type != Type::Invalid; }
bool is_number() const { return m_type == Type::Number; } bool is_number() const { return m_type == Type::Number; }
bool is_string() const { return m_type == Type::String; } bool is_string() const { return m_type == Type::String; }
@ -57,7 +61,7 @@ public:
const FlyString& as_string() const { return m_string; } const FlyString& as_string() const { return m_string; }
private: private:
Type m_type; Type m_type { Type::Invalid };
FlyString m_string; FlyString m_string;
i32 m_number { 0 }; i32 m_number { 0 };
}; };

View file

@ -39,6 +39,14 @@
namespace JS { namespace JS {
Value::Value()
: m_type(Type::Empty)
{
// dbg() << "Create empty value";
// dump_backtrace();
}
bool Value::is_array() const bool Value::is_array() const
{ {
return is_object() && as_object().is_array(); return is_object() && as_object().is_array();
@ -119,6 +127,9 @@ Object* Value::to_object(Heap& heap) const
Value Value::to_number() const Value Value::to_number() const
{ {
switch (m_type) { switch (m_type) {
case Type::Empty:
ASSERT_NOT_REACHED();
return {};
case Type::Boolean: case Type::Boolean:
return Value(m_value.as_bool ? 1 : 0); return Value(m_value.as_bool ? 1 : 0);
case Type::Number: case Type::Number:
@ -280,6 +291,9 @@ Value typed_eq(Value lhs, Value rhs)
return Value(false); return Value(false);
switch (lhs.type()) { switch (lhs.type()) {
case Value::Type::Empty:
ASSERT_NOT_REACHED();
return {};
case Value::Type::Undefined: case Value::Type::Undefined:
return Value(true); return Value(true);
case Value::Type::Null: case Value::Type::Null:

View file

@ -36,6 +36,7 @@ namespace JS {
class Value { class Value {
public: public:
enum class Type { enum class Type {
Empty,
Undefined, Undefined,
Null, Null,
Number, Number,
@ -44,6 +45,7 @@ public:
Boolean, Boolean,
}; };
bool is_empty() const { return m_type == Type::Empty; }
bool is_undefined() const { return m_type == Type::Undefined; } bool is_undefined() const { return m_type == Type::Undefined; }
bool is_null() const { return m_type == Type::Null; } bool is_null() const { return m_type == Type::Null; }
bool is_number() const { return m_type == Type::Number; } bool is_number() const { return m_type == Type::Number; }
@ -56,10 +58,7 @@ public:
bool is_nan() const { return is_number() && __builtin_isnan(as_double()); } bool is_nan() const { return is_number() && __builtin_isnan(as_double()); }
bool is_infinity() const { return is_number() && __builtin_isinf(as_double()); } bool is_infinity() const { return is_number() && __builtin_isinf(as_double()); }
Value() Value();
: m_type(Type::Undefined)
{
}
explicit Value(bool value) explicit Value(bool value)
: m_type(Type::Boolean) : m_type(Type::Boolean)

View file

@ -78,7 +78,7 @@ JS::Value WindowObject::alert(JS::Interpreter& interpreter)
if (arguments.size() < 1) if (arguments.size() < 1)
return {}; return {};
impl->alert(arguments[0].to_string()); impl->alert(arguments[0].to_string());
return {}; return JS::js_undefined();
} }
JS::Value WindowObject::set_interval(JS::Interpreter& interpreter) JS::Value WindowObject::set_interval(JS::Interpreter& interpreter)
@ -95,7 +95,7 @@ JS::Value WindowObject::set_interval(JS::Interpreter& interpreter)
if (!callback_object->is_function()) if (!callback_object->is_function())
return interpreter.throw_exception<JS::Error>("TypeError", "Not a function"); return interpreter.throw_exception<JS::Error>("TypeError", "Not a function");
impl->set_interval(*static_cast<JS::Function*>(callback_object), arguments[1].to_i32()); impl->set_interval(*static_cast<JS::Function*>(callback_object), arguments[1].to_i32());
return {}; return JS::js_undefined();
} }
JS::Value WindowObject::set_timeout(JS::Interpreter& interpreter) JS::Value WindowObject::set_timeout(JS::Interpreter& interpreter)
@ -117,7 +117,7 @@ JS::Value WindowObject::set_timeout(JS::Interpreter& interpreter)
interval = arguments[1].to_i32(); interval = arguments[1].to_i32();
impl->set_timeout(*static_cast<JS::Function*>(callback_object), interval); impl->set_timeout(*static_cast<JS::Function*>(callback_object), interval);
return {}; return JS::js_undefined();
} }
JS::Value WindowObject::request_animation_frame(JS::Interpreter& interpreter) JS::Value WindowObject::request_animation_frame(JS::Interpreter& interpreter)
@ -145,7 +145,7 @@ JS::Value WindowObject::cancel_animation_frame(JS::Interpreter& interpreter)
if (arguments.size() < 1) if (arguments.size() < 1)
return {}; return {};
impl->cancel_animation_frame(arguments[0].to_i32()); impl->cancel_animation_frame(arguments[0].to_i32());
return {}; return JS::js_undefined();
} }
JS::Value WindowObject::document_getter(JS::Interpreter& interpreter) JS::Value WindowObject::document_getter(JS::Interpreter& interpreter)

View file

@ -119,6 +119,8 @@ static void print_object(const JS::Object& object, HashTable<JS::Object*>& seen_
fputs("{ ", stdout); fputs("{ ", stdout);
for (size_t i = 0; i < object.elements().size(); ++i) { for (size_t i = 0; i < object.elements().size(); ++i) {
if (object.elements()[i].is_empty())
continue;
printf("\"\033[33;1m%zu\033[0m\": ", i); printf("\"\033[33;1m%zu\033[0m\": ", i);
print_value(object.elements()[i], seen_objects); print_value(object.elements()[i], seen_objects);
if (i != object.elements().size() - 1) if (i != object.elements().size() - 1)