// 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 "qv4object_p.h"

#include <private/qv4argumentsobject_p.h>
#include <private/qv4identifiertable_p.h>
#include <private/qv4jscall_p.h>
#include <private/qv4lookup_p.h>
#include <private/qv4memberdata_p.h>
#include <private/qv4mm_p.h>
#include <private/qv4proxy_p.h>
#include <private/qv4scopedvalue_p.h>
#include <private/qv4stackframe_p.h>
#include <private/qv4stringobject_p.h>
#include <private/qv4symbol_p.h>

#include <QtCore/qloggingcategory.h>

#include <stdint.h>

using namespace QV4;
using namespace Qt::Literals::StringLiterals;

Q_STATIC_LOGGING_CATEGORY(lcJavaScriptGlobals, "qt.qml.js.globals")

DEFINE_OBJECT_VTABLE(Object);

void Object::setInternalClass(Heap::InternalClass *ic)
{
    Q_ASSERT(ic && ic->vtable);
    Heap::Object *p = d();

    if (ic->numRedundantTransitions < p->internalClass.get()->numRedundantTransitions) {
        // IC was rebuilt. The indices are different now. We need to move everything.

        Scope scope(engine());

        // We allocate before setting the new IC. Protect it from GC.
        Scoped<InternalClass> newIC(scope, ic);

        // Pick the members of the old IC that are still valid in the new IC.
        // Order them by index in memberData (or inline data).
        Scoped<MemberData> newMembers(scope, MemberData::allocate(scope.engine, ic->size));
        for (uint i = 0; i < ic->size; ++i) {
            // Note that some members might have been deleted. The key may be invalid.
            const PropertyKey key = ic->nameMap.at(i);
            // fromReturnedValue is safe, we directly write it through the WriteBarrier
            newMembers->set(scope.engine, i,
                            QV4::Value::fromReturnedValue(key.isValid() ? get(key) : Encode::undefined()));
        }

        p->internalClass.set(scope.engine, ic);
        const uint nInline = p->vtable()->nInlineProperties;

        if (ic->size > nInline)
            p->memberData.set(scope.engine, MemberData::allocate(ic->engine, ic->size - nInline));
        else
            p->memberData.set(scope.engine, nullptr);

        const auto &memberValues = newMembers->d()->values;
        for (uint i = 0; i < ic->size; ++i)
            setProperty(i, memberValues[i]);
    } else {
        // The old indices are still the same. No need to move any values.
        // We may need to re-allocate, though.

        p->internalClass.set(ic->engine, ic);
        const uint nInline = p->vtable()->nInlineProperties;
        if (ic->size > nInline) {
            const uint requiredSize = ic->size - nInline;
            if ((p->memberData ? p->memberData->values.size : 0) < requiredSize) {
                p->memberData.set(ic->engine, MemberData::allocate(
                                      ic->engine, requiredSize, p->memberData));
            }
        }
    }

    // Before the engine is done initializing, we cannot have any lookups.
    // Therefore, there is no point in updating the proto IDs.
    if (ic->engine->isInitialized && ic->isUsedAsProto())
        ic->updateProtoUsage(p);

}

void Object::getProperty(const InternalClassEntry &entry, Property *p) const
{
    p->value = *propertyData(entry.index);
    if (entry.attributes.isAccessor())
        p->set = *propertyData(entry.setterIndex);
}

void Object::setProperty(const InternalClassEntry &entry, const Property *p)
{
    setProperty(entry.index, p->value);
    if (entry.attributes.isAccessor())
        setProperty(entry.setterIndex, p->set);
}

void Heap::Object::setUsedAsProto()
{
    internalClass.set(internalClass->engine, internalClass->asProtoClass());
}

ReturnedValue Object::getValueAccessor(const Value *thisObject, const Value &v, PropertyAttributes attrs)
{
    if (!attrs.isAccessor())
        return v.asReturnedValue();
    const QV4::FunctionObject *f = v.as<FunctionObject>();
    if (!f)
        return Encode::undefined();

    Scope scope(f->engine());
    JSCallArguments jsCallData(scope);
    if (thisObject)
        *jsCallData.thisObject = *thisObject;
    return checkedResult(scope.engine, f->call(jsCallData));
}

bool Object::putValue(uint memberIndex, PropertyAttributes attrs, const Value &value)
{
    Heap::InternalClass *ic = internalClass();
    if (ic->engine->hasException)
        return false;

    if (attrs.isAccessor()) {
        const FunctionObject *set = propertyData(memberIndex)->as<FunctionObject>();
        if (set) {
            Scope scope(ic->engine);
            ScopedFunctionObject setter(scope, set);
            JSCallArguments jsCallData(scope, 1);
            jsCallData.args[0] = value;
            *jsCallData.thisObject = this;
            setter->call(jsCallData);
            return !ic->engine->hasException;
        }
        return false;
    }

    if (!attrs.isWritable())
        return false;

    setProperty(memberIndex, value);
    return true;
}

void Object::defineDefaultProperty(const QString &name, const Value &value, PropertyAttributes attributes)
{
    ExecutionEngine *e = engine();
    Scope scope(e);
    ScopedString s(scope, e->newIdentifier(name));
    defineDefaultProperty(s, value, attributes);
}

