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

LibJS: Handle Proxy with Array target in IsArray() abstract operation

This was missing from Value::is_array(), which is equivalent to the
spec's IsArray() abstract operation - it treats a Proxy value with an
Array target object as being an Array.
It can throw, so needs both the global object and an exception check
now.
This commit is contained in:
Linus Groh 2021-06-08 21:53:36 +01:00 committed by Andreas Kling
parent 9b35231453
commit 83be39c91a
11 changed files with 52 additions and 24 deletions

View file

@ -1669,7 +1669,7 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o
return {};
if (property.type() == ObjectProperty::Type::Spread) {
if (key.is_array()) {
if (key.is_object() && key.as_object().is_array()) {
auto& array_to_spread = static_cast<Array&>(key.as_object());
for (auto& entry : array_to_spread.indexed_properties()) {
object->indexed_properties().put(object, entry.index(), entry.value_and_attributes(&array_to_spread).value);
@ -1695,7 +1695,6 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o
return {};
}
}
continue;
}

View file

@ -58,11 +58,10 @@ void MarkupGenerator::value_to_html(Value value, StringBuilder& output_html, Has
seen_objects.set(&value.as_object());
}
if (value.is_array())
return array_to_html(static_cast<const Array&>(value.as_object()), output_html, seen_objects);
if (value.is_object()) {
auto& object = value.as_object();
if (object.is_array())
return array_to_html(static_cast<const Array&>(object), output_html, seen_objects);
output_html.append(wrap_string_in_style(object.class_name(), StyleType::ObjectType));
if (object.is_function())
return function_to_html(object, output_html, seen_objects);

View file

@ -142,7 +142,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::is_array)
{
auto value = vm.argument(0);
return Value(value.is_array());
return Value(value.is_array(global_object));
}
JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::of)

View file

@ -358,14 +358,14 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::concat)
for (size_t i = 0; i < vm.argument_count(); ++i) {
auto argument = vm.argument(i);
if (argument.is_array()) {
if (argument.is_array(global_object)) {
auto& argument_object = argument.as_object();
new_array->indexed_properties().append_all(&argument_object, argument_object.indexed_properties());
if (vm.exception())
return {};
} else {
new_array->indexed_properties().append(argument);
continue;
}
if (vm.exception())
return {};
new_array->indexed_properties().append(argument);
}
return Value(new_array);
@ -1027,10 +1027,12 @@ static void recursive_array_flat(VM& vm, GlobalObject& global_object, Array& new
if (vm.exception())
return;
if (depth > 0 && value.is_array()) {
if (depth > 0 && value.is_array(global_object)) {
recursive_array_flat(vm, global_object, new_array, value.as_array(), depth - 1);
continue;
}
if (vm.exception())
return;
if (!value.is_empty()) {
new_array.indexed_properties().append(value);
if (vm.exception())

View file

@ -48,7 +48,7 @@ String JSONObject::stringify_impl(GlobalObject& global_object, Value value, Valu
if (replacer.is_object()) {
if (replacer.as_object().is_function()) {
state.replacer_function = &replacer.as_function();
} else if (replacer.is_array()) {
} else if (replacer.is_array(global_object)) {
auto& replacer_object = replacer.as_object();
auto replacer_length = length_of_array_like(global_object, replacer_object);
if (vm.exception())
@ -77,6 +77,8 @@ String JSONObject::stringify_impl(GlobalObject& global_object, Value value, Valu
}
state.property_list = list;
}
if (vm.exception())
return {};
}
if (space.is_object()) {
@ -172,8 +174,10 @@ String JSONObject::serialize_json_property(GlobalObject& global_object, Stringif
return "null";
}
if (value.is_object() && !value.is_function()) {
if (value.is_array())
if (value.is_array(global_object))
return serialize_json_array(global_object, state, static_cast<Array&>(value.as_object()));
if (vm.exception())
return {};
return serialize_json_object(global_object, state, value.as_object());
}
if (value.is_bigint())

View file

@ -46,7 +46,6 @@ private:
virtual void visit_edges(Visitor&) override;
virtual bool is_function() const override { return m_target.is_function(); }
virtual bool is_array() const override { return m_target.is_array(); };
Object& m_target;
Object& m_handler;

View file

@ -72,7 +72,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::raw)
vm.throw_exception<TypeError>(global_object, ErrorType::StringRawCannotConvert, raw.is_null() ? "null" : "undefined");
return {};
}
if (!raw.is_array())
// FIXME: This should use length_of_array_like() and work with any object
if (!raw.is_object() || !raw.as_object().is_array())
return js_string(vm, "");
auto* array = static_cast<Array*>(raw.to_object(global_object));

View file

@ -26,6 +26,7 @@
#include <LibJS/Runtime/NumberObject.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/ProxyObject.h>
#include <LibJS/Runtime/RegExpObject.h>
#include <LibJS/Runtime/StringObject.h>
#include <LibJS/Runtime/Symbol.h>
@ -196,14 +197,29 @@ static String double_to_string(double d)
return builder.to_string();
}
bool Value::is_array() const
// 7.2.2 IsArray, https://tc39.es/ecma262/#sec-isarray
bool Value::is_array(GlobalObject& global_object) const
{
return is_object() && as_object().is_array();
if (!is_object())
return false;
auto& object = as_object();
if (object.is_array())
return true;
if (is<ProxyObject>(object)) {
auto& proxy = static_cast<ProxyObject const&>(object);
if (proxy.is_revoked()) {
auto& vm = global_object.vm();
vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
return false;
}
return Value(&proxy.target()).is_array(global_object);
}
return false;
}
Array& Value::as_array()
{
VERIFY(is_array());
VERIFY(is_object() && as_object().is_array());
return static_cast<Array&>(*m_value.as_object);
}

View file

@ -59,10 +59,10 @@ public:
bool is_native_property() const { return m_type == Type::NativeProperty; }
bool is_nullish() const { return is_null() || is_undefined(); }
bool is_cell() const { return is_string() || is_accessor() || is_object() || is_bigint() || is_symbol() || is_native_property(); }
bool is_array() const;
bool is_array(GlobalObject&) const;
bool is_function() const;
bool is_constructor() const;
bool is_regexp(GlobalObject& global_object) const;
bool is_regexp(GlobalObject&) const;
bool is_nan() const { return is_number() && __builtin_isnan(as_double()); }
bool is_infinity() const { return is_number() && __builtin_isinf(as_double()); }

View file

@ -22,4 +22,13 @@ test("arguments that evaluate to true", () => {
expect(Array.isArray(new Array(10))).toBeTrue();
expect(Array.isArray(new Array("a", "b", "c"))).toBeTrue();
expect(Array.isArray(Array.prototype)).toBeTrue();
expect(Array.isArray(new Proxy([], {}))).toBeTrue();
});
test("Revoked Proxy as argument throws", () => {
const revocable = Proxy.revocable([], {});
revocable.revoke();
expect(() => {
Array.isArray(revocable.proxy);
}).toThrowWithMessage(TypeError, "An operation was performed on a revoked Proxy object");
});

View file

@ -386,11 +386,10 @@ static void print_value(JS::Value value, HashTable<JS::Object*>& seen_objects)
seen_objects.set(&value.as_object());
}
if (value.is_array())
return print_array(static_cast<JS::Array&>(value.as_object()), seen_objects);
if (value.is_object()) {
auto& object = value.as_object();
if (object.is_array())
return print_array(static_cast<JS::Array&>(object), seen_objects);
if (object.is_function())
return print_function(object, seen_objects);
if (is<JS::Date>(object))