Coverage Report

Created: 2025-11-16 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibJS/Runtime/Shape.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2020-2024, Andreas Kling <kling@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <LibJS/Heap/DeferGC.h>
8
#include <LibJS/Runtime/Shape.h>
9
#include <LibJS/Runtime/VM.h>
10
11
namespace JS {
12
13
JS_DEFINE_ALLOCATOR(Shape);
14
JS_DEFINE_ALLOCATOR(PrototypeChainValidity);
15
16
static HashTable<JS::GCPtr<Shape>> s_all_prototype_shapes;
17
18
Shape::~Shape()
19
0
{
20
0
    if (m_is_prototype_shape)
21
0
        s_all_prototype_shapes.remove(this);
22
0
}
23
24
NonnullGCPtr<Shape> Shape::create_cacheable_dictionary_transition()
25
0
{
26
0
    auto new_shape = heap().allocate_without_realm<Shape>(m_realm);
27
0
    new_shape->m_dictionary = true;
28
0
    new_shape->m_cacheable = true;
29
0
    new_shape->m_prototype = m_prototype;
30
0
    invalidate_prototype_if_needed_for_new_prototype(new_shape);
31
0
    ensure_property_table();
32
0
    new_shape->ensure_property_table();
33
0
    (*new_shape->m_property_table) = *m_property_table;
34
0
    new_shape->m_property_count = new_shape->m_property_table->size();
35
0
    return new_shape;
36
0
}
37
38
NonnullGCPtr<Shape> Shape::create_uncacheable_dictionary_transition()
39
0
{
40
0
    auto new_shape = heap().allocate_without_realm<Shape>(m_realm);
41
0
    new_shape->m_dictionary = true;
42
0
    new_shape->m_cacheable = true;
43
0
    new_shape->m_prototype = m_prototype;
44
0
    invalidate_prototype_if_needed_for_new_prototype(new_shape);
45
0
    ensure_property_table();
46
0
    new_shape->ensure_property_table();
47
0
    (*new_shape->m_property_table) = *m_property_table;
48
0
    new_shape->m_property_count = new_shape->m_property_table->size();
49
0
    return new_shape;
50
0
}
51
52
GCPtr<Shape> Shape::get_or_prune_cached_forward_transition(TransitionKey const& key)
53
0
{
54
0
    if (m_is_prototype_shape)
55
0
        return nullptr;
56
0
    if (!m_forward_transitions)
57
0
        return nullptr;
58
0
    auto it = m_forward_transitions->find(key);
59
0
    if (it == m_forward_transitions->end())
60
0
        return nullptr;
61
0
    if (!it->value) {
62
        // The cached forward transition has gone stale (from garbage collection). Prune it.
63
0
        m_forward_transitions->remove(it);
64
0
        return nullptr;
65
0
    }
66
0
    return it->value.ptr();
67
0
}
68
69
GCPtr<Shape> Shape::get_or_prune_cached_delete_transition(StringOrSymbol const& key)
70
0
{
71
0
    if (m_is_prototype_shape)
72
0
        return nullptr;
73
0
    if (!m_delete_transitions)
74
0
        return nullptr;
75
0
    auto it = m_delete_transitions->find(key);
76
0
    if (it == m_delete_transitions->end())
77
0
        return nullptr;
78
0
    if (!it->value) {
79
        // The cached delete transition has gone stale (from garbage collection). Prune it.
80
0
        m_delete_transitions->remove(it);
81
0
        return nullptr;
82
0
    }
83
0
    return it->value.ptr();
84
0
}
85
86
GCPtr<Shape> Shape::get_or_prune_cached_prototype_transition(Object* prototype)
87
0
{
88
0
    if (m_is_prototype_shape)
89
0
        return nullptr;
90
0
    if (!m_prototype_transitions)
91
0
        return nullptr;
92
0
    auto it = m_prototype_transitions->find(prototype);
93
0
    if (it == m_prototype_transitions->end())
94
0
        return nullptr;
95
0
    if (!it->value) {
96
        // The cached prototype transition has gone stale (from garbage collection). Prune it.
97
0
        m_prototype_transitions->remove(it);
98
0
        return nullptr;
99
0
    }
100
0
    return it->value.ptr();
101
0
}
102
103
NonnullGCPtr<Shape> Shape::create_put_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
104
0
{
105
0
    TransitionKey key { property_key, attributes };
106
0
    if (auto existing_shape = get_or_prune_cached_forward_transition(key))
107
0
        return *existing_shape;
108
0
    auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, attributes, TransitionType::Put);
109
0
    invalidate_prototype_if_needed_for_new_prototype(new_shape);
110
0
    if (!m_is_prototype_shape) {
111
0
        if (!m_forward_transitions)
112
0
            m_forward_transitions = make<HashMap<TransitionKey, WeakPtr<Shape>>>();
113
0
        m_forward_transitions->set(key, new_shape.ptr());
114
0
    }
115
0
    return new_shape;
116
0
}
117
118
NonnullGCPtr<Shape> Shape::create_configure_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
119
0
{
120
0
    TransitionKey key { property_key, attributes };
121
0
    if (auto existing_shape = get_or_prune_cached_forward_transition(key))
122
0
        return *existing_shape;
123
0
    auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, attributes, TransitionType::Configure);
