Coverage Report

Created: 2026-09-14 07:15

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
17.5k
{
20
17.5k
    if (m_is_prototype_shape)
21
1.54k
        s_all_prototype_shapes.remove(this);
22
17.5k
}
23
24
NonnullGCPtr<Shape> Shape::create_cacheable_dictionary_transition()
25
51
{
26
51
    auto new_shape = heap().allocate_without_realm<Shape>(m_realm);
27
51
    new_shape->m_dictionary = true;
28
51
    new_shape->m_cacheable = true;
29
51
    new_shape->m_prototype = m_prototype;
30
51
    invalidate_prototype_if_needed_for_new_prototype(new_shape);
31
51
    ensure_property_table();
32
51
    new_shape->ensure_property_table();
33
51
    (*new_shape->m_property_table) = *m_property_table;
34
51
    new_shape->m_property_count = new_shape->m_property_table->size();
35
51
    return new_shape;
36
51
}
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
39.2k
{
54
39.2k
    if (m_is_prototype_shape)
55
1.12k
        return nullptr;
56
38.1k
    if (!m_forward_transitions)
57
14.2k
        return nullptr;
58
23.8k
    auto it = m_forward_transitions->find(key);
59
23.8k
    if (it == m_forward_transitions->end())
60
1.02k
        return nullptr;
61
22.8k
    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
22.8k
    return it->value.ptr();
67
22.8k
}
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
12.6k
{
88
12.6k
    if (m_is_prototype_shape)
89
0
        return nullptr;
90
12.6k
    if (!m_prototype_transitions)
91
102
        return nullptr;
92
12.5k
    auto it = m_prototype_transitions->find(prototype);
93
12.5k
    if (it == m_prototype_transitions->end())
94
372
        return nullptr;
95
12.1k
    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
12.1k
    return it->value.ptr();
101
12.1k
}
102
103
NonnullGCPtr<Shape> Shape::create_put_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
104
38.9k
{
105
38.9k
    TransitionKey key { property_key, attributes };
106
38.9k
    if (auto existing_shape = get_or_prune_cached_forward_transition(key))
107
22.8k
        return *existing_shape;
108
16.1k
    auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, attributes, TransitionType::Put);
109
16.1k
    invalidate_prototype_if_needed_for_new_prototype(new_shape);
110
16.1k
    if (!m_is_prototype_shape) {
111
15.0k
        if (!m_forward_transitions)
112
14.0k
            m_forward_transitions = make<HashMap<TransitionKey, WeakPtr<Shape>>>();
113
15.0k
        m_forward_transitions->set(key, new_shape.ptr());
114
15.0k
    }
115
16.1k
    return new_shape;
116
38.9k
}
117
118
NonnullGCPtr<Shape> Shape::create_configure_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
119
255
{
120
255
    TransitionKey key { property_key, attributes };
121
255
    if (auto existing_shape = get_or_prune_cached_forward_transition(key))
122
51
        return *existing_shape;
123
204
    auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, attributes, TransitionType::Configure);
124
204
    invalidate_prototype_if_needed_for_new_prototype(new_shape);
125
204
    if (!m_is_prototype_shape) {
126
204
        if (!m_forward_transitions)
127
204
            m_forward_transitions = make<HashMap<TransitionKey, WeakPtr<Shape>>>();
128
204
        m_forward_transitions->set(key, new_shape.ptr());
129
204
    }
130
204
    return new_shape;
131
255
}
132
133
NonnullGCPtr<Shape> Shape::create_prototype_transition(Object* new_prototype)
134
12.6k
{
135
12.6k
    if (new_prototype)
136
12.6k
        new_prototype->convert_to_prototype_if_needed();
137
12.6k
    if (auto existing_shape = get_or_prune_cached_prototype_transition(new_prototype))
138
12.1k
        return *existing_shape;
139
474
    auto new_shape = heap().allocate_without_realm<Shape>(*this, new_prototype);
140
474
    invalidate_prototype_if_needed_for_new_prototype(new_shape);
141
474
    if (!m_is_prototype_shape) {
142
474
        if (!m_prototype_transitions)
143
102
            m_prototype_transitions = make<HashMap<GCPtr<Object>, WeakPtr<Shape>>>();
144
474
        m_prototype_transitions->set(new_prototype, new_shape.ptr());
145
474
    }
146
474
    return new_shape;
147
12.6k
}
148
149
Shape::Shape(Realm& realm)
150
729
    : m_realm(realm)
151
729
{
152
729
}
153
154
Shape::Shape(Shape& previous_shape, StringOrSymbol const& property_key, PropertyAttributes attributes, TransitionType transition_type)
155
16.3k
    : m_realm(previous_shape.m_realm)
156
16.3k
    , m_previous(&previous_shape)
157
16.3k
    , m_property_key(property_key)
158
16.3k
    , m_prototype(previous_shape.m_prototype)
159
16.3k
    , m_property_count(transition_type == TransitionType::Put ? previous_shape.m_property_count + 1 : previous_shape.m_property_count)
160
16.3k
    , m_attributes(attributes)
161
16.3k
    , m_transition_type(transition_type)
162
16.3k
{
163
16.3k
}
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
474
    : m_realm(previous_shape.m_realm)
178
474
    , m_previous(&previous_shape)
179
474
    , m_prototype(new_prototype)
180
474
    , m_property_count(previous_shape.m_property_count)
181
474
    , m_transition_type(TransitionType::Prototype)
