ladybird/Libraries/LibJS/Runtime/IndexedProperties.cpp
Andreas Kling bdb16434cc LibJS: Preserve sparse elements below length failures
ArraySetLength deletes array index properties in descending order. When
a non-configurable index cannot be deleted, the length is restored to
that index plus one, and lower indexes are not touched.

The dictionary indexed storage path already restored the final length,
but removed configurable entries below the failing index too. Keep those
entries when truncation stops early and add sparse array coverage.
2026-05-22 01:56:57 +02:00

97 lines
2.6 KiB
C++

/*
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/QuickSort.h>
#include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/IndexedProperties.h>
namespace JS {
bool GenericIndexedPropertyStorage::has_index(u32 index) const
{
return m_sparse_elements.contains(index);
}
Optional<ValueAndAttributes> GenericIndexedPropertyStorage::get(u32 index) const
{
if (index >= m_array_size)
return {};
return m_sparse_elements.get(index).copy();
}
void GenericIndexedPropertyStorage::put(u32 index, Value value, PropertyAttributes attributes)
{
if (index >= m_array_size)
m_array_size = index + 1;
m_sparse_elements.set(index, { value, attributes });
}
void GenericIndexedPropertyStorage::remove(u32 index)
{
VERIFY(index < m_array_size);
m_sparse_elements.remove(index);
}
ValueAndAttributes GenericIndexedPropertyStorage::take_first()
{
VERIFY(m_array_size > 0);
m_array_size--;
auto indices = m_sparse_elements.keys();
quick_sort(indices);
auto it = m_sparse_elements.find(indices.first());
auto first_element = it->value;
m_sparse_elements.remove(it);
return first_element;
}
ValueAndAttributes GenericIndexedPropertyStorage::take_last()
{
VERIFY(m_array_size > 0);
m_array_size--;
auto result = m_sparse_elements.get(m_array_size);
if (!result.has_value())
return {};
m_sparse_elements.remove(m_array_size);
return result.value();
}
bool GenericIndexedPropertyStorage::set_array_like_size(size_t new_size)
{
if (new_size == m_array_size)
return true;
if (new_size >= m_array_size) {
m_array_size = new_size;
return true;
}
Optional<u32> highest_non_configurable_index;
for (auto& entry : m_sparse_elements) {
if (entry.key >= new_size && !entry.value.attributes.is_configurable()) {
if (!highest_non_configurable_index.has_value() || entry.key > highest_non_configurable_index.value())
highest_non_configurable_index = entry.key;
}
}
auto failed = highest_non_configurable_index.has_value();
auto truncation_boundary = failed ? highest_non_configurable_index.value() + 1 : new_size;
GC::ConservativeHashMap<u32, ValueAndAttributes> new_sparse_elements;
for (auto& entry : m_sparse_elements) {
if (entry.key >= truncation_boundary && entry.value.attributes.is_configurable())
continue;
new_sparse_elements.set(entry.key, entry.value);
}
m_array_size = truncation_boundary;
m_sparse_elements = move(new_sparse_elements);
return !failed;
}
}