124
0
    invalidate_prototype_if_needed_for_new_prototype(new_shape);
125
0
    if (!m_is_prototype_shape) {
126
0
        if (!m_forward_transitions)
127
0
            m_forward_transitions = make<HashMap<TransitionKey, WeakPtr<Shape>>>();
128
0
        m_forward_transitions->set(key, new_shape.ptr());
129
0
    }
130
0
    return new_shape;
131
0
}
132
133
NonnullGCPtr<Shape> Shape::create_prototype_transition(Object* new_prototype)
134
0
{
135
0
    if (new_prototype)
136
0
        new_prototype->convert_to_prototype_if_needed();
137
0
    if (auto existing_shape = get_or_prune_cached_prototype_transition(new_prototype))
138
0
        return *existing_shape;
139
0
    auto new_shape = heap().allocate_without_realm<Shape>(*this, new_prototype);
140
0
    invalidate_prototype_if_needed_for_new_prototype(new_shape);
141
0
    if (!m_is_prototype_shape) {
142
0
        if (!m_prototype_transitions)
143
0
            m_prototype_transitions = make<HashMap<GCPtr<Object>, WeakPtr<Shape>>>();
144
0
        m_prototype_transitions->set(new_prototype, new_shape.ptr());
145
0
    }
146
0
    return new_shape;
147
0
}
148
149
Shape::Shape(Realm& realm)
150
0
    : m_realm(realm)
151
0
{
152
0
}
153
154
Shape::Shape(Shape& previous_shape, StringOrSymbol const& property_key, PropertyAttributes attributes, TransitionType transition_type)
155
0
    : m_realm(previous_shape.m_realm)
156
0
    , m_previous(&previous_shape)
157
0
    , m_property_key(property_key)
158
0
    , m_prototype(previous_shape.m_prototype)
159
0
    , m_property_count(transition_type == TransitionType::Put ? previous_shape.m_property_count + 1 : previous_shape.m_property_count)
160
0
    , m_attributes(attributes)
161
0
    , m_transition_type(transition_type)
162
0
{
163
0
}
164
165
Shape::Shape(Shape& previous_shape, StringOrSymbol const& property_key, TransitionType transition_type)
166
0
    : m_realm(previous_shape.m_realm)
167
0
    , m_previous(&previous_shape)
168
0
    , m_property_key(property_key)
169
0
    , m_prototype(previous_shape.m_prototype)
170
0
    , m_property_count(previous_shape.m_property_count - 1)
171
0
    , m_transition_type(transition_type)
172
0
{
173
0
    VERIFY(transition_type == TransitionType::Delete);
174
0
}
175
176
Shape::Shape(Shape& previous_shape, Object* new_prototype)
177
0
    : m_realm(previous_shape.m_realm)
178
0
    , m_previous(&previous_shape)
179
0
    , m_prototype(new_prototype)
180
0
    , m_property_count(previous_shape.m_property_count)
181
0
    , m_transition_type(TransitionType::Prototype)
