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

#include <private/qqmlvaluetypewrapper_p.h>
#include <private/qv4functionobject_p.h>
#include <private/qv4identifiertable_p.h>
#include <private/qv4qobjectwrapper_p.h>
#include <private/qv4runtime_p.h>
#include <private/qv4stackframe_p.h>

QT_BEGIN_NAMESPACE

using namespace QV4;


void Lookup::resolveProtoGetter(PropertyKey name, const Heap::Object *proto)
{
    while (proto) {
        auto index = proto->internalClass->findValueOrGetter(name);
        if (index.isValid()) {
            PropertyAttributes attrs = index.attrs;
            protoLookup.data = proto->propertyData(index.index);
            if (attrs.isData()) {
                call = Call::GetterProto;
            } else {
                call = Call::GetterProtoAccessor;
            }
            return;
        }
        proto = proto->prototype();
    }
    // ### put in a getterNotFound!
    call = Call::GetterQObjectPropertyFallback;
}

ReturnedValue Lookup::resolveGetter(ExecutionEngine *engine, const Object *object)
{
    return object->resolveLookupGetter(engine, this);
}

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

    primitiveLookup.type = object.type();
    switch (primitiveLookup.type) {
    case Value::Undefined_Type:
    case Value::Null_Type: {
        Scope scope(engine);
        ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]);
        const QString message = QStringLiteral("Cannot read property '%1' of %2").arg(name->toQString())
            .arg(QLatin1String(primitiveLookup.type == Value::Undefined_Type ? "undefined" : "null"));
        return engine->throwTypeError(message);
    }
    case Value::Boolean_Type:
        primitiveLookup.proto.set(engine, engine->booleanPrototype()->d());
        break;
    case Value::Managed_Type: {
        // ### Should move this over to the Object path, as strings also have an internalClass
        Q_ASSERT(object.isStringOrSymbol());
        primitiveLookup.proto.set(engine, static_cast<const Managed &>(object).internalClass()->prototype);
        Q_ASSERT(primitiveLookup.proto);
        Scope scope(engine);
        ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]);
        if (object.isString() && name->equals(engine->id_length())) {
            // special case, as the property is on the object itself
            call = Call::GetterStringLength;
            return stringLengthGetter(this, engine, object);
        }
        break;
    }
    case Value::Integer_Type:
    default: // Number
        primitiveLookup.proto.set(engine, engine->numberPrototype()->d());
    }

    PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]);
    protoLookup.protoId = primitiveLookup.proto->internalClass->protoId;
    resolveProtoGetter(name, primitiveLookup.proto);

    switch (call) {
    case Call::GetterProto:
        call = Call::GetterProtoPrimitive;
        break;
    case Call::GetterProtoAccessor:
        call = Call::GetterAccessorPrimitive;
        break;
    default:
        break;
    }

    return getter(engine, object);
}

ReturnedValue Lookup::resolveGlobalGetter(ExecutionEngine *engine)
{
    // Otherwise we cannot trust the protoIds
    Q_ASSERT(engine->isInitialized);

    Object *o = engine->globalObject;
    PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]);
    protoLookup.protoId = o->internalClass()->protoId;
    resolveProtoGetter(name, o->d());

    switch (call) {
    case Call::GetterProto:
        call = Call::GlobalGetterProto;
        break;
    case Call::GetterProtoAccessor:
        call = Call::GlobalGetterProtoAccessor;
        break;
    default:
        call = Call::GlobalGetterGeneric;
        Scope scope(engine);
        ScopedString n(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]);
        return engine->throwReferenceError(n);
    }

    return globalGetter(engine);
}

ReturnedValue Lookup::getterGeneric(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    if (const Object *o = object.as<Object>())
        return lookup->resolveGetter(engine, o);
    return lookup->resolvePrimitiveGetter(engine, object);
}

static inline void setupObjectLookupTwoClasses(Lookup *lookup, const Lookup &first, const Lookup &second)
{
    Heap::InternalClass *ic1 = first.objectLookup.ic;
    const uint offset1 = first.objectLookup.offset;
    Heap::InternalClass *ic2 = second.objectLookup.ic;
    const uint offset2 = second.objectLookup.offset;
    auto engine = ic1->engine;

    lookup->objectLookupTwoClasses.ic.set(engine, ic1);
    lookup->objectLookupTwoClasses.ic2.set(engine, ic2);
    lookup->objectLookupTwoClasses.offset = offset1;
    lookup->objectLookupTwoClasses.offset2 = offset2;
}