void Object::defineDefaultProperty(const QString &name, VTable::Call code,
                                   int argumentCount, PropertyAttributes attributes)
{
    ExecutionEngine *e = engine();
    Scope scope(e);
    ScopedString s(scope, e->newIdentifier(name));
    ScopedFunctionObject function(scope, FunctionObject::createBuiltinFunction(e, s, code, argumentCount));
    defineDefaultProperty(s, function, attributes);
}

void Object::defineDefaultProperty(StringOrSymbol *nameOrSymbol, VTable::Call code,
                                   int argumentCount, PropertyAttributes attributes)
{
    ExecutionEngine *e = engine();
    Scope scope(e);
    ScopedFunctionObject function(scope, FunctionObject::createBuiltinFunction(e, nameOrSymbol, code, argumentCount));
    defineDefaultProperty(nameOrSymbol, function, attributes);
}

void Object::defineAccessorProperty(const QString &name, VTable::Call getter, VTable::Call setter)
{
    ExecutionEngine *e = engine();
    Scope scope(e);
    ScopedString s(scope, e->newIdentifier(name));
    defineAccessorProperty(s, getter, setter);
}

void Object::defineAccessorProperty(StringOrSymbol *name, VTable::Call getter, VTable::Call setter)
{
    ExecutionEngine *v4 = engine();
    QV4::Scope scope(v4);
    ScopedProperty p(scope);
    QString n = name->toQString();
    if (!n.isEmpty() && n.at(0) == '@'_L1)
        n = '['_L1 + QStringView{n}.mid(1) + ']'_L1;
    if (getter) {
        ScopedString getName(scope, v4->newString("get "_L1 + n));
        p->setGetter(ScopedFunctionObject(scope, FunctionObject::createBuiltinFunction(v4, getName, getter, 0)));
    } else {
        p->setGetter(nullptr);
    }
    if (setter) {
        ScopedString setName(scope, v4->newString("set "_L1 + n));
        p->setSetter(ScopedFunctionObject(scope, FunctionObject::createBuiltinFunction(v4, setName, setter, 0)));
    } else {
        p->setSetter(nullptr);
    }
    insertMember(name, p, QV4::Attr_Accessor|QV4::Attr_NotEnumerable);
}



void Object::defineReadonlyProperty(const QString &name, const Value &value)
{
    QV4::ExecutionEngine *e = engine();
    Scope scope(e);
    ScopedString s(scope, e->newIdentifier(name));
    defineReadonlyProperty(s, value);
}

void Object::defineReadonlyProperty(String *name, const Value &value)
{
    insertMember(name, value, Attr_ReadOnly);
}

void Object::defineReadonlyConfigurableProperty(const QString &name, const Value &value)
{
    QV4::ExecutionEngine *e = engine();
    Scope scope(e);
    ScopedString s(scope, e->newIdentifier(name));
    defineReadonlyConfigurableProperty(s, value);
}

void Object::defineReadonlyConfigurableProperty(StringOrSymbol *name, const Value &value)
{
    insertMember(name, value, Attr_ReadOnly_ButConfigurable);
}

void Object::addSymbolSpecies()
{
    Scope scope(engine());
    ScopedProperty p(scope);
    p->setGetter(scope.engine->getSymbolSpecies());
    p->setSetter(nullptr);
    insertMember(scope.engine->symbol_species(), p, QV4::Attr_Accessor|QV4::Attr_NotWritable|QV4::Attr_NotEnumerable);
}

void Heap::Object::markObjects(Heap::Base *b, MarkStack *stack)
{
    Base::markObjects(b, stack);
    Object *o = static_cast<Object *>(b);
    if (o->memberData)
        o->memberData->mark(stack);
    if (o->arrayData)
        o->arrayData->mark(stack);
    uint nInline = o->vtable()->nInlineProperties;
    Value *v = reinterpret_cast<Value *>(o) + o->vtable()->inlinePropertyOffset;
    const Value *end = v + nInline;
    while (v < end) {
        v->mark(stack);
        ++v;
    }
}

void Object::insertMember(StringOrSymbol *s, const Property *p, PropertyAttributes attributes)
{
    InternalClassEntry idx;
    PropertyKey key = s->toPropertyKey();
    Heap::InternalClass::addMember(this, key, attributes, &idx);

    setProperty(idx.index, p->value);
    if (attributes.isAccessor())
        setProperty(idx.setterIndex, p->set);
}

void Object::setPrototypeUnchecked(const Object *p)
{
    setInternalClass(internalClass()->changePrototype(p ? p->d() : nullptr));
}