182
0
{
183
0
}
184
185
void Shape::visit_edges(Cell::Visitor& visitor)
186
0
{
187
0
    Base::visit_edges(visitor);
188
0
    visitor.visit(m_realm);
189
0
    visitor.visit(m_prototype);
190
0
    visitor.visit(m_previous);
191
0
    m_property_key.visit_edges(visitor);
192
193
    // NOTE: We don't need to mark the keys in the property table, since they are guaranteed
194
    //       to also be marked by the chain of shapes leading up to this one.
195
196
0
    visitor.ignore(m_prototype_transitions);
197
198
    // FIXME: The forward transition keys should be weak, but we have to mark them for now in case they go stale.
199
0
    if (m_forward_transitions) {
200
0
        for (auto& it : *m_forward_transitions)
201
0
            it.key.property_key.visit_edges(visitor);
202
0
    }
203
204
    // FIXME: The delete transition keys should be weak, but we have to mark them for now in case they go stale.
205
0
    if (m_delete_transitions) {
206
0
        for (auto& it : *m_delete_transitions)
207
0
            it.key.visit_edges(visitor);
208
0
    }
209
210
0
    visitor.visit(m_prototype_chain_validity);
211
0
}
212
213
Optional<PropertyMetadata> Shape::lookup(StringOrSymbol const& property_key) const
214
0
{
215
0
    if (m_property_count == 0)
216
0
        return {};
217
0
    auto property = property_table().get(property_key);
218
0
    if (!property.has_value())
219
0
        return {};
220
0
    return property.copy();
221
0
}
222
223
FLATTEN OrderedHashMap<StringOrSymbol, PropertyMetadata> const& Shape::property_table() const
224
0
{
225
0
    ensure_property_table();
226
0
    return *m_property_table;
227
0
}
228
229
void Shape::ensure_property_table() const
230
0
{
231
0
    if (m_property_table)
232
0
        return;
233
0
    m_property_table = make<OrderedHashMap<StringOrSymbol, PropertyMetadata>>();
234
235
0
    u32 next_offset = 0;
236
237
0
    Vector<Shape const&, 64> transition_chain;
238
0
    transition_chain.append(*this);
239
0
    for (auto shape = m_previous; shape; shape = shape->m_previous) {
240
0
        if (shape->m_property_table) {
241
0
            *m_property_table = *shape->m_property_table;
242
0
            next_offset = shape->m_property_count;
243
0
            break;
244
0
        }
245
0
        transition_chain.append(*shape);
246
0
    }
247
248
0
    for (auto const& shape : transition_chain.in_reverse()) {
249
0
        if (!shape.m_property_key.is_valid()) {
250
            // Ignore prototype transitions as they don't affect the key map.
251
0
            continue;
252
0
        }
253
0
        if (shape.m_transition_type == TransitionType::Put) {
254
0
            m_property_table->set(shape.m_property_key, { next_offset++, shape.m_attributes });
255
0
        } else if (shape.m_transition_type == TransitionType::Configure) {
256
0
            auto it = m_property_table->find(shape.m_property_key);
257
0
            VERIFY(it != m_property_table->end());
258
0
            it->value.attributes = shape.m_attributes;
259
0
        } else if (shape.m_transition_type == TransitionType::Delete) {
260
0
            auto remove_it = m_property_table->find(shape.m_property_key);
261
0
            VERIFY(remove_it != m_property_table->end());
262
0
            auto removed_offset = remove_it->value.offset;
263
0
            m_property_table->remove(remove_it);
264
0
            for (auto& it : *m_property_table) {
265
0
                if (it.value.offset > removed_offset)
266
0
                    --it.value.offset;
267
0
            }
268
0
            --next_offset;
269
0
        }
270
0
    }
271
0
}
272
273
NonnullGCPtr<Shape> Shape::create_delete_transition(StringOrSymbol const& property_key)
274
0
{
275
0
    if (auto existing_shape = get_or_prune_cached_delete_transition(property_key))
276
0
        return *existing_shape;
277
0
    auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, TransitionType::Delete);
278
0
    invalidate_prototype_if_needed_for_new_prototype(new_shape);
279
0
    if (!m_delete_transitions)
280
0
        m_delete_transitions = make<HashMap<StringOrSymbol, WeakPtr<Shape>>>();
281
0
    m_delete_transitions->set(property_key, new_shape.ptr());
282
0
    return new_shape;