static inline void setupProtoLookupTwoClasses(Lookup *lookup, const Lookup &first, const Lookup &second)
{
    const quintptr protoId1 = first.protoLookup.protoId;
    const Value *data1 = first.protoLookup.data;
    const quintptr protoId2 = second.protoLookup.protoId;
    const Value *data2 = second.protoLookup.data;

    lookup->protoLookupTwoClasses.protoId = protoId1;
    lookup->protoLookupTwoClasses.protoId2 = protoId2;
    lookup->protoLookupTwoClasses.data = data1;
    lookup->protoLookupTwoClasses.data2 = data2;
}

ReturnedValue Lookup::getterTwoClasses(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    if (const Object *o = object.as<Object>()) {

        // Do the resolution on a second lookup, then merge.
        Lookup second;
        memset(&second, 0, sizeof(Lookup));
        second.nameIndex = lookup->nameIndex;
        second.forCall = lookup->forCall;
        second.call = Call::GetterGeneric;
        const ReturnedValue result = second.resolveGetter(engine, o);

        switch (lookup->call) {
        case Call::Getter0Inline: {
            switch (second.call) {
            case Call::Getter0Inline:
                setupObjectLookupTwoClasses(lookup, *lookup, second);
                lookup->call = Call::Getter0InlineGetter0Inline;
                return result;
            case Call::Getter0MemberData:
                setupObjectLookupTwoClasses(lookup, *lookup, second);
                lookup->call = Call::Getter0InlineGetter0MemberData;
                return result;
            default:
                break;
            }
            break;
        }
        case Call::Getter0MemberData:
            switch (second.call) {
            case Call::Getter0Inline:
                // NB: Reversed order, so that we can use the same lookup function as above.
                setupObjectLookupTwoClasses(lookup, second, *lookup);
                lookup->call = Call::Getter0InlineGetter0MemberData;
                return result;
            case Call::Getter0MemberData:
                setupObjectLookupTwoClasses(lookup, *lookup, second);
                lookup->call = Call::Getter0MemberDataGetter0MemberData;
                return result;
            default:
                break;
            }
            break;
        case Call::GetterProto:
            switch (second.call) {
            case Call::GetterProto:
                setupProtoLookupTwoClasses(lookup, *lookup, second);
                lookup->call =  Call::GetterProtoTwoClasses;
                return result;
            default:
                break;
            }
            break;
        case Call::GetterProtoAccessor:
            switch (second.call) {
            case Call::GetterProtoAccessor:
                setupProtoLookupTwoClasses(lookup, *lookup, second);
                lookup->call = Call::GetterProtoAccessorTwoClasses;
                return result;
            default:
                break;
            }
            break;
        default:
            break;
        }

        // If any of the above options were true, the propertyCache was inactive.
        second.releasePropertyCache();
    }

    lookup->call = Call::GetterQObjectPropertyFallback;
    return getterFallback(lookup, engine, object);
}

ReturnedValue Lookup::getterFallback(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    QV4::Scope scope(engine);
    QV4::ScopedObject o(scope, object.toObject(scope.engine));
    if (!o)
        return Encode::undefined();
    ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]);
    return o->get(name);
}

ReturnedValue Lookup::getter0MemberData(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    // we can safely cast to a QV4::Object here. If object is actually a string,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o) {
        if (lookup->objectLookup.ic == o->internalClass)
            return o->memberData->values.data()[lookup->objectLookup.offset].asReturnedValue();
    }
    return getterTwoClasses(lookup, engine, object);
}

ReturnedValue Lookup::getter0Inline(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    // we can safely cast to a QV4::Object here. If object is actually a string,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o) {
        if (lookup->objectLookup.ic == o->internalClass)
            return o->inlinePropertyDataWithOffset(lookup->objectLookup.offset)->asReturnedValue();
    }
    return getterTwoClasses(lookup, engine, object);
}

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

    // we can safely cast to a QV4::Object here. If object is actually a string,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o) {
        if (lookup->protoLookup.protoId == o->internalClass->protoId)
            return lookup->protoLookup.data->asReturnedValue();
    }
    return getterTwoClasses(lookup, engine, object);
}

