LibJS: Invalidate PrototypeChainValidity when prototype shape is a dict

Adds missing PrototypeChainValidity invalidation for add/set/delete
operations on dictionary-mode prototype shapes.
This commit is contained in:
Aliaksandr Kalenik 2025-09-22 18:01:56 +02:00 committed by Alexander Kalenik
parent 3272b8c0f3
commit 2a288f6d53
3 changed files with 75 additions and 0 deletions

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -285,6 +286,7 @@ GC::Ref<Shape> Shape::create_delete_transition(PropertyKey const& property_key)
void Shape::add_property_without_transition(PropertyKey const& property_key, PropertyAttributes attributes)
{
invalidate_prototype_if_needed_for_change_without_transition();
ensure_property_table();
if (m_property_table->set(property_key, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry) {
VERIFY(m_property_count < NumericLimits<u32>::max());
@ -294,6 +296,7 @@ void Shape::add_property_without_transition(PropertyKey const& property_key, Pro
void Shape::set_property_attributes_without_transition(PropertyKey const& property_key, PropertyAttributes attributes)
{
invalidate_prototype_if_needed_for_change_without_transition();
VERIFY(is_dictionary());
VERIFY(m_property_table);
auto it = m_property_table->find(property_key);
@ -304,6 +307,7 @@ void Shape::set_property_attributes_without_transition(PropertyKey const& proper
void Shape::remove_property_without_transition(PropertyKey const& property_key, u32 offset)
{
invalidate_prototype_if_needed_for_change_without_transition();
VERIFY(is_uncacheable_dictionary());
VERIFY(m_property_table);
if (m_property_table->remove(property_key))
@ -356,6 +360,16 @@ void Shape::invalidate_prototype_if_needed_for_new_prototype(GC::Ref<Shape> new_
invalidate_all_prototype_chains_leading_to_this();
}
void Shape::invalidate_prototype_if_needed_for_change_without_transition()
{
if (!m_is_prototype_shape)
return;
m_prototype_chain_validity->set_valid(false);
m_prototype_chain_validity = heap().allocate<PrototypeChainValidity>();
invalidate_all_prototype_chains_leading_to_this();
}
void Shape::invalidate_all_prototype_chains_leading_to_this()
{
HashTable<Shape*> shapes_to_invalidate;

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -113,6 +114,7 @@ private:
Shape(Shape& previous_shape, Object* new_prototype);
void invalidate_prototype_if_needed_for_new_prototype(GC::Ref<Shape> new_prototype_shape);
void invalidate_prototype_if_needed_for_change_without_transition();
void invalidate_all_prototype_chains_leading_to_this();
virtual void visit_edges(Visitor&) override;

View File

@ -32,3 +32,62 @@ test("Overriding an inherited getter with a data property on an intermediate pro
Object.defineProperty(proto, "hm", { value: 321 });
f(obj, 321);
});
test("Modifying prototype in dictionary mode should cause prototype-chain validity invalidation (dict-mode prototype is in the middle of prototype chain)", () => {
function f(obj, expected) {
expect(obj.hm).toBe(expected);
}
const midProto = {};
midProto.__proto__ = {
get hm() {
return 321;
},
};
const proto = {};
proto.__proto__ = midProto;
const obj = Object.create(proto);
// put midProto into dictionary mode
for (let i = 0; i < 1000; i++) {
midProto["i" + i] = i;
}
f(obj, 321);
Object.defineProperty(midProto, "hm", {
get() {
return 123;
},
configurable: true,
});
f(obj, 123);
});
test("Modifying prototype in dictionary mode should cause prototype-chain validity invalidation (dict-mode prototype is direct prototype of target object)", () => {
function f(obj, expected) {
expect(obj.hm).toBe(expected);
}
const proto = {};
proto.__proto__ = {
get hm() {
return 321;
},
};
const obj = Object.create(proto);
// put proto into dictionary mode
for (let i = 0; i < 1000; i++) {
proto["i" + i] = i;
}
f(obj, 321);
Object.defineProperty(proto, "hm", {
get() {
return 123;
},
configurable: true,
});
f(obj, 123);
});