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

#include <private/qjsvalue_p.h>
#include <private/qqmlanybinding_p.h>
#include <private/qqmlbinding_p.h>
#include <private/qqmlboundsignal_p.h>
#include <private/qqmlcomponent_p.h>
#include <private/qqmlcomponentattached_p.h>
#include <private/qqmlcustomparser_p.h>
#include <private/qqmldebugconnector_p.h>
#include <private/qqmldebugserviceinterfaces_p.h>
#include <private/qqmlengine_p.h>
#include <private/qqmlpropertybinding_p.h>
#include <private/qqmlpropertyvalueinterceptor_p.h>
#include <private/qqmlscriptdata_p.h>
#include <private/qqmlscriptstring_p.h>
#include <private/qqmlsourcecoordinate_p.h>
#include <private/qqmlstringconverters_p.h>
#include <private/qqmlvaluetypeproxybinding_p.h>
#include <private/qqmlvme_p.h>
#include <private/qqmlvmemetaobject_p.h>
#include <private/qv4function_p.h>
#include <private/qv4functionobject_p.h>
#include <private/qv4generatorobject_p.h>
#include <private/qv4qobjectwrapper_p.h>
#include <private/qv4referenceobject_p.h>
#include <private/qv4resolvedtypereference_p.h>

#include <qtqml_tracepoints_p.h>

#include <QtCore/qscopedvaluerollback.h>
#include <QtCore/qloggingcategory.h>

Q_STATIC_LOGGING_CATEGORY(lcQmlDefaultMethod, "qt.qml.defaultmethod")

QT_USE_NAMESPACE

Q_TRACE_PREFIX(qtqml,
"namespace QV4 {" \
"struct ExecutionEngine;" \
"class ExecutableCompilationUnit;" \
"namespace CompiledData {" \
"struct Object;" \
"}}" \
"class QQmlEngine;"
)

Q_TRACE_POINT(qtqml, QQmlObjectCreator_createInstance_entry, const QV4::ExecutableCompilationUnit *compilationUnit, const QV4::CompiledData::Object *object, const QUrl &url)
Q_TRACE_POINT(qtqml, QQmlObjectCreator_createInstance_exit, const QString &typeName)

QQmlObjectCreator::QQmlObjectCreator(
        const QQmlRefPointer<QQmlContextData> &parentContext,
        const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
        const QQmlRefPointer<QQmlContextData> &creationContext,
        const QString &inlineComponentName,
        QQmlIncubatorPrivate *incubator)
    : phase(Startup)
    , m_inlineComponentName(inlineComponentName)
    , compilationUnit(compilationUnit)
    , propertyCaches(compilationUnit->propertyCachesPtr())
    , sharedState(new QQmlObjectCreatorSharedState, QQmlRefPointer<QQmlObjectCreatorSharedState>::Adopt)
    , topLevelCreator(true)
    , isContextObject(true)
    , incubator(incubator)
{
    init(parentContext);

    sharedState->componentAttached = nullptr;
    sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList();
    sharedState->creationContext = creationContext;
    sharedState->rootContext.reset();
    sharedState->hadTopLevelRequiredProperties = false;

    if (auto profiler = QQmlEnginePrivate::get(engine)->profiler) {
        Q_QML_PROFILE_IF_ENABLED(QQmlProfilerDefinitions::ProfileCreating, profiler,
                sharedState->profiler.init(profiler));
    } else {
        Q_UNUSED(profiler);
    }
}

QQmlObjectCreator::QQmlObjectCreator(const QQmlRefPointer<QQmlContextData> &parentContext,
                                     const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QString inlineComponentName,
                                     QQmlObjectCreatorSharedState *inheritedSharedState, bool isContextObject)
    : phase(Startup)
    , m_inlineComponentName(inlineComponentName)
    , compilationUnit(compilationUnit)
    , propertyCaches(compilationUnit->propertyCachesPtr())
    , sharedState(inheritedSharedState)
    , topLevelCreator(false)
    , isContextObject(isContextObject)
    , incubator(nullptr)
{
    init(parentContext);
}

void QQmlObjectCreator::init(const QQmlRefPointer<QQmlContextData> &providedParentContext)
{
    parentContext = providedParentContext;
    engine = parentContext->engine();
    v4 = engine->handle();

    Q_ASSERT(compilationUnit);
    Q_ASSERT(compilationUnit->engine == v4);
    if (!compilationUnit->runtimeStrings)
        compilationUnit->populate();

    qmlUnit = compilationUnit->unitData();
    _qobject = nullptr;
    _scopeObject = nullptr;
    _bindingTarget = nullptr;
    _valueTypeProperty = nullptr;
    _compiledObject = nullptr;
    _compiledObjectIndex = -1;
    _ddata = nullptr;
    _vmeMetaObject = nullptr;
    _qmlContext = nullptr;
}

QQmlObjectCreator::~QQmlObjectCreator()
{
    if (topLevelCreator) {
        {
            // This signals to other methods further up the stack that we have
            // "recursed" and they should be aborted.
            QQmlObjectCreatorRecursionWatcher watcher(this);
        }
        while (sharedState->componentAttached) {
            QQmlComponentAttached *a = sharedState->componentAttached;
            a->removeFromList();
        }
    }
}

QObject *QQmlObjectCreator::create(int subComponentIndex, QObject *parent, QQmlInstantiationInterrupt *interrupt, int flags)
{
    if (phase == CreatingObjectsPhase2) {
        phase = ObjectsCreated;
        return context->contextObject();
    }
    Q_ASSERT(phase == Startup);
    phase = CreatingObjects;

    int objectToCreate;
    bool isComponentRoot = false; // either a "real" component of or an inline component

    if (subComponentIndex == -1) {
        objectToCreate = /*root object*/0;
        isComponentRoot = true;
    } else {
        Q_ASSERT(subComponentIndex >= 0);
        if (flags & CreationFlags::InlineComponent) {
            if (compilationUnit->componentsAreBound()
                    && compilationUnit != parentContext->typeCompilationUnit()) {
                recordError({}, tr("Cannot instantiate bound inline component in different file"));
                phase = ObjectsCreated;
                return nullptr;
            }
            objectToCreate = subComponentIndex;
            isComponentRoot = true;
        } else {
            Q_ASSERT(flags & CreationFlags::NormalObject);
            if (compilationUnit->componentsAreBound()
                    && sharedState->creationContext != parentContext) {
                recordError({}, tr("Cannot instantiate bound component "
                                   "outside its creation context"));
                phase = ObjectsCreated;
                return nullptr;
            }
            if (subComponentIndex < compilationUnit->objectCount()) {
                const QV4::CompiledData::Object *compObj
                        = compilationUnit->objectAt(subComponentIndex);
                objectToCreate = compObj->bindingTable()->value.objectIndex;
            } else {
                // Implicit component wrappers have index >= objectCount().
                // Read child index from the property cache instead of the CU object.
                objectToCreate = compilationUnit->resolvedIndex(subComponentIndex);
            }
        }
    }

    QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(engine);
    context = isComponentRoot
            ? enginePriv->createComponentRootContext(compilationUnit, parentContext,
                                                     subComponentIndex)
            : enginePriv->createBareContext(compilationUnit, parentContext, subComponentIndex);

    if (!sharedState->rootContext) {
        sharedState->rootContext = context;
        sharedState->rootContext->setIncubator(incubator);
        sharedState->rootContext->setRootObjectInCreation(true);
    }

    QV4::Scope scope(v4);

    Q_ASSERT(sharedState->allJavaScriptObjects.canTrack() || topLevelCreator);
    if (topLevelCreator)
        sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList(scope);

    if (!isComponentRoot && sharedState->creationContext) {
        // otherwise QQmlEnginePrivate::createComponentRootContext() handles it
        QV4::ScopedValue scripts(scope, sharedState->creationContext->importedScripts());
        context->setImportedScripts(v4, scripts);
    }

    QObject *instance = createInstance(objectToCreate, parent, /*isContextObject*/true);
    if (instance) {
        QQmlData *ddata = QQmlData::get(instance);
        Q_ASSERT(ddata);
        ddata->compilationUnit = compilationUnit;
    }

    if (topLevelCreator)
        sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList();

    phase = CreatingObjectsPhase2;

    if (interrupt && interrupt->shouldInterrupt())
        return nullptr;

    phase = ObjectsCreated;

    if (instance) {
        if (QQmlEngineDebugService *service
                = QQmlDebugConnector::service<QQmlEngineDebugService>()) {
            if (!parentContext->isInternal())
                parentContext->asQQmlContextPrivate()->appendInstance(instance);
            service->objectCreated(engine, instance);
        } else if (!parentContext->isInternal() && QQmlDebugConnector::service<QV4DebugService>()) {
            parentContext->asQQmlContextPrivate()->appendInstance(instance);
        }
    }

    return instance;
}

void QQmlObjectCreator::beginPopulateDeferred(const QQmlRefPointer<QQmlContextData> &newContext)
{
    context = newContext;
    sharedState->rootContext = newContext;

    Q_ASSERT(topLevelCreator);
    Q_ASSERT(!sharedState->allJavaScriptObjects.canTrack());

    // FIXME (QTBUG-122956): allocating from the short lived scope does not make any sense
    QV4::Scope valueScope(v4);
    sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList(valueScope);
}

void QQmlObjectCreator::populateDeferred(QObject *instance, int deferredIndex,
                                         const QQmlPropertyPrivate *qmlProperty,
                                         const QV4::CompiledData::Binding *binding)
{
    doPopulateDeferred(instance, deferredIndex, [this, qmlProperty, binding]() {
        Q_ASSERT(qmlProperty);
        Q_ASSERT(binding->hasFlag(QV4::CompiledData::Binding::IsDeferredBinding));

        QQmlListProperty<QObject> savedList;
        qSwap(_currentList, savedList);

        const QQmlPropertyData &property = qmlProperty->core;

        if (property.propType().flags().testFlag(QMetaType::IsQmlList)) {
            void *argv[1] = { (void*)&_currentList };
            QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property.coreIndex(), argv);
        } else if (_currentList.object) {
            _currentList = QQmlListProperty<QObject>();
        }

        setPropertyBinding(&property, binding);

        qSwap(_currentList, savedList);
    });
}

void QQmlObjectCreator::populateDeferred(QObject *instance, int deferredIndex)
{
    doPopulateDeferred(instance, deferredIndex, [this]() { setupBindings(ApplyDeferred); });
}

bool QQmlObjectCreator::populateDeferredProperties(QObject *instance,
                                                   const QQmlData::DeferredData *deferredData)
{
    beginPopulateDeferred(deferredData->context);
    populateDeferred(instance, deferredData->deferredIdx);
    finalizePopulateDeferred();
    return errors.isEmpty();
}

void QQmlObjectCreator::populateDeferredBinding(const QQmlProperty &qmlProperty, int deferredIndex,
                                                const QV4::CompiledData::Binding *binding)
{
    if (binding) {
        populateDeferred(qmlProperty.object(), deferredIndex, QQmlPropertyPrivate::get(qmlProperty),
                         binding);
    } else {
        populateDeferred(qmlProperty.object(), deferredIndex);
    }
}

