1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

LibJS: Skip caching get_by_id() if object's shape is changed by a getter

Fixes a bug that reproduces with the following steps:
1. Create an object with a getter for property "a" in its prototype,
   where the getter adds an "a" property to the object itself.
2. Call the "a" getter in a loop for the first time. This triggers
   caching of metadata indicating that the "a" property is located in
   the prototype chain.
3. Call the "a" getter in a loop for the second time. Oops, the cache
   says the getter is in the prototype chain, but the object now
   also has its own "a" property that was added by the first getter
   call.
This commit is contained in:
Aliaksandr Kalenik 2025-05-20 23:42:17 +03:00 committed by Alexander Kalenik
parent 7af188dc52
commit 95e1ec4abc
Notes: github-actions[bot] 2025-05-20 23:11:54 +00:00
2 changed files with 56 additions and 17 deletions

View file

@ -1022,23 +1022,28 @@ inline ThrowCompletionOr<Value> get_by_id(VM& vm, Optional<IdentifierTableIndex>
CacheablePropertyMetadata cacheable_metadata;
auto value = TRY(base_obj->internal_get(executable.get_identifier(property), this_value, &cacheable_metadata));
auto get_cache_slot = [&] -> PropertyLookupCache::Entry& {
for (size_t i = cache.entries.size() - 1; i >= 1; --i) {
cache.entries[i] = cache.entries[i - 1];
// If internal_get() caused object's shape change, we can no longer be sure
// that collected metadata is valid, e.g. if getter in prototype chain added
// property with the same name into the object itself.
if (&shape == &base_obj->shape()) {
auto get_cache_slot = [&] -> PropertyLookupCache::Entry& {
for (size_t i = cache.entries.size() - 1; i >= 1; --i) {
cache.entries[i] = cache.entries[i - 1];
}
cache.entries[0] = {};
return cache.entries[0];
};
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
auto& entry = get_cache_slot();
entry.shape = shape;
entry.property_offset = cacheable_metadata.property_offset.value();
} else if (cacheable_metadata.type == CacheablePropertyMetadata::Type::InPrototypeChain) {
auto& entry = get_cache_slot();
entry.shape = &base_obj->shape();
entry.property_offset = cacheable_metadata.property_offset.value();
entry.prototype = *cacheable_metadata.prototype;
entry.prototype_chain_validity = *cacheable_metadata.prototype->shape().prototype_chain_validity();
}
cache.entries[0] = {};
return cache.entries[0];
};
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
auto& entry = get_cache_slot();
entry.shape = shape;
entry.property_offset = cacheable_metadata.property_offset.value();
} else if (cacheable_metadata.type == CacheablePropertyMetadata::Type::InPrototypeChain) {
auto& entry = get_cache_slot();
entry.shape = &base_obj->shape();
entry.property_offset = cacheable_metadata.property_offset.value();
entry.prototype = *cacheable_metadata.prototype;
entry.prototype_chain_validity = *cacheable_metadata.prototype->shape().prototype_chain_validity();
}
return value;
@ -1227,6 +1232,7 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
break;
}
case Op::PropertyKind::KeyValue: {
auto& shape = object->shape();
if (caches) {
for (auto& cache : caches->entries) {
if (cache.prototype) {
@ -1262,7 +1268,10 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
CacheablePropertyMetadata cacheable_metadata;
bool succeeded = TRY(object->internal_set(name, value, this_value, &cacheable_metadata));
if (succeeded && caches) {
// If internal_set() caused object's shape change, we can no longer be sure
// that collected metadata is valid, e.g. if setter in prototype chain added
// property with the same name into the object itself.
if (succeeded && caches && &shape == &object->shape()) {
auto get_cache_slot = [&] -> PropertyLookupCache::Entry& {
for (size_t i = caches->entries.size() - 1; i >= 1; --i) {
caches->entries[i] = caches->entries[i - 1];

View file

@ -0,0 +1,30 @@
test("Mutation of object in getter should result in skipping of collecting inline cache data", () => {
class Color {
get rgb() {
const value = [];
Object.defineProperty(this, "rgb", { value });
return value;
}
set rgb(value) {
Object.defineProperty(this, "rgb", { value });
}
}
function testGetting() {
const c = new Color();
for (let i = 0; i < 2; i++) {
c.rgb;
}
}
function testSetting() {
const c = new Color();
for (let i = 0; i < 2; i++) {
c.rgb = i;
}
}
expect(testGetting).not.toThrow();
expect(testSetting).not.toThrow();
});