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