// Section 8.12.2
PropertyIndex Object::getValueOrSetter(PropertyKey id, PropertyAttributes *attrs)
{
    if (id.isArrayIndex()) {
        uint index = id.asArrayIndex();
        Heap::Object *o = d();
        while (o) {
            if (o->arrayData) {
                uint idx = o->arrayData->mappedIndex(index);
                if (idx != UINT_MAX) {
                    *attrs = o->arrayData->attributes(index);
                    return { o->arrayData , o->arrayData->values.values + (attrs->isAccessor() ? idx + SetterOffset : idx) };
                }
            }
            if (o->vtable()->type == Type_StringObject) {
                if (index < static_cast<const Heap::StringObject *>(o)->length()) {
                    // this is an evil hack, but it works, as the method is only ever called from put,
                    // where we don't use the returned pointer there for non writable attributes
                    *attrs = (Attr_NotWritable|Attr_NotConfigurable);
                    return { reinterpret_cast<Heap::ArrayData *>(0x1), nullptr };
                }
            }
            o = o->prototype();
        }
    } else {
        Heap::Object *o = d();
        while (o) {
            auto idx = o->internalClass->findValueOrSetter(id);
            if (idx.isValid()) {
                *attrs = idx.attrs;
                return o->writablePropertyData(idx.index);
            }

            o = o->prototype();
        }
    }
    *attrs = Attr_Invalid;
    return { nullptr, nullptr };
}

ReturnedValue Object::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty)
{
    return static_cast<const Object *>(m)->internalGet(id, receiver, hasProperty);
}

bool Object::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver)
{
    return static_cast<Object *>(m)->internalPut(id, value, receiver);
}

bool Object::virtualDeleteProperty(Managed *m, PropertyKey id)
{
    return static_cast<Object *>(m)->internalDeleteProperty(id);
}

PropertyKey ObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs)
{
    if (arrayIndex != UINT_MAX && o->arrayData()) {
        SparseArrayNode *arrayNode = nullptr;
        if (o->arrayType() == Heap::ArrayData::Sparse) {
            SparseArray *sparse = o->arrayData()->sparse;
            arrayNode = arrayIndex ? sparse->lowerBound(arrayIndex) : sparse->begin();
        }

        // sparse arrays
        if (arrayNode) {
            while (arrayNode != o->sparseEnd()) {
                uint k = arrayNode->key();
                uint pidx = arrayNode->value;
                Heap::SparseArrayData *sa = o->d()->arrayData.cast<Heap::SparseArrayData>();
                const Property *p = reinterpret_cast<const Property *>(sa->values.data() + pidx);
                arrayNode = arrayNode->nextNode();
                PropertyAttributes a = sa->attrs ? sa->attrs[pidx] : Attr_Data;
                arrayIndex = k + 1;
                if (pd)
                    pd->copy(p, a);
                if (attrs)
                    *attrs = a;
                return PropertyKey::fromArrayIndex(k);
            }
            arrayIndex = UINT_MAX;
        }
        // dense arrays
        while (arrayIndex < o->d()->arrayData->values.size) {
            Heap::SimpleArrayData *sa = o->d()->arrayData.cast<Heap::SimpleArrayData>();
            const Value &val = sa->data(arrayIndex);
            PropertyAttributes a = o->arrayData()->attributes(arrayIndex);
            int index = arrayIndex;
            ++arrayIndex;
            if (!val.isEmpty()) {
                if (pd)
                    pd->value = val;
                if (attrs)
                    *attrs = a;
                return PropertyKey::fromArrayIndex(index);
            }
        }
        arrayIndex = UINT_MAX;
    }

    while (true) {
        while (memberIndex < o->internalClass()->size) {
            PropertyKey n = o->internalClass()->nameMap.at(memberIndex);
            ++memberIndex;
            if (!n.isStringOrSymbol())
                // accessor properties have a dummy entry with n == 0
                continue;
            if (!iterateOverSymbols && n.isSymbol())
                continue;
            if (iterateOverSymbols && !n.isSymbol())
                continue;

            InternalClassEntry e = o->internalClass()->find(n);
            if (!e.isValid())
                continue;
            if (pd) {
                pd->value = *o->propertyData(e.index);
                if (e.attributes.isAccessor())
                    pd->set = *o->propertyData(e.setterIndex);
            }
            if (attrs)
                *attrs = e.attributes;
            return n;
        }
        if (iterateOverSymbols)
            break;
        iterateOverSymbols = true;
        memberIndex = 0;
    }

    return PropertyKey::invalid();
}

OwnPropertyKeyIterator *Object::virtualOwnPropertyKeys(const Object *o, Value *target)
{
    *target = *o;
    return new ObjectOwnPropertyKeyIterator;
}

// Section 8.12.3
ReturnedValue Object::internalGet(PropertyKey id, const Value *receiver, bool *hasProperty) const
{
    Heap::Object *o = d();

    if (id.isArrayIndex()) {
        const uint index = id.asArrayIndex();
        Scope scope(this);
        PropertyAttributes attrs;
        ScopedProperty pd(scope);
        while (1) {
            if (o->arrayData && o->arrayData->getProperty(index, pd, &attrs)) {
                if (hasProperty)
                    *hasProperty = true;
                return Object::getValue(receiver, pd->value, attrs);
            }
            if (o->internalClass->vtable->type == Type_StringObject) {
                ScopedString str(scope, static_cast<Heap::StringObject *>(o)->getIndex(index));
                if (str) {
                    attrs = (Attr_NotWritable|Attr_NotConfigurable);
                    if (hasProperty)
                        *hasProperty = true;
                    return str.asReturnedValue();
                }
            }
            o = o->prototype();
            if (!o || o->internalClass->vtable->get != Object::virtualGet)
                break;
        }
    } else {
        while (1) {
            auto idx = o->internalClass->findValueOrGetter(id);
            if (idx.isValid()) {
                if (hasProperty)
                    *hasProperty = true;
                return Object::getValue(receiver, *o->propertyData(idx.index), idx.attrs);
            }
            o = o->prototype();
            if (!o || o->internalClass->vtable->get != Object::virtualGet)
                break;
        }
    }

    if (o) {
        const Value v = Value::fromHeapObject(o);
        const Object &obj = static_cast<const Object &>(v);
        return obj.get(id, receiver, hasProperty);
    }

    if (hasProperty)
        *hasProperty = false;
    return Encode::undefined();
}