void QQmlObjectCreator::populateDeferredInstance(
        QObject *outerObject, int deferredIndex, int index, QObject *instance,
        QObject *bindingTarget, const QQmlPropertyData *valueTypeProperty,
        const QV4::CompiledData::Binding *binding)
{
    doPopulateDeferred(outerObject, deferredIndex, [&]() {
        populateInstance(index, instance, bindingTarget, valueTypeProperty, binding);
    });
}

void QQmlObjectCreator::finalizePopulateDeferred()
{
    phase = ObjectsCreated;
}

void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const QV4::CompiledData::Binding *binding)
{
    QQmlPropertyData::WriteFlags propertyWriteFlags = QQmlPropertyData::BypassInterceptor | QQmlPropertyData::RemoveBindingOnAliasWrite;
    QV4::Scope scope(v4);

    QMetaType propertyType = property->propType();

    if (property->isEnum()) {
        if (binding->hasFlag(QV4::CompiledData::Binding::IsResolvedEnum) ||
                // TODO: For historical reasons you can assign any number to an enum property alias
                //       This can be fixed with an opt-out mechanism, for example a pragma.
                (property->isAlias() && binding->isNumberBinding())) {
            propertyType = property->propType().underlyingType();
        } else {
            // ### This should be resolved earlier at compile time and the binding value should be changed accordingly.
            QVariant value = compilationUnit->bindingValueAsString(binding);
            bool ok = QQmlPropertyPrivate::write(_qobject, *property, value, context);
            Q_ASSERT(ok);
            Q_UNUSED(ok);
            return;
        }
    }

    auto assertOrNull = [&](bool ok)
    {
        Q_ASSERT(ok || binding->type() == QV4::CompiledData::Binding::Type_Null);
        Q_UNUSED(ok);
    };

    auto assertType = [&](QV4::CompiledData::Binding::Type type)
    {
        Q_ASSERT(binding->type()== type || binding->type() == QV4::CompiledData::Binding::Type_Null);
        Q_UNUSED(type);
    };

    if (property->isQObject()) {
        if (binding->type() == QV4::CompiledData::Binding::Type_Null) {
            QObject *value = nullptr;
            const bool ok = property->writeProperty(_qobject, &value, propertyWriteFlags);
            Q_ASSERT(ok);
            Q_UNUSED(ok);
            return;
        }
    }

    switch (propertyType.id()) {
    case QMetaType::QVariant: {
        if (binding->type() == QV4::CompiledData::Binding::Type_Number) {
            double n = compilationUnit->bindingValueAsNumber(binding);
            if (double(int(n)) == n) {
                if (property->isVarProperty()) {
                    _vmeMetaObject->setVMEProperty(property->coreIndex(), QV4::Value::fromInt32(int(n)));
                } else {
                    int i = int(n);
                    QVariant value(i);
                    property->writeProperty(_qobject, &value, propertyWriteFlags);
                }
            } else {
                if (property->isVarProperty()) {
                    _vmeMetaObject->setVMEProperty(property->coreIndex(), QV4::Value::fromDouble(n));
                } else {
                    QVariant value(n);
                    property->writeProperty(_qobject, &value, propertyWriteFlags);
                }
            }
        } else if (binding->type() == QV4::CompiledData::Binding::Type_Boolean) {
            if (property->isVarProperty()) {
                _vmeMetaObject->setVMEProperty(property->coreIndex(), QV4::Value::fromBoolean(binding->valueAsBoolean()));
            } else {
                QVariant value(binding->valueAsBoolean());
                property->writeProperty(_qobject, &value, propertyWriteFlags);
            }
        } else if (binding->type() == QV4::CompiledData::Binding::Type_Null) {
            if (property->isVarProperty()) {
                _vmeMetaObject->setVMEProperty(property->coreIndex(), QV4::Value::nullValue());
            } else {
                QVariant nullValue = QVariant::fromValue(nullptr);
                property->writeProperty(_qobject, &nullValue, propertyWriteFlags);
            }
        } else {
            QString stringValue = compilationUnit->bindingValueAsString(binding);
            if (property->isVarProperty()) {
                QV4::ScopedString s(scope, v4->newString(stringValue));
                _vmeMetaObject->setVMEProperty(property->coreIndex(), s);
            } else {
                QVariant value = stringValue;
                property->writeProperty(_qobject, &value, propertyWriteFlags);
            }
        }
    }
    break;
    case QMetaType::QString: {
        assertOrNull(binding->evaluatesToString());
        QString value = compilationUnit->bindingValueAsString(binding);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QStringList: {
        assertOrNull(binding->evaluatesToString());
        QStringList value(compilationUnit->bindingValueAsString(binding));
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QByteArray: {
        assertType(QV4::CompiledData::Binding::Type_String);
        QByteArray value(compilationUnit->bindingValueAsString(binding).toUtf8());
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QUrl: {
        assertType(QV4::CompiledData::Binding::Type_String);
        const QString string = compilationUnit->bindingValueAsString(binding);
        QUrl value = (!string.isEmpty() && QQmlPropertyPrivate::resolveUrlsOnAssignment())
                ? compilationUnit->finalUrl().resolved(QUrl(string))
                : QUrl(string);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::UInt: {
        assertType(QV4::CompiledData::Binding::Type_Number);
        double d = compilationUnit->bindingValueAsNumber(binding);
        uint value = uint(d);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
        break;
    }
    break;
    case QMetaType::Int: {
        assertType(QV4::CompiledData::Binding::Type_Number);
        double d = compilationUnit->bindingValueAsNumber(binding);
        int value = int(d);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
        break;
    }
    break;
    case QMetaType::SChar: {
        assertType(QV4::CompiledData::Binding::Type_Number);
        double d = compilationUnit->bindingValueAsNumber(binding);
        qint8 value = qint8(d);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
        break;
    }
    case QMetaType::UChar: {
        assertType(QV4::CompiledData::Binding::Type_Number);
        double d = compilationUnit->bindingValueAsNumber(binding);
        quint8 value = quint8(d);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
        break;
    }
    case QMetaType::Short: {
        assertType(QV4::CompiledData::Binding::Type_Number);
        double d = compilationUnit->bindingValueAsNumber(binding);
        qint16 value = qint16(d);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
        break;
    }
    case QMetaType::UShort: {
        assertType(QV4::CompiledData::Binding::Type_Number);
        double d = compilationUnit->bindingValueAsNumber(binding);
        quint16 value = quint16(d);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
        break;
    }
    case QMetaType::LongLong: {
        assertType(QV4::CompiledData::Binding::Type_Number);
        double d = compilationUnit->bindingValueAsNumber(binding);
        qint64 value = qint64(d);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
        break;
    }
    case QMetaType::ULongLong: {
        assertType(QV4::CompiledData::Binding::Type_Number);
        double d = compilationUnit->bindingValueAsNumber(binding);
        quint64 value = quint64(d);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
        break;
    }
    break;
    case QMetaType::Float: {
        assertType(QV4::CompiledData::Binding::Type_Number);
        float value = float(compilationUnit->bindingValueAsNumber(binding));
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::Double: {
        assertType(QV4::CompiledData::Binding::Type_Number);
        double value = compilationUnit->bindingValueAsNumber(binding);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QColor: {
        bool ok = false;
        QVariant data = QQmlStringConverters::colorFromString(
                compilationUnit->bindingValueAsString(binding), &ok);
        Q_ASSERT(ok); // We've checked this in QQmlPropertyValidator
        property->writeProperty(_qobject, data.data(), propertyWriteFlags);
    }
    break;
#if QT_CONFIG(datestring)
    case QMetaType::QDate: {
        bool ok = false;
        QDate value = QQmlStringConverters::dateFromString(compilationUnit->bindingValueAsString(binding), &ok);
        assertOrNull(ok);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QTime: {
        bool ok = false;
        QTime value = QQmlStringConverters::timeFromString(compilationUnit->bindingValueAsString(binding), &ok);
        assertOrNull(ok);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QDateTime: {
        bool ok = false;
        QDateTime value = QQmlStringConverters::dateTimeFromString(
                    compilationUnit->bindingValueAsString(binding), &ok);
        assertOrNull(ok);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
#endif // datestring
    case QMetaType::QPoint: {
        bool ok = false;
        QPoint value = QQmlStringConverters::pointFFromString(compilationUnit->bindingValueAsString(binding), &ok).toPoint();
        assertOrNull(ok);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QPointF: {
        bool ok = false;
        QPointF value = QQmlStringConverters::pointFFromString(compilationUnit->bindingValueAsString(binding), &ok);
        assertOrNull(ok);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QSize: {
        bool ok = false;
        QSize value = QQmlStringConverters::sizeFFromString(compilationUnit->bindingValueAsString(binding), &ok).toSize();
        assertOrNull(ok);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QSizeF: {
        bool ok = false;
        QSizeF value = QQmlStringConverters::sizeFFromString(compilationUnit->bindingValueAsString(binding), &ok);
        assertOrNull(ok);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QRect: {
        bool ok = false;
        QRect value = QQmlStringConverters::rectFFromString(compilationUnit->bindingValueAsString(binding), &ok).toRect();
        assertOrNull(ok);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QRectF: {
        bool ok = false;
        QRectF value = QQmlStringConverters::rectFFromString(compilationUnit->bindingValueAsString(binding), &ok);
        assertOrNull(ok);
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::Bool: {
        assertType(QV4::CompiledData::Binding::Type_Boolean);
        bool value = binding->valueAsBoolean();
        property->writeProperty(_qobject, &value, propertyWriteFlags);
    }
    break;
    case QMetaType::QVector2D:
    case QMetaType::QVector3D:
    case QMetaType::QVector4D:
    case QMetaType::QQuaternion: {
        QVariant result = QQmlValueTypeProvider::createValueType(
                    compilationUnit->bindingValueAsString(binding), propertyType);
        assertOrNull(result.isValid());
        property->writeProperty(_qobject, result.data(), propertyWriteFlags);
        break;
    }
    default: {
        // generate single literal value assignment to a list property if required
        if (propertyType == QMetaType::fromType<QList<qreal>>()) {
            assertType(QV4::CompiledData::Binding::Type_Number);
            QList<qreal> value;
            value.append(compilationUnit->bindingValueAsNumber(binding));
            property->writeProperty(_qobject, &value, propertyWriteFlags);
            break;
        } else if (propertyType == QMetaType::fromType<QList<int>>()) {
            assertType(QV4::CompiledData::Binding::Type_Number);
            double n = compilationUnit->bindingValueAsNumber(binding);
            QList<int> value;
            value.append(int(n));
            property->writeProperty(_qobject, &value, propertyWriteFlags);
            break;
        } else if (propertyType == QMetaType::fromType<QList<bool>>()) {
            assertType(QV4::CompiledData::Binding::Type_Boolean);
            QList<bool> value;
            value.append(binding->valueAsBoolean());
            property->writeProperty(_qobject, &value, propertyWriteFlags);
            break;
        } else if (propertyType == QMetaType::fromType<QList<QUrl>>()) {
            assertType(QV4::CompiledData::Binding::Type_String);
            const QUrl url(compilationUnit->bindingValueAsString(binding));
            QList<QUrl> value {
                QQmlPropertyPrivate::resolveUrlsOnAssignment()
                        ? compilationUnit->finalUrl().resolved(url)
                        : url
            };
            property->writeProperty(_qobject, &value, propertyWriteFlags);
            break;
        } else if (propertyType == QMetaType::fromType<QList<QString>>()) {
            assertOrNull(binding->evaluatesToString());
            QList<QString> value;
            value.append(compilationUnit->bindingValueAsString(binding));
            property->writeProperty(_qobject, &value, propertyWriteFlags);
            break;
        } else if (propertyType == QMetaType::fromType<QJSValue>()) {
            QJSValue value;
            switch (binding->type()) {
            case QV4::CompiledData::Binding::Type_Boolean:
                value = QJSValue(binding->valueAsBoolean());
                break;
            case QV4::CompiledData::Binding::Type_Number: {
                const double n = compilationUnit->bindingValueAsNumber(binding);
                if (double(int(n)) == n)
                    value = QJSValue(int(n));
                else
                    value = QJSValue(n);
                break;
            }
            case QV4::CompiledData::Binding::Type_Null:
                value = QJSValue::NullValue;
                break;
            default:
                value = QJSValue(compilationUnit->bindingValueAsString(binding));
                break;
            }
            property->writeProperty(_qobject, &value, propertyWriteFlags);
            break;
        } else {
            QVariant source;
            switch (binding->type()) {
            case QV4::CompiledData::Binding::Type_Boolean:
                source = binding->valueAsBoolean();
                break;
            case QV4::CompiledData::Binding::Type_Number: {
                const double n = compilationUnit->bindingValueAsNumber(binding);
                if (double(int(n)) == n)
                    source = int(n);
                else
                    source = n;
                break;
            }
            case QV4::CompiledData::Binding::Type_Null:
                source = QVariant::fromValue<std::nullptr_t>(nullptr);
                break;
            case QV4::CompiledData::Binding::Type_Invalid:
                break;
            default:
                source = compilationUnit->bindingValueAsString(binding);
                break;
            }

            QVariant target = QQmlValueTypeProvider::createValueType(
                    source, propertyType, engine->handle());
            if (target.isValid()) {
                property->writeProperty(_qobject, target.data(), propertyWriteFlags);
                break;
            }
        }

        // string converters are not exposed, so ending up here indicates an error
        QString stringValue = compilationUnit->bindingValueAsString(binding);
        QMetaProperty metaProperty = _qobject->metaObject()->property(property->coreIndex());
        recordError(binding->location, tr("Cannot assign value %1 to property"
" %2").arg(stringValue, QString::fromUtf8(metaProperty.name())));
    }
    break;
    }
}

static QQmlType qmlTypeForObject(QObject *object)
{
    QQmlType type;
    const QMetaObject *mo = object->metaObject();
    while (mo && !type.isValid()) {
        type = QQmlMetaType::qmlType(mo);
        mo = mo->superClass();
    }
    return type;
}

void QQmlObjectCreator::setupBindings(BindingSetupFlags mode)
{
    QQmlListProperty<QObject> savedList;
    qSwap(_currentList, savedList);

    const QV4::CompiledData::BindingPropertyData *propertyData
            = compilationUnit->bindingPropertyDataPerObjectAt(_compiledObjectIndex);

    if (_compiledObject->idNameIndex) {
        const QQmlPropertyData *idProperty = propertyData->last();
        Q_ASSERT(!idProperty || !idProperty->isValid() || idProperty->name(_qobject) == QLatin1String("id"));
        if (idProperty && idProperty->isValid() && idProperty->isWritable() && idProperty->propType().id() == QMetaType::QString) {
            QV4::CompiledData::Binding idBinding;
            idBinding.propertyNameIndex = 0; // Not used
            idBinding.clearFlags();
            idBinding.setType(QV4::CompiledData::Binding::Type_String);
            idBinding.stringIndex = _compiledObject->idNameIndex;
            idBinding.location = _compiledObject->location; // ###
            idBinding.value.nullMarker = 0; // zero the value field to make codechecker happy
            setPropertyValue(idProperty, &idBinding);
        }
    }

    // ### this is best done through type-compile-time binding skip lists.
    if (_valueTypeProperty) {
        QQmlAbstractBinding *binding = QQmlPropertyPrivate::binding(_bindingTarget, QQmlPropertyIndex(_valueTypeProperty->coreIndex()));

        if (binding && binding->kind() != QQmlAbstractBinding::ValueTypeProxy) {
            QQmlPropertyPrivate::removeBinding(
                    _bindingTarget, QQmlPropertyIndex(_valueTypeProperty->coreIndex()),
                    QQmlPropertyPrivate::OverrideSticky);
        } else if (binding) {
            QQmlValueTypeProxyBinding *proxy = static_cast<QQmlValueTypeProxyBinding *>(binding);

            if (qmlTypeForObject(_bindingTarget).isValid()) {
                quint32 bindingSkipList = 0;

                const QQmlPropertyData *defaultProperty = _compiledObject->indexOfDefaultPropertyOrAlias != -1 ? _propertyCache->parent()->defaultProperty() : _propertyCache->defaultProperty();

                const QV4::CompiledData::Binding *binding = _compiledObject->bindingTable();
                for (quint32 i = 0; i < _compiledObject->nBindings; ++i, ++binding) {
                    const QQmlPropertyData *property = binding->propertyNameIndex != 0
                            ? _propertyCache->property(stringAt(binding->propertyNameIndex),
                                                       _qobject, context)
                            : defaultProperty;
                    if (property)
                        bindingSkipList |= (1 << property->coreIndex());
                }

                proxy->removeBindings(bindingSkipList);
            }
        }
    }

    const QQmlPropertyData *defaultProperty = _compiledObject->indexOfDefaultPropertyOrAlias != -1
            ? _propertyCache->parent()->defaultProperty()
            : _propertyCache->defaultProperty();

    int currentListPropertyIndex = -1;

    // Prepare list property state and call setPropertyBinding for one binding.
    const auto applyBinding = [&](const QQmlPropertyData *property,
                                  const QV4::CompiledData::Binding *binding) -> bool {
        if (property && property->propType().flags().testFlag(QMetaType::IsQmlList)) {
            if (property->coreIndex() != currentListPropertyIndex) {
                void *argv[1] = { (void*)&_currentList };
                QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property->coreIndex(), argv);
                currentListPropertyIndex = property->coreIndex();

                // manage override behavior
                const QMetaObject *const metaobject = _qobject->metaObject();
                const int qmlListBehavorClassInfoIndex = metaobject->indexOfClassInfo("QML.ListPropertyAssignBehavior");
                if (qmlListBehavorClassInfoIndex != -1) { // QML.ListPropertyAssignBehavior class info is set
                    const char *overrideBehavior =
                            metaobject->classInfo(qmlListBehavorClassInfoIndex).value();
                    if (!strcmp(overrideBehavior,
                                "Replace")) {
                        if (_currentList.clear) {
                            _currentList.clear(&_currentList);
                        }
                    } else {
                        bool isDefaultProperty =
                                (property->name(_qobject)
                                 == QString::fromUtf8(
                                         metaobject
                                                 ->classInfo(metaobject->indexOfClassInfo(
                                                         "DefaultProperty"))
                                                 .value()));
                        if (!isDefaultProperty
                            && (!strcmp(overrideBehavior,
                                        "ReplaceIfNotDefault"))) {
                            if (_currentList.clear) {
                                _currentList.clear(&_currentList);
                            }
                        }
                    }
                }
            }
        } else if (_currentList.object) {
            _currentList = QQmlListProperty<QObject>();
            currentListPropertyIndex = -1;
        }

        return setPropertyBinding(property, binding);
    };

    // Explicit default property bindings (propertyNameIndex != 0 but resolving
    // to the default property) are deferred during iteration over the prepended
    // section of the binding list and applied in file-offset order once we reach
    // the appended implicit default bindings (propertyNameIndex == 0).
    // NB: They have to be contiguous, so we can flush them all in a row.
    const QV4::CompiledData::Binding *explicitDefaultPropertyBindings = nullptr;
    quint32 numExplicitDefaultPropertyBindings = 0;

    const auto applyExplicitDefaultPropertyBindings = [&]() -> bool {
        for (; numExplicitDefaultPropertyBindings > 0; --numExplicitDefaultPropertyBindings) {
            if (!applyBinding(defaultProperty, explicitDefaultPropertyBindings++))
                return false;
        }
        return true;
    };

    const QV4::CompiledData::Binding *binding = _compiledObject->bindingTable();
    for (quint32 i = 0; i < _compiledObject->nBindings; ++i, ++binding) {
        const QQmlPropertyData *const property = propertyData->at(i);
        if (property) {
            const QQmlPropertyData *targetProperty = property;
            if (targetProperty->isAlias()) {
                // follow alias
                QQmlPropertyIndex originalIndex(targetProperty->coreIndex(),
                                                _valueTypeProperty ? _valueTypeProperty->coreIndex()
                                                                   : -1);
                auto [targetObject, targetIndex] =
                        QQmlPropertyPrivate::findAliasTarget(_bindingTarget, originalIndex);
                QQmlData *data = QQmlData::get(targetObject);
                Q_ASSERT(data && data->propertyCache);
                targetProperty = data->propertyCache->property(targetIndex.coreIndex());
                sharedState->requiredProperties.remove({ targetObject, targetProperty });
            }
            sharedState->requiredProperties.remove({ _bindingTarget, property });
        }

        if (binding->hasFlag(QV4::CompiledData::Binding::IsCustomParserBinding))
            continue;

        if (binding->hasFlag(QV4::CompiledData::Binding::IsDeferredBinding)) {
            if (!(mode & ApplyDeferred))
                continue;
        } else if (!(mode & ApplyImmediate)) {
            continue;
        }

        // Defer explicit bindings to the default property until we reach the
        // implicit default bindings, where they will be interleaved by offset.
        if (binding->propertyNameIndex != quint32(0)
                && defaultProperty
                && property == defaultProperty) {
            if (++numExplicitDefaultPropertyBindings == 1)
                explicitDefaultPropertyBindings = binding;
            continue;
        }

        // Flush deferred explicit default bindings if file offset is <= the
        // current implicit default binding's offset.
        if (binding->propertyNameIndex == quint32(0)
                && numExplicitDefaultPropertyBindings > 0
                && !(binding->location < explicitDefaultPropertyBindings->location)) {
            if (!applyExplicitDefaultPropertyBindings())
                return;
        }

        if (!applyBinding(property, binding))
            return;
    }

    // Flush any remaining deferred explicit default bindings.
    if (!applyExplicitDefaultPropertyBindings())
        return;
    qSwap(_currentList, savedList);
}

bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProperty, const QV4::CompiledData::Binding *binding)
{
    const QV4::CompiledData::Binding::Type bindingType = binding->type();
    if (bindingType == QV4::CompiledData::Binding::Type_AttachedProperty) {
        const QV4::CompiledData::Object *obj = compilationUnit->objectAt(binding->value.objectIndex);
        QQmlObjectCreationProfiler profiler(sharedState->profiler.profiler, obj);

        Q_ASSERT(stringAt(obj->inheritedTypeNameIndex).isEmpty());
        QV4::ResolvedTypeReference *tr = resolvedType(binding->propertyNameIndex);
        Q_ASSERT(tr);
        QQmlType attachedType = tr->type();
        QQmlTypeLoader *typeLoader = QQmlTypeLoader::get(engine);
        if (!attachedType.isValid()) {
            QQmlTypeNameCache::Result res
                    = context->imports()->query(stringAt(binding->propertyNameIndex), typeLoader);
            if (res.isValid())
                attachedType = res.type;
            else
                return false;
        }

        Q_QML_OC_PROFILE(
                sharedState->profiler,
                profiler.update(compilationUnit.data(), obj, attachedType.qmlTypeName(),
                                context->url()));

        QObject *qmlObject = qmlAttachedPropertiesObject(
                _qobject, attachedType.attachedPropertiesFunction(typeLoader));
        if (!qmlObject) {
            recordError(binding->location,
                        QStringLiteral("Could not create attached properties object '%1'")
                        .arg(QString::fromUtf8(attachedType.typeName())));
            return false;
        }

        const size_t objectIndex = sharedState->allCreatedObjects.size();
        sharedState->allCreatedObjects.push_back(qmlObject);
        const QQmlType attachedObjectType
                = QQmlMetaType::qmlType(attachedType.attachedPropertiesType(typeLoader));
        const int parserStatusCast = attachedObjectType.parserStatusCast();
        QQmlParserStatus *parserStatus  = nullptr;
        if (parserStatusCast != -1)
            parserStatus = reinterpret_cast<QQmlParserStatus*>(reinterpret_cast<char *>(qmlObject) + parserStatusCast);
        if (parserStatus) {
            parserStatus->classBegin();
            // we ignore them for profiling, because it doesn't interact well with the logic anyway
            sharedState->allParserStatusCallbacks.push_back({objectIndex, parserStatusCast});
        }

        if (!populateInstance(binding->value.objectIndex, qmlObject, qmlObject,
                              /*value type property*/ nullptr, binding))
            return false;
        return true;
    }

    // ### resolve this at compile time
    if (bindingProperty && bindingProperty->propType() == QMetaType::fromType<QQmlScriptString>()) {
        QQmlScriptString ss(compilationUnit->bindingValueAsScriptString(binding),
                            context->asQQmlContext(), _scopeObject);
        ss.d.data()->bindingId = bindingType == QV4::CompiledData::Binding::Type_Script ? binding->value.compiledScriptIndex : (quint32)QQmlBinding::Invalid;
        ss.d.data()->lineNumber = binding->location.line();
        ss.d.data()->columnNumber = binding->location.column();
        ss.d.data()->isStringLiteral = bindingType == QV4::CompiledData::Binding::Type_String;
        ss.d.data()->isNumberLiteral = bindingType == QV4::CompiledData::Binding::Type_Number;
        ss.d.data()->numberValue = compilationUnit->bindingValueAsNumber(binding);

        QQmlPropertyData::WriteFlags propertyWriteFlags = QQmlPropertyData::BypassInterceptor |
                                                            QQmlPropertyData::RemoveBindingOnAliasWrite;
        int propertyWriteStatus = -1;
        void *argv[] = { &ss, nullptr, &propertyWriteStatus, &propertyWriteFlags };
        QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, bindingProperty->coreIndex(), argv);
        return true;
    }

    QObject *createdSubObject = nullptr;
    if (bindingType == QV4::CompiledData::Binding::Type_Object) {
        // This is not a top level object. Its required properties don't count towards the
        // top level required properties.
        QScopedValueRollback topLevelRequired(sharedState->hadTopLevelRequiredProperties);
        int objectIndex = binding->value.objectIndex;
        if (const int wrapperIdx = compilationUnit->implicitComponentForObject(objectIndex);
                wrapperIdx != -1) {
            // If the child has an implicit component wrapper, redirect to it.
            objectIndex = wrapperIdx;
        }
        createdSubObject = createInstance(objectIndex, _bindingTarget);
        if (!createdSubObject)
            return false;
    }

    if (bindingType == QV4::CompiledData::Binding::Type_GroupProperty) {
        const QV4::CompiledData::Object *obj = compilationUnit->objectAt(binding->value.objectIndex);
        if (stringAt(obj->inheritedTypeNameIndex).isEmpty()) {

            QObject *groupObject = nullptr;
            QQmlGadgetPtrWrapper *valueType = nullptr;
            const QQmlPropertyData *valueTypeProperty = nullptr;
            QObject *bindingTarget = _bindingTarget;
            int groupObjectIndex = binding->value.objectIndex;

            if (!bindingProperty) {
                for (int i = 0, end = compilationUnit->objectCount(); i != end; ++i) {
                    const QV4::CompiledData::Object *external = compilationUnit->objectAt(i);
                    if (external->idNameIndex == binding->propertyNameIndex) {
                        bindingTarget = groupObject = context->idValue(external->objectId());
                        break;
                    }
                }
                if (!groupObject)
                    return true;
            } else if (QQmlMetaType::isValueType(bindingProperty->propType())) {
                valueType = QQmlGadgetPtrWrapper::instance(engine, bindingProperty->propType());
                if (!valueType) {
                    recordError(binding->location, tr("Cannot set properties on %1 as it is null").arg(stringAt(binding->propertyNameIndex)));
                    return false;
                }

                valueType->read(_qobject, bindingProperty->coreIndex());

                groupObject = valueType;
                valueTypeProperty = bindingProperty;
            } else if (bindingProperty->propType().flags() & QMetaType::PointerToQObject) {
                void *argv[1] = { &groupObject };
                QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, bindingProperty->coreIndex(), argv);
                if (!groupObject) {
                    QQmlPropertyIndex index(bindingProperty->coreIndex());
                    auto anyBinding = QQmlAnyBinding::ofProperty(_qobject, index);
                    if (anyBinding) {
                        // if there is a binding, try to force-evaluate it now
                        // this might instantiate a necessary part of a grouped property
                        anyBinding.refresh();
                        QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, bindingProperty->coreIndex(), argv);
                    }
                    if (!groupObject) {
                        recordError(binding->location, tr("Cannot set properties on %1 as it is null").arg(stringAt(binding->propertyNameIndex)));
                        return false;
                    }
                }

                bindingTarget = groupObject;
            } else {
                recordError(
                        binding->location,
                        tr("Using grouped property syntax on %1 which has no properties")
                                .arg(stringAt(binding->propertyNameIndex)));
                return false;
            }

            if (!populateInstance(groupObjectIndex, groupObject, bindingTarget, valueTypeProperty,
                                  binding)) {
                return false;
            }

            if (valueType) {
                valueType->write(
                        _qobject, bindingProperty->coreIndex(),
                        QQmlPropertyData::BypassInterceptor,
                        QV4::ReferenceObject::AllProperties);
            }

            return true;
        }
    }

    if (!bindingProperty) // ### error
        return true;

    const QV4::CompiledData::Binding::Flags bindingFlags = binding->flags();
    const bool allowedToRemoveBinding
            = !(bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerExpression)
            && !(bindingFlags & QV4::CompiledData::Binding::IsOnAssignment)
            && !(bindingFlags & QV4::CompiledData::Binding::IsPropertyObserver)
            && !_valueTypeProperty;

    if (allowedToRemoveBinding) {
        if (bindingProperty->acceptsQBinding()) {
            removePendingBinding(_bindingTarget, bindingProperty->coreIndex());
        } else {
            QQmlPropertyPrivate::removeBinding(
                    _bindingTarget, QQmlPropertyIndex(bindingProperty->coreIndex()),
                    QQmlPropertyPrivate::OverrideSticky);
        }
    }

    if (bindingType == QV4::CompiledData::Binding::Type_Script || binding->isTranslationBinding()) {
        if (bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerExpression
            || bindingFlags & QV4::CompiledData::Binding::IsPropertyObserver) {
            QV4::Function *runtimeFunction = compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
            int signalIndex = _propertyCache->methodIndexToSignalIndex(bindingProperty->coreIndex());
            QQmlBoundSignalExpression *expr = new QQmlBoundSignalExpression(
                        _bindingTarget, signalIndex, context,
                        _scopeObject, runtimeFunction, currentQmlContext());

            if (bindingProperty->notifiesViaBindable()) {
                auto target = _bindingTarget;
                if (bindingProperty->isAlias()) {
                    // If the property is an alias, we cannot obtain the bindable interface directly with qt_metacall
                    // so instead, we resolve the alias to obtain the actual target
                    // This should be faster than doing a detour through the metaobject of the target, and relying on
                    // QMetaObject::metacall doing the correct resolution
                    QQmlPropertyIndex originalIndex(bindingProperty->coreIndex(), _valueTypeProperty ? _valueTypeProperty->coreIndex() : -1);
                    auto [aliasTargetObject, aliasTargetIndex] = QQmlPropertyPrivate::findAliasTarget(target, originalIndex);
                    target = aliasTargetObject;
                    QQmlData *data = QQmlData::get(target);
                    Q_ASSERT(data && data->propertyCache);
                    bindingProperty = data->propertyCache->property(aliasTargetIndex.coreIndex());
                }
                auto &observer = QQmlData::get(_scopeObject)->propertyObservers.emplace_back(expr);
                QUntypedBindable bindable;
                void *argv[] = { &bindable };
                target->qt_metacall(QMetaObject::BindableProperty, bindingProperty->coreIndex(), argv);
                Q_ASSERT(bindable.isValid());
                bindable.observe(&observer);
            } else {
                QQmlBoundSignal *bs = new QQmlBoundSignal(_bindingTarget, signalIndex, _scopeObject, engine);
                bs->takeExpression(expr);
            }
        } else if (bindingProperty->acceptsQBinding()) {
            QUntypedPropertyBinding qmlBinding;
            if (binding->isTranslationBinding()) {
                qmlBinding = QQmlTranslationPropertyBinding::create(bindingProperty, compilationUnit, binding);
            } else {
                QV4::Function *runtimeFunction = compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
                QQmlPropertyIndex index(bindingProperty->coreIndex(), -1);
                qmlBinding = QQmlPropertyBinding::create(bindingProperty, runtimeFunction, _scopeObject, context, currentQmlContext(), _bindingTarget, index);
            }
            sharedState.data()->allQPropertyBindings.push_back(DeferredQPropertyBinding {_bindingTarget, bindingProperty->coreIndex(), qmlBinding });

            QQmlData *data = QQmlData::get(_bindingTarget, true);
            data->setBindingBit(_bindingTarget, bindingProperty->coreIndex());
        } else {
            // When writing bindings to grouped properties implemented as value types,
            // such as point.x: { someExpression; }, then the binding is installed on
            // the point property (_qobjectForBindings) and after evaluating the expression,
            // the result is written to a value type virtual property, that contains the sub-index
            // of the "x" property.
            QQmlBinding::Ptr qmlBinding;
            const QQmlPropertyData *targetProperty = bindingProperty;
            const QQmlPropertyData *subprop = nullptr;
            if (_valueTypeProperty) {
                targetProperty = _valueTypeProperty;
                subprop = bindingProperty;
            }
            if (binding->isTranslationBinding()) {
                qmlBinding = QQmlBinding::createTranslationBinding(
                            compilationUnit, binding, _scopeObject, context);
            } else {
                QV4::Function *runtimeFunction = compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
                qmlBinding = QQmlBinding::create(targetProperty, runtimeFunction, _scopeObject,
                                                 context, currentQmlContext());
            }

            auto bindingTarget = _bindingTarget;
            auto valueTypeProperty = _valueTypeProperty;
            auto assignBinding = [qmlBinding, bindingTarget, targetProperty, subprop, bindingProperty, valueTypeProperty](QQmlObjectCreatorSharedState *sharedState) mutable -> bool {
                if (!qmlBinding->setTarget(bindingTarget, *targetProperty, subprop) && targetProperty->isAlias())
                    return false;

                sharedState->allCreatedBindings.push_back(qmlBinding);

                if (bindingProperty->isAlias()) {
                    QQmlPropertyPrivate::setBinding(qmlBinding.data(), QQmlPropertyPrivate::DontEnable);
                } else {
                    qmlBinding->addToObject();

                    if (!valueTypeProperty) {
                        QQmlData *targetDeclarativeData = QQmlData::get(bindingTarget);
                        Q_ASSERT(targetDeclarativeData);
                        targetDeclarativeData->setPendingBindingBit(bindingTarget, bindingProperty->coreIndex());
                    }
                }

                return true;
            };
            if (!assignBinding(sharedState.data()))
                pendingAliasBindings.push_back(assignBinding);
        }
        return true;
    }

    if (bindingType == QV4::CompiledData::Binding::Type_Object) {
        if (bindingFlags & QV4::CompiledData::Binding::IsOnAssignment) {
            // ### determine value source and interceptor casts ahead of time.
            QQmlType type = qmlTypeForObject(createdSubObject);
            Q_ASSERT(type.isValid());

            int valueSourceCast = type.propertyValueSourceCast();
            if (valueSourceCast != -1) {
                QQmlPropertyValueSource *vs = reinterpret_cast<QQmlPropertyValueSource *>(reinterpret_cast<char *>(createdSubObject) + valueSourceCast);
                QObject *target = createdSubObject->parent();
                QQmlProperty prop;
                if (_valueTypeProperty) {
                    prop = QQmlPropertyPrivate::restore(target, *_valueTypeProperty,
                                                        bindingProperty, context);
                } else {
                    prop = QQmlPropertyPrivate::restore(target, *bindingProperty, nullptr, context);
                }
                vs->setTarget(prop);
                return true;
            }
            int valueInterceptorCast = type.propertyValueInterceptorCast();
            if (valueInterceptorCast != -1) {
                QQmlPropertyValueInterceptor *vi = reinterpret_cast<QQmlPropertyValueInterceptor *>(reinterpret_cast<char *>(createdSubObject) + valueInterceptorCast);
                QObject *target = createdSubObject->parent();

                QQmlPropertyIndex propertyIndex;
                if (bindingProperty->isAlias()) {
                    QQmlPropertyIndex originalIndex(bindingProperty->coreIndex(), _valueTypeProperty ? _valueTypeProperty->coreIndex() : -1);
                    auto aliasTarget = QQmlPropertyPrivate::findAliasTarget(target, originalIndex);
                    target = aliasTarget.targetObject;
                    QQmlData *data = QQmlData::get(target);
                    if (!data || !data->propertyCache) {
                        qWarning() << "can't resolve property alias for 'on' assignment";
                        return false;
                    }

                    // we can't have aliasses on subproperties of value types, so:
                    QQmlPropertyData targetPropertyData = *data->propertyCache->property(aliasTarget.targetIndex.coreIndex());
                    auto prop = QQmlPropertyPrivate::restore(
                                target, targetPropertyData, nullptr, context);
                    vi->setTarget(prop);
                    propertyIndex = QQmlPropertyPrivate::propertyIndex(prop);
                } else {
                    QQmlProperty prop;
                    if (_valueTypeProperty) {
                        prop = QQmlPropertyPrivate::restore(
                                    target, *_valueTypeProperty, bindingProperty, context);
                    } else {
                        prop = QQmlPropertyPrivate::restore(
                                    target, *bindingProperty, nullptr, context);
                    }
                    vi->setTarget(prop);
                    propertyIndex = QQmlPropertyPrivate::propertyIndex(prop);
                }

                QQmlInterceptorMetaObject *mo = QQmlInterceptorMetaObject::get(target);
                if (!mo)
                    mo = new QQmlInterceptorMetaObject(target, QQmlData::get(target)->propertyCache);
                mo->registerInterceptor(propertyIndex, vi);
                return true;
            }
            return false;
        }

        // Assigning object to signal property? ### Qt 7: Remove that functionality
        if (bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerObject) {
            if (!bindingProperty->isFunction()) {
                recordError(binding->valueLocation, tr("Cannot assign an object to signal property %1").arg(bindingProperty->name(_qobject)));
                return false;
            }
            QMetaMethod method = QQmlMetaType::defaultMethod(createdSubObject);
            if (!method.isValid()) {
                recordError(binding->valueLocation, tr("Cannot assign object type %1 with no default method").arg(QString::fromLatin1(createdSubObject->metaObject()->className())));
                return false;
            }
            qCWarning(lcQmlDefaultMethod) << "Assigning an object to a signal handler is deprecated. "
                                             "Instead, create the object, give it an id, and call the desired slot "
                                             "from the signal handler. The object is:" << createdSubObject;

            QMetaMethod signalMethod = _qobject->metaObject()->method(bindingProperty->coreIndex());
            if (!QMetaObject::checkConnectArgs(signalMethod, method)) {
                recordError(binding->valueLocation,
                            tr("Cannot connect mismatched signal/slot %1 vs %2")
                                    .arg(QString::fromUtf8(method.methodSignature()),
                                         QString::fromUtf8(signalMethod.methodSignature())));
                return false;
            }

            QQmlPropertyPrivate::connect(_qobject, bindingProperty->coreIndex(), createdSubObject, method.methodIndex());
            return true;
        }

        QQmlPropertyData::WriteFlags propertyWriteFlags = QQmlPropertyData::BypassInterceptor |
                                                            QQmlPropertyData::RemoveBindingOnAliasWrite;
        int propertyWriteStatus = -1;
        void *argv[] = { nullptr, nullptr, &propertyWriteStatus, &propertyWriteFlags };

        if (const char *iid = QQmlMetaType::interfaceIId(bindingProperty->propType())) {
            void *ptr = createdSubObject->qt_metacast(iid);
            if (ptr) {
                argv[0] = &ptr;
                QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, bindingProperty->coreIndex(), argv);
            } else {
                recordError(binding->location, tr("Cannot assign object to interface property"));
                return false;
            }
        } else if (bindingProperty->propType() == QMetaType::fromType<QVariant>()) {
            if (bindingProperty->isVarProperty()) {
                QV4::Scope scope(v4);
                QV4::ScopedValue wrappedObject(scope, QV4::QObjectWrapper::wrap(engine->handle(), createdSubObject));
                _vmeMetaObject->setVMEProperty(bindingProperty->coreIndex(), wrappedObject);
            } else {
                QVariant value = QVariant::fromValue(createdSubObject);
                argv[0] = &value;
                QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, bindingProperty->coreIndex(), argv);
            }
        } else if (bindingProperty->propType() == QMetaType::fromType<QJSValue>()) {
            QV4::Scope scope(v4);
            QV4::ScopedValue wrappedObject(scope, QV4::QObjectWrapper::wrap(engine->handle(), createdSubObject));
            if (bindingProperty->isVarProperty()) {
                _vmeMetaObject->setVMEProperty(bindingProperty->coreIndex(), wrappedObject);
            } else {
                QJSValue value;
                QJSValuePrivate::setValue(&value, wrappedObject);
                argv[0] = &value;
                QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, bindingProperty->coreIndex(), argv);
            }
        } else if (bindingProperty->propType().flags().testFlag(QMetaType::IsQmlList)) {
            Q_ASSERT(_currentList.object);

            QObject *itemToAdd = createdSubObject;

            QMetaType listItemType = QQmlMetaType::listValueType(bindingProperty->propType());
            if (listItemType.isValid()) {
                const char *iid = QQmlMetaType::interfaceIId(listItemType);
                if (iid)
                    itemToAdd = static_cast<QObject *>(createdSubObject->qt_metacast(iid));
            }

            if (_currentList.append)
                _currentList.append(&_currentList, itemToAdd);
            else {
                recordError(binding->location, tr("Cannot assign object to read only list"));
                return false;
            }
        } else if (bindingProperty->propType().flags().testFlag(QMetaType::PointerToQObject)) {
            // pointer compatibility was tested in QQmlPropertyValidator at type compile time
            argv[0] = &createdSubObject;
            QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, bindingProperty->coreIndex(), argv);
        } else {
            QVariant target = QQmlValueTypeProvider::createValueType(
                    QVariant::fromValue(createdSubObject), bindingProperty->propType(), v4);
            if (target.isValid())
                bindingProperty->writeProperty(_qobject, target.data(), propertyWriteFlags);
            else
                recordError(binding->location, tr("Cannot construct value type from given object"));
        }
        return true;
    }

    if (bindingProperty->isQList()) {
        recordError(binding->location, tr("Cannot assign primitives to lists"));
        return false;
    }

    setPropertyValue(bindingProperty, binding);
    return true;
}

void QQmlObjectCreator::setupFunctions()
{
    QV4::Scope scope(v4);
    QV4::ScopedValue function(scope);
    QV4::ScopedContext qmlContext(scope, currentQmlContext());

    const quint32_le *functionIdx = _compiledObject->functionOffsetTable();
    for (quint32 i = 0; i < _compiledObject->nFunctions; ++i, ++functionIdx) {
        QV4::Function *runtimeFunction = compilationUnit->runtimeFunctions[*functionIdx];
        const QString name = runtimeFunction->name()->toQString();

        const QQmlPropertyData *property = _propertyCache->property(name, _qobject, context);
        if (!property->isVMEFunction())
            continue;

        if (runtimeFunction->isGenerator())
            function = QV4::GeneratorFunction::create(qmlContext, runtimeFunction);
        else
            function = QV4::FunctionObject::createScriptFunction(qmlContext, runtimeFunction);
        _vmeMetaObject->setVmeMethod(property->coreIndex(), function);
    }
}

void QQmlObjectCreator::recordError(const QV4::CompiledData::Location &location, const QString &description)
{
    QQmlError error;
    error.setUrl(compilationUnit->url());
    error.setLine(qmlConvertSourceCoordinate<quint32, int>(location.line()));
    error.setColumn(qmlConvertSourceCoordinate<quint32, int>(location.column()));
    error.setDescription(description);
    errors << error;
}

void QQmlObjectCreator::registerObjectWithContextById(const QV4::CompiledData::Object *object, QObject *instance) const
{
    if (object->objectId() >= 0)
        context->setIdValue(object->objectId(), instance);
}

QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isContextObject)
{
    const QV4::CompiledData::Object *obj = compilationUnit->objectAt(index);
    Q_TRACE(QQmlObjectCreator_createInstance_entry, compilationUnit.data(), obj, context->url());
    QQmlObjectCreationProfiler profiler(sharedState->profiler.profiler, obj);

    const bool isImplicitComponent = index >= compilationUnit->objectCount();
    const InitFlags flags = (isContextObject ? InitFlag::IsContextObject : InitFlag::None)
            | (index == 0 ? InitFlag::IsDocumentRoot : InitFlag::None)
            | (isImplicitComponent ? InitFlag::IsImplicitComponent : InitFlag::None);

    if (obj->hasFlag(QV4::CompiledData::Object::IsComponent) || isImplicitComponent) {
        Q_TRACE_EXIT(QQmlObjectCreator_createInstance_exit, QStringLiteral("<component>"));
        Q_QML_OC_PROFILE(
                sharedState->profiler,
                profiler.update(
                        compilationUnit.data(), obj, QStringLiteral("<component>"),
                        context->url()));
        return initializeComponent(
                obj, createComponent(engine, compilationUnit.data(), index, parent, context),
                flags);
    }

    const QV4::ResolvedTypeReference *typeRef = resolvedType(obj->inheritedTypeNameIndex);
    Q_ASSERT(typeRef);
    const QQmlType type = typeRef->type();
    Q_ASSERT(type.isValid());

    Q_TRACE_EXIT(QQmlObjectCreator_createInstance_exit, type.qmlTypeName());
    Q_QML_OC_PROFILE(
            sharedState->profiler,
            profiler.update(compilationUnit.data(), obj, type.qmlTypeName(), context->url()));

    if (type.isCompositeSingleton()) {
        recordError(
                obj->location,
                tr("Composite Singleton Type %1 is not creatable")
                        .arg(stringAt(obj->inheritedTypeNameIndex)));
        return nullptr;
    }

    if (!type.isComposite() && !type.isInlineComponentType()) {
        if (QObject *instance = type.createWithQQmlData())
            return initializeNonComposite(index, obj, typeRef, instance, parent, flags);
        recordError(
                obj->location,
                tr("Unable to create object of type %1").arg(
                        stringAt(obj->inheritedTypeNameIndex)));
        return nullptr;
    }

    QQmlRefPointer<QV4::ExecutableCompilationUnit> executableCu = typeRef->isSelfReference()
            ? compilationUnit
            : engine->handle()->executableCompilationUnit(typeRef->compilationUnit());
    Q_ASSERT(executableCu);

    const bool isInlineComponent = type.isInlineComponentType();
    const QString inlineComponentName = isInlineComponent ? type.elementName() : QString();
    QQmlObjectCreator subCreator(
            context, executableCu, inlineComponentName, sharedState.data(),
            isContextObject);

    if (QObject *instance = isInlineComponent
                ? subCreator.create(
                          executableCu->inlineComponentId(inlineComponentName), nullptr, nullptr,
                          CreationFlags::InlineComponent)
                : subCreator.create()) {
        return initializeComposite(index, obj, typeRef, instance, parent, flags);
    }

    errors += subCreator.errors;
    return nullptr;
}

void QQmlObjectCreator::initializeDData(
        const QV4::CompiledData::Object *obj, QObject *instance, QQmlData *ddata, InitFlags flags)
{
    ddata->lineNumber = obj->location.line();
    ddata->columnNumber = obj->location.column();
    ddata->setImplicitDestructible();

    // inline components are root objects, but their index is != 0, so we need
    // an additional check
    const bool documentRoot = (flags & InitFlag::IsDocumentRoot)
            || ddata->rootObjectInCreation
            || obj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot);

    context->installContext(
            ddata, documentRoot ? QQmlContextData::DocumentRoot : QQmlContextData::OrdinaryObject);

    // Register the context object in the context early on in order for pending binding
    // initialization to find it available.
    if ((flags & (InitFlag::IsContextObject | InitFlag::IsImplicitComponent))
            == InitFlag::IsContextObject) {
        context->setContextObject(instance);
    }
}

void QQmlObjectCreator::initializePropertyCache(
        int index, QQmlData *ddata, const QV4::ResolvedTypeReference *typeRef)
{
    if (!typeRef->isFullyDynamicType()) {
        QQmlPropertyCache::ConstPtr cache = propertyCaches->at(index);
        Q_ASSERT(!cache.isNull());
        ddata->propertyCache = std::move(cache);
    }
}

void QQmlObjectCreator::initializeParent(QObject *instance, QObject *parent)
{
    if (instance->isWidgetType()) {
        if (parent && parent->isWidgetType()) {
            QAbstractDeclarativeData::setWidgetParent(instance, parent);
        } else {
            // No parent! Layouts need to handle this through a default property that
            // reparents accordingly. Otherwise the garbage collector will collect.
        }
    } else if (parent) {
        QQml_setParent_noEvent(instance, parent);
    }
}

QObject *QQmlObjectCreator::populateInstanceAndAliasBindings(
        int index, QObject *instance, InitFlags flags)
{
    const auto doPopulate = [&]() {
        if (!populateInstance(
                    index, instance, /*binding target*/instance, /*value type property*/nullptr)) {
            // an error occurred, so we can't setup the pending alias bindings
            pendingAliasBindings.clear();
            return false;
        }

        if (!flags.testFlag(InitFlag::IsContextObject))
            return true;

        while (!pendingAliasBindings.empty()) {
            for (std::vector<PendingAliasBinding>::iterator it = pendingAliasBindings.begin();
                    it != pendingAliasBindings.end(); ) {
                if ((*it)(sharedState.data()))
                    it = pendingAliasBindings.erase(it);
                else
                    ++it;
            }
        }

        return true;
    };

    QObject *scopeObject = instance;
    qSwap(_scopeObject, scopeObject);

    Q_ASSERT(sharedState->allJavaScriptObjects.canTrack());
    sharedState->allJavaScriptObjects.trackObject(v4, instance);

    QV4::Scope valueScope(v4);
    QV4::QmlContext *qmlContext = static_cast<QV4::QmlContext *>(valueScope.constructUndefined(1));

    qSwap(_qmlContext, qmlContext);

    const bool ok = doPopulate();

    qSwap(_qmlContext, qmlContext);
    qSwap(_scopeObject, scopeObject);

    return ok ? instance : nullptr;
}

QObject *QQmlObjectCreator::initializeComponent(
        const QV4::CompiledData::Object *obj, QObject *instance, InitFlags flags)
{
    Q_ASSERT(instance);
    Q_ASSERT(obj);

    QScopedValueRollback<QQmlObjectCreator*> ocRestore(
            QQmlEnginePrivate::get(engine)->activeObjectCreator, this);

    // we just created it inside createComponent; so QQmlData::get() without create = true;
    initializeDData(obj, instance, QQmlData::get(instance), flags);

    if (!flags.testFlag(InitFlag::IsImplicitComponent))
        registerObjectWithContextById(obj, instance);
    return instance;
}

QObject *QQmlObjectCreator::initializeComposite(
        int index, const QV4::CompiledData::Object *obj, const QV4::ResolvedTypeReference *typeRef,
        QObject *instance, QObject *parent, InitFlags flags)
{
    Q_ASSERT(instance);
    Q_ASSERT(typeRef);

    QScopedValueRollback<QQmlObjectCreator*> ocRestore(
            QQmlEnginePrivate::get(engine)->activeObjectCreator, this);

    initializeParent(instance, parent);
    QQmlData *ddata = QQmlData::get(instance, /*create*/true);
    initializeDData(obj, instance, ddata, flags);

    initializePropertyCache(index, ddata, typeRef);

    return populateInstanceAndAliasBindings(index, instance, flags);
}

QObject *QQmlObjectCreator::initializeNonComposite(
        int index, const QV4::CompiledData::Object *obj, const QV4::ResolvedTypeReference *typeRef,
        QObject *instance, QObject *parent, InitFlags flags)
{
    Q_ASSERT(instance);
    Q_ASSERT(obj);
    Q_ASSERT(!obj->hasFlag(QV4::CompiledData::Object::IsComponent));
    Q_ASSERT(typeRef);

    const QQmlType type = typeRef->type();
    Q_ASSERT(type.isValid());
    Q_ASSERT(!type.isComposite() && !type.isInlineComponentType());

    QScopedValueRollback<QQmlObjectCreator*> ocRestore(QQmlEnginePrivate::get(engine)->activeObjectCreator, this);

    if (const int finalizerCast = type.finalizerCast(); finalizerCast != -1) {
        const auto hook = reinterpret_cast<QQmlFinalizerHook *>(
                reinterpret_cast<char *>(instance) + finalizerCast);
        sharedState->finalizeHooks.push_back(hook);
    }

    QQmlData *ddata = QQmlData::get(instance, /*create*/true);
    if (sharedState->rootContext && sharedState->rootContext->isRootObjectInCreation()) {
        ddata->rootObjectInCreation = true;
        sharedState->rootContext->setRootObjectInCreation(false);
    }

    const size_t instanceIndex = sharedState->allCreatedObjects.size();
    sharedState->allCreatedObjects.push_back(instance);

    initializeParent(instance, parent);
    initializeDData(obj, instance, ddata, flags);

    if (const int parserStatusCast = type.parserStatusCast(); parserStatusCast != -1) {
        if (QQmlParserStatus *parserStatus = reinterpret_cast<QQmlParserStatus*>(
                    reinterpret_cast<char *>(instance) + parserStatusCast)) {
            parserStatus->classBegin();
            // push() the profiler state here, together with the parserStatus, as we'll pop() them
            // together, too.
            Q_QML_OC_PROFILE(sharedState->profiler, sharedState->profiler.push(obj));
            sharedState->allParserStatusCallbacks.push_back({ instanceIndex, parserStatusCast });
        }
    }

    if (QQmlCustomParser *customParser = type.customParser();
            customParser && obj->hasFlag(QV4::CompiledData::Object::HasCustomParserBindings)) {
        customParser->engine = QQmlEnginePrivate::get(engine);
        customParser->imports = compilationUnit->typeNameCache().data();

        QList<const QV4::CompiledData::Binding *> bindings;
        const QV4::CompiledData::Object *obj = compilationUnit->objectAt(index);
        const QV4::CompiledData::Binding *binding = obj->bindingTable();
        for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) {
            if (binding->hasFlag(QV4::CompiledData::Binding::IsCustomParserBinding))
                bindings << binding;
        }
        customParser->applyBindings(instance, compilationUnit, bindings);

        customParser->engine = nullptr;
        customParser->imports = (QQmlTypeNameCache*)nullptr;
    }

    initializePropertyCache(index, ddata, typeRef);
    return populateInstanceAndAliasBindings(index, instance, flags);
}

/*!
 * \internal
 * Create a single object at \a objectIndex within an already-existing \a existingContext.
 * Unlike create(), this does not allocate a new context — the object is registered as an
 * OrdinaryObject in \a existingContext.
 */
QObject *QQmlObjectCreator::createObjectInContext(
        int objectIndex, QObject *parent,
        const QQmlRefPointer<QQmlContextData> &existingContext)
{
    Q_ASSERT(phase == Startup);
    phase = CreatingObjects;

    context = existingContext;
    if (!sharedState->rootContext)
        sharedState->rootContext = context;

    QV4::Scope scope(v4);
    if (topLevelCreator)
        sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList(scope);

    QObject *rv = createInstance(objectIndex, parent, /*isContextObject=*/false);
    phase = ObjectsCreated;
    return rv;
}

bool QQmlObjectCreator::finalize(QQmlInstantiationInterrupt &interrupt)
{
    Q_ASSERT(phase == ObjectsCreated || phase == Finalizing);
    phase = Finalizing;

    QQmlObjectCreatorRecursionWatcher watcher(this);
    QScopedValueRollback<QQmlObjectCreator*> ocRestore(QQmlEnginePrivate::get(engine)->activeObjectCreator, this);

    /* We install all pending bindings (both plain QML and QProperty), and remove the ones which do not
       actually have dependencies.
       It is necessary to install the binding so that it runs at least once, which causes it to capture any
       dependencies.
       We then check for the following conditions:
       - Is the binding in an error state?
       - Does the binding has any dependencies (from properties)?
       - Does it depend on anything in the context, which has not been resolved yet (and thus couldn't be
         captured)?
       If the answer to all of those questions is "no", it is safe to remove the binding, as there is no
       way for it to change its value afterwards from that point on.
    */

    while (!sharedState->allCreatedBindings.empty()) {
        QQmlAbstractBinding::Ptr b = sharedState->allCreatedBindings.back();
        sharedState->allCreatedBindings.pop_back();
        Q_ASSERT(b);
        // skip, if b is not added to an object
        if (!b->isAddedToObject())
            continue;
        QQmlData *data = QQmlData::get(b->targetObject());
        Q_ASSERT(data);
        data->clearPendingBindingBit(b->targetPropertyIndex().coreIndex());
        b->setEnabled(true, QQmlPropertyData::BypassInterceptor |
                      QQmlPropertyData::DontRemoveBinding);
        if (b->kind() == QQmlAbstractBinding::QmlBinding) {
            QQmlBinding *binding = static_cast<QQmlBinding*>(b.data());
            if (!binding->hasError() && !binding->hasDependencies()
                    && !binding->hasUnresolvedNames()) {
                b->removeFromObject();
            }
        }

        if (watcher.hasRecursed() || interrupt.shouldInterrupt())
            return false;
    }

    while (!sharedState->allQPropertyBindings.isEmpty()) {
        auto& [target, index, qmlBinding] = sharedState->allQPropertyBindings.first();

        QQmlData *data = QQmlData::get(target);
        if (!data || !data->hasBindingBit(index)) {
            // The target property has been overwritten since we stashed the binding.
            sharedState->allQPropertyBindings.pop_front();
            continue;
        }

        QUntypedBindable bindable;
        void *argv[] = { &bindable };
        // allow interception
        target->metaObject()->metacall(target, QMetaObject::BindableProperty, index, argv);
        const bool success = bindable.setBinding(qmlBinding);

        const auto bindingPrivateRefCount = QPropertyBindingPrivate::get(qmlBinding)->refCount();

        // Only pop_front after setting the binding as the bindings are refcounted.
        sharedState->allQPropertyBindings.pop_front();

        // If the binding was actually not set, it's deleted now.
        if (success && bindingPrivateRefCount > 1) {
            if (auto priv = QPropertyBindingPrivate::get(qmlBinding); priv->hasCustomVTable()) {
                auto qmlBindingPriv = static_cast<QQmlPropertyBinding *>(priv);
                auto jsExpression = qmlBindingPriv->jsExpression();
                const bool canRemove = !qmlBinding.error().hasError()
                        && !qmlBindingPriv->hasDependencies()
                        && !jsExpression->hasUnresolvedNames();
                if (canRemove)
                    bindable.takeBinding();
            }
        }

        if (watcher.hasRecursed() || interrupt.shouldInterrupt())
            return false;
    }

    if (QQmlVME::componentCompleteEnabled()) { // the qml designer does the component complete later
        while (!sharedState->allParserStatusCallbacks.empty()) {
            QQmlObjectCompletionProfiler profiler(&sharedState->profiler);
            const ParserStatus status = sharedState->allParserStatusCallbacks.back();
            sharedState->allParserStatusCallbacks.pop_back();

            const QQmlGuard<QObject> &instance = sharedState->allCreatedObjects[status.objectIndex];
            if (!instance)
                continue;

            QQmlParserStatus *parserStatus
                    = reinterpret_cast<QQmlParserStatus *>(
                            reinterpret_cast<char *>(instance.data()) + status.parserStatusCast);
            parserStatus->componentComplete();

            if (watcher.hasRecursed() || interrupt.shouldInterrupt())
                return false;
        }
    }

    for (QQmlFinalizerHook *hook: std::as_const(sharedState->finalizeHooks)) {
        hook->componentFinalized();
        if (watcher.hasRecursed())
            return false;
    }
    sharedState->finalizeHooks.clear();

    while (sharedState->componentAttached) {
        QQmlComponentAttached *a = sharedState->componentAttached;
        a->removeFromList();
        QQmlData *d = QQmlData::get(a->parent());
        Q_ASSERT(d);
        Q_ASSERT(d->context);
        d->context->addComponentAttached(a);
        if (QQmlVME::componentCompleteEnabled())
            emit a->completed();

        if (watcher.hasRecursed() || interrupt.shouldInterrupt())
            return false;
    }

    phase = Done;

    return true;
}

void QQmlObjectCreator::clear()
{
    if (phase == Done || phase == Finalizing || phase == Startup)
        return;
    Q_ASSERT(phase != Startup);

    while (!sharedState->allCreatedObjects.empty()) {
        auto object = sharedState->allCreatedObjects.back();
        sharedState->allCreatedObjects.pop_back();
        if (engine->objectOwnership(object) != QQmlEngine::CppOwnership) {
            delete object;
        }
    }

    while (sharedState->componentAttached) {
        QQmlComponentAttached *a = sharedState->componentAttached;
        a->removeFromList();
    }

    phase = Done;
}

void QQmlObjectCreator::registerPostHocRequiredProperties(const QV4::CompiledData::Binding *binding)
{
    const qsizetype oldRequiredPropertiesCount = sharedState->requiredProperties.size();
    QSet<QString> postHocRequired;
    for (auto it = _compiledObject->requiredPropertyExtraDataBegin(); it != _compiledObject->requiredPropertyExtraDataEnd(); ++it)
        postHocRequired.insert(stringAt(it->nameIndex));
    bool hadInheritedRequiredProperties = !postHocRequired.empty();

    for (int propertyIndex = 0; propertyIndex != _compiledObject->propertyCount(); ++propertyIndex) {
        const QV4::CompiledData::Property* property = _compiledObject->propertiesBegin() + propertyIndex;
        const QQmlPropertyData *propertyData = _propertyCache->property(_propertyCache->propertyOffset() + propertyIndex);
        // only compute stringAt if there's a chance for the lookup to succeed
        auto postHocIt = postHocRequired.isEmpty() ? postHocRequired.end() : postHocRequired.find(stringAt(property->nameIndex()));
        if (!property->isRequired() && postHocRequired.end() == postHocIt)
            continue;
        if (postHocIt != postHocRequired.end())
            postHocRequired.erase(postHocIt);
        if (isContextObject)
            sharedState->hadTopLevelRequiredProperties = true;
        sharedState->requiredProperties.insert({_qobject, propertyData},
                                               RequiredPropertyInfo {compilationUnit->stringAt(property->nameIndex()), compilationUnit->finalUrl(), property->location, {}});

    }

    const auto getPropertyCacheRange = [&]() -> std::pair<int, int> {
        // the logic in a nutshell: we work with QML instances here. every
        // instance has a QQmlType:
        // * if QQmlType is valid && not an inline component, it's a C++ type
        // * otherwise, it's a QML-defined type (a.k.a. Composite type), where
        //   invalid type == "comes from another QML document"
        //
        // 1. if the type we inherit from comes from C++, we must check *all*
        //    properties in the property cache so far - since we can have
        //    required properties defined in C++
        // 2. otherwise - the type comes from QML, it's enough to check just
        //    *own* properties in the property cache, because there's a previous
        //    type in the hierarchy that has checked the C++ properties (via 1.)
        // 3. required attached properties are explicitly not supported. to
        //    achieve that, go through all its properties
        // 4. required group properties: the group itself is covered by 1.
        //    required sub-properties are not properly handled (QTBUG-96544), so
        //    just return the old range here for consistency
        QV4::ResolvedTypeReference *typeRef = resolvedType(_compiledObject->inheritedTypeNameIndex);
        if (!typeRef) { // inside a binding on attached/group property
            Q_ASSERT(binding);
            if (binding->isAttachedProperty())
                return { 0, _propertyCache->propertyCount() }; // 3.
            Q_ASSERT(binding->isGroupProperty());
            return { 0, _propertyCache->propertyOffset() + 1 }; // 4.
        }
        Q_ASSERT(!_compiledObject->hasFlag(QV4::CompiledData::Object::IsComponent));
        QQmlType type = typeRef->type();
        if (type.isComposite() || type.isInlineComponentType())
            return { _propertyCache->propertyOffset(), _propertyCache->propertyCount() }; // 2.
        return { 0, _propertyCache->propertyCount() }; // 1.
    };
    const auto [offset, count] = getPropertyCacheRange();
    for (int i = offset; i < count; ++i) {
        const QQmlPropertyData *propertyData = _propertyCache->maybeUnresolvedProperty(i);
        if (!propertyData)
            continue;
        // TODO: the property might be a group property (in which case we need
        // to dive into its sub-properties and check whether there are any
        // required elements there) - QTBUG-96544
        if (!propertyData->isRequired() && postHocRequired.isEmpty())
            continue;
        QString name = propertyData->name(_qobject);
        const auto postHocIt = postHocRequired.constFind(name);
        if (postHocIt == postHocRequired.constEnd()) {
            if (!propertyData->isRequired())
                continue;
        } else {
            postHocRequired.erase(postHocIt);
        }

        if (isContextObject)
            sharedState->hadTopLevelRequiredProperties = true;
        sharedState->requiredProperties.insert(
                {_qobject, propertyData},
                RequiredPropertyInfo {
                    std::move(name), compilationUnit->finalUrl(), _compiledObject->location, {}
                });
    }

    if (binding && binding->isAttachedProperty()
        && sharedState->requiredProperties.size() != oldRequiredPropertiesCount) {
        recordError(
                binding->location,
                QLatin1String("Attached property has required properties. This is not supported"));
    }

    // Note: there's a subtle case with the above logic: if we process a random
    // QML-defined leaf type, it could have a required attribute overwrite on an
    // *existing* property: `import QtQuick; Text { required text }`. in this
    // case, we must add the property to a required list
    if (!postHocRequired.isEmpty()) {
        // NB: go through [0, offset) range as [offset, count) is already done
        for (int i = 0; i < offset; ++i) {
            const QQmlPropertyData *propertyData = _propertyCache->maybeUnresolvedProperty(i);
            if (!propertyData)
                continue;
            QString name = propertyData->name(_qobject);
            const auto postHocIt = postHocRequired.constFind(name);
            if (postHocIt == postHocRequired.constEnd())
                continue;
            postHocRequired.erase(postHocIt);

            if (isContextObject)
                sharedState->hadTopLevelRequiredProperties = true;
            sharedState->requiredProperties.insert(
                    {_qobject, propertyData},
                    RequiredPropertyInfo {
                        std::move(name), compilationUnit->finalUrl(), _compiledObject->location, {}
                    });
        }
    }

    if (!postHocRequired.isEmpty() && hadInheritedRequiredProperties)
        recordError({}, QLatin1String("Property %1 was marked as required but does not exist").arg(*postHocRequired.begin()));
}

bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject *bindingTarget,
                                         const QQmlPropertyData *valueTypeProperty,
                                         const QV4::CompiledData::Binding *binding)
{
    Q_ASSERT(instance);
    QQmlData *declarativeData = QQmlData::get(instance, /*create*/true);

    qSwap(_qobject, instance);
    qSwap(_valueTypeProperty, valueTypeProperty);
    qSwap(_compiledObjectIndex, index);
    const QV4::CompiledData::Object *obj = compilationUnit->objectAt(_compiledObjectIndex);
    qSwap(_compiledObject, obj);
    qSwap(_ddata, declarativeData);
    qSwap(_bindingTarget, bindingTarget);

    QV4::Scope valueScope(v4);
    QV4::ScopedValue scopeObjectProtector(valueScope);

    QQmlPropertyCache::ConstPtr cache = propertyCaches->at(_compiledObjectIndex);

    QQmlVMEMetaObject *vmeMetaObject = nullptr;
    if (propertyCaches->needsVMEMetaObject(_compiledObjectIndex)) {
        Q_ASSERT(!cache.isNull());
        // install on _object
        vmeMetaObject = new QQmlVMEMetaObject(v4, _qobject, cache, compilationUnit, _compiledObjectIndex);
        _ddata->propertyCache = cache;
        scopeObjectProtector = _ddata->jsWrapper.value();
    } else {
        vmeMetaObject = QQmlVMEMetaObject::get(_qobject);
    }

    registerObjectWithContextById(_compiledObject, _qobject);

    qSwap(_propertyCache, cache);
    qSwap(_vmeMetaObject, vmeMetaObject);

    // If it's a group property object, don't populate CU and object index.
    // It might be populated at the actual instantiation point, or the object might not be
    // created by QML at all.
    if (!stringAt(_compiledObject->inheritedTypeNameIndex).isEmpty()) {
        _ddata->compilationUnit = compilationUnit;
        _ddata->cuObjectIndex = int(_compiledObjectIndex);
    }

    if (_compiledObject->hasFlag(QV4::CompiledData::Object::HasDeferredBindings))
        _ddata->deferData(_compiledObjectIndex, compilationUnit, context, m_inlineComponentName);

    registerPostHocRequiredProperties(binding);

    if (_compiledObject->nFunctions > 0)
        setupFunctions();
    setupBindings((binding && binding->hasFlag(QV4::CompiledData::Binding::IsDeferredBinding))
                  ? BindingMode::ApplyAll
                  : BindingMode::ApplyImmediate);

    for (int aliasIndex = 0; aliasIndex != _compiledObject->aliasCount(); ++aliasIndex) {
        // Ensure aliasChanged() signals are connected during object creation.
        // This is necessary because alias signals may not have been connected
        // if the signal handlers are defined and connected in C++ code rather
        // than being declared in QML.
        _vmeMetaObject->connectAlias(_compiledObject, aliasIndex);
        if (!context->isIdValueSet(0)) // TODO: Do we really want 0 here?
            continue;
        const QQmlPropertyData *aliasProperty =
                _propertyCache->property(_vmeMetaObject->aliasOffset() + aliasIndex);
        if (!aliasProperty)
            continue;
        QObject *target = context->idValue(aliasProperty->aliasTargetObjectId());
        if (!target)
            continue;
        QQmlData *targetDData = QQmlData::get(target, /*create*/false);
        if (targetDData == nullptr || targetDData->propertyCache.isNull())
            continue;

        const int targetPropertyIndex = aliasProperty->aliasTarget();
        int coreIndex = QQmlPropertyIndex::fromEncoded(targetPropertyIndex).coreIndex();

        const QQmlPropertyData *const targetProperty = targetDData->propertyCache->property(coreIndex);
        if (!targetProperty)
            continue;
        auto it = sharedState->requiredProperties.find({target, targetProperty});
        if (it != sharedState->requiredProperties.end()) {
            const QV4::CompiledData::Alias* alias = _compiledObject->aliasesBegin() + aliasIndex;
            it->aliasesToRequired.push_back(
                    AliasToRequiredInfo {
                            compilationUnit->stringAt(alias->nameIndex()),
                            compilationUnit->finalUrl()
                    });
        }
    }

    qSwap(_vmeMetaObject, vmeMetaObject);
    qSwap(_bindingTarget, bindingTarget);
    qSwap(_ddata, declarativeData);
    qSwap(_compiledObject, obj);
    qSwap(_compiledObjectIndex, index);
    qSwap(_valueTypeProperty, valueTypeProperty);
    qSwap(_qobject, instance);
    qSwap(_propertyCache, cache);

    return errors.isEmpty();
}

/*!
    \internal
*/
QQmlComponent *QQmlObjectCreator::createComponent(
        QQmlEngine *engine, QV4::ExecutableCompilationUnit *compilationUnit, int index,
        QObject *parent, const QQmlRefPointer<QQmlContextData> &context)
{
    QQmlComponent *component = new QQmlComponent(engine, compilationUnit, index, parent);
    QQmlComponentPrivate::get(component)->setCreationContext(context);
    QQmlData *ddata = QQmlData::get(component, /*create*/ true);
    Q_ASSERT(ddata);
    ddata->compilationUnit = compilationUnit;
    ddata->cuObjectIndex = index;
    return component;
}

/*!
    \internal
    Re-populate bindings, functions and child objects on an existing \a instance
    whose VME metaobject chain has already been (re-)created externally.

    Unlike initializeInstance(), this does not create a new VME — it uses the one
    already installed on the object. It sets up the context, registers the object
    by id, installs functions, evaluates bindings (which may create child objects),
    and connects aliases.

    Used by the QML preview/hot-reload system to rebuild an object's contents
    after its VME hierarchy has been reconstructed.
*/
void QQmlObjectCreator::repopulateBindings(
        int index, QObject *instance,
        const QQmlRefPointer<QQmlContextData> &instanceContext, InitFlags flags)
{
    phase = QQmlObjectCreator::ObjectsCreated;

    // If the object doesn't exist in the CU, there's nothing to do.
    if (compilationUnit->objectCount() <= index)
        return;

    const auto oldContext = std::exchange(context, instanceContext);
    const auto contextGuard = qScopeGuard([&]() {
        context = oldContext;
    });

    auto rootContextGuard = qScopeGuard([&]() {
        sharedState->rootContext.reset();
    });
    if (!sharedState->rootContext)
        sharedState->rootContext = context;
    else
        rootContextGuard.dismiss();

    QV4::Scope scope(engine->handle());
    auto jsObjectsGuard = qScopeGuard([&]() {
        sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList();
    });
    if (!sharedState->allJavaScriptObjects.canTrack())
        sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList(scope);
    else
        jsObjectsGuard.dismiss();

    QQmlData *ddata = QQmlData::get(instance, /*create=*/true);
    Q_ASSERT(ddata);

    QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(instance);

    const QV4::CompiledData::Object *obj = compilationUnit->objectAt(index);

    // Set up internal state mirroring populateInstanceAndAliasBindings + populateInstance.
    qSwap(_qobject, instance);
    qSwap(_compiledObjectIndex, index);
    qSwap(_compiledObject, obj);
    qSwap(_ddata, ddata);
    QObject *bindingTarget = _qobject;
    qSwap(_bindingTarget, bindingTarget);
    QObject *scopeObject = _qobject;
    qSwap(_scopeObject, scopeObject);
    const QQmlPropertyData *valueTypeProperty = nullptr;
    qSwap(_valueTypeProperty, valueTypeProperty);

    Q_ASSERT(sharedState->allJavaScriptObjects.canTrack());
    sharedState->allJavaScriptObjects.trackObject(v4, _qobject);

    QV4::QmlContext *qmlContext = static_cast<QV4::QmlContext *>(scope.constructUndefined(1));
    qSwap(_qmlContext, qmlContext);

    QV4::ScopedValue scopeObjectProtector(scope);

    QQmlPropertyCache::ConstPtr cache = propertyCaches->at(_compiledObjectIndex);
    _ddata->propertyCache = cache;
    qSwap(_propertyCache, cache);
    qSwap(_vmeMetaObject, vmeMetaObject);

    if (_compiledObject->objectId() < context->numIdValues())
        registerObjectWithContextById(_compiledObject, _qobject);

    _ddata->compilationUnit = compilationUnit;
    _ddata->cuObjectIndex = int(_compiledObjectIndex);

    if (_compiledObject->nFunctions > 0)
        setupFunctions();
    setupBindings(BindingMode::ApplyImmediate);

    if (flags.testFlag(InitFlag::IsContextObject)) {
        while (!pendingAliasBindings.empty()) {
            for (auto it = pendingAliasBindings.begin(); it != pendingAliasBindings.end();) {
                if ((*it)(sharedState.data()))
                    it = pendingAliasBindings.erase(it);
                else
                    ++it;
            }
        }
    }

    // Restore swapped state.
    qSwap(_vmeMetaObject, vmeMetaObject);
    qSwap(_propertyCache, cache);
    qSwap(_qmlContext, qmlContext);
    qSwap(_scopeObject, scopeObject);
    qSwap(_valueTypeProperty, valueTypeProperty);
    qSwap(_bindingTarget, bindingTarget);
    qSwap(_ddata, ddata);
    qSwap(_compiledObject, obj);
    qSwap(_compiledObjectIndex, index);
    qSwap(_qobject, instance);
}

QQmlObjectCreatorRecursionWatcher::QQmlObjectCreatorRecursionWatcher(QQmlObjectCreator *creator)
    : sharedState(creator->sharedState)
    , watcher(creator->sharedState.data())
{
}

void ObjectInCreationGCAnchorList::trackObject(QV4::ExecutionEngine *engine, QObject *instance)
{
    QV4::Value *wrapper = allocationScope->construct(1, QV4::QObjectWrapper::wrap(engine, instance));
    // we have to handle the case where the gc is already running, but the scope is discarded
    // before the collector runs again. In that case, rescanning won't help us. Thus, mark the
    // object.
    QV4::WriteBarrier::markCustom(engine, [wrapper](QV4::MarkStack *ms) {
        wrapper->heapObject()->mark(ms);
    });
}
