1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <qv4internalclass_p.h>
5#include <qv4string_p.h>
6#include <qv4engine_p.h>
7#include <qv4identifierhash_p.h>
8#include "qv4object_p.h"
9#include "qv4value_p.h"
10#include "qv4mm_p.h"
11#include <private/qprimefornumbits_p.h>
12
13QT_BEGIN_NAMESPACE
14
15namespace QV4 {
16
17PropertyHashData::PropertyHashData(int numBits)
18 : refCount(1)
19 , size(0)
20 , numBits(numBits)
21{
22 alloc = qPrimeForNumBits(numBits);
23 entries = (PropertyHash::Entry *)malloc(size: alloc*sizeof(PropertyHash::Entry));
24 memset(s: entries, c: 0, n: alloc*sizeof(PropertyHash::Entry));
25}
26
27void PropertyHash::addEntry(const PropertyHash::Entry &entry, int classSize)
28{
29 // fill up to max 50%
30 bool grow = (d->alloc <= d->size*2);
31
32 if (classSize < d->size || grow)
33 detach(grow, classSize);
34
35 uint idx = entry.identifier.id() % d->alloc;
36 while (d->entries[idx].identifier.isValid()) {
37 ++idx;
38 idx %= d->alloc;
39 }
40 d->entries[idx] = entry;
41 ++d->size;
42}
43
44void PropertyHash::detach(bool grow, int classSize)
45{
46 if (d->refCount == 1 && !grow)
47 return;
48
49 PropertyHashData *dd = new PropertyHashData(grow ? d->numBits + 1 : d->numBits);
50 for (int i = 0; i < d->alloc; ++i) {
51 const Entry &e = d->entries[i];
52 if (!e.identifier.isValid() || e.index >= static_cast<unsigned>(classSize))
53 continue;
54 uint idx = e.identifier.id() % dd->alloc;
55 while (dd->entries[idx].identifier.isValid()) {
56 ++idx;
57 idx %= dd->alloc;
58 }
59 dd->entries[idx] = e;
60 }
61 dd->size = classSize;
62 if (!--d->refCount)
63 delete d;
64 d = dd;
65}
66
67
68SharedInternalClassDataPrivate<PropertyKey>::SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate<PropertyKey> &other)
69 : refcount(1),
70 engine(other.engine),
71 data(nullptr)
72{
73 if (other.alloc()) {
74 const uint s = other.size();
75 data = MemberData::allocate(e: engine, n: other.alloc(), old: other.data);
76 setSize(s);
77 }
78}
79
80SharedInternalClassDataPrivate<PropertyKey>::SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate<PropertyKey> &other,
81 uint pos, PropertyKey value)
82 : refcount(1),
83 engine(other.engine)
84{
85 data = MemberData::allocate(e: engine, n: other.alloc(), old: nullptr);
86 memcpy(dest: data, src: other.data, n: sizeof(Heap::MemberData) - sizeof(Value) + pos*sizeof(Value));
87 data->values.size = pos + 1;
88 data->values.set(e: engine, index: pos, v: Value::fromReturnedValue(val: value.id()));
89}
90
91void SharedInternalClassDataPrivate<PropertyKey>::grow()
92{
93 const uint a = alloc() * 2;
94 const uint s = size();
95 data = MemberData::allocate(e: engine, n: a, old: data);
96 setSize(s);
97 Q_ASSERT(alloc() >= a);
98}
99
100uint SharedInternalClassDataPrivate<PropertyKey>::alloc() const
101{
102 return data ? data->values.alloc : 0;
103}
104
105uint SharedInternalClassDataPrivate<PropertyKey>::size() const
106{
107 return data ? data->values.size : 0;
108}
109
110void SharedInternalClassDataPrivate<PropertyKey>::setSize(uint s)
111{
112 Q_ASSERT(data && s <= alloc());
113 data->values.size = s;
114}
115
116PropertyKey SharedInternalClassDataPrivate<PropertyKey>::at(uint i) const
117{
118 Q_ASSERT(data && i < size());
119 return PropertyKey::fromId(id: data->values.values[i].rawValue());
120}
121
122void SharedInternalClassDataPrivate<PropertyKey>::set(uint i, PropertyKey t)
123{
124 Q_ASSERT(data && i < size());
125 data->values.values[i].rawValueRef() = t.id();
126}
127
128void SharedInternalClassDataPrivate<PropertyKey>::mark(MarkStack *s)
129{
130 if (data)
131 data->mark(markStack: s);
132}
133
134SharedInternalClassDataPrivate<PropertyAttributes>::SharedInternalClassDataPrivate(
135 const SharedInternalClassDataPrivate<PropertyAttributes> &other, uint pos,
136 PropertyAttributes value)
137 : refcount(1),
138 m_alloc(qMin(a: other.m_alloc, b: pos + 8)),
139 m_size(pos + 1),
140 m_engine(other.m_engine)
141{
142 Q_ASSERT(m_size <= m_alloc);
143 Q_ASSERT(m_alloc > 0);
144
145 m_engine->memoryManager->changeUnmanagedHeapSizeUsage(delta: m_alloc * sizeof(PropertyAttributes));
146 const PropertyAttributes *source = other.m_alloc > NumAttributesInPointer
147 ? other.m_data
148 : other.m_inlineData;
149 PropertyAttributes *target;
150 if (m_alloc > NumAttributesInPointer)
151 m_data = target = new PropertyAttributes[m_alloc];
152 else
153 target = m_inlineData;
154
155 memcpy(dest: target, src: source, n: (m_size - 1) * sizeof(PropertyAttributes));
156 target[pos] = value;
157}
158
159SharedInternalClassDataPrivate<PropertyAttributes>::SharedInternalClassDataPrivate(
160 const SharedInternalClassDataPrivate<PropertyAttributes> &other)
161 : refcount(1),
162 m_alloc(other.m_alloc),
163 m_size(other.m_size),
164 m_engine(other.m_engine)
165{
166 m_engine->memoryManager->changeUnmanagedHeapSizeUsage(delta: m_alloc * sizeof(PropertyAttributes));
167 if (m_alloc > NumAttributesInPointer) {
168 m_data = new PropertyAttributes[m_alloc];
169 memcpy(dest: m_data, src: other.m_data, n: m_size*sizeof(PropertyAttributes));
170 } else if (m_alloc > 0) {
171 memcpy(dest: m_inlineData, src: other.m_inlineData, n: m_alloc * sizeof(PropertyAttributes));
172 } else {
173 m_data = nullptr;
174 }
175}
176
177SharedInternalClassDataPrivate<PropertyAttributes>::~SharedInternalClassDataPrivate()
178{
179 m_engine->memoryManager->changeUnmanagedHeapSizeUsage(
180 delta: -qptrdiff(m_alloc * sizeof(PropertyAttributes)));
181 if (m_alloc > NumAttributesInPointer)
182 delete [] m_data;
183}
184
185void SharedInternalClassDataPrivate<PropertyAttributes>::grow() {
186 uint alloc;
187 if (!m_alloc) {
188 alloc = NumAttributesInPointer;
189 m_engine->memoryManager->changeUnmanagedHeapSizeUsage(delta: alloc * sizeof(PropertyAttributes));
190 } else {
191 // yes, signed. We don't want to deal with stuff > 2G
192 const uint currentSize = m_alloc * sizeof(PropertyAttributes);
193 if (currentSize < uint(std::numeric_limits<int>::max() / 2))
194 alloc = m_alloc * 2;
195 else
196 alloc = std::numeric_limits<int>::max() / sizeof(PropertyAttributes);
197
198 m_engine->memoryManager->changeUnmanagedHeapSizeUsage(
199 delta: (alloc - m_alloc) * sizeof(PropertyAttributes));
200 }
201
202 if (alloc > NumAttributesInPointer) {
203 auto *n = new PropertyAttributes[alloc];
204 if (m_alloc > NumAttributesInPointer) {
205 memcpy(dest: n, src: m_data, n: m_alloc * sizeof(PropertyAttributes));
206 delete [] m_data;
207 } else if (m_alloc > 0) {
208 memcpy(dest: n, src: m_inlineData, n: m_alloc * sizeof(PropertyAttributes));
209 }
210 m_data = n;
211 }
212 m_alloc = alloc;
213}
214
215namespace Heap {
216
217void InternalClass::init(ExecutionEngine *engine)
218{
219// InternalClass is automatically zeroed during allocation:
220// prototype = nullptr;
221// parent = nullptr;
222// size = 0;
223// numRedundantTransitions = 0;
224// flags = 0;
225
226 Base::init();
227 new (&propertyTable) PropertyHash();
228 new (&nameMap) SharedInternalClassData<PropertyKey>(engine);
229 new (&propertyData) SharedInternalClassData<PropertyAttributes>(engine);
230 new (&transitions) std::vector<Transition>();
231
232 this->engine = engine;
233 vtable = QV4::InternalClass::staticVTable();
234 protoId = engine->newProtoId();
235
236 // Also internal classes need an internal class pointer. Simply make it point to itself
237 internalClass.set(e: engine, newVal: this);
238}
239
240
241void InternalClass::init(Heap::InternalClass *other)
242{
243 Base::init();
244 new (&propertyTable) PropertyHash(other->propertyTable);
245 new (&nameMap) SharedInternalClassData<PropertyKey>(other->nameMap);
246 new (&propertyData) SharedInternalClassData<PropertyAttributes>(other->propertyData);
247 new (&transitions) std::vector<Transition>();
248
249 engine = other->engine;
250 vtable = other->vtable;
251 prototype = other->prototype;
252 parent = other;
253 size = other->size;
254 numRedundantTransitions = other->numRedundantTransitions;
255 flags = other->flags;
256 protoId = engine->newProtoId();
257
258 internalClass.set(e: engine, newVal: other->internalClass);
259}
260
261void InternalClass::destroy()
262{
263 for (const auto &t : transitions) {
264 if (t.lookup) {
265#ifndef QT_NO_DEBUG
266 Q_ASSERT(t.lookup->parent == this);
267#endif
268 t.lookup->parent = nullptr;
269 }
270 }
271
272 if (parent && parent->engine && parent->isMarked())
273 parent->removeChildEntry(child: this);
274
275 propertyTable.~PropertyHash();
276 nameMap.~SharedInternalClassData<PropertyKey>();
277 propertyData.~SharedInternalClassData<PropertyAttributes>();
278 transitions.~vector<Transition>();
279 engine = nullptr;
280 Base::destroy();
281}
282
283QString InternalClass::keyAt(uint index) const
284{
285 return nameMap.at(i: index).toQString();
286}
287
288void InternalClass::changeMember(QV4::Object *object, PropertyKey id, PropertyAttributes data, InternalClassEntry *entry)
289{
290 Q_ASSERT(id.isStringOrSymbol());
291
292 Heap::InternalClass *oldClass = object->internalClass();
293 Heap::InternalClass *newClass = oldClass->changeMember(identifier: id, data, entry);
294 object->setInternalClass(newClass);
295}
296
297InternalClassTransition &InternalClass::lookupOrInsertTransition(const InternalClassTransition &t)
298{
299 std::vector<Transition>::iterator it = std::lower_bound(first: transitions.begin(), last: transitions.end(), val: t);
300 if (it != transitions.end() && *it == t) {
301 return *it;
302 } else {
303 it = transitions.insert(position: it, x: t);
304 return *it;
305 }
306}
307
308static void addDummyEntry(InternalClass *newClass, PropertyHash::Entry e)
309{
310 // add a dummy entry, since we need two entries for accessors
311 newClass->propertyTable.addEntry(entry: e, classSize: newClass->size);
312 newClass->nameMap.add(pos: newClass->size, value: PropertyKey::invalid());
313 newClass->propertyData.add(pos: newClass->size, value: PropertyAttributes());
314 ++newClass->size;
315}
316
317static PropertyAttributes attributesFromFlags(int flags)
318{
319 PropertyAttributes attributes;
320 attributes.m_all = uchar(flags);
321 return attributes;
322}
323
324static Heap::InternalClass *cleanInternalClass(Heap::InternalClass *orig)
325{
326 if (++orig->numRedundantTransitions < Heap::InternalClass::MaxRedundantTransitions)
327 return orig;
328
329 // We will generally add quite a few transitions here. We have 255 redundant ones.
330 // We can expect at least as many significant ones in addition.
331 std::vector<InternalClassTransition> transitions;
332
333 Scope scope(orig->engine);
334 Scoped<QV4::InternalClass> child(scope, orig);
335
336 {
337 quint8 remainingRedundantTransitions = orig->numRedundantTransitions;
338 QSet<PropertyKey> properties;
339 int structureChanges = 0;
340
341 Scoped<QV4::InternalClass> parent(scope, orig->parent);
342 while (parent && remainingRedundantTransitions > 0) {
343 Q_ASSERT(child->d() != scope.engine->classes[ExecutionEngine::Class_Empty]);
344 const auto it = std::find_if(
345 first: parent->d()->transitions.begin(), last: parent->d()->transitions.end(),
346 pred: [&child](const InternalClassTransition &t) {
347 return child->d() == t.lookup;
348 });
349 Q_ASSERT(it != parent->d()->transitions.end());
350
351 if (it->flags & InternalClassTransition::StructureChange) {
352 // A structural change. Each kind of structural change has to be recorded only once.
353 if ((structureChanges & it->flags) != it->flags) {
354 transitions.push_back(x: *it);
355 structureChanges |= it->flags;
356 } else {
357 --remainingRedundantTransitions;
358 }
359 } else if (!properties.contains(value: it->id)) {
360 // We only need the final state of the property.
361 properties.insert(value: it->id);
362
363 // Property removal creates _two_ redundant transitions.
364 // We don't have to replay either, but numRedundantTransitions only records one.
365 if (it->flags != 0)
366 transitions.push_back(x: *it);
367 } else {
368 --remainingRedundantTransitions;
369 }
370
371 child = parent->d();
372 parent = child->d()->parent;
373 Q_ASSERT(child->d() != parent->d());
374 }
375 }
376
377 for (auto it = transitions.rbegin(); it != transitions.rend(); ++it) {
378 switch (it->flags) {
379 case InternalClassTransition::NotExtensible:
380 child = child->d()->nonExtensible();
381 continue;
382 case InternalClassTransition::VTableChange:
383 child = child->d()->changeVTable(vt: it->vtable);
384 continue;
385 case InternalClassTransition::PrototypeChange:
386 child = child->d()->changePrototype(proto: it->prototype);
387 continue;
388 case InternalClassTransition::ProtoClass:
389 child = child->d()->asProtoClass();
390 continue;
391 case InternalClassTransition::Sealed:
392 child = child->d()->sealed();
393 continue;
394 case InternalClassTransition::Frozen:
395 child = child->d()->frozen();
396 continue;
397 case InternalClassTransition::Locked:
398 child = child->d()->locked();
399 continue;
400 default:
401 Q_ASSERT(it->flags != 0);
402 Q_ASSERT(it->flags < InternalClassTransition::StructureChange);
403 child = child->addMember(identifier: it->id, data: attributesFromFlags(flags: it->flags));
404 continue;
405 }
406 }
407
408 return child->d();
409}
410
411Heap::InternalClass *InternalClass::changeMember(
412 PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry)
413{
414 if (!data.isEmpty())
415 data.resolve();
416 PropertyHash::Entry *e = findEntry(id: identifier);
417 Q_ASSERT(e && e->index != UINT_MAX);
418 uint idx = e->index;
419 Q_ASSERT(idx != UINT_MAX);
420
421 if (entry) {
422 entry->index = idx;
423 entry->setterIndex = e->setterIndex;
424 entry->attributes = data;
425 }
426
427 if (data == propertyData.at(i: idx))
428 return this;
429
430 Transition temp = { { .id: identifier }, .lookup: nullptr, .flags: int(data.all()) };
431 Transition &t = lookupOrInsertTransition(t: temp);
432 if (t.lookup)
433 return t.lookup;
434
435 // create a new class and add it to the tree
436 Heap::InternalClass *newClass = engine->newClass(other: this);
437 if (data.isAccessor() && e->setterIndex == UINT_MAX) {
438 Q_ASSERT(!propertyData.at(idx).isAccessor());
439
440 // add a dummy entry for the accessor
441 if (entry)
442 entry->setterIndex = newClass->size;
443 e->setterIndex = newClass->size;
444 addDummyEntry(newClass, e: *e);
445 }
446
447 newClass->propertyData.set(pos: idx, value: data);
448
449 t.lookup = newClass;
450 Q_ASSERT(t.lookup);
451
452 return cleanInternalClass(orig: newClass);
453}
454
455Heap::InternalClass *InternalClass::changePrototypeImpl(Heap::Object *proto)
456{
457 Scope scope(engine);
458 ScopedValue protectThis(scope, this);
459 if (proto)
460 proto->setUsedAsProto();
461 Q_ASSERT(prototype != proto);
462 Q_ASSERT(!proto || proto->internalClass->isUsedAsProto());
463
464 Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: Transition::PrototypeChange };
465 temp.prototype = proto;
466
467 Transition &t = lookupOrInsertTransition(t: temp);
468 if (t.lookup)
469 return t.lookup;
470
471 // create a new class and add it to the tree
472 Heap::InternalClass *newClass = engine->newClass(other: this);
473 newClass->prototype = proto;
474
475 t.lookup = newClass;
476 return prototype ? cleanInternalClass(orig: newClass) : newClass;
477}
478
479Heap::InternalClass *InternalClass::changeVTableImpl(const VTable *vt)
480{
481 Q_ASSERT(vtable != vt);
482
483 Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: Transition::VTableChange };
484 temp.vtable = vt;
485
486 Transition &t = lookupOrInsertTransition(t: temp);
487 if (t.lookup)
488 return t.lookup;
489
490 // create a new class and add it to the tree
491 Heap::InternalClass *newClass = engine->newClass(other: this);
492 newClass->vtable = vt;
493
494 t.lookup = newClass;
495 Q_ASSERT(t.lookup);
496 Q_ASSERT(newClass->vtable);
497 return vtable == QV4::InternalClass::staticVTable()
498 ? newClass
499 : cleanInternalClass(orig: newClass);
500}
501
502Heap::InternalClass *InternalClass::nonExtensible()
503{
504 if (!isExtensible())
505 return this;
506
507 Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: Transition::NotExtensible};
508 Transition &t = lookupOrInsertTransition(t: temp);
509 if (t.lookup)
510 return t.lookup;
511
512 Heap::InternalClass *newClass = engine->newClass(other: this);
513 newClass->flags |= NotExtensible;
514
515 t.lookup = newClass;
516 Q_ASSERT(t.lookup);
517 return newClass;
518}
519
520InternalClass *InternalClass::locked()
521{
522 if (isLocked())
523 return this;
524
525 Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: Transition::Locked};
526 Transition &t = lookupOrInsertTransition(t: temp);
527 if (t.lookup)
528 return t.lookup;
529
530 Heap::InternalClass *newClass = engine->newClass(other: this);
531 newClass->flags |= Locked;
532
533 t.lookup = newClass;
534 Q_ASSERT(t.lookup);
535 return newClass;
536}
537
538void InternalClass::addMember(QV4::Object *object, PropertyKey id, PropertyAttributes data, InternalClassEntry *entry)
539{
540 Q_ASSERT(id.isStringOrSymbol());
541 if (!data.isEmpty())
542 data.resolve();
543 PropertyHash::Entry *e = object->internalClass()->findEntry(id);
544 if (e) {
545 changeMember(object, id, data, entry);
546 return;
547 }
548
549 Heap::InternalClass *newClass = object->internalClass()->addMemberImpl(identifier: id, data, entry);
550 object->setInternalClass(newClass);
551}
552
553Heap::InternalClass *InternalClass::addMember(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry)
554{
555 Q_ASSERT(identifier.isStringOrSymbol());
556 if (!data.isEmpty())
557 data.resolve();
558
559 PropertyHash::Entry *e = findEntry(id: identifier);
560 if (e)
561 return changeMember(identifier, data, entry);
562
563 return addMemberImpl(identifier, data, entry);
564}
565
566Heap::InternalClass *InternalClass::addMemberImpl(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry)
567{
568 Transition temp = { { .id: identifier }, .lookup: nullptr, .flags: int(data.all()) };
569 Transition &t = lookupOrInsertTransition(t: temp);
570
571 if (entry) {
572 entry->index = size;
573 entry->setterIndex = data.isAccessor() ? size + 1 : UINT_MAX;
574 entry->attributes = data;
575 }
576
577 if (t.lookup)
578 return t.lookup;
579
580 // create a new class and add it to the tree
581 Scope scope(engine);
582 Scoped<QV4::InternalClass> ic(scope, engine->newClass(other: this));
583 InternalClass *newClass = ic->d();
584 PropertyHash::Entry e = { .identifier: identifier, .index: newClass->size, .setterIndex: data.isAccessor() ? newClass->size + 1 : UINT_MAX };
585 newClass->propertyTable.addEntry(entry: e, classSize: newClass->size);
586
587 newClass->nameMap.add(pos: newClass->size, value: identifier);
588 newClass->propertyData.add(pos: newClass->size, value: data);
589 ++newClass->size;
590 if (data.isAccessor())
591 addDummyEntry(newClass, e);
592
593 t.lookup = newClass;
594 Q_ASSERT(t.lookup);
595 return newClass;
596}
597
598void InternalClass::removeChildEntry(InternalClass *child)
599{
600 Q_ASSERT(engine);
601 for (auto &t : transitions) {
602 if (t.lookup == child) {
603 t.lookup = nullptr;
604 return;
605 }
606 }
607 Q_UNREACHABLE();
608
609}
610
611void InternalClass::removeMember(QV4::Object *object, PropertyKey identifier)
612{
613#ifndef QT_NO_DEBUG
614 Heap::InternalClass *oldClass = object->internalClass();
615 Q_ASSERT(oldClass->findEntry(identifier) != nullptr);
616#endif
617
618 changeMember(object, id: identifier, data: Attr_Invalid);
619
620#ifndef QT_NO_DEBUG
621 // We didn't remove the data slot, just made it inaccessible.
622 // ... unless we've rebuilt the whole class. Then all the deleted properties are gone.
623 Q_ASSERT(object->internalClass()->numRedundantTransitions == 0
624 || object->internalClass()->size == oldClass->size);
625#endif
626}
627
628Heap::InternalClass *InternalClass::sealed()
629{
630 if (isSealed())
631 return this;
632
633 Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: InternalClassTransition::Sealed };
634 Transition &t = lookupOrInsertTransition(t: temp);
635
636 if (t.lookup) {
637 Q_ASSERT(t.lookup && t.lookup->isSealed());
638 return t.lookup;
639 }
640
641 Scope scope(engine);
642 Scoped<QV4::InternalClass> ic(scope, engine->newClass(other: this));
643 Heap::InternalClass *s = ic->d();
644
645 if (!isFrozen()) { // freezing also makes all properties non-configurable
646 for (uint i = 0; i < size; ++i) {
647 PropertyAttributes attrs = propertyData.at(i);
648 if (attrs.isEmpty())
649 continue;
650 attrs.setConfigurable(false);
651 s->propertyData.set(pos: i, value: attrs);
652 }
653 }
654 s->flags |= Sealed;
655
656 t.lookup = s;
657 return s;
658}
659
660Heap::InternalClass *InternalClass::frozen()
661{
662 if (isFrozen())
663 return this;
664
665 Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: InternalClassTransition::Frozen };
666 Transition &t = lookupOrInsertTransition(t: temp);
667
668 if (t.lookup) {
669 Q_ASSERT(t.lookup && t.lookup->isFrozen());
670 return t.lookup;
671 }
672
673 Scope scope(engine);
674 Scoped<QV4::InternalClass> ic(scope, engine->newClass(other: this));
675 Heap::InternalClass *f = ic->d();
676
677 for (uint i = 0; i < size; ++i) {
678 PropertyAttributes attrs = propertyData.at(i);
679 if (attrs.isEmpty())
680 continue;
681 if (attrs.isData())
682 attrs.setWritable(false);
683 attrs.setConfigurable(false);
684 f->propertyData.set(pos: i, value: attrs);
685 }
686 f->flags |= Frozen;
687
688 t.lookup = f;
689 return f;
690}
691
692InternalClass *InternalClass::canned()
693{
694 // scope the intermediate result to prevent it from getting garbage collected
695 Scope scope(engine);
696 Scoped<QV4::InternalClass> ic(scope, sealed());
697 return ic->d()->nonExtensible();
698}
699
700InternalClass *InternalClass::cryopreserved()
701{
702 // scope the intermediate result to prevent it from getting garbage collected
703 Scope scope(engine);
704 Scoped<QV4::InternalClass> ic(scope, frozen());
705 return ic->d()->canned();
706}
707
708bool InternalClass::isImplicitlyFrozen() const
709{
710 if (isFrozen())
711 return true;
712
713 for (uint i = 0; i < size; ++i) {
714 const PropertyAttributes attrs = propertyData.at(i);
715 if (attrs.isEmpty())
716 continue;
717 if ((attrs.isData() && attrs.isWritable()) || attrs.isConfigurable())
718 return false;
719 }
720
721 return true;
722}
723
724Heap::InternalClass *InternalClass::asProtoClass()
725{
726 if (isUsedAsProto())
727 return this;
728
729 Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: Transition::ProtoClass };
730 Transition &t = lookupOrInsertTransition(t: temp);
731 if (t.lookup)
732 return t.lookup;
733
734 Heap::InternalClass *newClass = engine->newClass(other: this);
735 newClass->flags |= UsedAsProto;
736
737 t.lookup = newClass;
738 Q_ASSERT(t.lookup);
739 return newClass;
740}
741
742static void updateProtoUsage(Heap::Object *o, Heap::InternalClass *ic)
743{
744 if (ic->prototype == o)
745 ic->protoId = ic->engine->newProtoId();
746 for (auto &t : ic->transitions) {
747 if (t.lookup)
748 updateProtoUsage(o, ic: t.lookup);
749 }
750}
751
752
753void InternalClass::updateProtoUsage(Heap::Object *o)
754{
755 Q_ASSERT(isUsedAsProto());
756 Heap::InternalClass *ic = engine->internalClasses(icType: EngineBase::Class_Empty);
757 Q_ASSERT(!ic->prototype);
758
759 Heap::updateProtoUsage(o, ic);
760}
761
762void InternalClass::markObjects(Heap::Base *b, MarkStack *stack)
763{
764 Heap::InternalClass *ic = static_cast<Heap::InternalClass *>(b);
765 if (ic->prototype)
766 ic->prototype->mark(markStack: stack);
767
768 if (ic->parent)
769 ic->parent->mark(markStack: stack);
770
771 ic->nameMap.mark(s: stack);
772}
773
774}
775
776}
777
778QT_END_NAMESPACE
779

source code of qtdeclarative/src/qml/jsruntime/qv4internalclass.cpp