// Section 8.12.5
bool Object::internalPut(PropertyKey id, const Value &value, Value *receiver)
{
    Scope scope(this);
    if (scope.hasException())
        return false;

    Object *r = receiver->objectValue();
    if (r && r->d() == d()) {
        // receiver and this object are the same
        if (d()->internalClass->vtable->getOwnProperty == Object::virtualGetOwnProperty) {
            // This object standard methods in the vtable, so we can take a shortcut
            // and avoid the calls to getOwnProperty and defineOwnProperty

            PropertyAttributes attrs;
            PropertyIndex propertyIndex{nullptr, nullptr};

            if (id.isArrayIndex()) {
                if (arrayData())
                    propertyIndex = arrayData()->getValueOrSetter(id.asArrayIndex(), &attrs);
            } else {
                auto member = internalClass()->findValueOrSetter(id);
                if (member.isValid()) {
                    attrs = member.attrs;
                    propertyIndex = d()->writablePropertyData(member.index);
                }
            }

            if (!propertyIndex.isNull() && !attrs.isAccessor()) {
                if (!attrs.isWritable())
                    return false;
                else if (isArrayObject() && id == scope.engine->id_length()->propertyKey()) {
                    bool ok;
                    uint l = value.asArrayLength(&ok);
                    if (!ok) {
                        scope.engine->throwRangeError(value);
                        return false;
                    }
                    ok = setArrayLength(l);
                    if (!ok)
                        return false;
                } else {
                    propertyIndex.set(scope.engine, value);
                }
                return true;
            }
        }
    }

    ScopedProperty p(scope);
    PropertyAttributes attrs;
    attrs = getOwnProperty(id, p);
    if (attrs == Attr_Invalid) {
        ScopedObject p(scope, getPrototypeOf());
        if (p)
            return p->put(id, value, receiver);
        attrs = Attr_Data;
    }

    if (attrs.isAccessor()) {
        ScopedFunctionObject setter(scope, p->setter());
        if (!setter)
            return false;
        JSCallArguments jsCallData(scope, 1);
        jsCallData.args[0] = value;
        *jsCallData.thisObject = *receiver;
        setter->call(jsCallData);
        return !scope.hasException();
    }

    // Data property
    if (!attrs.isWritable())
        return false;
    if (!r)
        return false;
    attrs = r->getOwnProperty(id, p);

    if (attrs != Attr_Invalid) {
        if (attrs.isAccessor() || !attrs.isWritable())
            return false;
    } else {
        if (!r->isExtensible())
            return false;
        attrs = Attr_Data;
    }

    if (r->internalClass()->vtable->defineOwnProperty == virtualDefineOwnProperty) {
        // standard object, we can avoid some more checks
        if (id.isArrayIndex()) {
            r->arraySet(id.asArrayIndex(), value);
        } else {
            ScopedStringOrSymbol s(scope, id.asStringOrSymbol());
            r->insertMember(s, value);
        }
        return true;
    }

    p->value = value;
    return r->defineOwnProperty(id, p, attrs);
}

// Section 8.12.7
bool Object::internalDeleteProperty(PropertyKey id)
{
    if (internalClass()->engine->hasException)
        return false;

    if (id.isArrayIndex()) {
        uint index = id.asArrayIndex();
        Scope scope(engine());
        if (scope.hasException())
            return false;

        Scoped<ArrayData> ad(scope, arrayData());
        if (!ad || ad->vtable()->del(this, index))
            return true;

        return false;
    }

    auto memberIdx = internalClass()->findValueOrGetter(id);
    if (memberIdx.isValid()) {
        if (memberIdx.attrs.isConfigurable()) {
            Heap::InternalClass::removeMember(this, id);
            return true;
        }
        return false;
    }

    return true;
}

