diff options
Diffstat (limited to 'Libraries/LibJS')
-rw-r--r-- | Libraries/LibJS/AST.cpp | 73 | ||||
-rw-r--r-- | Libraries/LibJS/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Libraries/LibJS/MarkupGenerator.cpp | 27 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Accessor.h | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Array.cpp | 4 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Array.h | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ArrayConstructor.cpp | 6 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ArrayPrototype.cpp | 63 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/IndexedProperties.cpp | 373 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/IndexedProperties.h | 175 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Object.cpp | 116 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Object.h | 10 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ObjectConstructor.cpp | 13 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ScriptFunction.cpp | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Shape.h | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/StringConstructor.cpp | 14 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/Array.js | 4 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/Array.of.js | 9 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/Object.defineProperty.js | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/array-basic.js | 17 |
20 files changed, 771 insertions, 144 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 7f8944c18a..8b713fe036 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -57,9 +57,8 @@ static void update_function_name(Value& value, const FlyString& name) function.set_name(name); } else if (object.is_array()) { auto& array = static_cast<Array&>(object); - for (size_t i = 0; i < array.elements().size(); ++i) { - update_function_name(array.elements()[i], name); - } + for (auto& entry : array.indexed_properties().values_unordered()) + update_function_name(entry.value, name); } } @@ -142,20 +141,22 @@ Value CallExpression::execute(Interpreter& interpreter) const return {}; if (m_arguments[i].is_spread) { // FIXME: Support generic iterables - Vector<Value> iterables; if (value.is_string()) { for (auto ch : value.as_string().string()) - iterables.append(Value(js_string(interpreter, String::format("%c", ch)))); + arguments.append(Value(js_string(interpreter, String::format("%c", ch)))); } else if (value.is_object() && value.as_object().is_array()) { - iterables = static_cast<const Array&>(value.as_object()).elements(); + auto& array = static_cast<Array&>(value.as_object()); + for (auto& entry : array.indexed_properties()) { + arguments.append(entry.value_and_attributes(&array).value); + if (interpreter.exception()) + return {}; + } } else if (value.is_object() && value.as_object().is_string_object()) { for (auto ch : static_cast<const StringObject&>(value.as_object()).primitive_string().string()) - iterables.append(Value(js_string(interpreter, String::format("%c", ch)))); + arguments.append(Value(js_string(interpreter, String::format("%c", ch)))); } else { interpreter.throw_exception<TypeError>(String::format("%s is not iterable", value.to_string_without_side_effects().characters())); } - for (auto& value : iterables) - arguments.append(value); } else { arguments.append(value); } @@ -361,8 +362,10 @@ Value ForInStatement::execute(Interpreter& interpreter) const auto* object = rhs_result.to_object(interpreter); while (object) { auto property_names = object->get_own_properties(*object, Object::GetOwnPropertyMode::Key, Attribute::Enumerable); - for (auto& property_name : static_cast<Array&>(property_names.as_object()).elements()) { - interpreter.set_variable(variable_name, property_name); + for (auto& property_name : property_names.as_object().indexed_properties()) { + interpreter.set_variable(variable_name, property_name.value_and_attributes(object).value); + if (interpreter.exception()) + return {}; last_value = interpreter.run(*m_body); if (interpreter.exception()) return {}; @@ -406,9 +409,13 @@ Value ForOfStatement::execute(Interpreter& interpreter) const size_t index = 0; auto next = [&]() -> Optional<Value> { if (rhs_result.is_array()) { - auto array_elements = static_cast<Array*>(&rhs_result.as_object())->elements(); - if (index < array_elements.size()) - return Value(array_elements.at(index)); + auto& array_elements = rhs_result.as_object().indexed_properties(); + if (index < array_elements.array_like_size()) { + auto result = array_elements.get(&rhs_result.as_object(), index); + if (interpreter.exception()) + return {}; + return result.value().value; + } } else if (rhs_result.is_string()) { auto string = rhs_result.as_string().string(); if (index < string.length()) @@ -1277,12 +1284,10 @@ Value ObjectExpression::execute(Interpreter& interpreter) const if (property.type() == ObjectProperty::Type::Spread) { if (key_result.is_array()) { auto& array_to_spread = static_cast<Array&>(key_result.as_object()); - auto& elements = array_to_spread.elements(); - - for (size_t i = 0; i < elements.size(); ++i) { - auto element = elements.at(i); - if (!element.is_empty()) - object->define_property(i, element); + for (auto& entry : array_to_spread.indexed_properties()) { + object->indexed_properties().append(entry.value_and_attributes(&array_to_spread).value); + if (interpreter.exception()) + return {}; } } else if (key_result.is_object()) { auto& obj_to_spread = key_result.as_object(); @@ -1441,30 +1446,29 @@ Value ArrayExpression::execute(Interpreter& interpreter) const // FIXME: Support arbitrary iterables if (value.is_array()) { auto& array_to_spread = static_cast<Array&>(value.as_object()); - for (auto& it : array_to_spread.elements()) { - if (it.is_empty()) { - array->elements().append(js_undefined()); - } else { - array->elements().append(it); - } + for (auto& entry : array_to_spread.indexed_properties()) { + array->indexed_properties().append(entry.value_and_attributes(&array_to_spread).value); + if (interpreter.exception()) + return {}; } continue; } if (value.is_string() || (value.is_object() && value.as_object().is_string_object())) { String string_to_spread; - if (value.is_string()) + if (value.is_string()) { string_to_spread = value.as_string().string(); - else + } else { string_to_spread = static_cast<const StringObject&>(value.as_object()).primitive_string().string(); + } for (size_t i = 0; i < string_to_spread.length(); ++i) - array->elements().append(js_string(interpreter, string_to_spread.substring(i, 1))); + array->indexed_properties().append(js_string(interpreter, string_to_spread.substring(i, 1))); continue; } interpreter.throw_exception<TypeError>(String::format("%s is not iterable", value.to_string_without_side_effects().characters())); return {}; } } - array->elements().append(value); + array->indexed_properties().append(value); } return array; } @@ -1524,10 +1528,11 @@ Value TaggedTemplateLiteral::execute(Interpreter& interpreter) const return {}; // tag`${foo}` -> "", foo, "" -> tag(["", ""], foo) // tag`foo${bar}baz${qux}` -> "foo", bar, "baz", qux, "" -> tag(["foo", "baz", ""], bar, qux) - if (i % 2 == 0) - strings->elements().append(value); - else + if (i % 2 == 0) { + strings->indexed_properties().append(value); + } else { arguments.append(value); + } } auto* raw_strings = Array::create(interpreter.global_object()); @@ -1535,7 +1540,7 @@ Value TaggedTemplateLiteral::execute(Interpreter& interpreter) const auto value = raw_string.execute(interpreter); if (interpreter.exception()) return {}; - raw_strings->elements().append(value); + raw_strings->indexed_properties().append(value); } strings->define_property("raw", raw_strings, 0); diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index f47cf3dbb8..b9795afd79 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/Libraries/LibJS/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES Runtime/Function.cpp Runtime/FunctionPrototype.cpp Runtime/GlobalObject.cpp + Runtime/IndexedProperties.cpp Runtime/LexicalEnvironment.cpp Runtime/MarkedValueList.cpp Runtime/MathObject.cpp diff --git a/Libraries/LibJS/MarkupGenerator.cpp b/Libraries/LibJS/MarkupGenerator.cpp index 001bf3971a..9db68b2077 100644 --- a/Libraries/LibJS/MarkupGenerator.cpp +++ b/Libraries/LibJS/MarkupGenerator.cpp @@ -116,10 +116,13 @@ void MarkupGenerator::value_to_html(Value value, StringBuilder& output_html, Has void MarkupGenerator::array_to_html(const Array& array, StringBuilder& html_output, HashTable<Object*>& seen_objects) { html_output.append(wrap_string_in_style("[ ", StyleType::Punctuation)); - for (size_t i = 0; i < array.elements().size(); ++i) { - value_to_html(array.elements()[i], html_output, seen_objects); - if (i != array.elements().size() - 1) + bool first = true; + for (auto it = array.indexed_properties().begin(false); it != array.indexed_properties().end(); ++it) { + if (!first) html_output.append(wrap_string_in_style(", ", StyleType::Punctuation)); + first = false; + // FIXME: Exception check + value_to_html(it.value_and_attributes(const_cast<Array*>(&array)).value, html_output, seen_objects); } html_output.append(wrap_string_in_style(" ]", StyleType::Punctuation)); } @@ -127,18 +130,18 @@ void MarkupGenerator::array_to_html(const Array& array, StringBuilder& html_outp void MarkupGenerator::object_to_html(const Object& object, StringBuilder& html_output, HashTable<Object*>& seen_objects) { html_output.append(wrap_string_in_style("{ ", StyleType::Punctuation)); - - for (size_t i = 0; i < object.elements().size(); ++i) { - if (object.elements()[i].is_empty()) - continue; - html_output.append(wrap_string_in_style(String::format("%zu", i), StyleType::Number)); - html_output.append(wrap_string_in_style(": ", StyleType::Punctuation)); - value_to_html(object.elements()[i], html_output, seen_objects); - if (i != object.elements().size() - 1) + bool first = true; + for (auto& entry : object.indexed_properties()) { + if (!first) html_output.append(wrap_string_in_style(", ", StyleType::Punctuation)); + first = false; + html_output.append(wrap_string_in_style(String::number(entry.index()), StyleType::Number)); + html_output.append(wrap_string_in_style(": ", StyleType::Punctuation)); + // FIXME: Exception check + value_to_html(entry.value_and_attributes(const_cast<Object*>(&object)).value, html_output, seen_objects); } - if (!object.elements().is_empty() && object.shape().property_count()) + if (!object.indexed_properties().is_empty() && object.shape().property_count()) html_output.append(wrap_string_in_style(", ", StyleType::Punctuation)); size_t index = 0; diff --git a/Libraries/LibJS/Runtime/Accessor.h b/Libraries/LibJS/Runtime/Accessor.h index 9f7fd9451a..82ade250a2 100644 --- a/Libraries/LibJS/Runtime/Accessor.h +++ b/Libraries/LibJS/Runtime/Accessor.h @@ -27,7 +27,9 @@ #pragma once +#include <LibJS/Interpreter.h> #include <LibJS/Runtime/Function.h> +#include <LibJS/Runtime/MarkedValueList.h> namespace JS { diff --git a/Libraries/LibJS/Runtime/Array.cpp b/Libraries/LibJS/Runtime/Array.cpp index 38d78cb161..8c28966236 100644 --- a/Libraries/LibJS/Runtime/Array.cpp +++ b/Libraries/LibJS/Runtime/Array.cpp @@ -66,7 +66,7 @@ Value Array::length_getter(Interpreter& interpreter) auto* array = array_from(interpreter); if (!array) return {}; - return Value(array->length()); + return Value(static_cast<i32>(array->indexed_properties().array_like_size())); } void Array::length_setter(Interpreter& interpreter, Value value) @@ -81,7 +81,7 @@ void Array::length_setter(Interpreter& interpreter, Value value) interpreter.throw_exception<RangeError>("Invalid array length"); return; } - array->elements().resize(length.as_double()); + array->indexed_properties().set_array_like_size(length.as_double()); } } diff --git a/Libraries/LibJS/Runtime/Array.h b/Libraries/LibJS/Runtime/Array.h index 6b580b2889..a45ac0e9eb 100644 --- a/Libraries/LibJS/Runtime/Array.h +++ b/Libraries/LibJS/Runtime/Array.h @@ -39,8 +39,6 @@ public: explicit Array(Object& prototype); virtual ~Array() override; - i32 length() const { return static_cast<i32>(elements().size()); } - private: virtual const char* class_name() const override { return "Array"; } virtual bool is_array() const override { return true; } diff --git a/Libraries/LibJS/Runtime/ArrayConstructor.cpp b/Libraries/LibJS/Runtime/ArrayConstructor.cpp index 3af09146b3..800a386836 100644 --- a/Libraries/LibJS/Runtime/ArrayConstructor.cpp +++ b/Libraries/LibJS/Runtime/ArrayConstructor.cpp @@ -63,13 +63,13 @@ Value ArrayConstructor::call(Interpreter& interpreter) return {}; } auto* array = Array::create(interpreter.global_object()); - array->elements().resize(array_length_value.as_i32()); + array->indexed_properties().set_array_like_size(array_length_value.as_i32()); return array; } auto* array = Array::create(interpreter.global_object()); for (size_t i = 0; i < interpreter.argument_count(); ++i) - array->elements().append(interpreter.argument(i)); + array->indexed_properties().append(interpreter.argument(i)); return array; } @@ -91,7 +91,7 @@ Value ArrayConstructor::of(Interpreter& interpreter) { auto* array = Array::create(interpreter.global_object()); for (size_t i = 0; i < interpreter.argument_count(); ++i) - array->elements().append(interpreter.argument(i)); + array->indexed_properties().append(interpreter.argument(i)); return array; } diff --git a/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Libraries/LibJS/Runtime/ArrayPrototype.cpp index 1c8a95035c..bbe6a929aa 100644 --- a/Libraries/LibJS/Runtime/ArrayPrototype.cpp +++ b/Libraries/LibJS/Runtime/ArrayPrototype.cpp @@ -143,7 +143,7 @@ Value ArrayPrototype::filter(Interpreter& interpreter) auto* new_array = Array::create(interpreter.global_object()); for_each_item(interpreter, "filter", [&](auto, auto value, auto callback_result) { if (callback_result.to_boolean()) - new_array->elements().append(value); + new_array->indexed_properties().append(value); return IterationDecision::Continue; }); return Value(new_array); @@ -166,9 +166,9 @@ Value ArrayPrototype::map(Interpreter& interpreter) if (interpreter.exception()) return {}; auto* new_array = Array::create(interpreter.global_object()); - new_array->elements().resize(initial_length); + new_array->indexed_properties().set_array_like_size(initial_length); for_each_item(interpreter, "map", [&](auto index, auto, auto callback_result) { - new_array->elements()[index] = callback_result; + new_array->put(index, callback_result); return IterationDecision::Continue; }); return Value(new_array); @@ -182,8 +182,8 @@ Value ArrayPrototype::push(Interpreter& interpreter) if (this_object->is_array()) { auto* array = static_cast<Array*>(this_object); for (size_t i = 0; i < interpreter.argument_count(); ++i) - array->elements().append(interpreter.argument(i)); - return Value(array->length()); + array->indexed_properties().append(interpreter.argument(i)); + return Value(static_cast<i32>(array->indexed_properties().array_like_size())); } auto length = get_length(interpreter, *this_object); if (interpreter.exception()) @@ -207,8 +207,8 @@ Value ArrayPrototype::unshift(Interpreter& interpreter) if (!array) return {}; for (size_t i = 0; i < interpreter.argument_count(); ++i) - array->elements().insert(i, interpreter.argument(i)); - return Value(array->length()); + array->indexed_properties().insert(i, interpreter.argument(i)); + return Value(static_cast<i32>(array->indexed_properties().array_like_size())); } Value ArrayPrototype::pop(Interpreter& interpreter) @@ -218,9 +218,9 @@ Value ArrayPrototype::pop(Interpreter& interpreter) return {}; if (this_object->is_array()) { auto* array = static_cast<Array*>(this_object); - if (array->elements().is_empty()) + if (array->indexed_properties().is_empty()) return js_undefined(); - return array->elements().take_last().value_or(js_undefined()); + return array->indexed_properties().take_last(array).value.value_or(js_undefined()); } auto length = get_length(interpreter, *this_object); if (length == 0) { @@ -243,9 +243,12 @@ Value ArrayPrototype::shift(Interpreter& interpreter) auto* array = array_from(interpreter); if (!array) return {}; - if (array->elements().is_empty()) + if (array->indexed_properties().is_empty()) return js_undefined(); - return array->elements().take_first().value_or(js_undefined()); + auto result = array->indexed_properties().take_first(array); + if (interpreter.exception()) + return {}; + return result.value.value_or(js_undefined()); } Value ArrayPrototype::to_string(Interpreter& interpreter) @@ -299,15 +302,19 @@ Value ArrayPrototype::concat(Interpreter& interpreter) return {}; auto* new_array = Array::create(interpreter.global_object()); - new_array->elements().append(array->elements()); + new_array->indexed_properties().append_all(array, array->indexed_properties()); + if (interpreter.exception()) + return {}; for (size_t i = 0; i < interpreter.argument_count(); ++i) { auto argument = interpreter.argument(i); if (argument.is_array()) { auto& argument_object = argument.as_object(); - new_array->elements().append(argument_object.elements()); + new_array->indexed_properties().append_all(&argument_object, argument_object.indexed_properties()); + if (interpreter.exception()) + return {}; } else { - new_array->elements().append(argument); + new_array->indexed_properties().append(argument); } } @@ -322,11 +329,13 @@ Value ArrayPrototype::slice(Interpreter& interpreter) auto* new_array = Array::create(interpreter.global_object()); if (interpreter.argument_count() == 0) { - new_array->elements().append(array->elements()); + new_array->indexed_properties().append_all(array, array->indexed_properties()); + if (interpreter.exception()) + return {}; return new_array; } - ssize_t array_size = static_cast<ssize_t>(array->elements().size()); + ssize_t array_size = static_cast<ssize_t>(array->indexed_properties().array_like_size()); auto start_slice = interpreter.argument(0).to_i32(interpreter); if (interpreter.exception()) return {}; @@ -348,10 +357,10 @@ Value ArrayPrototype::slice(Interpreter& interpreter) end_slice = array_size; } - size_t array_capacity = start_slice + array_size - end_slice; - new_array->elements().ensure_capacity(array_capacity); for (ssize_t i = start_slice; i < end_slice; ++i) { - new_array->elements().append(array->elements().at(i)); + new_array->indexed_properties().append(array->get(i)); + if (interpreter.exception()) + return {}; } return new_array; @@ -512,16 +521,20 @@ Value ArrayPrototype::reverse(Interpreter& interpreter) if (!array) return {}; - if (array->elements().size() == 0) + if (array->indexed_properties().is_empty()) return array; Vector<Value> array_reverse; - array_reverse.ensure_capacity(array->elements().size()); + auto size = array->indexed_properties().array_like_size(); + array_reverse.ensure_capacity(size); - for (ssize_t i = array->elements().size() - 1; i >= 0; --i) - array_reverse.append(array->elements().at(i)); + for (ssize_t i = size - 1; i >= 0; --i) { + array_reverse.append(array->get(i)); + if (interpreter.exception()) + return {}; + } - array->elements() = move(array_reverse); + array->set_indexed_property_elements(move(array_reverse)); return array; } @@ -691,7 +704,7 @@ Value ArrayPrototype::splice(Interpreter& interpreter) if (interpreter.exception()) return {}; - removed_elements->elements().append(value); + removed_elements->indexed_properties().append(value); } if (insert_count < actual_delete_count) { diff --git a/Libraries/LibJS/Runtime/IndexedProperties.cpp b/Libraries/LibJS/Runtime/IndexedProperties.cpp new file mode 100644 index 0000000000..5da0f570b6 --- /dev/null +++ b/Libraries/LibJS/Runtime/IndexedProperties.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibJS/Runtime/Accessor.h> +#include <LibJS/Runtime/IndexedProperties.h> + +namespace JS { + +SimpleIndexedPropertyStorage::SimpleIndexedPropertyStorage(Vector<Value>&& initial_values) + : m_array_size(initial_values.size()) + , m_packed_elements(move(initial_values)) +{ +} + +bool SimpleIndexedPropertyStorage::has_index(u32 index) const +{ + return index < m_array_size && !m_packed_elements[index].is_empty(); +} + +Optional<ValueAndAttributes> SimpleIndexedPropertyStorage::get(u32 index) const +{ + if (index >= m_array_size) + return {}; + return ValueAndAttributes { m_packed_elements[index], default_attributes }; +} + +void SimpleIndexedPropertyStorage::put(u32 index, Value value, u8 attributes) +{ + ASSERT(attributes == default_attributes); + ASSERT(index < SPARSE_ARRAY_THRESHOLD); + + if (index >= m_array_size) { + m_array_size = index + 1; + if (index >= m_packed_elements.size()) + m_packed_elements.resize(index + MIN_PACKED_RESIZE_AMOUNT >= SPARSE_ARRAY_THRESHOLD ? SPARSE_ARRAY_THRESHOLD : index + MIN_PACKED_RESIZE_AMOUNT); + } + m_packed_elements[index] = value; +} + +void SimpleIndexedPropertyStorage::remove(u32 index) +{ + if (index < m_array_size) + m_packed_elements[index] = {}; +} + +void SimpleIndexedPropertyStorage::insert(u32 index, Value value, u8 attributes) +{ + ASSERT(attributes == default_attributes); + ASSERT(index < SPARSE_ARRAY_THRESHOLD); + m_array_size++; + ASSERT(m_array_size <= SPARSE_ARRAY_THRESHOLD); + m_packed_elements.insert(index, value); +} + +ValueAndAttributes SimpleIndexedPropertyStorage::take_first() +{ + m_array_size--; + return { m_packed_elements.take_first(), default_attributes }; +} + +ValueAndAttributes SimpleIndexedPropertyStorage::take_last() +{ + m_array_size--; + auto last_element = m_packed_elements[m_array_size]; + m_packed_elements[m_array_size] = {}; + return { last_element, default_attributes }; +} + +void SimpleIndexedPropertyStorage::set_array_like_size(size_t new_size) +{ + ASSERT(new_size <= SPARSE_ARRAY_THRESHOLD); + m_array_size = new_size; + m_packed_elements.resize(new_size); +} + +GenericIndexedPropertyStorage::GenericIndexedPropertyStorage(SimpleIndexedPropertyStorage&& storage) +{ + m_array_size = storage.array_like_size(); + for (auto& element : move(storage.m_packed_elements)) + m_packed_elements.append({ element, default_attributes }); +} + +bool GenericIndexedPropertyStorage::has_index(u32 index) const +{ + if (index < SPARSE_ARRAY_THRESHOLD) + return index < m_packed_elements.size() && !m_packed_elements[index].value.is_empty(); + return m_sparse_elements.contains(index); +} + +Optional<ValueAndAttributes> GenericIndexedPropertyStorage::get(u32 index) const +{ + if (index >= m_array_size) + return {}; + if (index < SPARSE_ARRAY_THRESHOLD) { + if (index >= m_packed_elements.size()) + return {}; + return m_packed_elements[index]; + } + return m_sparse_elements.get(index); +} + +void GenericIndexedPropertyStorage::put(u32 index, Value value, u8 attributes) +{ + if (index >= m_array_size) + m_array_size = index + 1; + if (index < SPARSE_ARRAY_THRESHOLD) { + if (index >= m_packed_elements.size()) + m_packed_elements.resize(index + MIN_PACKED_RESIZE_AMOUNT >= SPARSE_ARRAY_THRESHOLD ? SPARSE_ARRAY_THRESHOLD : index + MIN_PACKED_RESIZE_AMOUNT); + m_packed_elements[index] = { value, attributes }; + } else { + m_sparse_elements.set(index, { value, attributes }); + } +} + +void GenericIndexedPropertyStorage::remove(u32 index) +{ + if (index >= m_array_size) + return; + if (index + 1 == m_array_size) { + take_last(); + return; + } + if (index < SPARSE_ARRAY_THRESHOLD) { + if (index < m_packed_elements.size()) + m_packed_elements[index] = {}; + } else { + m_sparse_elements.remove(index); + } +} + +void GenericIndexedPropertyStorage::insert(u32 index, Value value, u8 attributes) +{ + if (index >= m_array_size) { + put(index, value, attributes); + return; + } + + m_array_size++; + + if (!m_sparse_elements.is_empty()) { + HashMap<u32, ValueAndAttributes> new_sparse_elements; + for (auto& entry : m_sparse_elements) + new_sparse_elements.set(entry.key >= index ? entry.key + 1 : entry.key, entry.value); + m_sparse_elements = move(new_sparse_elements); + } + + if (index < SPARSE_ARRAY_THRESHOLD) { + m_packed_elements.insert(index, { value, attributes }); + } else { + m_sparse_elements.set(index, { value, attributes }); + } +} + +ValueAndAttributes GenericIndexedPropertyStorage::take_first() +{ + ASSERT(m_array_size > 0); + m_array_size--; + + if (!m_sparse_elements.is_empty()) { + HashMap<u32, ValueAndAttributes> new_sparse_elements; + for (auto& entry : m_sparse_elements) + new_sparse_elements.set(entry.key - 1, entry.value); + m_sparse_elements = move(new_sparse_elements); + } + + return m_packed_elements.take_first(); +} + +ValueAndAttributes GenericIndexedPropertyStorage::take_last() +{ + ASSERT(m_array_size > 0); + m_array_size--; + + if (m_array_size <= SPARSE_ARRAY_THRESHOLD) { + auto last_element = m_packed_elements[m_array_size]; + m_packed_elements[m_array_size] = {}; + return last_element; + } else { + auto result = m_sparse_elements.get(m_array_size); + m_sparse_elements.remove(m_array_size); + ASSERT(result.has_value()); + return result.value(); + } +} + +void GenericIndexedPropertyStorage::set_array_like_size(size_t new_size) +{ + if (new_size < SPARSE_ARRAY_THRESHOLD) { + m_packed_elements.resize(new_size); + m_sparse_elements.clear(); + } else { + m_packed_elements.resize(SPARSE_ARRAY_THRESHOLD); + + HashMap<u32, ValueAndAttributes> new_sparse_elements; + for (auto& entry : m_sparse_elements) { + if (entry.key < new_size) + new_sparse_elements.set(entry.key, entry.value); + } + m_sparse_elements = move(new_sparse_elements); + } +} + +IndexedPropertyIterator::IndexedPropertyIterator(const IndexedProperties& indexed_properties, u32 staring_index, bool skip_empty) + : m_indexed_properties(indexed_properties) + , m_index(staring_index) + , m_skip_empty(skip_empty) +{ + while (m_skip_empty && m_index < m_indexed_properties.array_like_size()) { + if (m_indexed_properties.has_index(m_index)) + break; + m_index++; + } +} + +IndexedPropertyIterator& IndexedPropertyIterator::operator++() +{ + m_index++; + + while (m_skip_empty && m_index < m_indexed_properties.array_like_size()) { + if (m_indexed_properties.has_index(m_index)) + break; + m_index++; + }; + + return *this; +} + +IndexedPropertyIterator& IndexedPropertyIterator::operator*() +{ + return *this; +} + +bool IndexedPropertyIterator::operator!=(const IndexedPropertyIterator& other) const +{ + return m_index != other.m_index; +} + +ValueAndAttributes IndexedPropertyIterator::value_and_attributes(Object* this_object, bool evaluate_accessors) +{ + if (m_index < m_indexed_properties.array_like_size()) + return m_indexed_properties.get(this_object, m_index, evaluate_accessors).value(); + return {}; +} + +Optional<ValueAndAttributes> IndexedProperties::get(Object* this_object, u32 index, bool evaluate_accessors) const +{ + auto result = m_storage->get(index); + if (!evaluate_accessors) + return result; + if (!result.has_value()) + return {}; + auto value = result.value(); + if (value.value.is_accessor()) { + ASSERT(this_object); + auto& accessor = value.value.as_accessor(); + return ValueAndAttributes { accessor.call_getter(this_object), value.attributes }; + } + return result; +} + +void IndexedProperties::put(Object* this_object, u32 index, Value value, u8 attributes, bool evaluate_accessors) +{ + if (m_storage->is_simple_storage() && (index >= SPARSE_ARRAY_THRESHOLD || attributes != default_attributes)) + switch_to_generic_storage(); + if (m_storage->is_simple_storage() || !evaluate_accessors) { + m_storage->put(index, value, attributes); + return; + } + + auto value_here = m_storage->get(index); + if (value_here.has_value() && value_here.value().value.is_accessor()) { + ASSERT(this_object); + value_here.value().value.as_accessor().call_setter(this_object, value); + } else { + m_storage->put(index, value, attributes); + } +} + +bool IndexedProperties::remove(u32 index) +{ + auto result = m_storage->get(index); + if (!result.has_value()) + return true; + if (!(result.value().attributes & Attribute::Configurable)) + return false; + m_storage->remove(index); + return true; +} + +void IndexedProperties::insert(u32 index, Value value, u8 attributes) +{ + if (m_storage->is_simple_storage() && (index >= SPARSE_ARRAY_THRESHOLD || attributes != default_attributes || array_like_size() == SPARSE_ARRAY_THRESHOLD)) + switch_to_generic_storage(); + m_storage->insert(index, value, attributes); +} + +ValueAndAttributes IndexedProperties::take_first(Object *this_object) +{ + auto first = m_storage->take_first(); + if (first.value.is_accessor()) + return { first.value.as_accessor().call_getter(this_object), first.attributes }; + return first; +} + +ValueAndAttributes IndexedProperties::take_last(Object *this_object) +{ + auto last = m_storage->take_last(); + if (last.value.is_accessor()) + return { last.value.as_accessor().call_getter(this_object), last.attributes }; + return last; +} + +void IndexedProperties::append_all(Object* this_object, const IndexedProperties& properties, bool evaluate_accessors) +{ + if (m_storage->is_simple_storage() && !properties.m_storage->is_simple_storage()) + switch_to_generic_storage(); + + for (auto it = properties.begin(false); it != properties.end(); ++it) { + auto element = it.value_and_attributes(this_object, evaluate_accessors); + if (this_object && this_object->interpreter().exception()) + return; + m_storage->put(m_storage->array_like_size(), element.value, element.attributes); + } +} + +Vector<ValueAndAttributes> IndexedProperties::values_unordered() const +{ + if (m_storage->is_simple_storage()) { + auto elements = static_cast<const SimpleIndexedPropertyStorage&>(*m_storage).elements(); + Vector<ValueAndAttributes> with_attributes; + for (auto& value : elements) + with_attributes.append({ value, default_attributes }); + return with_attributes; + } + + auto storage = static_cast<const GenericIndexedPropertyStorage&>(*m_storage); + auto values = storage.packed_elements(); + values.ensure_capacity(values.size() + storage.sparse_elements().size()); + for (auto& entry : storage.sparse_elements()) + values.unchecked_append(entry.value); + return values; +} + +void IndexedProperties::switch_to_generic_storage() +{ + auto storage = static_cast<const SimpleIndexedPropertyStorage&>(*m_storage); + m_storage = make<GenericIndexedPropertyStorage>(move(storage)); +} + +} diff --git a/Libraries/LibJS/Runtime/IndexedProperties.h b/Libraries/LibJS/Runtime/IndexedProperties.h new file mode 100644 index 0000000000..9456a9dafe --- /dev/null +++ b/Libraries/LibJS/Runtime/IndexedProperties.h @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/NonnullOwnPtr.h> +#include <LibJS/Runtime/Shape.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +const u32 SPARSE_ARRAY_THRESHOLD = 200; +const u32 MIN_PACKED_RESIZE_AMOUNT = 20; + +struct ValueAndAttributes { + Value value; + u8 attributes { default_attributes }; +}; + +class IndexedProperties; +class IndexedPropertyIterator; +class GenericIndexedPropertyStorage; + +class IndexedPropertyStorage { +public: + virtual ~IndexedPropertyStorage() {}; + + virtual bool has_index(u32 index) const = 0; + virtual Optional<ValueAndAttributes> get(u32 index) const = 0; + virtual void put(u32 index, Value value, u8 attributes = default_attributes) = 0; + virtual void remove(u32 index) = 0; + + virtual void insert(u32 index, Value value, u8 attributes = default_attributes) = 0; + virtual ValueAndAttributes take_first() = 0; + virtual ValueAndAttributes take_last() = 0; + + virtual size_t size() const = 0; + virtual size_t array_like_size() const = 0; + virtual void set_array_like_size(size_t new_size) = 0; + + virtual bool is_simple_storage() const { return false; } +}; + +class SimpleIndexedPropertyStorage final : public IndexedPropertyStorage { +public: + SimpleIndexedPropertyStorage() = default; + explicit SimpleIndexedPropertyStorage(Vector<Value>&& initial_values); + + virtual bool has_index(u32 index) const override; + virtual Optional<ValueAndAttributes> get(u32 index) const override; + virtual void put(u32 index, Value value, u8 attributes = default_attributes) override; + virtual void remove(u32 index) override; + + virtual void insert(u32 index, Value value, u8 attributes = default_attributes) override; + virtual ValueAndAttributes take_first() override; + virtual ValueAndAttributes take_last() override; + + virtual size_t size() const override { return m_packed_elements.size(); } + virtual size_t array_like_size() const override { return m_array_size; } + virtual void set_array_like_size(size_t new_size) override; + + virtual bool is_simple_storage() const override { return true; } + Vector<Value> elements() const { return m_packed_elements; } + +private: + friend GenericIndexedPropertyStorage; + + size_t m_array_size { 0 }; + Vector<Value> m_packed_elements; +}; + +class GenericIndexedPropertyStorage final : public IndexedPropertyStorage { +public: + explicit GenericIndexedPropertyStorage(SimpleIndexedPropertyStorage&&); + + virtual bool has_index(u32 index) const override; + virtual Optional<ValueAndAttributes> get(u32 index) const override; + virtual void put(u32 index, Value value, u8 attributes = default_attributes) override; + virtual void remove(u32 index) override; + + virtual void insert(u32 index, Value value, u8 attributes = default_attributes) override; + virtual ValueAndAttributes take_first() override; + virtual ValueAndAttributes take_last() override; + + virtual size_t size() const override { return m_packed_elements.size() + m_sparse_elements.size(); } + virtual size_t array_like_size() const override { return m_array_size; } + virtual void set_array_like_size(size_t new_size) override; + + Vector<ValueAndAttributes> packed_elements() const { return m_packed_elements; } + HashMap<u32, ValueAndAttributes> sparse_elements() const { return m_sparse_elements; } + +private: + size_t m_array_size { 0 }; + Vector<ValueAndAttributes> m_packed_elements; + HashMap<u32, ValueAndAttributes> m_sparse_elements; +}; + +class IndexedPropertyIterator { +public: + IndexedPropertyIterator(const IndexedProperties&, u32 starting_index, bool skip_empty); + + IndexedPropertyIterator& operator++(); + IndexedPropertyIterator& operator*(); + bool operator!=(const IndexedPropertyIterator&) const; + + u32 index() const { return m_index; }; + ValueAndAttributes value_and_attributes(Object* this_object, bool evaluate_accessors = true); + +private: + const IndexedProperties& m_indexed_properties; + u32 m_index; + bool m_skip_empty; +}; + +class IndexedProperties { +public: + IndexedProperties() = default; + + IndexedProperties(Vector<Value>&& values) + : m_storage(make<SimpleIndexedPropertyStorage>(move(values))) + { + } + + bool has_index(u32 index) const { return m_storage->has_index(index); } + Optional<ValueAndAttributes> get(Object* this_object, u32 index, bool evaluate_accessors = true) const; + void put(Object* this_object, u32 index, Value value, u8 attributes = default_attributes, bool evaluate_accessors = true); + bool remove(u32 index); + + void insert(u32 index, Value value, u8 attributes = default_attributes); + ValueAndAttributes take_first(Object* this_object); + ValueAndAttributes take_last(Object* this_object); + + void append(Value value, u8 attributes = default_attributes) { put(nullptr, array_like_size(), value, attributes, false); } + void append_all(Object* this_object, const IndexedProperties& properties, bool evaluate_accessors = true); + + IndexedPropertyIterator begin(bool skip_empty = true) const { return IndexedPropertyIterator(*this, 0, skip_empty); }; + IndexedPropertyIterator end() const { return IndexedPropertyIterator(*this, array_like_size(), false); }; + + size_t size() const { return m_storage->size(); } + bool is_empty() const { return size() == 0; } + size_t array_like_size() const { return m_storage->array_like_size(); } + void set_array_like_size(size_t new_size) { m_storage->set_array_like_size(new_size); }; + + Vector<ValueAndAttributes> values_unordered() const; + +private: + void switch_to_generic_storage(); + + NonnullOwnPtr<IndexedPropertyStorage> m_storage { make<SimpleIndexedPropertyStorage>() }; +}; + +} diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp index 8e4043cbb8..bab44c3b88 100644 --- a/Libraries/LibJS/Runtime/Object.cpp +++ b/Libraries/LibJS/Runtime/Object.cpp @@ -94,9 +94,10 @@ Value Object::get_own_property(const Object& this_object, PropertyName property_ Value value_here; if (property_name.is_number()) { - if (static_cast<size_t>(property_name.as_number()) >= m_elements.size()) + auto existing_property = m_indexed_properties.get(nullptr, property_name.as_number(), false); + if (!existing_property.has_value()) return {}; - value_here = m_elements[property_name.as_number()]; + value_here = existing_property.value().value; } else { auto metadata = shape().lookup(property_name.as_string()); if (!metadata.has_value()) @@ -145,19 +146,20 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k } size_t property_index = 0; - for (size_t i = 0; i < m_elements.size(); ++i) { - if (m_elements.at(i).is_empty()) - continue; - + for (auto& entry : m_indexed_properties) { if (kind == GetOwnPropertyMode::Key) { - properties_array->put_by_index(property_index, js_string(interpreter(), String::number(i))); + properties_array->define_property(property_index, js_string(interpreter(), String::number(entry.index()))); } else if (kind == GetOwnPropertyMode::Value) { - properties_array->put_by_index(property_index, m_elements.at(i)); + properties_array->define_property(property_index, entry.value_and_attributes(const_cast<Object*>(&this_object)).value); + if (interpreter().exception()) + return {}; } else { auto* entry_array = Array::create(interpreter().global_object()); - entry_array->put_by_index(0, js_string(interpreter(), String::number(i))); - entry_array->put_by_index(1, m_elements.at(i)); - properties_array->put_by_index(property_index, entry_array); + entry_array->define_property(0, js_string(interpreter(), String::number(entry.index()))); + entry_array->define_property(1, entry.value_and_attributes(const_cast<Object*>(&this_object)).value); + if (interpreter().exception()) + return {}; + properties_array->define_property(property_index, entry_array); } ++property_index; @@ -168,14 +170,18 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k size_t offset = it.value.offset + property_index; if (kind == GetOwnPropertyMode::Key) { - properties_array->put_by_index(offset, js_string(interpreter(), it.key)); + properties_array->define_property(offset, js_string(interpreter(), it.key)); } else if (kind == GetOwnPropertyMode::Value) { - properties_array->put_by_index(offset, this_object.get(it.key)); + properties_array->define_property(offset, this_object.get(it.key)); + if (interpreter().exception()) + return {}; } else { auto* entry_array = Array::create(interpreter().global_object()); - entry_array->put_by_index(0, js_string(interpreter(), it.key)); - entry_array->put_by_index(1, this_object.get(it.key)); - properties_array->put_by_index(offset, entry_array); + entry_array->define_property(0, js_string(interpreter(), it.key)); + entry_array->define_property(1, this_object.get(it.key)); + if (interpreter().exception()) + return {}; + properties_array->define_property(offset, entry_array); } } } @@ -189,9 +195,11 @@ Value Object::get_own_property_descriptor(PropertyName property_name) const u8 attributes; if (property_name.is_number()) { - if (static_cast<size_t>(property_name.as_number()) >= m_elements.size()) - return {}; - value = m_elements[property_name.as_number()]; + auto existing_value = m_indexed_properties.get(nullptr, property_name.as_number(), false); + if (!existing_value.has_value()) + return js_undefined(); + value = existing_value.value().value; + attributes = existing_value.value().attributes; attributes = default_attributes; } else { auto metadata = shape().lookup(property_name.as_string()); @@ -381,9 +389,9 @@ bool Object::put_own_property_by_index(Object& this_object, u32 property_index, attributes |= Attribute::HasSet; } - auto new_property = property_index >= m_elements.size(); - auto existing_property = new_property ? Value() : m_elements[property_index]; - auto existing_attributes = default_attributes; + auto existing_property = m_indexed_properties.get(nullptr, property_index, false); + auto new_property = !existing_property.has_value(); + auto existing_attributes = new_property ? 0 : existing_property.value().attributes; if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !(existing_attributes & Attribute::Configurable) && attributes != existing_attributes) { dbg() << "Disallow reconfig of non-configurable property"; @@ -392,7 +400,7 @@ bool Object::put_own_property_by_index(Object& this_object, u32 property_index, return false; } - auto value_here = existing_property; + auto value_here = new_property ? Value() : existing_property.value().value; if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !(existing_attributes & Attribute::Writable)) { dbg() << "Disallow write to non-writable property"; return false; @@ -409,9 +417,7 @@ bool Object::put_own_property_by_index(Object& this_object, u32 property_index, native_property.set(interpreter, value); interpreter.pop_call_frame(); } else { - if (new_property) - m_elements.resize(property_index + 1); - m_elements[property_index] = value; + m_indexed_properties.put(&this_object, property_index, value, attributes, mode == PutOwnPropertyMode::Put); } return true; } @@ -419,13 +425,8 @@ bool Object::put_own_property_by_index(Object& this_object, u32 property_index, Value Object::delete_property(PropertyName property_name) { ASSERT(property_name.is_valid()); - if (property_name.is_number()) { - if (property_name.as_number() < static_cast<i32>(elements().size())) { - elements()[property_name.as_number()] = {}; - return Value(true); - } - return Value(true); - } + if (property_name.is_number()) + return Value(m_indexed_properties.remove(property_name.as_number())); auto metadata = shape().lookup(property_name.as_string()); if (!metadata.has_value()) return Value(true); @@ -459,11 +460,13 @@ Value Object::get_by_index(u32 property_index) const return js_string(heap(), string.substring(property_index, 1)); return js_undefined(); } - if (static_cast<size_t>(property_index) < object->m_elements.size()) { - auto value = object->m_elements[property_index]; - if (value.is_empty()) + if (static_cast<size_t>(property_index) < object->m_indexed_properties.array_like_size()) { + auto result = object->m_indexed_properties.get(const_cast<Object*>(this), property_index); + if (interpreter().exception()) return {}; - return value; + if (result.has_value() && !result.value().value.is_empty()) + return result.value().value; + return {}; } object = object->prototype(); } @@ -494,12 +497,31 @@ Value Object::get(PropertyName property_name) const bool Object::put_by_index(u32 property_index, Value value) { ASSERT(!value.is_empty()); - // FIXME: Implement some kind of sparse storage for arrays with huge indices. - // Also: Take attributes into account here - if (static_cast<size_t>(property_index) >= m_elements.size()) - m_elements.resize(property_index + 1); - m_elements[property_index] = value; - return true; + + // If there's a setter in the prototype chain, we go to the setter. + // Otherwise, it goes in the own property storage. + Object* object = this; + while (object) { + auto existing_value = object->m_indexed_properties.get(nullptr, property_index, false); + if (existing_value.has_value()) { + auto value_here = existing_value.value(); + if (value_here.value.is_accessor()) { + value_here.value.as_accessor().call_setter(object, value); + return true; + } + if (value_here.value.is_object() && value_here.value.as_object().is_native_property()) { + auto& native_property = static_cast<NativeProperty&>(value_here.value.as_object()); + auto& interpreter = const_cast<Object*>(this)->interpreter(); + auto& call_frame = interpreter.push_call_frame(); + call_frame.this_value = this; + native_property.set(interpreter, value); + interpreter.pop_call_frame(); + return true; + } + } + object = object->prototype(); + } + return put_own_property_by_index(*this, property_index, value, default_attributes, PutOwnPropertyMode::Put); } bool Object::put(PropertyName property_name, Value value) @@ -562,8 +584,8 @@ void Object::visit_children(Cell::Visitor& visitor) for (auto& value : m_storage) visitor.visit(value); - for (auto& value : m_elements) - visitor.visit(value); + for (auto& value : m_indexed_properties.values_unordered()) + visitor.visit(value.value); } bool Object::has_property(PropertyName property_name) const @@ -582,9 +604,7 @@ bool Object::has_own_property(PropertyName property_name) const auto has_indexed_property = [&](u32 index) -> bool { if (is_string_object()) return index < static_cast<const StringObject*>(this)->primitive_string().string().length(); - if (static_cast<size_t>(index) >= m_elements.size()) - return false; - return !m_elements[index].is_empty(); + return m_indexed_properties.has_index(index); }; if (property_name.is_number()) diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index 752d186c62..ad334a2d8a 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -30,6 +30,7 @@ #include <AK/String.h> #include <LibJS/Forward.h> #include <LibJS/Runtime/Cell.h> +#include <LibJS/Runtime/IndexedProperties.h> #include <LibJS/Runtime/PrimitiveString.h> #include <LibJS/Runtime/PropertyName.h> #include <LibJS/Runtime/Shape.h> @@ -37,8 +38,6 @@ namespace JS { -const u8 default_attributes = Attribute::Configurable | Attribute::Writable | Attribute::Enumerable; - class Object : public Cell { public: static Object* create_empty(Interpreter&, GlobalObject&); @@ -110,8 +109,9 @@ public: Value get_direct(size_t index) const { return m_storage[index]; } - const Vector<Value>& elements() const { return m_elements; } - Vector<Value>& elements() { return m_elements; } + const IndexedProperties& indexed_properties() const { return m_indexed_properties; } + IndexedProperties& indexed_properties() { return m_indexed_properties; } + void set_indexed_property_elements(Vector<Value>&& values) { m_indexed_properties = IndexedProperties(move(values)); } private: virtual Value get_by_index(u32 property_index) const; @@ -124,7 +124,7 @@ private: Shape* m_shape { nullptr }; Vector<Value> m_storage; - Vector<Value> m_elements; + IndexedProperties m_indexed_properties; }; } diff --git a/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Libraries/LibJS/Runtime/ObjectConstructor.cpp index e9f9459f5b..828e0db2ea 100644 --- a/Libraries/LibJS/Runtime/ObjectConstructor.cpp +++ b/Libraries/LibJS/Runtime/ObjectConstructor.cpp @@ -75,14 +75,11 @@ Value ObjectConstructor::get_own_property_names(Interpreter& interpreter) if (interpreter.exception()) return {}; auto* result = Array::create(interpreter.global_object()); - for (size_t i = 0; i < object->elements().size(); ++i) { - if (!object->elements()[i].is_empty()) - result->elements().append(js_string(interpreter, String::number(i))); - } - - for (auto& it : object->shape().property_table_ordered()) { - result->elements().append(js_string(interpreter, it.key)); - } + for (auto& entry : object->indexed_properties()) + result->indexed_properties().append(js_string(interpreter, String::number(entry.index()))); + for (auto& it : object->shape().property_table_ordered()) + result->indexed_properties().append(js_string(interpreter, it.key)); + return result; } diff --git a/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Libraries/LibJS/Runtime/ScriptFunction.cpp index 4d8e0a4597..1b6aa3ef3b 100644 --- a/Libraries/LibJS/Runtime/ScriptFunction.cpp +++ b/Libraries/LibJS/Runtime/ScriptFunction.cpp @@ -104,7 +104,7 @@ Value ScriptFunction::call(Interpreter& interpreter) if (parameter.is_rest) { auto* array = Array::create(interpreter.global_object()); for (size_t rest_index = i; rest_index < argument_values.size(); ++rest_index) - array->elements().append(argument_values[rest_index]); + array->indexed_properties().append(argument_values[rest_index]); value = Value(array); } else { if (i < argument_values.size() && !argument_values[i].is_undefined()) { diff --git a/Libraries/LibJS/Runtime/Shape.h b/Libraries/LibJS/Runtime/Shape.h index e3d5305dca..ac19d775c7 100644 --- a/Libraries/LibJS/Runtime/Shape.h +++ b/Libraries/LibJS/Runtime/Shape.h @@ -45,6 +45,8 @@ struct Attribute { }; }; +const u8 default_attributes = Attribute::Configurable | Attribute::Writable | Attribute::Enumerable; + struct PropertyMetadata { size_t offset { 0 }; u8 attributes { 0 }; diff --git a/Libraries/LibJS/Runtime/StringConstructor.cpp b/Libraries/LibJS/Runtime/StringConstructor.cpp index a4f5a12ca3..fd24023a74 100644 --- a/Libraries/LibJS/Runtime/StringConstructor.cpp +++ b/Libraries/LibJS/Runtime/StringConstructor.cpp @@ -83,14 +83,20 @@ Value StringConstructor::raw(Interpreter& interpreter) if (!raw.is_array()) return js_string(interpreter, ""); - auto& raw_array_elements = static_cast<Array*>(raw.to_object(interpreter))->elements(); + auto* array = static_cast<Array*>(raw.to_object(interpreter)); + auto& raw_array_elements = array->indexed_properties(); StringBuilder builder; - for (size_t i = 0; i < raw_array_elements.size(); ++i) { - builder.append(raw_array_elements.at(i).to_string(interpreter)); + for (size_t i = 0; i < raw_array_elements.array_like_size(); ++i) { + auto result = raw_array_elements.get(array, i); if (interpreter.exception()) return {}; - if (i + 1 < interpreter.argument_count() && i < raw_array_elements.size() - 1) { + if (!result.has_value()) + continue; + builder.append(result.value().value.to_string(interpreter)); + if (interpreter.exception()) + return {}; + if (i + 1 < interpreter.argument_count() && i < raw_array_elements.array_like_size() - 1) { builder.append(interpreter.argument(i + 1).to_string(interpreter)); if (interpreter.exception()) return {}; diff --git a/Libraries/LibJS/Tests/Array.js b/Libraries/LibJS/Tests/Array.js index c103025bb0..a96388a006 100644 --- a/Libraries/LibJS/Tests/Array.js +++ b/Libraries/LibJS/Tests/Array.js @@ -29,6 +29,10 @@ try { assert(a[0][1] === 2); assert(a[0][2] === 3); + a = new Array(1, 2, 3); + Object.defineProperty(a, 3, { get() { return 10; } }); + assert(a.toString() === "1,2,3,10"); + [-1, -100, -0.1, 0.1, 1.23, Infinity, -Infinity, NaN].forEach(value => { assertThrowsError(() => { new Array(value); diff --git a/Libraries/LibJS/Tests/Array.of.js b/Libraries/LibJS/Tests/Array.of.js index 1fb4200847..49b1561d88 100644 --- a/Libraries/LibJS/Tests/Array.of.js +++ b/Libraries/LibJS/Tests/Array.of.js @@ -31,6 +31,15 @@ try { assert(a[0][1] === 2); assert(a[0][2] === 3); + let t = [1, 2, 3]; + Object.defineProperty(t, 3, { get() { return 4; } }); + a = Array.of(...t); + assert(a.length === 4); + assert(a[0] === 1); + assert(a[1] === 2); + assert(a[2] === 3); + assert(a[3] === 4); + console.log("PASS"); } catch (e) { console.log("FAIL: " + e); diff --git a/Libraries/LibJS/Tests/Object.defineProperty.js b/Libraries/LibJS/Tests/Object.defineProperty.js index 846b62ddfa..db12bb38bb 100644 --- a/Libraries/LibJS/Tests/Object.defineProperty.js +++ b/Libraries/LibJS/Tests/Object.defineProperty.js @@ -7,6 +7,8 @@ try { assert(o.foo === 1); o.foo = 2; assert(o.foo === 1); + Object.defineProperty(o, 2, { get() { return 10; } }); + assert(o[2] === 10); var d = Object.getOwnPropertyDescriptor(o, "foo"); assert(d.configurable === false); diff --git a/Libraries/LibJS/Tests/array-basic.js b/Libraries/LibJS/Tests/array-basic.js index b39bcda134..57e0b5d624 100644 --- a/Libraries/LibJS/Tests/array-basic.js +++ b/Libraries/LibJS/Tests/array-basic.js @@ -40,6 +40,23 @@ try { assert(a[4] === undefined); assert(a[5] === 3); + a = [1,,2,,,3,]; + Object.defineProperty(a, 1, { + get() { + return this.secret_prop; + }, + set(value) { + this.secret_prop = value; + }, + }); + assert(a.length === 6); + assert(a.toString() === "1,,2,,,3"); + assert(a.secret_prop === undefined); + a[1] = 20; + assert(a.length === 6); + assert(a.toString() === "1,20,2,,,3"); + assert(a.secret_prop === 20); + console.log("PASS"); } catch (e) { console.log("FAIL: " + e); |