ReturnedValue Lookup::getter0Inlinegetter0Inline(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    // we can safely cast to a QV4::Object here. If object is actually a string,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o) {
        if (lookup->objectLookupTwoClasses.ic == o->internalClass)
            return o->inlinePropertyDataWithOffset(lookup->objectLookupTwoClasses.offset)->asReturnedValue();
        if (lookup->objectLookupTwoClasses.ic2 == o->internalClass)
            return o->inlinePropertyDataWithOffset(lookup->objectLookupTwoClasses.offset2)->asReturnedValue();
    }
    lookup->call = Call::GetterQObjectPropertyFallback;
    return getterFallback(lookup, engine, object);
}

ReturnedValue Lookup::getter0Inlinegetter0MemberData(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    // we can safely cast to a QV4::Object here. If object is actually a string,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o) {
        if (lookup->objectLookupTwoClasses.ic == o->internalClass)
            return o->inlinePropertyDataWithOffset(lookup->objectLookupTwoClasses.offset)->asReturnedValue();
        if (lookup->objectLookupTwoClasses.ic2 == o->internalClass)
            return o->memberData->values.data()[lookup->objectLookupTwoClasses.offset2].asReturnedValue();
    }
    lookup->call = Call::GetterQObjectPropertyFallback;
    return getterFallback(lookup, engine, object);
}

ReturnedValue Lookup::getter0MemberDatagetter0MemberData(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    // we can safely cast to a QV4::Object here. If object is actually a string,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o) {
        if (lookup->objectLookupTwoClasses.ic == o->internalClass)
            return o->memberData->values.data()[lookup->objectLookupTwoClasses.offset].asReturnedValue();
        if (lookup->objectLookupTwoClasses.ic2 == o->internalClass)
            return o->memberData->values.data()[lookup->objectLookupTwoClasses.offset2].asReturnedValue();
    }
    lookup->call = Call::GetterQObjectPropertyFallback;
    return getterFallback(lookup, engine, object);
}

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

    // we can safely cast to a QV4::Object here. If object is actually a string,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o) {
        if (lookup->protoLookupTwoClasses.protoId == o->internalClass->protoId)
            return lookup->protoLookupTwoClasses.data->asReturnedValue();
        if (lookup->protoLookupTwoClasses.protoId2 == o->internalClass->protoId)
            return lookup->protoLookupTwoClasses.data2->asReturnedValue();
        return getterFallback(lookup, engine, object);
    }
    lookup->call = Call::GetterQObjectPropertyFallback;
    return getterFallback(lookup, engine, object);
}

ReturnedValue Lookup::getterAccessor(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    // we can safely cast to a QV4::Object here. If object is actually a string,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o) {
        if (lookup->objectLookup.ic == o->internalClass) {
            const Value *getter = o->propertyData(lookup->objectLookup.offset);
            if (!getter->isFunctionObject()) // ### catch at resolve time
                return Encode::undefined();

            return checkedResult(engine, static_cast<const FunctionObject *>(getter)->call(
                                     &object, nullptr, 0));
        }
    }
    lookup->call = Call::GetterQObjectPropertyFallback;
    return getterFallback(lookup, engine, object);
}

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

    // we can safely cast to a QV4::Object here. If object is actually a string,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o && lookup->protoLookup.protoId == o->internalClass->protoId) {
        const Value *getter = lookup->protoLookup.data;
        if (!getter->isFunctionObject()) // ### catch at resolve time
            return Encode::undefined();

        return checkedResult(engine, static_cast<const FunctionObject *>(getter)->call(
                                 &object, nullptr, 0));
    }
    return getterTwoClasses(lookup, engine, object);
}

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

    // we can safely cast to a QV4::Object here. If object is actually a string,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o) {
        const Value *getter = nullptr;
        if (lookup->protoLookupTwoClasses.protoId == o->internalClass->protoId)
            getter = lookup->protoLookupTwoClasses.data;
        else if (lookup->protoLookupTwoClasses.protoId2 == o->internalClass->protoId)
            getter = lookup->protoLookupTwoClasses.data2;
        if (getter) {
            if (!getter->isFunctionObject()) // ### catch at resolve time
                return Encode::undefined();

            return checkedResult(engine, static_cast<const FunctionObject *>(getter)->call(
                                     &object, nullptr, 0));
        }
    }
    lookup->call = Call::GetterQObjectPropertyFallback;
    return getterFallback(lookup, engine, object);
}