bool Object::internalDefineOwnProperty(ExecutionEngine *engine, uint index, const InternalClassEntry *memberEntry, const Property *p, PropertyAttributes attrs)
{
    // clause 5
    if (attrs.isEmpty())
        return true;

    Scope scope(engine);
    ScopedProperty current(scope);
    PropertyAttributes cattrs;
    if (memberEntry) {
        getProperty(*memberEntry, current);
        cattrs = memberEntry->attributes;
    } else if (arrayData()) {
        arrayData()->getProperty(index, current, &cattrs);
        cattrs = arrayData()->attributes(index);
    }

    // clause 6
    if (p->isSubset(attrs, current, cattrs))
        return true;

    // clause 7
    if (!cattrs.isConfigurable()) {
        if (attrs.isConfigurable())
            return false;
        if (attrs.hasEnumerable() && attrs.isEnumerable() != cattrs.isEnumerable())
            return false;
    }

    // clause 8
    if (attrs.isGeneric() || current->value.isEmpty())
        goto accept;

    // clause 9
    if (cattrs.isData() != attrs.isData()) {
        // 9a
        if (!cattrs.isConfigurable())
            return false;
        if (cattrs.isData()) {
            // 9b
            cattrs.setType(PropertyAttributes::Accessor);
            cattrs.clearWritable();
            if (!memberEntry) {
                // need to convert the array and the slot
                initSparseArray();
                Q_ASSERT(arrayData());
                setArrayAttributes(index, cattrs);
            }
            current->setGetter(nullptr);
            current->setSetter(nullptr);
        } else {
            // 9c
            cattrs.setType(PropertyAttributes::Data);
            cattrs.setWritable(false);
            if (!memberEntry) {
                // need to convert the array and the slot
                setArrayAttributes(index, cattrs);
            }
            current->value = Value::undefinedValue();
        }
    } else if (cattrs.isData() && attrs.isData()) { // clause 10
        if (!cattrs.isConfigurable() && !cattrs.isWritable()) {
            if (attrs.isWritable() || !current->value.sameValue(p->value))
                return false;
        }
    } else { // clause 10
        Q_ASSERT(cattrs.isAccessor() && attrs.isAccessor());
        if (!cattrs.isConfigurable()) {
            if (!p->value.isEmpty() && current->value.rawValue() != p->value.rawValue())
                return false;
            if (!p->set.isEmpty() && current->set.rawValue() != p->set.rawValue())
                return false;
        }
    }

  accept:

    current->merge(cattrs, p, attrs);
    if (memberEntry) {
        PropertyKey key = internalClass()->nameMap.at(memberEntry->index);
        InternalClassEntry e;
        Heap::InternalClass::changeMember(this, key, cattrs, &e);
        setProperty(e, current);
    } else {
        setArrayAttributes(index, cattrs);
        arrayData()->setProperty(scope.engine, index, current);
    }
    return true;
}

void Object::copyArrayData(Object *other)
{
    Q_ASSERT(isArrayObject());
    Scope scope(engine());

    if (other->protoHasArray() || ArgumentsObject::isNonStrictArgumentsObject(other) ||
        (other->arrayType() == Heap::ArrayData::Sparse && other->arrayData()->attrs)) {
        uint len = other->getLength();
        Q_ASSERT(len);

        ScopedValue v(scope);
        for (uint i = 0; i < len; ++i) {
            arraySet(i, (v = other->get(i)));
        }
    } else if (!other->arrayData()) {
        ;
    } else {
        Q_ASSERT(!arrayData() && other->arrayData());
        ArrayData::realloc(this, static_cast<ArrayData::Type>(other->d()->arrayData->type),
                           other->d()->arrayData->values.alloc, false);
        if (other->arrayType() == Heap::ArrayData::Sparse) {
            Heap::ArrayData *od = other->d()->arrayData;
            Heap::ArrayData *dd = d()->arrayData;
            dd->sparse = new SparseArray(*od->sparse);
        } else {
            Heap::ArrayData *dd = d()->arrayData;
            dd->values.size = other->d()->arrayData->values.size;
            dd->offset = other->d()->arrayData->offset;
        }
        // ### need a write barrier
        memcpy(d()->arrayData->values.values, other->d()->arrayData->values.values, other->d()->arrayData->values.alloc*sizeof(Value));
    }
    setArrayLengthUnchecked(other->getLength());
}

qint64 Object::virtualGetLength(const Managed *m)
{
    Scope scope(static_cast<const Object *>(m)->engine());
    ScopedValue v(scope, static_cast<Object *>(const_cast<Managed *>(m))->get(scope.engine->id_length()));
    return v->toLength();
}

// 'var' is 'V' in 15.3.5.3.
ReturnedValue Object::virtualInstanceOf(const Object *typeObject, const Value &var)
{
    QV4::ExecutionEngine *engine = typeObject->internalClass()->engine;

    // 15.3.5.3, Assume F is a Function object.
    const FunctionObject *function = typeObject->as<FunctionObject>();
    if (!function)
        return engine->throwTypeError();

    return checkedInstanceOf(engine, function, var);
}

ReturnedValue Object::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup)
{
    // Otherwise we cannot trust the protoIds
    Q_ASSERT(engine->isInitialized);

    Heap::Object *obj = object->d();
    PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]);
    if (object->as<QV4::ProxyObject>()) {
        // proxies invalidate assumptions that we normally maek in lookups
        // so we always need to use the fallback path
        lookup->call = Lookup::Call::GetterQObjectPropertyFallback;
        return lookup->getter(engine, *object);
    }
    if (name.isArrayIndex()) {
        lookup->indexedLookup.index = name.asArrayIndex();
        lookup->call = Lookup::Call::GetterIndexed;
        return lookup->getter(engine, *object);
    }

    auto index = obj->internalClass->findValueOrGetter(name);
    if (index.isValid()) {
        PropertyAttributes attrs = index.attrs;
        uint nInline = obj->vtable()->nInlineProperties;
        if (attrs.isData()) {
            if (index.index < obj->vtable()->nInlineProperties) {
                index.index += obj->vtable()->inlinePropertyOffset;
                lookup->call = Lookup::Call::Getter0Inline;
            } else {
                index.index -= nInline;
                lookup->call = Lookup::Call::Getter0MemberData;
            }
        } else {
            lookup->call = Lookup::Call::GetterAccessor;
        }
        lookup->objectLookup.ic.set(engine, obj->internalClass.get());
        lookup->objectLookup.offset = index.index;
        return lookup->getter(engine, *object);
    }

    lookup->protoLookup.protoId = obj->internalClass->protoId;
    lookup->resolveProtoGetter(name, obj->prototype());
    return lookup->getter(engine, *object);
}

