Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include <qv4internalclass_p.h>
++ #include <qv4string_p.h>
++ #include <qv4engine_p.h>
++ #include <qv4identifierhash_p.h>
++ #include "qv4object_p.h"
++ #include "qv4value_p.h"
++ #include "qv4mm_p.h"
++ #include <private/qprimefornumbits_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QV4 {
++
++ PropertyHashData::PropertyHashData(int numBits)
++ : refCount(1)
++ , size(0)
++ , numBits(numBits)
++ {
++ alloc = qPrimeForNumBits(numBits);
++ entries = (PropertyHash::Entry *)malloc(alloc*sizeof(PropertyHash::Entry));
++ memset(entries, 0, alloc*sizeof(PropertyHash::Entry));
++ }
++
++ void PropertyHash::addEntry(const PropertyHash::Entry &entry, int classSize)
++ {
++ // fill up to max 50%
++ bool grow = (d->alloc <= d->size*2);
++
++ if (classSize < d->size || grow)
++ detach(grow, classSize);
++
++ uint idx = entry.identifier.id() % d->alloc;
++ while (d->entries[idx].identifier.isValid()) {
++ ++idx;
++ idx %= d->alloc;
++ }
++ d->entries[idx] = entry;
++ ++d->size;
++ }
++
++ void PropertyHash::detach(bool grow, int classSize)
++ {
++ if (d->refCount == 1 && !grow)
++ return;
++
++ PropertyHashData *dd = new PropertyHashData(grow ? d->numBits + 1 : d->numBits);
++ for (int i = 0; i < d->alloc; ++i) {
++ const Entry &e = d->entries[i];
++ if (!e.identifier.isValid() || e.index >= static_cast<unsigned>(classSize))
++ continue;
++ uint idx = e.identifier.id() % dd->alloc;
++ while (dd->entries[idx].identifier.isValid()) {
++ ++idx;
++ idx %= dd->alloc;
++ }
++ dd->entries[idx] = e;
++ }
++ dd->size = classSize;
++ if (!--d->refCount)
++ delete d;
++ d = dd;
++ }
++
++
++ SharedInternalClassDataPrivate<PropertyKey>::SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate<PropertyKey> &other)
++ : refcount(1),
++ engine(other.engine)
++ {
++ if (other.alloc()) {
++ const uint s = other.size();
++ data.set(engine, MemberData::allocate(engine, other.alloc(), other.data));
++ setSize(s);
++ }
++ }
++
++ SharedInternalClassDataPrivate<PropertyKey>::SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate<PropertyKey> &other,
++ uint pos, PropertyKey value)
++ : refcount(1),
++ engine(other.engine)
++ {
++ data.set(engine, MemberData::allocate(engine, other.alloc(), nullptr));
++ memcpy(data, other.data, sizeof(Heap::MemberData) - sizeof(Value) + pos*sizeof(Value));
++ data->values.size = pos + 1;
++ data->values.set(engine, pos, Value::fromReturnedValue(value.id()));
++ }
++
++ void SharedInternalClassDataPrivate<PropertyKey>::grow()
++ {
++ const uint a = alloc() * 2;
++ const uint s = size();
++ data.set(engine, MemberData::allocate(engine, a, data));
++ setSize(s);
++ Q_ASSERT(alloc() >= a);
++ }
++
++ uint SharedInternalClassDataPrivate<PropertyKey>::alloc() const
++ {
++ return data ? data->values.alloc : 0;
++ }
++
++ uint SharedInternalClassDataPrivate<PropertyKey>::size() const
++ {
++ return data ? data->values.size : 0;
++ }
++
++ void SharedInternalClassDataPrivate<PropertyKey>::setSize(uint s)
++ {
++ Q_ASSERT(data && s <= alloc());
++ data->values.size = s;
++ }
++
++ PropertyKey SharedInternalClassDataPrivate<PropertyKey>::at(uint i) const
++ {
++ Q_ASSERT(data && i < size());
++ return PropertyKey::fromId(data->values.values[i].rawValue());
++ }
++
++ void SharedInternalClassDataPrivate<PropertyKey>::set(uint i, PropertyKey t)
++ {
++ Q_ASSERT(data && i < size());
++ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack) {
++ if constexpr (QV4::WriteBarrier::isInsertionBarrier)
++ if (auto string = t.asStringOrSymbol())
++ string->mark(stack);
++ });
++ data->values.values[i].rawValueRef() = t.id();
++ }
++
++ void SharedInternalClassDataPrivate<PropertyKey>::mark(MarkStack *s)
++ {
++ if (data)
++ data->mark(s);
++ }
++
++ SharedInternalClassDataPrivate<PropertyAttributes>::SharedInternalClassDataPrivate(
++ const SharedInternalClassDataPrivate<PropertyAttributes> &other, uint pos,
++ PropertyAttributes value)
++ : refcount(1),
++ m_alloc(qMin(other.m_alloc, pos + 8)),
++ m_size(pos + 1),
++ m_engine(other.m_engine)
++ {
++ Q_ASSERT(m_size <= m_alloc);
++ Q_ASSERT(m_alloc > 0);
++
++ m_engine->memoryManager->changeUnmanagedHeapSizeUsage(m_alloc * sizeof(PropertyAttributes));
++ const PropertyAttributes *source = other.m_alloc > NumAttributesInPointer
++ ? other.m_data
++ : other.m_inlineData;
++ PropertyAttributes *target;
++ if (m_alloc > NumAttributesInPointer)
++ m_data = target = new PropertyAttributes[m_alloc];
++ else
++ target = m_inlineData;
++
++ memcpy(target, source, (m_size - 1) * sizeof(PropertyAttributes));
++ target[pos] = value;
++ }
++
++ SharedInternalClassDataPrivate<PropertyAttributes>::SharedInternalClassDataPrivate(
++ const SharedInternalClassDataPrivate<PropertyAttributes> &other)
++ : refcount(1),
++ m_alloc(other.m_alloc),
++ m_size(other.m_size),
++ m_engine(other.m_engine)
++ {
++ m_engine->memoryManager->changeUnmanagedHeapSizeUsage(m_alloc * sizeof(PropertyAttributes));
++ if (m_alloc > NumAttributesInPointer) {
++ m_data = new PropertyAttributes[m_alloc];
++ memcpy(m_data, other.m_data, m_size*sizeof(PropertyAttributes));
++ } else if (m_alloc > 0) {
++ memcpy(m_inlineData, other.m_inlineData, m_alloc * sizeof(PropertyAttributes));
++ } else {
++ m_data = nullptr;
++ }
++ }
++
++ SharedInternalClassDataPrivate<PropertyAttributes>::~SharedInternalClassDataPrivate()
++ {
++ m_engine->memoryManager->changeUnmanagedHeapSizeUsage(
++ -qptrdiff(m_alloc * sizeof(PropertyAttributes)));
++ if (m_alloc > NumAttributesInPointer)
++ delete [] m_data;
++ }
++
++ void SharedInternalClassDataPrivate<PropertyAttributes>::grow() {
++ uint alloc;
++ if (!m_alloc) {
++ alloc = NumAttributesInPointer;
++ m_engine->memoryManager->changeUnmanagedHeapSizeUsage(alloc * sizeof(PropertyAttributes));
++ } else {
++ // yes, signed. We don't want to deal with stuff > 2G
++ const uint currentSize = m_alloc * sizeof(PropertyAttributes);
++ if (currentSize < uint(std::numeric_limits<int>::max() / 2))
++ alloc = m_alloc * 2;
++ else
++ alloc = std::numeric_limits<int>::max() / sizeof(PropertyAttributes);
++
++ m_engine->memoryManager->changeUnmanagedHeapSizeUsage(
++ (alloc - m_alloc) * sizeof(PropertyAttributes));
++ }
++
++ if (alloc > NumAttributesInPointer) {
++ auto *n = new PropertyAttributes[alloc];
++ if (m_alloc > NumAttributesInPointer) {
++ memcpy(n, m_data, m_alloc * sizeof(PropertyAttributes));
++ delete [] m_data;
++ } else if (m_alloc > 0) {
++ memcpy(n, m_inlineData, m_alloc * sizeof(PropertyAttributes));
++ }
++ m_data = n;
++ }
++ m_alloc = alloc;
++ }
++
++ namespace Heap {
++
++ void InternalClass::init(ExecutionEngine *engine)
++ {
++ // InternalClass is automatically zeroed during allocation:
++ // prototype = nullptr;
++ // parent = nullptr;
++ // size = 0;
++ // numRedundantTransitions = 0;
++ // flags = 0;
++
++ Base::init();
++ new (&propertyTable) PropertyHash();
++ new (&nameMap) SharedInternalClassData<PropertyKey>(engine);
++ new (&propertyData) SharedInternalClassData<PropertyAttributes>(engine);
++ new (&transitions) QVarLengthArray<Transition, 1>();
++
++ this->engine = engine;
++ vtable = QV4::InternalClass::staticVTable();
++ protoId = engine->newProtoId();
++
++ // Also internal classes need an internal class pointer. Simply make it point to itself
++ internalClass.set(engine, this);
++ }
++
++
++ void InternalClass::init(Heap::InternalClass *other)
++ {
++ Base::init();
++ new (&propertyTable) PropertyHash(other->propertyTable);
++ new (&nameMap) SharedInternalClassData<PropertyKey>(other->nameMap);
++ new (&propertyData) SharedInternalClassData<PropertyAttributes>(other->propertyData);
++ new (&transitions) QVarLengthArray<Transition, 1>();
++
++ engine = other->engine;
++ vtable = other->vtable;
++ prototype = other->prototype;
++ parent = other;
++ size = other->size;
++ numRedundantTransitions = other->numRedundantTransitions;
++ flags = other->flags;
++ protoId = engine->newProtoId();
++
++ internalClass.set(engine, other->internalClass);
++ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack) {
++ if constexpr (QV4::WriteBarrier::isInsertionBarrier) {
++ other->mark(stack);
++ }
++ });
++ }
++
++ void InternalClass::destroy()
++ {
++ for (const auto &t : transitions) {
++ if (t.lookup) {
++ #ifndef QT_NO_DEBUG
++ Q_ASSERT(t.lookup->parent == this);
++ #endif
++ t.lookup->parent = nullptr;
++ }
++ }
++
++ if (parent && parent->engine && parent->isMarked())
++ parent->removeChildEntry(this);
++
++ propertyTable.~PropertyHash();
++ nameMap.~SharedInternalClassData<PropertyKey>();
++ propertyData.~SharedInternalClassData<PropertyAttributes>();
++ transitions.~QVarLengthArray<Transition, 1>();
++ engine = nullptr;
++ Base::destroy();
++ }
++
++ ReturnedValue InternalClass::keyAt(uint index) const
++ {
++ PropertyKey key = nameMap.at(index);
++ if (!key.isValid())
++ return Encode::undefined();
++ if (key.isArrayIndex())
++ return Encode(key.asArrayIndex());
++ Q_ASSERT(key.isStringOrSymbol());
++ return key.asStringOrSymbol()->asReturnedValue();
++ }
++
++ void InternalClass::changeMember(QV4::Object *object, PropertyKey id, PropertyAttributes data, InternalClassEntry *entry)
++ {
++ Q_ASSERT(id.isStringOrSymbol());
++
++ Heap::InternalClass *oldClass = object->internalClass();
++ Heap::InternalClass *newClass = oldClass->changeMember(id, data, entry);
++ object->setInternalClass(newClass);
++ }
++
++ InternalClassTransition &InternalClass::lookupOrInsertTransition(const InternalClassTransition &t)
++ {
++ QVarLengthArray<Transition, 1>::iterator it = std::lower_bound(transitions.begin(), transitions.end(), t);
++ if (it != transitions.end() && *it == t) {
++ return *it;
++ } else {
++ it = transitions.insert(it, t);
++ return *it;
++ }
++ }
++
++ static void addDummyEntry(InternalClass *newClass, PropertyHash::Entry e)
++ {
++ // add a dummy entry, since we need two entries for accessors
++ newClass->propertyTable.addEntry(e, newClass->size);
++ newClass->nameMap.add(newClass->size, PropertyKey::invalid());
++ newClass->propertyData.add(newClass->size, PropertyAttributes());
++ ++newClass->size;
++ }
++
++ static PropertyAttributes attributesFromFlags(int flags)
++ {
++ PropertyAttributes attributes;
++ attributes.m_all = uchar(flags);
++ return attributes;
++ }
++
++ static Heap::InternalClass *cleanInternalClass(Heap::InternalClass *orig)
++ {
++ if (++orig->numRedundantTransitions < Heap::InternalClass::MaxRedundantTransitions)
++ return orig;
++
++ // We will generally add quite a few transitions here. We have 255 redundant ones.
++ // We can expect at least as many significant ones in addition.
++ QVarLengthArray<InternalClassTransition, 1> transitions;
++
++ Scope scope(orig->engine);
++ Scoped<QV4::InternalClass> child(scope, orig);
++
++ {
++ quint8 remainingRedundantTransitions = orig->numRedundantTransitions;
++ QSet<PropertyKey> properties;
++ int structureChanges = 0;
++
++ Scoped<QV4::InternalClass> parent(scope, orig->parent);
++ while (parent && remainingRedundantTransitions > 0) {
++ Q_ASSERT(child->d() != scope.engine->classes[ExecutionEngine::Class_Empty]);
++ const auto it = std::find_if(
++ parent->d()->transitions.begin(), parent->d()->transitions.end(),
++ [&child](const InternalClassTransition &t) {
++ return child->d() == t.lookup;
++ });
++ Q_ASSERT(it != parent->d()->transitions.end());
++
++ if (it->flags & InternalClassTransition::StructureChange) {
++ // A structural change. Each kind of structural change has to be recorded only once.
++ if ((structureChanges & it->flags) != it->flags) {
++ transitions.push_back(*it);
++ structureChanges |= it->flags;
++ } else {
++ --remainingRedundantTransitions;
++ }
++ } else if (!properties.contains(it->id)) {
++ // We only need the final state of the property.
++ properties.insert(it->id);
++
++ // Property removal creates _two_ redundant transitions.
++ // We don't have to replay either, but numRedundantTransitions only records one.
++ if (it->flags != 0)
++ transitions.push_back(*it);
++ } else {
++ --remainingRedundantTransitions;
++ }
++
++ child = parent->d();
++ parent = child->d()->parent;
++ Q_ASSERT(child->d() != parent->d());
++ }
++ }
++
++ for (auto it = transitions.rbegin(); it != transitions.rend(); ++it) {
++ switch (it->flags) {
++ case InternalClassTransition::NotExtensible:
++ child = child->d()->nonExtensible();
++ continue;
++ case InternalClassTransition::VTableChange:
++ child = child->d()->changeVTable(it->vtable);
++ continue;
++ case InternalClassTransition::PrototypeChange:
++ child = child->d()->changePrototype(it->prototype);
++ continue;
++ case InternalClassTransition::ProtoClass:
++ child = child->d()->asProtoClass();
++ continue;
++ case InternalClassTransition::Sealed:
++ child = child->d()->sealed();
++ continue;
++ case InternalClassTransition::Frozen:
++ child = child->d()->frozen();
++ continue;
++ case InternalClassTransition::Locked:
++ child = child->d()->locked();
++ continue;
++ default:
++ Q_ASSERT(it->flags != 0);
++ Q_ASSERT(it->flags < InternalClassTransition::StructureChange);
++ child = child->addMember(it->id, attributesFromFlags(it->flags));
++ continue;
++ }
++ }
++
++ return child->d();
++ }
++
++ Heap::InternalClass *InternalClass::changeMember(
++ PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry)
++ {
++ if (!data.isEmpty())
++ data.resolve();
++ PropertyHash::Entry *e = findEntry(identifier);
++ Q_ASSERT(e && e->index != UINT_MAX);
++ uint idx = e->index;
++ Q_ASSERT(idx != UINT_MAX);
++
++ if (entry) {
++ entry->index = idx;
++ entry->setterIndex = e->setterIndex;
++ entry->attributes = data;
++ }
++
++ if (data == propertyData.at(idx))
++ return this;
++
++ Transition temp = { { identifier }, nullptr, int(data.all()) };
++ Transition &t = lookupOrInsertTransition(temp);
++ if (t.lookup)
++ return t.lookup;
++
++ // create a new class and add it to the tree
++ Scope scope(engine);
++ Scoped<QV4::InternalClass> scopedNewClass(scope, engine->newClass(this));
++ auto newClass = scopedNewClass->d();
++ if (data.isAccessor() && e->setterIndex == UINT_MAX) {
++ Q_ASSERT(!propertyData.at(idx).isAccessor());
++
++ // add a dummy entry for the accessor
++ if (entry)
++ entry->setterIndex = newClass->size;
++ e->setterIndex = newClass->size;
++ addDummyEntry(newClass, *e);
++ }
++
++ newClass->propertyData.set(idx, data);
++
++ t.lookup = newClass;
++ Q_ASSERT(t.lookup);
++
++ Heap::InternalClass *cleanedIC = cleanInternalClass(newClass);
++ // cleanInternalClass might have invalidated the index
++ if (newClass == cleanedIC)
++ return cleanedIC; // IC stayed as is, no index invalidation
++ // in that case, we refetch the entry if needed
++ if (entry) {
++ auto updatedEntry = cleanedIC->findEntry(identifier);
++ Q_ASSERT(updatedEntry); // cleanup must not remove the entry
++ entry->index = updatedEntry->index;
++ entry->setterIndex = updatedEntry->setterIndex;
++ // entry->attributes is still valid, doesn't depend on the IC
++ }
++ return cleanedIC;
++ }
++
++ Heap::InternalClass *InternalClass::changePrototypeImpl(Heap::Object *proto)
++ {
++ Scope scope(engine);
++ ScopedValue protectThis(scope, this);
++ if (proto)
++ proto->setUsedAsProto();
++ Q_ASSERT(prototype != proto);
++ Q_ASSERT(!proto || proto->internalClass->isUsedAsProto());
++
++ Transition temp = { { PropertyKey::invalid() }, nullptr, Transition::PrototypeChange };
++ temp.prototype = proto;
++
++ Transition &t = lookupOrInsertTransition(temp);
++ if (t.lookup)
++ return t.lookup;
++
++ // create a new class and add it to the tree
++ Scoped<QV4::InternalClass> scopedNewClass(scope, engine->newClass(this));
++ auto newClass = scopedNewClass->d();
++ QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack) {
++ if (proto && QV4::WriteBarrier::isInsertionBarrier)
++ proto->mark(stack);
++ });
++ newClass->prototype = proto;
++
++ t.lookup = newClass;
++ return prototype ? cleanInternalClass(newClass) : newClass;
++ }
++
++ Heap::InternalClass *InternalClass::changeVTableImpl(const VTable *vt)
++ {
++ Q_ASSERT(vtable != vt);
++
++ Transition temp = { { PropertyKey::invalid() }, nullptr, Transition::VTableChange };
++ temp.vtable = vt;
++
++ Transition &t = lookupOrInsertTransition(temp);
++ if (t.lookup)
++ return t.lookup;
++
++ // create a new class and add it to the tree
++ Scope scope(engine);
++ Scoped<QV4::InternalClass> scopedNewClass(scope, engine->newClass(this));
++ auto newClass = scopedNewClass->d();
++ newClass->vtable = vt;
++
++ t.lookup = newClass;
++ Q_ASSERT(t.lookup);
++ Q_ASSERT(newClass->vtable);
++ return vtable == QV4::InternalClass::staticVTable()
++ ? newClass
++ : cleanInternalClass(newClass);
++ }
++
++ Heap::InternalClass *InternalClass::nonExtensible()
++ {
++ if (!isExtensible())
++ return this;
++
++ Transition temp = { { PropertyKey::invalid() }, nullptr, Transition::NotExtensible};
++ Transition &t = lookupOrInsertTransition(temp);
++ if (t.lookup)
++ return t.lookup;
++
++ Scope scope(engine);
++ Scoped<QV4::InternalClass> scopedNewClass(scope, engine->newClass(this));
++ auto newClass = scopedNewClass->d();
++ newClass->flags |= NotExtensible;
++
++ t.lookup = newClass;
++ Q_ASSERT(t.lookup);
++ return newClass;
++ }
++
++ InternalClass *InternalClass::locked()
++ {
++ if (isLocked())
++ return this;
++
++ Transition temp = { { PropertyKey::invalid() }, nullptr, Transition::Locked};
++ Transition &t = lookupOrInsertTransition(temp);
++ if (t.lookup)
++ return t.lookup;
++
++ Scope scope(engine);
++ Scoped<QV4::InternalClass> scopedNewClass(scope, engine->newClass(this));
++ auto newClass = scopedNewClass->d();
++ newClass->flags |= Locked;
++
++ t.lookup = newClass;
++ Q_ASSERT(t.lookup);
++ return newClass;
++ }
++
++ void InternalClass::addMember(QV4::Object *object, PropertyKey id, PropertyAttributes data, InternalClassEntry *entry)
++ {
++ Heap::InternalClass *oldClass = object->internalClass();
++ Heap::InternalClass *newClass = oldClass->addMember(id, data, entry);
++ if (newClass != oldClass)
++ object->setInternalClass(newClass);
++ }
++
++ Heap::InternalClass *InternalClass::addMember(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry)
++ {
++ Q_ASSERT(identifier.isStringOrSymbol());
++ if (!data.isEmpty())
++ data.resolve();
++
++ PropertyHash::Entry *e = findEntry(identifier);
++ if (e)
++ return changeMember(identifier, data, entry);
++
++ return addMemberImpl(identifier, data, entry);
++ }
++
++ Heap::InternalClass *InternalClass::addMemberImpl(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry)
++ {
++ Transition temp = { { identifier }, nullptr, int(data.all()) };
++ Transition &t = lookupOrInsertTransition(temp);
++
++ if (entry) {
++ entry->index = size;
++ entry->setterIndex = data.isAccessor() ? size + 1 : UINT_MAX;
++ entry->attributes = data;
++ }
++
++ if (t.lookup)
++ return t.lookup;
++
++ // create a new class and add it to the tree
++ Scope scope(engine);
++ Scoped<QV4::InternalClass> ic(scope, engine->newClass(this));
++ InternalClass *newClass = ic->d();
++ PropertyHash::Entry e = { identifier, newClass->size, data.isAccessor() ? newClass->size + 1 : UINT_MAX };
++ newClass->propertyTable.addEntry(e, newClass->size);
++
++ newClass->nameMap.add(newClass->size, identifier);
++ newClass->propertyData.add(newClass->size, data);
++ ++newClass->size;
++ if (data.isAccessor())
++ addDummyEntry(newClass, e);
++
++ t.lookup = newClass;
++ Q_ASSERT(t.lookup);
++ return newClass;
++ }
++
++ void InternalClass::removeChildEntry(InternalClass *child)
++ {
++ Q_ASSERT(engine);
++ for (auto &t : transitions) {
++ if (t.lookup == child) {
++ t.lookup = nullptr;
++ return;
++ }
++ }
++ Q_UNREACHABLE();
++
++ }
++
++ void InternalClass::removeMember(QV4::Object *object, PropertyKey identifier)
++ {
++ #ifndef QT_NO_DEBUG
++ Heap::InternalClass *oldClass = object->internalClass();
++ Q_ASSERT(oldClass->findEntry(identifier) != nullptr);
++ #endif
++
++ changeMember(object, identifier, Attr_Invalid);
++
++ #ifndef QT_NO_DEBUG
++ // We didn't remove the data slot, just made it inaccessible.
++ // ... unless we've rebuilt the whole class. Then all the deleted properties are gone.
++ Q_ASSERT(object->internalClass()->numRedundantTransitions == 0
++ || object->internalClass()->size == oldClass->size);
++ #endif
++ }
++
++ Heap::InternalClass *InternalClass::sealed()
++ {
++ if (isSealed())
++ return this;
++
++ Transition temp = { { PropertyKey::invalid() }, nullptr, InternalClassTransition::Sealed };
++ Transition &t = lookupOrInsertTransition(temp);
++
++ if (t.lookup) {
++ Q_ASSERT(t.lookup && t.lookup->isSealed());
++ return t.lookup;
++ }
++
++ Scope scope(engine);
++ Scoped<QV4::InternalClass> ic(scope, engine->newClass(this));
++ Heap::InternalClass *s = ic->d();
++
++ if (!isFrozen()) { // freezing also makes all properties non-configurable
++ for (uint i = 0; i < size; ++i) {
++ PropertyAttributes attrs = propertyData.at(i);
++ if (attrs.isEmpty())
++ continue;
++ attrs.setConfigurable(false);
++ s->propertyData.set(i, attrs);
++ }
++ }
++ s->flags |= Sealed;
++
++ t.lookup = s;
++ return s;
++ }
++
++ Heap::InternalClass *InternalClass::frozen()
++ {
++ if (isFrozen())
++ return this;
++
++ Transition temp = { { PropertyKey::invalid() }, nullptr, InternalClassTransition::Frozen };
++ Transition &t = lookupOrInsertTransition(temp);
++
++ if (t.lookup) {
++ Q_ASSERT(t.lookup && t.lookup->isFrozen());
++ return t.lookup;
++ }
++
++ Scope scope(engine);
++ Scoped<QV4::InternalClass> ic(scope, engine->newClass(this));
++ Heap::InternalClass *f = ic->d();
++
++ for (uint i = 0; i < size; ++i) {
++ PropertyAttributes attrs = propertyData.at(i);
++ if (attrs.isEmpty())
++ continue;
++ if (attrs.isData())
++ attrs.setWritable(false);
++ attrs.setConfigurable(false);
++ f->propertyData.set(i, attrs);
++ }
++ f->flags |= Frozen;
++
++ t.lookup = f;
++ return f;
++ }
++
++ InternalClass *InternalClass::canned()
++ {
++ // scope the intermediate result to prevent it from getting garbage collected
++ Scope scope(engine);
++ Scoped<QV4::InternalClass> ic(scope, sealed());
++ return ic->d()->nonExtensible();
++ }
++
++ InternalClass *InternalClass::cryopreserved()
++ {
++ // scope the intermediate result to prevent it from getting garbage collected
++ Scope scope(engine);
++ Scoped<QV4::InternalClass> ic(scope, frozen());
++ return ic->d()->canned();
++ }
++
++ bool InternalClass::isImplicitlyFrozen() const
++ {
++ if (isFrozen())
++ return true;
++
++ for (uint i = 0; i < size; ++i) {
++ const PropertyAttributes attrs = propertyData.at(i);
++ if (attrs.isEmpty())
++ continue;
++ if ((attrs.isData() && attrs.isWritable()) || attrs.isConfigurable())
++ return false;
++ }
++
++ return true;
++ }
++
++ Heap::InternalClass *InternalClass::asProtoClass()
++ {
++ if (isUsedAsProto())
++ return this;
++
++ Transition temp = { { PropertyKey::invalid() }, nullptr, Transition::ProtoClass };
++ Transition &t = lookupOrInsertTransition(temp);
++ if (t.lookup)
++ return t.lookup;
++
++ Scope scope(engine);
++ Scoped<QV4::InternalClass> scopedNewClass(scope, engine->newClass(this));
++ auto newClass = scopedNewClass->d();
++ newClass->flags |= UsedAsProto;
++
++ t.lookup = newClass;
++ Q_ASSERT(t.lookup);
++ return newClass;
++ }
++
++ static void updateProtoUsage(Heap::Object *o, Heap::InternalClass *ic)
++ {
++ if (ic->prototype == o)
++ ic->protoId = ic->engine->newProtoId();
++ for (auto &t : ic->transitions) {
++ if (t.lookup)
++ updateProtoUsage(o, t.lookup);
++ }
++ }
++
++
++ void InternalClass::updateProtoUsage(Heap::Object *o)
++ {
++ Q_ASSERT(isUsedAsProto());
++ Heap::InternalClass *ic = engine->internalClasses(EngineBase::Class_Empty);
++ Q_ASSERT(!ic->prototype);
++
++ Heap::updateProtoUsage(o, ic);
++ }
++
++ void InternalClass::markObjects(Heap::Base *b, MarkStack *stack)
++ {
++ Heap::InternalClass *ic = static_cast<Heap::InternalClass *>(b);
++ if (ic->prototype)
++ ic->prototype->mark(stack);
++
++ if (ic->parent)
++ ic->parent->mark(stack);
++
++ ic->nameMap.mark(stack);
++ }
++
++ }
++
++ }
++
++ QT_END_NAMESPACE