// 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