bool Object::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value)
{
    // Otherwise we cannot trust the protoIds
    Q_ASSERT(engine->isInitialized);

    Scope scope(engine);
    ScopedString name(scope, scope.engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]);

    Heap::InternalClass *c = object->internalClass();
    PropertyKey key = name->toPropertyKey();
    auto idx = c->findValueOrSetter(key);
    if (idx.isValid()) {
        if (object->isArrayObject() && idx.index == Heap::ArrayObject::LengthPropertyIndex) {
            Q_ASSERT(!idx.attrs.isAccessor());
            lookup->call = Lookup::Call::SetterArrayLength;
            return lookup->setter(engine, *object, value);
        } else if (idx.attrs.isData() && idx.attrs.isWritable()) {
            lookup->objectLookup.ic.set(engine, object->internalClass());
            lookup->objectLookup.index = idx.index;
            const auto nInline = object->d()->vtable()->nInlineProperties;
            if (idx.index < nInline) {
                lookup->call = Lookup::Call::Setter0Inline;
                lookup->objectLookup.offset = idx.index + object->d()->vtable()->inlinePropertyOffset;
            } else {
                lookup->call = Lookup::Call::Setter0MemberData;
                lookup->objectLookup.offset = idx.index - nInline;
            }
            return lookup->setter(engine, *object, value);
        } else {
            // ### handle setter
            lookup->call = Lookup::Call::SetterQObjectPropertyFallback;
        }
        return lookup->setter(engine, *object, value);
    }

    lookup->insertionLookup.protoId = c->protoId;
    if (!object->put(key, value)) {
        lookup->call = Lookup::Call::SetterQObjectPropertyFallback;
        return false;
    }

    if (object->internalClass() == c) {
        // ### setter in the prototype, should handle this
        lookup->call = Lookup::Call::SetterQObjectPropertyFallback;
        return true;
    }
    idx = object->internalClass()->findValueOrSetter(key);
    if (!idx.isValid() || idx.attrs.isAccessor()) { // ### can this even happen?
        lookup->call = Lookup::Call::SetterQObjectPropertyFallback;
        return false;
    }
    lookup->insertionLookup.newClass.set(engine, object->internalClass());
    lookup->insertionLookup.offset = idx.index;
    lookup->call = Lookup::Call::SetterInsert;
    return true;
}

/*!
 * \internal
 *
 * This method is modeled after \l{QMetaObject::metacall}. It takes a JavaScript
 * \a object and performs \a call on it, using \a index as identifier for the
 * property/method/etc to be used and \a a as arguments. Like
 * \l{QMetaObject::metacall} this method is overly generic in order to reduce the
 * API surface. On a plain Object it does nothing and returns 0. Specific
 * objects can override it and do some meaningful work. If the metacall succeeds
 * they should return a non-0 value. Otherwise they should return 0.
 *
 * Most prominently, \l QMetaObject::ReadProperty and \l QMetaObject::WriteProperty
 * calls can be used to manipulate properties of QObjects and Q_GADGETs wrapped
 * by QV4::QObjectWrapper, QV4::QQmlTypeWrapper and QV4::QQmlValueTypeWrapper.
 * They can also be used to manipulate elements in QV4::Sequence.
 */
int Object::virtualMetacall(Object *object, QMetaObject::Call call, int index, void **a)
{
    Q_UNUSED(object);
    Q_UNUSED(call);
    Q_UNUSED(index);
    Q_UNUSED(a);
    return 0;
}

ReturnedValue Object::checkedInstanceOf(ExecutionEngine *engine, const FunctionObject *f, const Value &var)
{
    Scope scope(engine);
    if (f->isBoundFunction()) {
        ScopedValue v(scope, static_cast<const BoundFunction *>(f)->target());
        f = v->as<FunctionObject>();
    }

    // 15.3.5.3, 1: HasInstance can only be used on an object
    const Object *lhs = var.as<Object>();
    if (!lhs)
        return Encode(false);

    // 15.3.5.3, 2
    Value p = Value::fromReturnedValue(f->protoProperty());
    const Object *o = p.objectValue();
    if (!o) // 15.3.5.3, 3
        return f->engine()->throwTypeError();

    Heap::Object *v = lhs->d();

    // 15.3.5.3, 4
    while (v) {
        // 15.3.5.3, 4, a
        v = v->prototype();

        // 15.3.5.3, 4, b
        if (!v)
            break; // will return false

        // 15.3.5.3, 4, c
        else if (o->d() == v)
            return Encode(true);
    }

    return Encode(false);
}