182
474
{
183
474
}
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
84.0k
{
215
84.0k
    if (m_property_count == 0)
216
34.1k
        return {};
217
49.9k
    auto property = property_table().get(property_key);
218
49.9k
    if (!property.has_value())
219
49.0k
        return {};
220
867
    return property.copy();
221
49.9k
}
222
223
FLATTEN OrderedHashMap<StringOrSymbol, PropertyMetadata> const& Shape::property_table() const
224
49.9k
{
225
49.9k
    ensure_property_table();
226
49.9k
    return *m_property_table;
227
49.9k
}
228
229
void Shape::ensure_property_table() const
230
51.1k
{
231
51.1k
    if (m_property_table)
232
35.1k
        return;
233
16.0k
    m_property_table = make<OrderedHashMap<StringOrSymbol, PropertyMetadata>>();
234
235
16.0k
    u32 next_offset = 0;
236
237
16.0k
    Vector<Shape const&, 64> transition_chain;
238
16.0k
    transition_chain.append(*this);
239
17.5k
    for (auto shape = m_previous; shape; shape = shape->m_previous) {
240
16.1k
        if (shape->m_property_table) {
241
14.6k
            *m_property_table = *shape->m_property_table;
242
14.6k
            next_offset = shape->m_property_count;
243
14.6k
            break;
244
14.6k
        }
245
1.53k
        transition_chain.append(*shape);
246
1.53k
    }
247
248
17.5k
    for (auto const& shape : transition_chain.in_reverse()) {
249
17.5k
        if (!shape.m_property_key.is_valid()) {
250
            // Ignore prototype transitions as they don't affect the key map.
251
2.15k
            continue;
252
2.15k
        }
253
15.4k
        if (shape.m_transition_type == TransitionType::Put) {
254
15.3k
            m_property_table->set(shape.m_property_key, { next_offset++, shape.m_attributes });
255
15.3k
        } else if (shape.m_transition_type == TransitionType::Configure) {
256
51
            auto it = m_property_table->find(shape.m_property_key);
257
51
            VERIFY(it != m_property_table->end());
258
51
            it->value.attributes = shape.m_attributes;
259
51
        } 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
15.4k
    }
271
16.0k
}
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
306
{
287
306
    VERIFY(property_key.is_valid());
288
306
    ensure_property_table();
289
306
    if (m_property_table->set(property_key, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry) {
290
306
        VERIFY(m_property_count < NumericLimits<u32>::max());
291
306
        ++m_property_count;
292
306
    }
293
306
}
294
295
FLATTEN void Shape::add_property_without_transition(PropertyKey const& property_key, PropertyAttributes attributes)
296
102
{
297
102
    VERIFY(property_key.is_valid());
298
102
    add_property_without_transition(property_key.to_string_or_symbol(), attributes);
299
102
}
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
423
{
336
423
    VERIFY(!m_is_prototype_shape);
337
423
    VERIFY(!m_prototype_chain_validity);
338
423
    auto new_shape = heap().allocate_without_realm<Shape>(m_realm);
339
423
    s_all_prototype_shapes.set(new_shape);
340
423
    new_shape->m_is_prototype_shape = true;
341
423
    new_shape->m_prototype = m_prototype;
342
423
    ensure_property_table();
343
423
    new_shape->ensure_property_table();
344
423
    (*new_shape->m_property_table) = *m_property_table;
345
423
    new_shape->m_property_count = new_shape->m_property_table->size();
346
423
    new_shape->m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
347
423
    return new_shape;
348
423
}
349
350
void Shape::set_prototype_without_transition(Object* new_prototype)
351
102
{
352
102
    VERIFY(new_prototype);
353
102
    new_prototype->convert_to_prototype_if_needed();
354
102
    m_prototype = new_prototype;
355
102
}
356
357
void Shape::set_prototype_shape()
358
1.12k
{
359
1.12k
    VERIFY(!m_is_prototype_shape);
360
1.12k
    s_all_prototype_shapes.set(this);
361
1.12k
    m_is_prototype_shape = true;
362
1.12k
    m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
363
1.12k
}
364
365
void Shape::invalidate_prototype_if_needed_for_new_prototype(NonnullGCPtr<Shape> new_prototype_shape)
366
16.9k
{
367
16.9k
    if (!m_is_prototype_shape)
368
15.7k
        return;
369
1.12k
    new_prototype_shape->set_prototype_shape();
370
1.12k
    m_prototype_chain_validity->set_valid(false);
371
372
1.12k
    invalidate_all_prototype_chains_leading_to_this();
373
1.12k
}
374
375
void Shape::invalidate_all_prototype_chains_leading_to_this()
376
1.12k
{
377
1.12k
    HashTable<Shape*> shapes_to_invalidate;
378
15.6k
    for (auto& candidate : s_all_prototype_shapes) {
379
15.6k
        if (!candidate->m_prototype)
380
6.88k
            continue;
381
12.3k
        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
8.77k
            if (current_prototype_shape == this) {
383
5.15k
                VERIFY(candidate->m_is_prototype_shape);
384
5.15k
                shapes_to_invalidate.set(candidate);
385
5.15k
                break;
386
5.15k
            }
387
8.77k
        }
388
8.77k
    }
389
1.12k
    if (shapes_to_invalidate.is_empty())
390
408
        return;
391
5.15k
    for (auto* shape : shapes_to_invalidate) {
392
5.15k
        shape->m_prototype_chain_validity->set_valid(false);
393
5.15k
        shape->m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
394
5.15k
    }
395
714
}
396
397
}