ReturnedValue Lookup::getterIndexed(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    Object *o = object.objectValue();
    if (o) {
        Heap::Object *ho = o->d();
        if (ho->arrayData && ho->arrayData->type == Heap::ArrayData::Simple) {
            Heap::SimpleArrayData *s = ho->arrayData.cast<Heap::SimpleArrayData>();
            if (lookup->indexedLookup.index < s->values.size)
                if (!s->data(lookup->indexedLookup.index).isEmpty())
                    return s->data(lookup->indexedLookup.index).asReturnedValue();
        }
        return o->get(lookup->indexedLookup.index);
    }
    lookup->call = Call::GetterQObjectPropertyFallback;
    return getterFallback(lookup, engine, object);
}

ReturnedValue Lookup::getterQObject(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    const auto revertLookup = [lookup, engine, &object]() {
        lookup->qobjectLookup.propertyCache->release();
        lookup->qobjectLookup.propertyCache = nullptr;
        lookup->call = Call::GetterGeneric;
        return Lookup::getterGeneric(lookup, engine, object);
    };

    const QObjectWrapper::Flags flags = lookup->forCall
            ? QObjectWrapper::AllowOverride
            : (QObjectWrapper::AllowOverride | QObjectWrapper::AttachMethods);

    return QObjectWrapper::lookupPropertyGetterImpl(lookup, engine, object, flags, revertLookup);
}

ReturnedValue Lookup::getterQObjectMethod(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    const auto revertLookup = [lookup, engine, &object]() {
        lookup->qobjectMethodLookup.propertyCache->release();
        lookup->qobjectMethodLookup.propertyCache = nullptr;
        lookup->call = Call::GetterGeneric;
        return Lookup::getterGeneric(lookup, engine, object);
    };

    const QObjectWrapper::Flags flags = lookup->forCall
            ? QObjectWrapper::AllowOverride
            : (QObjectWrapper::AllowOverride | QObjectWrapper::AttachMethods);

    return QObjectWrapper::lookupMethodGetterImpl(lookup, engine, object, flags, revertLookup);
}

ReturnedValue Lookup::getterFallbackMethod(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    const auto revertLookup = [lookup, engine, &object]() {
        lookup->call = Call::GetterGeneric;
        return Lookup::getterGeneric(lookup, engine, object);
    };

    const Object *o = object.as<Object>();
    if (!o || o->internalClass() != lookup->qobjectMethodLookup.ic)
        return revertLookup();

    const QObjectWrapper *This = static_cast<const QObjectWrapper *>(o);
    QObject *qobj = This->d()->object();
    if (QQmlData::wasDeleted(qobj))
        return QV4::Encode::undefined();

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

    QQmlPropertyData local;
    const QQmlPropertyData *property = QQmlPropertyCache::property(
            qobj, name, engine->callingQmlContext(), &local);
    if (!property)
        return Encode::undefined();

    QV4::ScopedValue result(
            scope, QObjectWrapper::getProperty(
                           engine, This->d(), qobj, property,
                           lookup->forCall ? QObjectWrapper::NoFlag : QObjectWrapper::AttachMethods));

    // In the general case we cannot rely on the method to exist or stay the same across calls.
    // However, the AOT compiler can prove it in certain cases. For these, we store the method.
    if (QObjectMethod *method = result->as<QV4::QObjectMethod>())
        lookup->qobjectMethodLookup.method.set(engine, method->d());

    return result->asReturnedValue();
}