bool Object::virtualHasProperty(const Managed *m, PropertyKey id)
{
    Scope scope(m->engine());
    ScopedObject o(scope, m);
    ScopedProperty p(scope);

    if (o->getOwnProperty(id, p) != Attr_Invalid)
        return true;

    o = o->getPrototypeOf();
    if (o)
        return o->hasProperty(id);

    return false;
}

PropertyAttributes Object::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p)
{
    PropertyAttributes attrs;
    const Object *o = static_cast<const Object *>(m);
    if (id.isArrayIndex()) {
        uint index = id.asArrayIndex();
        if (o->arrayData()) {
            if (o->arrayData()->getProperty(index, p, &attrs))
                return attrs;
        }
    } else {
        Q_ASSERT(id.asStringOrSymbol());

        auto member = o->internalClass()->find(id);
        if (member.isValid()) {
            attrs = member.attributes;
            if (p) {
                p->value = *o->propertyData(member.index);
                if (attrs.isAccessor())
                    p->set = *o->propertyData(member.setterIndex);
            }
            return attrs;
        }
    }

    return Attr_Invalid;
}

bool Object::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs)
{
    Object *o = static_cast<Object *>(m);
    Scope scope(o);

    if (id.isArrayIndex()) {
        uint index = id.asArrayIndex();

        bool hasProperty = false;

        if (o->arrayData()) {
            hasProperty = o->arrayData()->mappedIndex(index) != UINT_MAX;
            if (!hasProperty && o->isStringObject())
                hasProperty = (index < static_cast<StringObject *>(o)->length());
        }

        if (!hasProperty) {
            if (!o->isExtensible())
                return false;

            ScopedProperty pp(scope);
            pp->copy(p, attrs);
            pp->fullyPopulated(&attrs);
            if (attrs == Attr_Data) {
                ScopedValue v(scope, pp->value);
                o->arraySet(index, v);
            } else {
                o->arraySet(index, pp, attrs);
            }
            return true;
        }

        return o->internalDefineOwnProperty(scope.engine, index, nullptr, p, attrs);
    }

    Scoped<InternalClass> ic(scope, o->internalClass());
    auto memberIndex = ic->d()->find(id);

    if (!memberIndex.isValid()) {
        if (!o->isExtensible())
            return false;

        // If the IC is locked, you're not allowed to shadow any unconfigurable properties.
        if (ic->d()->isLocked()) {
            while (Heap::Object *prototype = ic->d()->prototype) {
                ic = prototype->internalClass;
                const auto entry = ic->d()->find(id);
                if (entry.isValid()) {
                    if (entry.attributes.isConfigurable())
                        break;
                    qCWarning(lcJavaScriptGlobals).noquote()
                            << QStringLiteral("You cannot shadow the locked property "
                                              "'%1' in QML.").arg(id.toQString());
                    return false;
                }
            }
        }

        Scoped<StringOrSymbol> name(scope, id.asStringOrSymbol());
        ScopedProperty pd(scope);
        pd->copy(p, attrs);
        pd->fullyPopulated(&attrs);
        o->insertMember(name, pd, attrs);
        return true;
    }

    return o->internalDefineOwnProperty(scope.engine, UINT_MAX, &memberIndex, p, attrs);
}

bool Object::virtualIsExtensible(const Managed *m)
{
    return m->d()->internalClass->isExtensible();
}

bool Object::virtualPreventExtensions(Managed *m)
{
    Q_ASSERT(m->isObject());
    Object *o = static_cast<Object *>(m);
    o->setInternalClass(o->internalClass()->nonExtensible());
    return true;
}

Heap::Object *Object::virtualGetPrototypeOf(const Managed *m)
{
    return m->internalClass()->prototype;
}

bool Object::virtualSetPrototypeOf(Managed *m, const Object *proto)
{
    Q_ASSERT(m->isObject());
    Object *o = static_cast<Object *>(m);
    Heap::InternalClass *ic = o->internalClass();
    Heap::Object *current = ic->prototype;
    Heap::Object *protod = proto ? proto->d() : nullptr;
    if (current == protod)
        return true;
    if (!ic->isExtensible() || ic->isLocked())
        return false;
    Heap::Object *p = protod;
    while (p) {
        if (p == o->d())
            return false;
        if (p->vtable()->getPrototypeOf != Object::staticVTable()->getPrototypeOf)
            break;
        p = p->prototype();
    }
    o->setInternalClass(ic->changePrototype(protod));
    return true;
}

bool Object::setArrayLength(uint newLen)
{
    Q_ASSERT(isArrayObject());
    if (!internalClass()->propertyData[Heap::ArrayObject::LengthPropertyIndex].isWritable())
        return false;
    uint oldLen = getLength();
    bool ok = true;
    if (newLen < oldLen) {
        if (arrayData()) {
            uint l = arrayData()->vtable()->truncate(this, newLen);
            if (l != newLen)
                ok = false;
            newLen = l;
        }
    } else {
        if (newLen >= 0x100000)
            initSparseArray();
        else
            ArrayData::realloc(this, arrayType(), newLen, false);
    }
    setArrayLengthUnchecked(newLen);
    return ok;
}

void Object::initSparseArray()
{
    if (arrayType() == Heap::ArrayData::Sparse)
        return;

    ArrayData::realloc(this, Heap::ArrayData::Sparse, 0, false);
}