283
0
}
284
285
void Shape::add_property_without_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
286
0
{
287
0
    VERIFY(property_key.is_valid());
288
0
    ensure_property_table();
289
0
    if (m_property_table->set(property_key, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry) {
290
0
        VERIFY(m_property_count < NumericLimits<u32>::max());
291
0
        ++m_property_count;
292
0
    }
293
0
}
294
295
FLATTEN void Shape::add_property_without_transition(PropertyKey const& property_key, PropertyAttributes attributes)
296
0
{
297
0
    VERIFY(property_key.is_valid());
298
0
    add_property_without_transition(property_key.to_string_or_symbol(), attributes);
299
0
}
300
301
void Shape::set_property_attributes_without_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
302
0
{
303
0
    VERIFY(is_dictionary());
304
0
    VERIFY(m_property_table);
305
0
    auto it = m_property_table->find(property_key);
306
0
    VERIFY(it != m_property_table->end());
307
0
    it->value.attributes = attributes;
308
0
    m_property_table->set(property_key, it->value);
309
0
}
310
311
void Shape::remove_property_without_transition(StringOrSymbol const& property_key, u32 offset)
312
0
{
313
0
    VERIFY(is_uncacheable_dictionary());
314
0
    VERIFY(m_property_table);
315
0
    if (m_property_table->remove(property_key))
316
0
        --m_property_count;
317
0
    for (auto& it : *m_property_table) {
318
0
        VERIFY(it.value.offset != offset);
319
0
        if (it.value.offset > offset)
320
0
            --it.value.offset;
321
0
    }
322
0
}
323
324
NonnullGCPtr<Shape> Shape::create_for_prototype(NonnullGCPtr<Realm> realm, GCPtr<Object> prototype)
325
0
{
326
0
    auto new_shape = realm->heap().allocate_without_realm<Shape>(realm);
327
0
    s_all_prototype_shapes.set(new_shape);
328
0
    new_shape->m_is_prototype_shape = true;
329
0
    new_shape->m_prototype = prototype;
330
0
    new_shape->m_prototype_chain_validity = realm->heap().allocate_without_realm<PrototypeChainValidity>();
331
0
    return new_shape;
332
0
}
333
334
NonnullGCPtr<Shape> Shape::clone_for_prototype()
335
0
{
336
0
    VERIFY(!m_is_prototype_shape);
337
0
    VERIFY(!m_prototype_chain_validity);
338
0
    auto new_shape = heap().allocate_without_realm<Shape>(m_realm);
339
0
    s_all_prototype_shapes.set(new_shape);
340
0
    new_shape->m_is_prototype_shape = true;
341
0
    new_shape->m_prototype = m_prototype;
342
0
    ensure_property_table();
343
0
    new_shape->ensure_property_table();
344
0
    (*new_shape->m_property_table) = *m_property_table;
345
0
    new_shape->m_property_count = new_shape->m_property_table->size();
346
0
    new_shape->m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
347
0
    return new_shape;
348
0
}
349
350
void Shape::set_prototype_without_transition(Object* new_prototype)
351
0
{
352
0
    VERIFY(new_prototype);
353
0
    new_prototype->convert_to_prototype_if_needed();
354
0
    m_prototype = new_prototype;
355
0
}
356
357
void Shape::set_prototype_shape()
358
0
{
359
0
    VERIFY(!m_is_prototype_shape);
360
0
    s_all_prototype_shapes.set(this);
361
0
    m_is_prototype_shape = true;
362
0
    m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
363
0
}
364
365
void Shape::invalidate_prototype_if_needed_for_new_prototype(NonnullGCPtr<Shape> new_prototype_shape)
366
0
{
367
0
    if (!m_is_prototype_shape)
368
0
        return;
369
0
    new_prototype_shape->set_prototype_shape();
370
0
    m_prototype_chain_validity->set_valid(false);
371
372
0
    invalidate_all_prototype_chains_leading_to_this();
373
0
}
374
375
void Shape::invalidate_all_prototype_chains_leading_to_this()
376
0
{
377
0
    HashTable<Shape*> shapes_to_invalidate;
378
0
    for (auto& candidate : s_all_prototype_shapes) {
379
0
        if (!candidate->m_prototype)
380
0
            continue;
381
0
        for (auto* current_prototype_shape = &candidate->m_prototype->shape(); current_prototype_shape; current_prototype_shape = current_prototype_shape->prototype() ? &current_prototype_shape->prototype()->shape() : nullptr) {
382
0
            if (current_prototype_shape == this) {
383
0
                VERIFY(candidate->m_is_prototype_shape);
384
0
                shapes_to_invalidate.set(candidate);
385
0
                break;
386
0
            }
387
0
        }
388
0
    }
389
0
    if (shapes_to_invalidate.is_empty())
390
0
        return;
391
0
    for (auto* shape : shapes_to_invalidate) {
392
0
        shape->m_prototype_chain_validity->set_valid(false);
393
0
        shape->m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
394
0
    }
395
0
}
396
397
}