ReturnedValue Lookup::getterValueType(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    const auto revertLookup = [lookup, engine, &object]() {
        lookup->qgadgetLookup.metaObject = quintptr(0);
        lookup->call = Call::GetterGeneric;
        return Lookup::getterGeneric(lookup, engine, object);
    };

    // we can safely cast to a QV4::Object here. If object is something else,
    // the internal class won't match
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (!o || o->internalClass != lookup->qgadgetLookup.ic)
        return revertLookup();

    Heap::QQmlValueTypeWrapper *valueTypeWrapper =
            const_cast<Heap::QQmlValueTypeWrapper*>(static_cast<const Heap::QQmlValueTypeWrapper *>(o));
    if (valueTypeWrapper->metaObject() != reinterpret_cast<const QMetaObject *>(lookup->qgadgetLookup.metaObject - 1))
        return revertLookup();

    if (valueTypeWrapper->isReference() && !valueTypeWrapper->readReference())
        return Encode::undefined();

    return QQmlValueTypeWrapper::getGadgetProperty(
            engine, valueTypeWrapper, QMetaType(lookup->qgadgetLookup.metaType),
            lookup->qgadgetLookup.coreIndex, lookup->qgadgetLookup.isFunction,
            lookup->qgadgetLookup.isEnum);
}

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

    if (object.type() == lookup->primitiveLookup.type && !object.isObject()) {
        Heap::Object *o = lookup->primitiveLookup.proto;
        if (lookup->primitiveLookup.protoId == o->internalClass->protoId)
            return lookup->primitiveLookup.data->asReturnedValue();
    }
    lookup->call = Call::GetterGeneric;
    return getterGeneric(lookup, engine, object);
}

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

    if (object.type() == lookup->primitiveLookup.type && !object.isObject()) {
        Heap::Object *o = lookup->primitiveLookup.proto;
        if (lookup->primitiveLookup.protoId == o->internalClass->protoId) {
            const Value *getter = lookup->primitiveLookup.data;
            if (!getter->isFunctionObject()) // ### catch at resolve time
                return Encode::undefined();

            return checkedResult(engine, static_cast<const FunctionObject *>(getter)->call(
                                     &object, nullptr, 0));
        }
    }
    lookup->call = Call::GetterGeneric;
    return getterGeneric(lookup, engine, object);
}

ReturnedValue Lookup::stringLengthGetter(Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
    if (const String *s = object.as<String>())
        return Encode(s->d()->length());

    lookup->call = Call::GetterGeneric;
    return getterGeneric(lookup, engine, object);
}

ReturnedValue Lookup::globalGetterGeneric(Lookup *lookup, ExecutionEngine *engine)
{
    return lookup->resolveGlobalGetter(engine);
}

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

    Heap::Object *o = engine->globalObject->d();
    if (lookup->protoLookup.protoId == o->internalClass->protoId)
        return lookup->protoLookup.data->asReturnedValue();
    lookup->call = Call::GlobalGetterGeneric;
    return globalGetterGeneric(lookup, engine);
}

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

    Heap::Object *o = engine->globalObject->d();
    if (lookup->protoLookup.protoId == o->internalClass->protoId) {
        const Value *getter = lookup->protoLookup.data;
        if (!getter->isFunctionObject()) // ### catch at resolve time
            return Encode::undefined();

        return checkedResult(engine, static_cast<const FunctionObject *>(getter)->call(
                                  engine->globalObject, nullptr, 0));
    }

    lookup->call = Call::GlobalGetterGeneric;
    return globalGetterGeneric(lookup, engine);
}

bool Lookup::resolveSetter(ExecutionEngine *engine, Object *object, const Value &value)
{
    return object->resolveLookupSetter(engine, this, value);
}

bool Lookup::setterGeneric(Lookup *lookup, ExecutionEngine *engine, Value &object, const Value &value)
{
    if (object.isObject())
        return lookup->resolveSetter(engine, static_cast<Object *>(&object), value);

    if (engine->currentStackFrame->v4Function->isStrict())
        return false;

    Scope scope(engine);
    ScopedObject o(scope, RuntimeHelpers::convertToObject(scope.engine, object));
    if (!o) // type error
        return false;
    ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]);
    return o->put(name, value);
}