bool Object::isConcatSpreadable() const
{
    Scope scope(this);
    ScopedValue spreadable(scope, get(scope.engine->symbol_isConcatSpreadable()));
    if (!spreadable->isUndefined())
        return spreadable->toBoolean();
    return isArray();
}

bool Object::isArray() const
{
    if (isArrayObject())
        return true;
    if (vtable() == ProxyObject::staticVTable()) {
        const ProxyObject *p = static_cast<const ProxyObject *>(this);
        Scope scope(this);
        if (!p->d()->handler) {
            scope.engine->throwTypeError();
            return false;
        }
        ScopedObject o(scope, p->d()->target);
        return o->isArray();
    }
    return false;
}

const FunctionObject *Object::speciesConstructor(Scope &scope, const FunctionObject *defaultConstructor) const
{
    ScopedValue C(scope, get(scope.engine->id_constructor()));
    if (C->isUndefined())
        return defaultConstructor;
    const Object *c = C->objectValue();
    if (!c) {
        scope.engine->throwTypeError();
        return nullptr;
    }
    ScopedValue S(scope, c->get(scope.engine->symbol_species()));
    if (S->isNullOrUndefined())
        return defaultConstructor;
    const FunctionObject *f = S->as<FunctionObject>();
    if (!f || !f->isConstructor()) {
        scope.engine->throwTypeError();
        return nullptr;
    }
    Q_ASSERT(f->isFunctionObject());
    return static_cast<const FunctionObject *>(f);
}

bool Object::setProtoFromNewTarget(const Value *newTarget)
{
    if (!newTarget || newTarget->isUndefined())
        return false;

    Q_ASSERT(newTarget->isFunctionObject());
    Scope scope(this);
    ScopedObject proto(scope, static_cast<const FunctionObject *>(newTarget)->protoProperty());
    if (proto) {
        setPrototypeOf(proto);
        return true;
    }
    return false;
}


DEFINE_OBJECT_VTABLE(ArrayObject);

void Heap::ArrayObject::init(const QStringList &list)
{
    Object::init();
    commonInit();
    Scope scope(internalClass->engine);
    ScopedObject a(scope, this);

    // Converts a QStringList to JS.
    // The result is a new Array object with length equal to the length
    // of the QStringList, and the elements being the QStringList's
    // elements converted to JS Strings.
    int len = list.size();
    a->arrayReserve(len);
    ScopedValue v(scope);
    for (int ii = 0; ii < len; ++ii)
        a->arrayPut(ii, (v = scope.engine->newString(list.at(ii))));
    a->setArrayLengthUnchecked(len);
}

qint64 ArrayObject::virtualGetLength(const Managed *m)
{
    const ArrayObject *a = static_cast<const ArrayObject *>(m);
    return a->propertyData(Heap::ArrayObject::LengthPropertyIndex)->toLength();
}

QStringList ArrayObject::toQStringList() const
{
    QStringList result;

    QV4::ExecutionEngine *engine = internalClass()->engine;
    Scope scope(engine);
    ScopedValue v(scope);

    uint length = getLength();
    result.reserve(length);
    for (uint i = 0; i < length; ++i) {
        v = const_cast<ArrayObject *>(this)->get(i);
        result.append(v->toQStringNoThrow());
    }
    return result;
}

bool ArrayObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs)
{
    Q_ASSERT(m->isArrayObject());
    ArrayObject *a = static_cast<ArrayObject *>(m);

    if (id.isArrayIndex()) {
        uint index = id.asArrayIndex();
        uint len = a->getLength();
        if (index >= len && !a->internalClass()->propertyData[Heap::ArrayObject::LengthPropertyIndex].isWritable())
            return false;

        bool succeeded = Object::virtualDefineOwnProperty(m, id, p, attrs);
        if (!succeeded)
            return false;

        if (index >= len)
            a->setArrayLengthUnchecked(index + 1);

        return true;
    }

    ExecutionEngine *engine = m->engine();
    if (id == engine->id_length()->propertyKey()) {
        Scope scope(engine);
        Q_ASSERT(a->internalClass()->verifyIndex(engine->id_length()->propertyKey(), Heap::ArrayObject::LengthPropertyIndex));
        ScopedProperty lp(scope);
        InternalClassEntry e = a->internalClass()->find(scope.engine->id_length()->propertyKey());
        a->getProperty(e, lp);
        if (attrs.isEmpty() || p->isSubset(attrs, lp, e.attributes))
            return true;
        if (!e.attributes.isWritable() || attrs.type() == PropertyAttributes::Accessor || attrs.isConfigurable() || attrs.isEnumerable())
            return false;
        bool succeeded = true;
        if (attrs.type() == PropertyAttributes::Data) {
            bool ok;
            uint l = p->value.asArrayLength(&ok);
            if (!ok) {
                ScopedValue v(scope, p->value);
                engine->throwRangeError(v);
                return false;
            }
            succeeded = a->setArrayLength(l);
        }
        if (attrs.hasWritable() && !attrs.isWritable()) {
            e.attributes.setWritable(false);
            Heap::InternalClass::changeMember(a, engine->id_length()->propertyKey(), e.attributes);
        }
        if (!succeeded)
            return false;
        return true;
    }
    return Object::virtualDefineOwnProperty(m, id, p, attrs);
}