bool Lookup::setterTwoClasses(Lookup *lookup, ExecutionEngine *engine, Value &object, const Value &value)
{
    // A precondition of this method is that lookup->objectLookup is the active variant of the union.
    Q_ASSERT(lookup->call == Call::Setter0MemberData || lookup->call == Call::Setter0Inline);

    if (object.isObject()) {

        // As lookup->objectLookup is active, we can stash some members here, before resolving.
        Heap::InternalClass *ic = lookup->objectLookup.ic;
        const uint index = lookup->objectLookup.index;

        if (!lookup->resolveSetter(engine, static_cast<Object *>(&object), value)) {
            lookup->call = Call::SetterQObjectPropertyFallback;
            return false;
        }

        if (lookup->call == Call::Setter0MemberData || lookup->call == Call::Setter0Inline) {
            auto engine = ic->engine;
            lookup->objectLookupTwoClasses.ic.set(engine, ic);
            lookup->objectLookupTwoClasses.ic2.set(engine, ic);
            lookup->objectLookupTwoClasses.offset = index;
            lookup->objectLookupTwoClasses.offset2 = index;
            lookup->call = Call::Setter0Setter0;
            return true;
        }

        lookup->releasePropertyCache();
    }

    lookup->call = Call::SetterQObjectPropertyFallback;
    return setterFallback(lookup, engine, object, value);
}

bool Lookup::setterFallback(Lookup *lookup, ExecutionEngine *engine, Value &object, const Value &value)
{
    QV4::Scope scope(engine);
    QV4::ScopedObject o(scope, object.toObject(scope.engine));
    if (!o)
        return false;

    ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]);
    return o->put(name, value);
}

bool Lookup::setter0MemberData(Lookup *lookup, ExecutionEngine *engine, Value &object, const Value &value)
{
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o && o->internalClass == lookup->objectLookup.ic) {
        o->memberData->values.set(engine, lookup->objectLookup.offset, value);
        return true;
    }

    return setterTwoClasses(lookup, engine, object, value);
}

bool Lookup::setter0Inline(Lookup *lookup, ExecutionEngine *engine, Value &object, const Value &value)
{
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o && o->internalClass == lookup->objectLookup.ic) {
        o->setInlinePropertyWithOffset(engine, lookup->objectLookup.offset, value);
        return true;
    }

    return setterTwoClasses(lookup, engine, object, value);
}

bool Lookup::setter0setter0(Lookup *lookup, ExecutionEngine *engine, Value &object, const Value &value)
{
    Heap::Object *o = static_cast<Heap::Object *>(object.heapObject());
    if (o) {
        if (o->internalClass == lookup->objectLookupTwoClasses.ic) {
            o->setProperty(engine, lookup->objectLookupTwoClasses.offset, value);
            return true;
        }
        if (o->internalClass == lookup->objectLookupTwoClasses.ic2) {
            o->setProperty(engine, lookup->objectLookupTwoClasses.offset2, value);
            return true;
        }
    }

    lookup->call = Call::SetterQObjectPropertyFallback;
    return setterFallback(lookup, engine, object, value);
}

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

    Object *o = static_cast<Object *>(object.managed());
    if (o && o->internalClass()->protoId == lookup->insertionLookup.protoId) {
        o->setInternalClass(lookup->insertionLookup.newClass);
        o->d()->setProperty(engine, lookup->insertionLookup.offset, value);
        return true;
    }

    lookup->call = Call::SetterQObjectPropertyFallback;
    return setterFallback(lookup, engine, object, value);
}

bool Lookup::setterQObject(Lookup *lookup, ExecutionEngine *engine, Value &object, const Value &v)
{
    // This setter just marks the presence of a qobjectlookup. It only does anything with it when
    // running AOT-compiled code, though.
    return setterFallback(lookup, engine, object, v);
}

bool Lookup::arrayLengthSetter(Lookup *, ExecutionEngine *engine, Value &object, const Value &value)
{
    Q_ASSERT(object.isObject() && static_cast<Object &>(object).isArrayObject());
    bool ok;
    uint len = value.asArrayLength(&ok);
    if (!ok) {
        engine->throwRangeError(value);
        return false;
    }
    ok = static_cast<Object &>(object).setArrayLength(len);
    if (!ok)
        return false;
    return true;
}

QT_END_NAMESPACE
