diff options
Diffstat (limited to 'src/qml/qml/qqmlobjectcreator.cpp')
-rw-r--r-- | src/qml/qml/qqmlobjectcreator.cpp | 1519 |
1 files changed, 948 insertions, 571 deletions
diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index d5b15a7a5a..a9b9140390 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 #include "qqmlobjectcreator_p.h" @@ -55,73 +19,96 @@ #include <private/qqmlvaluetypeproxybinding_p.h> #include <private/qqmldebugconnector_p.h> #include <private/qqmldebugserviceinterfaces_p.h> +#include <private/qqmlscriptdata_p.h> +#include <private/qqmlsourcecoordinate_p.h> #include <private/qjsvalue_p.h> +#include <private/qv4generatorobject_p.h> +#include <private/qv4resolvedtypereference_p.h> +#include <private/qqmlpropertybinding_p.h> +#include <private/qqmlanybinding_p.h> +#include <QtQml/private/qqmlvme_p.h> + +#include <QScopedValueRollback> #include <qtqml_tracepoints_p.h> +#include <QScopedValueRollback> +#include <QLoggingCategory> -QT_USE_NAMESPACE +Q_LOGGING_CATEGORY(lcQmlDefaultMethod, "qt.qml.defaultmethod") -namespace { -struct ActiveOCRestorer -{ - ActiveOCRestorer(QQmlObjectCreator *creator, QQmlEnginePrivate *ep) - : ep(ep), oldCreator(ep->activeObjectCreator) { ep->activeObjectCreator = creator; } - ~ActiveOCRestorer() { ep->activeObjectCreator = oldCreator; } - - QQmlEnginePrivate *ep; - QQmlObjectCreator *oldCreator; -}; -} +QT_USE_NAMESPACE -QQmlObjectCreator::QQmlObjectCreator(QQmlContextData *parentContext, const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, QQmlContextData *creationContext, - QQmlIncubatorPrivate *incubator) +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( + QQmlRefPointer<QQmlContextData> parentContext, + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, + const QQmlRefPointer<QQmlContextData> &creationContext, + QQmlIncubatorPrivate *incubator) : phase(Startup) , compilationUnit(compilationUnit) - , propertyCaches(&compilationUnit->propertyCaches) - , sharedState(new QQmlObjectCreatorSharedState) + , propertyCaches(compilationUnit->propertyCachesPtr()) + , sharedState(new QQmlObjectCreatorSharedState, QQmlRefPointer<QQmlObjectCreatorSharedState>::Adopt) , topLevelCreator(true) + , isContextObject(true) , incubator(incubator) { - init(parentContext); + init(std::move(parentContext)); sharedState->componentAttached = nullptr; - sharedState->allCreatedBindings.allocate(compilationUnit->totalBindingsCount); - sharedState->allParserStatusCallbacks.allocate(compilationUnit->totalParserStatusCount); - sharedState->allCreatedObjects.allocate(compilationUnit->totalObjectCount); - sharedState->allJavaScriptObjects = nullptr; + sharedState->allCreatedBindings.allocate(compilationUnit->totalBindingsCount()); + sharedState->allParserStatusCallbacks.allocate(compilationUnit->totalParserStatusCount()); + sharedState->allCreatedObjects.allocate(compilationUnit->totalObjectCount()); + sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList(); sharedState->creationContext = creationContext; - sharedState->rootContext = nullptr; + 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, compilationUnit->totalParserStatusCount)); + sharedState->profiler.init(profiler, compilationUnit->totalParserStatusCount())); } else { Q_UNUSED(profiler); } } -QQmlObjectCreator::QQmlObjectCreator(QQmlContextData *parentContext, const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, QQmlObjectCreatorSharedState *inheritedSharedState) +QQmlObjectCreator::QQmlObjectCreator(QQmlRefPointer<QQmlContextData> parentContext, + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, + QQmlObjectCreatorSharedState *inheritedSharedState, bool isContextObject) : phase(Startup) , compilationUnit(compilationUnit) - , propertyCaches(&compilationUnit->propertyCaches) + , propertyCaches(compilationUnit->propertyCachesPtr()) , sharedState(inheritedSharedState) , topLevelCreator(false) + , isContextObject(isContextObject) , incubator(nullptr) { - init(parentContext); + init(std::move(parentContext)); } -void QQmlObjectCreator::init(QQmlContextData *providedParentContext) +void QQmlObjectCreator::init(QQmlRefPointer<QQmlContextData> providedParentContext) { - parentContext = providedParentContext; - engine = parentContext->engine; + parentContext = std::move(providedParentContext); + engine = parentContext->engine(); v4 = engine->handle(); - if (compilationUnit && !compilationUnit->engine) - compilationUnit->linkToEngine(v4); + Q_ASSERT(compilationUnit); + Q_ASSERT(compilationUnit->engine == v4); + if (!compilationUnit->runtimeStrings) + compilationUnit->populate(); qmlUnit = compilationUnit->unitData(); - context = nullptr; _qobject = nullptr; _scopeObject = nullptr; _bindingTarget = nullptr; @@ -129,7 +116,6 @@ void QQmlObjectCreator::init(QQmlContextData *providedParentContext) _compiledObject = nullptr; _compiledObjectIndex = -1; _ddata = nullptr; - _propertyCache = nullptr; _vmeMetaObject = nullptr; _qmlContext = nullptr; } @@ -147,57 +133,69 @@ QQmlObjectCreator::~QQmlObjectCreator() } while (sharedState->componentAttached) { QQmlComponentAttached *a = sharedState->componentAttached; - a->rem(); + a->removeFromList(); } } } -QObject *QQmlObjectCreator::create(int subComponentIndex, QObject *parent, QQmlInstantiationInterrupt *interrupt) +QObject *QQmlObjectCreator::create(int subComponentIndex, QObject *parent, QQmlInstantiationInterrupt *interrupt, int flags) { if (phase == CreatingObjectsPhase2) { phase = ObjectsCreated; - return context->contextObject; + 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 { - const QV4::CompiledData::Object *compObj = compilationUnit->objectAt(subComponentIndex); - objectToCreate = compObj->bindingTable()->value.objectIndex; + 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; + } + const QV4::CompiledData::Object *compObj = compilationUnit->objectAt(subComponentIndex); + objectToCreate = compObj->bindingTable()->value.objectIndex; + } } - context = new QQmlContextData; - context->isInternal = true; - context->imports = compilationUnit->typeNameCache; - context->initFromTypeCompilationUnit(compilationUnit, subComponentIndex); - context->setParent(parentContext); + context = QQmlEnginePrivate::get(engine)->createInternalContext( + compilationUnit, parentContext, subComponentIndex, isComponentRoot); if (!sharedState->rootContext) { sharedState->rootContext = context; - sharedState->rootContext->incubator = incubator; - sharedState->rootContext->isRootObjectInCreation = true; + sharedState->rootContext->setIncubator(incubator); + sharedState->rootContext->setRootObjectInCreation(true); } QV4::Scope scope(v4); - Q_ASSERT(sharedState->allJavaScriptObjects || topLevelCreator); + Q_ASSERT(sharedState->allJavaScriptObjects.canTrack() || topLevelCreator); if (topLevelCreator) - sharedState->allJavaScriptObjects = scope.alloc(compilationUnit->totalObjectCount); - - if (subComponentIndex == -1 && compilationUnit->dependentScripts.count()) { - QV4::ScopedObject scripts(scope, v4->newArrayObject(compilationUnit->dependentScripts.count())); - context->importedScripts.set(v4, scripts); - QV4::ScopedValue v(scope); - for (int i = 0; i < compilationUnit->dependentScripts.count(); ++i) { - QQmlRefPointer<QQmlScriptData> s = compilationUnit->dependentScripts.at(i); - scripts->put(i, (v = s->scriptValueForContext(context))); - } - } else if (sharedState->creationContext) { - context->importedScripts = sharedState->creationContext->importedScripts; + sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList(scope, compilationUnit->totalObjectCount()); + + if (!isComponentRoot && sharedState->creationContext) { + // otherwise QQmlEnginePrivate::createInternalContext() handles it + context->setImportedScripts(sharedState->creationContext->importedScripts()); } QObject *instance = createInstance(objectToCreate, parent, /*isContextObject*/true); @@ -208,7 +206,7 @@ QObject *QQmlObjectCreator::create(int subComponentIndex, QObject *parent, QQmlI } if (topLevelCreator) - sharedState->allJavaScriptObjects = nullptr; + sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList(); phase = CreatingObjectsPhase2; @@ -220,145 +218,94 @@ QObject *QQmlObjectCreator::create(int subComponentIndex, QObject *parent, QQmlI if (instance) { if (QQmlEngineDebugService *service = QQmlDebugConnector::service<QQmlEngineDebugService>()) { - if (!parentContext->isInternal) - parentContext->asQQmlContextPrivate()->instances.append(instance); + if (!parentContext->isInternal()) + parentContext->asQQmlContextPrivate()->appendInstance(instance); service->objectCreated(engine, instance); - } else if (!parentContext->isInternal && QQmlDebugConnector::service<QV4DebugService>()) { - parentContext->asQQmlContextPrivate()->instances.append(instance); + } else if (!parentContext->isInternal() && QQmlDebugConnector::service<QV4DebugService>()) { + parentContext->asQQmlContextPrivate()->appendInstance(instance); } } return instance; } -// ### unify or keep in sync with populateDeferredBinding() -bool QQmlObjectCreator::populateDeferredProperties(QObject *instance, QQmlData::DeferredData *deferredData) +void QQmlObjectCreator::beginPopulateDeferred(const QQmlRefPointer<QQmlContextData> &newContext) { - QQmlData *declarativeData = QQmlData::get(instance); - context = deferredData->context; - sharedState->rootContext = context; - - QObject *bindingTarget = instance; - - QQmlRefPointer<QQmlPropertyCache> cache = declarativeData->propertyCache; - QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(instance); - - QObject *scopeObject = instance; - qSwap(_scopeObject, scopeObject); - - QV4::Scope valueScope(v4); + context = newContext; + sharedState->rootContext = newContext; Q_ASSERT(topLevelCreator); - Q_ASSERT(!sharedState->allJavaScriptObjects); - sharedState->allJavaScriptObjects = valueScope.alloc(compilationUnit->totalObjectCount); - - QV4::QmlContext *qmlContext = static_cast<QV4::QmlContext *>(valueScope.alloc()); + Q_ASSERT(!sharedState->allJavaScriptObjects.canTrack()); - qSwap(_qmlContext, qmlContext); - - qSwap(_propertyCache, cache); - qSwap(_qobject, instance); + // FIXME (QTBUG-122956): allocating from the short lived scope does not make any sense + QV4::Scope valueScope(v4); + sharedState->allJavaScriptObjects = ObjectInCreationGCAnchorList(valueScope, compilationUnit->totalObjectCount()); +} - int objectIndex = deferredData->deferredIdx; - qSwap(_compiledObjectIndex, objectIndex); +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)); - const QV4::CompiledData::Object *obj = compilationUnit->objectAt(_compiledObjectIndex); - qSwap(_compiledObject, obj); + QQmlListProperty<void> savedList; + qSwap(_currentList, savedList); - qSwap(_ddata, declarativeData); - qSwap(_bindingTarget, bindingTarget); - qSwap(_vmeMetaObject, vmeMetaObject); + const QQmlPropertyData &property = qmlProperty->core; - setupBindings(/*applyDeferredBindings=*/true); + 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<void>(); + } - qSwap(_vmeMetaObject, vmeMetaObject); - qSwap(_bindingTarget, bindingTarget); - qSwap(_ddata, declarativeData); - qSwap(_compiledObject, obj); - qSwap(_compiledObjectIndex, objectIndex); - qSwap(_qobject, instance); - qSwap(_propertyCache, cache); + setPropertyBinding(&property, binding); - qSwap(_qmlContext, qmlContext); - qSwap(_scopeObject, scopeObject); + qSwap(_currentList, savedList); + }); +} - deferredData->bindings.clear(); - phase = ObjectsCreated; +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(); } -// ### unify or keep in sync with populateDeferredProperties() -bool QQmlObjectCreator::populateDeferredBinding(const QQmlProperty &qmlProperty, QQmlData::DeferredData *deferredData, const QV4::CompiledData::Binding *binding) +void QQmlObjectCreator::populateDeferredBinding(const QQmlProperty &qmlProperty, int deferredIndex, + const QV4::CompiledData::Binding *binding) { - Q_ASSERT(binding->flags & QV4::CompiledData::Binding::IsDeferredBinding); - - QObject *instance = qmlProperty.object(); - QQmlData *declarativeData = QQmlData::get(instance); - context = deferredData->context; - sharedState->rootContext = context; - - QObject *bindingTarget = instance; - - QQmlRefPointer<QQmlPropertyCache> cache = declarativeData->propertyCache; - QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(instance); - - QObject *scopeObject = instance; - qSwap(_scopeObject, scopeObject); - - QV4::Scope valueScope(v4); - - Q_ASSERT(topLevelCreator); - if (!sharedState->allJavaScriptObjects) - sharedState->allJavaScriptObjects = valueScope.alloc(compilationUnit->totalObjectCount); - - QV4::QmlContext *qmlContext = static_cast<QV4::QmlContext *>(valueScope.alloc()); - - qSwap(_qmlContext, qmlContext); - - qSwap(_propertyCache, cache); - qSwap(_qobject, instance); - - int objectIndex = deferredData->deferredIdx; - qSwap(_compiledObjectIndex, objectIndex); - - const QV4::CompiledData::Object *obj = compilationUnit->objectAt(_compiledObjectIndex); - qSwap(_compiledObject, obj); - - qSwap(_ddata, declarativeData); - qSwap(_bindingTarget, bindingTarget); - qSwap(_vmeMetaObject, vmeMetaObject); - - QQmlListProperty<void> savedList; - qSwap(_currentList, savedList); - - const QQmlPropertyData &property = QQmlPropertyPrivate::get(qmlProperty)->core; - - if (property.isQList()) { - void *argv[1] = { (void*)&_currentList }; - QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property.coreIndex(), argv); - } else if (_currentList.object) { - _currentList = QQmlListProperty<void>(); + if (binding) { + populateDeferred(qmlProperty.object(), deferredIndex, QQmlPropertyPrivate::get(qmlProperty), + binding); + } else { + populateDeferred(qmlProperty.object(), deferredIndex); } +} - setPropertyBinding(&property, binding); - - qSwap(_currentList, savedList); - - qSwap(_vmeMetaObject, vmeMetaObject); - qSwap(_bindingTarget, bindingTarget); - qSwap(_ddata, declarativeData); - qSwap(_compiledObject, obj); - qSwap(_compiledObjectIndex, objectIndex); - qSwap(_qobject, instance); - qSwap(_propertyCache, cache); - - qSwap(_qmlContext, qmlContext); - qSwap(_scopeObject, scopeObject); +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; - - return errors.isEmpty(); } void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const QV4::CompiledData::Binding *binding) @@ -366,14 +313,17 @@ void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const QQmlPropertyData::WriteFlags propertyWriteFlags = QQmlPropertyData::BypassInterceptor | QQmlPropertyData::RemoveBindingOnAliasWrite; QV4::Scope scope(v4); - int propertyType = property->propType(); + QMetaType propertyType = property->propType(); if (property->isEnum()) { - if (binding->flags & QV4::CompiledData::Binding::IsResolvedEnum) { - propertyType = QMetaType::Int; + 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 = binding->valueAsString(compilationUnit.data()); + QVariant value = compilationUnit->bindingValueAsString(binding); bool ok = QQmlPropertyPrivate::write(_qobject, *property, value, context); Q_ASSERT(ok); Q_UNUSED(ok); @@ -381,10 +331,32 @@ void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const } } - switch (propertyType) { + 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 = binding->valueAsNumber(compilationUnit->constants); + 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))); @@ -401,288 +373,316 @@ void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const property->writeProperty(_qobject, &value, propertyWriteFlags); } } - } else if (binding->type == QV4::CompiledData::Binding::Type_Boolean) { + } 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 = binding->valueAsString(compilationUnit.data()); + QString stringValue = compilationUnit->bindingValueAsString(binding); if (property->isVarProperty()) { QV4::ScopedString s(scope, v4->newString(stringValue)); _vmeMetaObject->setVMEProperty(property->coreIndex(), s); } else { - QVariant value = QQmlStringConverters::variantFromString(stringValue); + QVariant value = stringValue; property->writeProperty(_qobject, &value, propertyWriteFlags); } } } break; - case QVariant::String: { - Q_ASSERT(binding->evaluatesToString()); - QString value = binding->valueAsString(compilationUnit.data()); + case QMetaType::QString: { + assertOrNull(binding->evaluatesToString()); + QString value = compilationUnit->bindingValueAsString(binding); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::StringList: { - Q_ASSERT(binding->evaluatesToString()); - QStringList value(binding->valueAsString(compilationUnit.data())); + case QMetaType::QStringList: { + assertOrNull(binding->evaluatesToString()); + QStringList value(compilationUnit->bindingValueAsString(binding)); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::ByteArray: { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_String); - QByteArray value(binding->valueAsString(compilationUnit.data()).toUtf8()); + case QMetaType::QByteArray: { + assertType(QV4::CompiledData::Binding::Type_String); + QByteArray value(compilationUnit->bindingValueAsString(binding).toUtf8()); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::Url: { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_String); - QString string = binding->valueAsString(compilationUnit.data()); - // Encoded dir-separators defeat QUrl processing - decode them first - string.replace(QLatin1String("%2f"), QLatin1String("/"), Qt::CaseInsensitive); - QUrl value = string.isEmpty() ? QUrl() : compilationUnit->finalUrl().resolved(QUrl(string)); - // Apply URL interceptor - if (engine->urlInterceptor()) - value = engine->urlInterceptor()->intercept(value, QQmlAbstractUrlInterceptor::UrlString); + 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 QVariant::UInt: { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Number); - double d = binding->valueAsNumber(compilationUnit->constants); + 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 QVariant::Int: { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Number); - double d = binding->valueAsNumber(compilationUnit->constants); + 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: { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Number); - float value = float(binding->valueAsNumber(compilationUnit->constants)); + assertType(QV4::CompiledData::Binding::Type_Number); + float value = float(compilationUnit->bindingValueAsNumber(binding)); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::Double: { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Number); - double value = binding->valueAsNumber(compilationUnit->constants); + case QMetaType::Double: { + assertType(QV4::CompiledData::Binding::Type_Number); + double value = compilationUnit->bindingValueAsNumber(binding); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::Color: { - bool ok = false; - uint colorValue = QQmlStringConverters::rgbaFromString(binding->valueAsString(compilationUnit.data()), &ok); - Q_ASSERT(ok); - struct { void *data[4]; } buffer; - if (QQml_valueTypeProvider()->storeValueType(property->propType(), &colorValue, &buffer, sizeof(buffer))) { - property->writeProperty(_qobject, &buffer, propertyWriteFlags); + case QMetaType::QColor: { + QVariant data = QQmlValueTypeProvider::createValueType( + compilationUnit->bindingValueAsString(binding), propertyType); + if (data.isValid()) { + property->writeProperty(_qobject, data.data(), propertyWriteFlags); } } break; #if QT_CONFIG(datestring) - case QVariant::Date: { + case QMetaType::QDate: { bool ok = false; - QDate value = QQmlStringConverters::dateFromString(binding->valueAsString(compilationUnit.data()), &ok); - Q_ASSERT(ok); + QDate value = QQmlStringConverters::dateFromString(compilationUnit->bindingValueAsString(binding), &ok); + assertOrNull(ok); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::Time: { + case QMetaType::QTime: { bool ok = false; - QTime value = QQmlStringConverters::timeFromString(binding->valueAsString(compilationUnit.data()), &ok); - Q_ASSERT(ok); + QTime value = QQmlStringConverters::timeFromString(compilationUnit->bindingValueAsString(binding), &ok); + assertOrNull(ok); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::DateTime: { + case QMetaType::QDateTime: { bool ok = false; - QDateTime value = QQmlStringConverters::dateTimeFromString(binding->valueAsString(compilationUnit.data()), &ok); - // ### VME compatibility :( - { - const qint64 date = value.date().toJulianDay(); - const int msecsSinceStartOfDay = value.time().msecsSinceStartOfDay(); - value = QDateTime(QDate::fromJulianDay(date), QTime::fromMSecsSinceStartOfDay(msecsSinceStartOfDay)); - } - Q_ASSERT(ok); + QDateTime value = QQmlStringConverters::dateTimeFromString( + compilationUnit->bindingValueAsString(binding), &ok); + assertOrNull(ok); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; #endif // datestring - case QVariant::Point: { + case QMetaType::QPoint: { bool ok = false; - QPoint value = QQmlStringConverters::pointFFromString(binding->valueAsString(compilationUnit.data()), &ok).toPoint(); - Q_ASSERT(ok); + QPoint value = QQmlStringConverters::pointFFromString(compilationUnit->bindingValueAsString(binding), &ok).toPoint(); + assertOrNull(ok); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::PointF: { + case QMetaType::QPointF: { bool ok = false; - QPointF value = QQmlStringConverters::pointFFromString(binding->valueAsString(compilationUnit.data()), &ok); - Q_ASSERT(ok); + QPointF value = QQmlStringConverters::pointFFromString(compilationUnit->bindingValueAsString(binding), &ok); + assertOrNull(ok); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::Size: { + case QMetaType::QSize: { bool ok = false; - QSize value = QQmlStringConverters::sizeFFromString(binding->valueAsString(compilationUnit.data()), &ok).toSize(); - Q_ASSERT(ok); + QSize value = QQmlStringConverters::sizeFFromString(compilationUnit->bindingValueAsString(binding), &ok).toSize(); + assertOrNull(ok); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::SizeF: { + case QMetaType::QSizeF: { bool ok = false; - QSizeF value = QQmlStringConverters::sizeFFromString(binding->valueAsString(compilationUnit.data()), &ok); - Q_ASSERT(ok); + QSizeF value = QQmlStringConverters::sizeFFromString(compilationUnit->bindingValueAsString(binding), &ok); + assertOrNull(ok); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::Rect: { + case QMetaType::QRect: { bool ok = false; - QRect value = QQmlStringConverters::rectFFromString(binding->valueAsString(compilationUnit.data()), &ok).toRect(); - Q_ASSERT(ok); + QRect value = QQmlStringConverters::rectFFromString(compilationUnit->bindingValueAsString(binding), &ok).toRect(); + assertOrNull(ok); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::RectF: { + case QMetaType::QRectF: { bool ok = false; - QRectF value = QQmlStringConverters::rectFFromString(binding->valueAsString(compilationUnit.data()), &ok); - Q_ASSERT(ok); + QRectF value = QQmlStringConverters::rectFFromString(compilationUnit->bindingValueAsString(binding), &ok); + assertOrNull(ok); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::Bool: { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Boolean); + case QMetaType::Bool: { + assertType(QV4::CompiledData::Binding::Type_Boolean); bool value = binding->valueAsBoolean(); property->writeProperty(_qobject, &value, propertyWriteFlags); } break; - case QVariant::Vector2D: { - struct { - float xp; - float yp; - } vec; - bool ok = QQmlStringConverters::createFromString(QMetaType::QVector2D, binding->valueAsString(compilationUnit.data()), &vec, sizeof(vec)); - Q_ASSERT(ok); - Q_UNUSED(ok); - property->writeProperty(_qobject, &vec, propertyWriteFlags); - } - break; - case QVariant::Vector3D: { - struct { - float xp; - float yp; - float zy; - } vec; - bool ok = QQmlStringConverters::createFromString(QMetaType::QVector3D, binding->valueAsString(compilationUnit.data()), &vec, sizeof(vec)); - Q_ASSERT(ok); - Q_UNUSED(ok); - property->writeProperty(_qobject, &vec, propertyWriteFlags); - } - break; - case QVariant::Vector4D: { - struct { - float xp; - float yp; - float zy; - float wp; - } vec; - bool ok = QQmlStringConverters::createFromString(QMetaType::QVector4D, binding->valueAsString(compilationUnit.data()), &vec, sizeof(vec)); - Q_ASSERT(ok); - Q_UNUSED(ok); - property->writeProperty(_qobject, &vec, propertyWriteFlags); - } - break; - case QVariant::Quaternion: { - struct { - float wp; - float xp; - float yp; - float zp; - } vec; - bool ok = QQmlStringConverters::createFromString(QMetaType::QQuaternion, binding->valueAsString(compilationUnit.data()), &vec, sizeof(vec)); - Q_ASSERT(ok); - Q_UNUSED(ok); - property->writeProperty(_qobject, &vec, propertyWriteFlags); - } - break; - case QVariant::RegExp: - Q_ASSERT(!"not possible"); + 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 (property->propType() == qMetaTypeId<QList<qreal> >()) { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Number); + if (propertyType == QMetaType::fromType<QList<qreal>>()) { + assertType(QV4::CompiledData::Binding::Type_Number); QList<qreal> value; - value.append(binding->valueAsNumber(compilationUnit->constants)); + value.append(compilationUnit->bindingValueAsNumber(binding)); property->writeProperty(_qobject, &value, propertyWriteFlags); break; - } else if (property->propType() == qMetaTypeId<QList<int> >()) { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Number); - double n = binding->valueAsNumber(compilationUnit->constants); + } 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 (property->propType() == qMetaTypeId<QList<bool> >()) { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Boolean); + } 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 (property->propType() == qMetaTypeId<QList<QUrl> >()) { - Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_String); - QString urlString = binding->valueAsString(compilationUnit.data()); - QUrl u = urlString.isEmpty() ? QUrl() - : compilationUnit->finalUrl().resolved(QUrl(urlString)); - QList<QUrl> value; - value.append(u); + } 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 (property->propType() == qMetaTypeId<QList<QString> >()) { - Q_ASSERT(binding->evaluatesToString()); + } else if (propertyType == QMetaType::fromType<QList<QString>>()) { + assertOrNull(binding->evaluatesToString()); QList<QString> value; - value.append(binding->valueAsString(compilationUnit.data())); + value.append(compilationUnit->bindingValueAsString(binding)); property->writeProperty(_qobject, &value, propertyWriteFlags); break; - } else if (property->propType() == qMetaTypeId<QJSValue>()) { + } else if (propertyType == QMetaType::fromType<QJSValue>()) { QJSValue value; - if (binding->type == QV4::CompiledData::Binding::Type_Boolean) { + switch (binding->type()) { + case QV4::CompiledData::Binding::Type_Boolean: value = QJSValue(binding->valueAsBoolean()); - } else if (binding->type == QV4::CompiledData::Binding::Type_Number) { - double n = binding->valueAsNumber(compilationUnit->constants); - if (double(int(n)) == n) { + break; + case QV4::CompiledData::Binding::Type_Number: { + const double n = compilationUnit->bindingValueAsNumber(binding); + if (double(int(n)) == n) value = QJSValue(int(n)); - } else + else value = QJSValue(n); - } else { - value = QJSValue(binding->valueAsString(compilationUnit.data())); + break; + } + case QV4::CompiledData::Binding::Type_Null: + value = QJSValue::NullValue; + break; + default: + value = QJSValue(compilationUnit->bindingValueAsString(binding)); + break; } property->writeProperty(_qobject, &value, propertyWriteFlags); break; - } - - // otherwise, try a custom type assignment - QString stringValue = binding->valueAsString(compilationUnit.data()); - QQmlMetaType::StringConverter converter = QQmlMetaType::customStringConverter(property->propType()); - Q_ASSERT(converter); - QVariant value = (*converter)(stringValue); + } 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; + } - QMetaProperty metaProperty = _qobject->metaObject()->property(property->coreIndex()); - if (value.isNull() || ((int)metaProperty.type() != property->propType() && metaProperty.userType() != property->propType())) { - recordError(binding->location, tr("Cannot assign value %1 to property %2").arg(stringValue).arg(QString::fromUtf8(metaProperty.name()))); - break; + QVariant target = QQmlValueTypeProvider::createValueType(source, propertyType); + if (target.isValid()) { + property->writeProperty(_qobject, target.data(), propertyWriteFlags); + break; + } } - property->writeProperty(_qobject, value.data(), propertyWriteFlags); + // 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; } @@ -699,23 +699,25 @@ static QQmlType qmlTypeForObject(QObject *object) return type; } -void QQmlObjectCreator::setupBindings(bool applyDeferredBindings) +void QQmlObjectCreator::setupBindings(BindingSetupFlags mode) { QQmlListProperty<void> savedList; qSwap(_currentList, savedList); - const QV4::CompiledData::BindingPropertyData &propertyData = compilationUnit->bindingPropertyDataPerObject.at(_compiledObjectIndex); + const QV4::CompiledData::BindingPropertyData *propertyData + = compilationUnit->bindingPropertyDataPerObjectAt(_compiledObjectIndex); if (_compiledObject->idNameIndex) { - const QQmlPropertyData *idProperty = propertyData.last(); + const QQmlPropertyData *idProperty = propertyData->last(); Q_ASSERT(!idProperty || !idProperty->isValid() || idProperty->name(_qobject) == QLatin1String("id")); - if (idProperty && idProperty->isValid() && idProperty->isWritable() && idProperty->propType() == QMetaType::QString) { + if (idProperty && idProperty->isValid() && idProperty->isWritable() && idProperty->propType().id() == QMetaType::QString) { QV4::CompiledData::Binding idBinding; idBinding.propertyNameIndex = 0; // Not used - idBinding.flags = 0; - idBinding.type = QV4::CompiledData::Binding::Type_String; + 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); } } @@ -724,7 +726,7 @@ void QQmlObjectCreator::setupBindings(bool applyDeferredBindings) if (_valueTypeProperty) { QQmlAbstractBinding *binding = QQmlPropertyPrivate::binding(_bindingTarget, QQmlPropertyIndex(_valueTypeProperty->coreIndex())); - if (binding && !binding->isValueTypeProxy()) { + if (binding && binding->kind() != QQmlAbstractBinding::ValueTypeProxy) { QQmlPropertyPrivate::removeBinding(_bindingTarget, QQmlPropertyIndex(_valueTypeProperty->coreIndex())); } else if (binding) { QQmlValueTypeProxyBinding *proxy = static_cast<QQmlValueTypeProxyBinding *>(binding); @@ -732,11 +734,14 @@ void QQmlObjectCreator::setupBindings(bool applyDeferredBindings) if (qmlTypeForObject(_bindingTarget).isValid()) { quint32 bindingSkipList = 0; - QQmlPropertyData *defaultProperty = _compiledObject->indexOfDefaultPropertyOrAlias != -1 ? _propertyCache->parent()->defaultProperty() : _propertyCache->defaultProperty(); + 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) { - QQmlPropertyData *property = binding->propertyNameIndex != 0 ? _propertyCache->property(stringAt(binding->propertyNameIndex), _qobject, context) : defaultProperty; + const QQmlPropertyData *property = binding->propertyNameIndex != 0 + ? _propertyCache->property(stringAt(binding->propertyNameIndex), + _qobject, context) + : defaultProperty; if (property) bindingSkipList |= (1 << property->coreIndex()); } @@ -750,24 +755,66 @@ void QQmlObjectCreator::setupBindings(bool applyDeferredBindings) const QV4::CompiledData::Binding *binding = _compiledObject->bindingTable(); for (quint32 i = 0; i < _compiledObject->nBindings; ++i, ++binding) { - if (binding->flags & QV4::CompiledData::Binding::IsCustomParserBinding) + 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->flags & QV4::CompiledData::Binding::IsDeferredBinding) { - if (!applyDeferredBindings) - continue; - } else { - if (applyDeferredBindings) + if (binding->hasFlag(QV4::CompiledData::Binding::IsDeferredBinding)) { + if (!(mode & ApplyDeferred)) continue; + } else if (!(mode & ApplyImmediate)) { + continue; } - const QQmlPropertyData *property = propertyData.at(i); - - if (property && property->isQList()) { + 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<void>(); @@ -783,34 +830,46 @@ void QQmlObjectCreator::setupBindings(bool applyDeferredBindings) bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProperty, const QV4::CompiledData::Binding *binding) { - if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { + const QV4::CompiledData::Binding::Type bindingType = binding->type(); + if (bindingType == QV4::CompiledData::Binding::Type_AttachedProperty) { Q_ASSERT(stringAt(compilationUnit->objectAt(binding->value.objectIndex)->inheritedTypeNameIndex).isEmpty()); - QV4::CompiledData::ResolvedTypeReference *tr = resolvedType(binding->propertyNameIndex); + QV4::ResolvedTypeReference *tr = resolvedType(binding->propertyNameIndex); Q_ASSERT(tr); - QQmlType attachedType = tr->type; + QQmlType attachedType = tr->type(); + QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine); if (!attachedType.isValid()) { - QQmlTypeNameCache::Result res = context->imports->query(stringAt(binding->propertyNameIndex)); + QQmlTypeNameCache::Result res = context->imports()->query( + stringAt(binding->propertyNameIndex), QQmlTypeLoader::get(enginePrivate)); if (res.isValid()) attachedType = res.type; else return false; } QObject *qmlObject = qmlAttachedPropertiesObject( - _qobject, attachedType.attachedPropertiesFunction(QQmlEnginePrivate::get(engine))); - if (!populateInstance(binding->value.objectIndex, qmlObject, qmlObject, /*value type property*/nullptr)) + _qobject, attachedType.attachedPropertiesFunction(enginePrivate)); + if (!qmlObject) { + recordError(binding->location, + QStringLiteral("Could not create attached properties object '%1'") + .arg(QString::fromUtf8(attachedType.typeName()))); + return false; + } + + 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() == qMetaTypeId<QQmlScriptString>()) { - QQmlScriptString ss(binding->valueAsScriptString(compilationUnit.data()), context->asQQmlContext(), _scopeObject); - ss.d.data()->bindingId = binding->type == 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 = binding->type == QV4::CompiledData::Binding::Type_String; - ss.d.data()->isNumberLiteral = binding->type == QV4::CompiledData::Binding::Type_Number; - ss.d.data()->numberValue = binding->valueAsNumber(compilationUnit->constants); + 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; @@ -821,26 +880,34 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper } QObject *createdSubObject = nullptr; - if (binding->type == QV4::CompiledData::Binding::Type_Object) { + if (bindingType == QV4::CompiledData::Binding::Type_Object) { createdSubObject = createInstance(binding->value.objectIndex, _bindingTarget); if (!createdSubObject) return false; } - if (!bindingProperty) // ### error - return true; - - if (binding->type == QV4::CompiledData::Binding::Type_GroupProperty) { + 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; - QQmlValueType *valueType = nullptr; + QQmlGadgetPtrWrapper *valueType = nullptr; const QQmlPropertyData *valueTypeProperty = nullptr; QObject *bindingTarget = _bindingTarget; - - if (QQmlValueTypeFactory::isValueType(bindingProperty->propType())) { - valueType = QQmlValueTypeFactory::valueType(bindingProperty->propType()); + 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; @@ -854,15 +921,27 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper void *argv[1] = { &groupObject }; 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; + 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; } - if (!populateInstance(binding->value.objectIndex, groupObject, bindingTarget, valueTypeProperty)) + if (!populateInstance(groupObjectIndex, groupObject, bindingTarget, valueTypeProperty, + binding)) { return false; + } if (valueType) valueType->write(_qobject, bindingProperty->coreIndex(), QQmlPropertyData::BypassInterceptor); @@ -871,20 +950,71 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper } } - if (_ddata->hasBindingBit(bindingProperty->coreIndex()) && !(binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression) - && !(binding->flags & QV4::CompiledData::Binding::IsOnAssignment) - && !_valueTypeProperty) - QQmlPropertyPrivate::removeBinding(_bindingTarget, QQmlPropertyIndex(bindingProperty->coreIndex())); + 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->isBindable()) { + removePendingBinding(_bindingTarget, bindingProperty->coreIndex()); + } else { + QQmlPropertyPrivate::removeBinding( + _bindingTarget, QQmlPropertyIndex(bindingProperty->coreIndex())); + } + } - if (binding->type == QV4::CompiledData::Binding::Type_Script || binding->isTranslationBinding()) { - if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression) { + 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()); - QQmlBoundSignal *bs = new QQmlBoundSignal(_bindingTarget, signalIndex, _scopeObject, engine); - QQmlBoundSignalExpression *expr = new QQmlBoundSignalExpression(_bindingTarget, signalIndex, - context, _scopeObject, runtimeFunction, currentQmlContext()); + QQmlBoundSignalExpression *expr = new QQmlBoundSignalExpression( + _bindingTarget, signalIndex, context, + _scopeObject, runtimeFunction, currentQmlContext()); - bs->takeExpression(expr); + if (bindingProperty->isBindable()) { + 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->isBindable()) { + 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 @@ -899,15 +1029,17 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper subprop = bindingProperty; } if (binding->isTranslationBinding()) { - qmlBinding = QQmlBinding::createTranslationBinding(compilationUnit, binding, _scopeObject, context); + qmlBinding = QQmlBinding::createTranslationBinding( + compilationUnit, binding, _scopeObject, context); } else { QV4::Function *runtimeFunction = compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex]; - qmlBinding = QQmlBinding::create(targetProperty, runtimeFunction, _scopeObject, context, currentQmlContext()); + qmlBinding = QQmlBinding::create(targetProperty, runtimeFunction, _scopeObject, + context, currentQmlContext()); } auto bindingTarget = _bindingTarget; auto valueTypeProperty = _valueTypeProperty; - auto assignBinding = [qmlBinding, bindingTarget, targetProperty, subprop, bindingProperty, valueTypeProperty](QQmlObjectCreatorSharedState *sharedState) -> bool { + auto assignBinding = [qmlBinding, bindingTarget, targetProperty, subprop, bindingProperty, valueTypeProperty](QQmlObjectCreatorSharedState *sharedState) mutable -> bool { if (!qmlBinding->setTarget(bindingTarget, *targetProperty, subprop) && targetProperty->isAlias()) return false; @@ -933,8 +1065,8 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper return true; } - if (binding->type == QV4::CompiledData::Binding::Type_Object) { - if (binding->flags & QV4::CompiledData::Binding::IsOnAssignment) { + 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()); @@ -944,10 +1076,12 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper 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 + if (_valueTypeProperty) { + prop = QQmlPropertyPrivate::restore(target, *_valueTypeProperty, + bindingProperty, context); + } else { prop = QQmlPropertyPrivate::restore(target, *bindingProperty, nullptr, context); + } vs->setTarget(prop); return true; } @@ -959,8 +1093,8 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper QQmlPropertyIndex propertyIndex; if (bindingProperty->isAlias()) { QQmlPropertyIndex originalIndex(bindingProperty->coreIndex(), _valueTypeProperty ? _valueTypeProperty->coreIndex() : -1); - QQmlPropertyIndex propIndex; - QQmlPropertyPrivate::findAliasTarget(target, originalIndex, &target, &propIndex); + 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"; @@ -968,16 +1102,20 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper } // we can't have aliasses on subproperties of value types, so: - QQmlPropertyData targetPropertyData = *data->propertyCache->property(propIndex.coreIndex()); - auto prop = QQmlPropertyPrivate::restore(target, targetPropertyData, nullptr, context); + 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); + if (_valueTypeProperty) { + prop = QQmlPropertyPrivate::restore( + target, *_valueTypeProperty, bindingProperty, context); + } else { + prop = QQmlPropertyPrivate::restore( + target, *bindingProperty, nullptr, context); + } vi->setTarget(prop); propertyIndex = QQmlPropertyPrivate::propertyIndex(prop); } @@ -991,8 +1129,8 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper return false; } - // Assigning object to signal property? - if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) { + // 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; @@ -1002,11 +1140,14 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper 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") + tr("Cannot connect mismatched signal/slot %1 vs %2") .arg(QString::fromUtf8(method.methodSignature())) .arg(QString::fromUtf8(signalMethod.methodSignature()))); return false; @@ -1030,7 +1171,7 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper recordError(binding->location, tr("Cannot assign object to interface property")); return false; } - } else if (bindingProperty->propType() == QMetaType::QVariant) { + } else if (bindingProperty->propType() == QMetaType::fromType<QVariant>()) { if (bindingProperty->isVarProperty()) { QV4::Scope scope(v4); QV4::ScopedValue wrappedObject(scope, QV4::QObjectWrapper::wrap(engine->handle(), createdSubObject)); @@ -1040,28 +1181,28 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper argv[0] = &value; QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, bindingProperty->coreIndex(), argv); } - } else if (bindingProperty->propType() == qMetaTypeId<QJSValue>()) { + } 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, v4, wrappedObject); + QJSValuePrivate::setValue(&value, wrappedObject); argv[0] = &value; QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, bindingProperty->coreIndex(), argv); } - } else if (bindingProperty->isQList()) { + } else if (bindingProperty->propType().flags().testFlag(QMetaType::IsQmlList)) { Q_ASSERT(_currentList.object); void *itemToAdd = createdSubObject; - const char *iid = nullptr; - int listItemType = QQmlEnginePrivate::get(engine)->listType(bindingProperty->propType()); - if (listItemType != -1) - iid = QQmlMetaType::interfaceIId(listItemType); - if (iid) - itemToAdd = createdSubObject->qt_metacast(iid); + QMetaType listItemType = QQmlMetaType::listValueType(bindingProperty->propType()); + if (listItemType.isValid()) { + const char *iid = QQmlMetaType::interfaceIId(listItemType); + if (iid) + itemToAdd = createdSubObject->qt_metacast(iid); + } if (_currentList.append) _currentList.append(&_currentList, itemToAdd); @@ -1098,11 +1239,14 @@ void QQmlObjectCreator::setupFunctions() QV4::Function *runtimeFunction = compilationUnit->runtimeFunctions[*functionIdx]; const QString name = runtimeFunction->name()->toQString(); - QQmlPropertyData *property = _propertyCache->property(name, _qobject, context); + const QQmlPropertyData *property = _propertyCache->property(name, _qobject, context); if (!property->isVMEFunction()) continue; - function = QV4::FunctionObject::createScriptFunction(qmlContext, runtimeFunction); + if (runtimeFunction->isGenerator()) + function = QV4::GeneratorFunction::create(qmlContext, runtimeFunction); + else + function = QV4::FunctionObject::createScriptFunction(qmlContext, runtimeFunction); _vmeMetaObject->setVmeMethod(property->coreIndex(), function); } } @@ -1111,21 +1255,16 @@ void QQmlObjectCreator::recordError(const QV4::CompiledData::Location &location, { QQmlError error; error.setUrl(compilationUnit->url()); - error.setLine(location.line); - error.setColumn(location.column); + 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->id >= 0) - context->setIdProperty(object->id, instance); -} - -void QQmlObjectCreator::createQmlContext() -{ - _qmlContext->setM(QV4::QmlContext::create(v4->rootContext(), context, _scopeObject)); + if (object->objectId() >= 0) + context->setIdValue(object->objectId(), instance); } QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isContextObject) @@ -1136,7 +1275,7 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo QString typeName; Q_TRACE_EXIT(QQmlObjectCreator_createInstance_exit, typeName); - ActiveOCRestorer ocRestorer(this, QQmlEnginePrivate::get(engine)); + QScopedValueRollback<QQmlObjectCreator*> ocRestore(QQmlEnginePrivate::get(engine)->activeObjectCreator, this); bool isComponent = false; QObject *instance = nullptr; @@ -1145,64 +1284,96 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo QQmlParserStatus *parserStatus = nullptr; bool installPropertyCache = true; - if (obj->flags & QV4::CompiledData::Object::IsComponent) { + if (obj->hasFlag(QV4::CompiledData::Object::IsComponent)) { isComponent = true; - QQmlComponent *component = new QQmlComponent(engine, compilationUnit.data(), index, parent); + instance = createComponent(engine, compilationUnit.data(), index, parent, context); typeName = QStringLiteral("<component>"); - QQmlComponentPrivate::get(component)->creationContext = context; - instance = component; - ddata = QQmlData::get(instance, /*create*/true); + ddata = QQmlData::get(instance); + Q_ASSERT(ddata); // we just created it inside createComponent } else { - QV4::CompiledData::ResolvedTypeReference *typeRef - = resolvedType(obj->inheritedTypeNameIndex); + QV4::ResolvedTypeReference *typeRef = resolvedType(obj->inheritedTypeNameIndex); Q_ASSERT(typeRef); - installPropertyCache = !typeRef->isFullyDynamicType; - QQmlType type = typeRef->type; - if (type.isValid()) { + installPropertyCache = !typeRef->isFullyDynamicType(); + const QQmlType type = typeRef->type(); + if (type.isValid() && !type.isInlineComponentType()) { typeName = type.qmlTypeName(); - void *ddataMemory = nullptr; - type.create(&instance, &ddataMemory, sizeof(QQmlData)); + instance = type.createWithQQmlData(); if (!instance) { recordError(obj->location, tr("Unable to create object of type %1").arg(stringAt(obj->inheritedTypeNameIndex))); return nullptr; } - { - QQmlData *ddata = new (ddataMemory) QQmlData; - ddata->ownMemory = false; - QObjectPrivate* p = QObjectPrivate::get(instance); - Q_ASSERT(!p->isDeletingChildren); - p->declarativeData = ddata; + const int finalizerCast = type.finalizerCast(); + if (finalizerCast != -1) { + auto hook = reinterpret_cast<QQmlFinalizerHook *>(reinterpret_cast<char *>(instance) + finalizerCast); + sharedState->finalizeHooks.push_back(hook); } - const int parserStatusCast = type.parserStatusCast(); if (parserStatusCast != -1) parserStatus = reinterpret_cast<QQmlParserStatus*>(reinterpret_cast<char *>(instance) + parserStatusCast); customParser = type.customParser(); - if (sharedState->rootContext && sharedState->rootContext->isRootObjectInCreation) { + if (sharedState->rootContext && sharedState->rootContext->isRootObjectInCreation()) { QQmlData *ddata = QQmlData::get(instance, /*create*/true); ddata->rootObjectInCreation = true; - sharedState->rootContext->isRootObjectInCreation = false; + sharedState->rootContext->setRootObjectInCreation(false); } sharedState->allCreatedObjects.push(instance); } else { - Q_ASSERT(typeRef->compilationUnit); - typeName = typeRef->compilationUnit->fileName(); - if (typeRef->compilationUnit->unitData()->isSingleton()) - { + auto compilationUnit = typeRef->compilationUnit(); + Q_ASSERT(compilationUnit); + typeName = compilationUnit->fileName(); + // compilation unit is shared between root type and its inline component types + // so isSingleton errorneously returns true for inline components + if (compilationUnit->unitData()->isSingleton() && !type.isInlineComponentType()) { recordError(obj->location, tr("Composite Singleton Type %1 is not creatable").arg(stringAt(obj->inheritedTypeNameIndex))); return nullptr; } - QQmlObjectCreator subCreator(context, typeRef->compilationUnit, sharedState.data()); - instance = subCreator.create(); - if (!instance) { - errors += subCreator.errors; - return nullptr; + if (!type.isInlineComponentType()) { + QQmlObjectCreator subCreator( + context, engine->handle()->executableCompilationUnit( + std::move(compilationUnit)), + sharedState.data(), isContextObject); + instance = subCreator.create(); + if (!instance) { + errors += subCreator.errors; + return nullptr; + } + } else { + QString subObjectName; + if (QString *icRootName = compilationUnit->icRootName.get()) { + subObjectName = type.elementName(); + std::swap(*icRootName, subObjectName); + } else { + compilationUnit->icRootName = std::make_unique<QString>(type.elementName()); + } + + const auto guard = qScopeGuard([&] { + if (subObjectName.isEmpty()) + compilationUnit->icRootName.reset(); + else + std::swap(*compilationUnit->icRootName, subObjectName); + }); + + const int inlineComponentId + = compilationUnit->inlineComponentId(*compilationUnit->icRootName); + QQmlObjectCreator subCreator( + context, + engine->handle()->executableCompilationUnit( + QQmlRefPointer<QV4::CompiledData::CompilationUnit>( + compilationUnit)), + sharedState.data(), + isContextObject); + instance = subCreator.create( + inlineComponentId, nullptr, nullptr, CreationFlags::InlineComponent); + if (!instance) { + errors += subCreator.errors; + return nullptr; + } } } if (instance->isWidgetType()) { @@ -1223,27 +1394,17 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo compilationUnit.data(), obj, typeName, context->url())); Q_UNUSED(typeName); // only relevant for tracing - ddata->lineNumber = obj->location.line; - ddata->columnNumber = obj->location.column; + ddata->lineNumber = obj->location.line(); + ddata->columnNumber = obj->location.column(); ddata->setImplicitDestructible(); - if (static_cast<quint32>(index) == /*root object*/0 || ddata->rootObjectInCreation) { - if (ddata->context) { - Q_ASSERT(ddata->context != context); - Q_ASSERT(ddata->outerContext); - Q_ASSERT(ddata->outerContext != context); - QQmlContextData *c = ddata->context; - while (c->linkedContext) c = c->linkedContext; - c->linkedContext = context; - } else { - ddata->context = context; - } - ddata->ownContext = ddata->context; - } else if (!ddata->context) { - ddata->context = context; - } - - context->addObject(ddata); + // inline components are root objects, but their index is != 0, so we need + // an additional check + const bool documentRoot = static_cast<quint32>(index) == /*root object*/ 0 + || ddata->rootObjectInCreation + || obj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot); + context->installContext( + ddata, documentRoot ? QQmlContextData::DocumentRoot : QQmlContextData::OrdinaryObject); if (parserStatus) { parserStatus->classBegin(); @@ -1257,21 +1418,20 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo // Register the context object in the context early on in order for pending binding // initialization to find it available. if (isContextObject) - context->contextObject = instance; + context->setContextObject(instance); - if (customParser && obj->flags & QV4::CompiledData::Object::HasCustomParserBindings) { + if (customParser && obj->hasFlag(QV4::CompiledData::Object::HasCustomParserBindings)) { customParser->engine = QQmlEnginePrivate::get(engine); - customParser->imports = compilationUnit->typeNameCache.data(); + 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->flags & QV4::CompiledData::Binding::IsCustomParserBinding) { + if (binding->hasFlag(QV4::CompiledData::Binding::IsCustomParserBinding)) bindings << binding; - } } - customParser->applyBindings(instance, compilationUnit.data(), bindings); + customParser->applyBindings(instance, compilationUnit, bindings); customParser->engine = nullptr; customParser->imports = (QQmlTypeNameCache*)nullptr; @@ -1282,21 +1442,16 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo return instance; } - QQmlRefPointer<QQmlPropertyCache> cache = propertyCaches->at(index); + QQmlPropertyCache::ConstPtr cache = propertyCaches->at(index); Q_ASSERT(!cache.isNull()); - if (installPropertyCache) { - if (ddata->propertyCache) - ddata->propertyCache->release();; - ddata->propertyCache = cache.data(); - ddata->propertyCache->addref(); - } + if (installPropertyCache) + ddata->propertyCache = cache; QObject *scopeObject = instance; qSwap(_scopeObject, scopeObject); - Q_ASSERT(sharedState->allJavaScriptObjects); - *sharedState->allJavaScriptObjects = QV4::QObjectWrapper::wrap(v4, instance); - ++sharedState->allJavaScriptObjects; + Q_ASSERT(sharedState->allJavaScriptObjects.canTrack()); + sharedState->allJavaScriptObjects.trackObject(v4, instance); QV4::Scope valueScope(v4); QV4::QmlContext *qmlContext = static_cast<QV4::QmlContext *>(valueScope.alloc()); @@ -1332,13 +1487,26 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo return ok ? instance : nullptr; } -QQmlContextData *QQmlObjectCreator::finalize(QQmlInstantiationInterrupt &interrupt) +bool QQmlObjectCreator::finalize(QQmlInstantiationInterrupt &interrupt) { Q_ASSERT(phase == ObjectsCreated || phase == Finalizing); phase = Finalizing; QQmlObjectCreatorRecursionWatcher watcher(this); - ActiveOCRestorer ocRestorer(this, QQmlEnginePrivate::get(engine)); + 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.isEmpty()) { QQmlAbstractBinding::Ptr b = sharedState->allCreatedBindings.pop(); @@ -1351,14 +1519,54 @@ QQmlContextData *QQmlObjectCreator::finalize(QQmlInstantiationInterrupt &interru data->clearPendingBindingBit(b->targetPropertyIndex().coreIndex()); b->setEnabled(true, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); - if (!b->isValueTypeProxy()) { + if (b->kind() == QQmlAbstractBinding::QmlBinding) { QQmlBinding *binding = static_cast<QQmlBinding*>(b.data()); - if (!binding->hasError() && !binding->hasDependencies()) + if (!binding->hasError() && !binding->hasDependencies() + && !binding->hasUnresolvedNames()) { b->removeFromObject(); + } } if (watcher.hasRecursed() || interrupt.shouldInterrupt()) - return nullptr; + 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 @@ -1372,39 +1580,34 @@ QQmlContextData *QQmlObjectCreator::finalize(QQmlInstantiationInterrupt &interru } if (watcher.hasRecursed() || interrupt.shouldInterrupt()) - return nullptr; + return false; } } - for (int ii = 0; ii < sharedState->finalizeCallbacks.count(); ++ii) { - QQmlEnginePrivate::FinalizeCallback callback = sharedState->finalizeCallbacks.at(ii); - QObject *obj = callback.first; - if (obj) { - void *args[] = { nullptr }; - QMetaObject::metacall(obj, QMetaObject::InvokeMetaMethod, callback.second, args); - } + for (QQmlFinalizerHook *hook: sharedState->finalizeHooks) { + hook->componentFinalized(); if (watcher.hasRecursed()) - return nullptr; + return false; } - sharedState->finalizeCallbacks.clear(); + sharedState->finalizeHooks.clear(); while (sharedState->componentAttached) { QQmlComponentAttached *a = sharedState->componentAttached; - a->rem(); + a->removeFromList(); QQmlData *d = QQmlData::get(a->parent()); Q_ASSERT(d); Q_ASSERT(d->context); - a->add(&d->context->componentAttached); + d->context->addComponentAttached(a); if (QQmlVME::componentCompleteEnabled()) emit a->completed(); if (watcher.hasRecursed() || interrupt.shouldInterrupt()) - return nullptr; + return false; } phase = Done; - return sharedState->rootContext; + return true; } void QQmlObjectCreator::clear() @@ -1413,19 +1616,26 @@ void QQmlObjectCreator::clear() return; Q_ASSERT(phase != Startup); - while (!sharedState->allCreatedObjects.isEmpty()) - delete sharedState->allCreatedObjects.pop(); + while (!sharedState->allCreatedObjects.isEmpty()) { + auto object = sharedState->allCreatedObjects.pop(); + if (engine->objectOwnership(object) != QQmlEngine::CppOwnership) { + delete object; + } + } while (sharedState->componentAttached) { QQmlComponentAttached *a = sharedState->componentAttached; - a->rem(); + a->removeFromList(); } phase = Done; } -bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject *bindingTarget, const QQmlPropertyData *valueTypeProperty) +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); @@ -1439,17 +1649,14 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * QV4::Scope valueScope(v4); QV4::ScopedValue scopeObjectProtector(valueScope); - QQmlRefPointer<QQmlPropertyCache> cache = propertyCaches->at(_compiledObjectIndex); + 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); - if (_ddata->propertyCache) - _ddata->propertyCache->release(); - _ddata->propertyCache = cache.data(); - _ddata->propertyCache->addref(); + _ddata->propertyCache = cache; scopeObjectProtector = _ddata->jsWrapper.value(); } else { vmeMetaObject = QQmlVMEMetaObject::get(_qobject); @@ -1460,12 +1667,159 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * qSwap(_propertyCache, cache); qSwap(_vmeMetaObject, vmeMetaObject); - if (_compiledObject->flags & QV4::CompiledData::Object::HasDeferredBindings) + _ddata->compilationUnit = compilationUnit; + if (_compiledObject->hasFlag(QV4::CompiledData::Object::HasDeferredBindings)) _ddata->deferData(_compiledObjectIndex, compilationUnit, context); + 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.isValid() && !type.isInlineComponentType()) { + return { 0, _propertyCache->propertyCount() }; // 1. + } + // Q_ASSERT(type.isComposite()); + return { _propertyCache->propertyOffset(), _propertyCache->propertyCount() }; // 2. + }; + 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); + auto postHocIt = postHocRequired.find(name); + if (!propertyData->isRequired() && postHocRequired.end() == postHocIt ) + continue; + + if (postHocIt != postHocRequired.end()) + postHocRequired.erase(postHocIt); + + if (isContextObject) + sharedState->hadTopLevelRequiredProperties = true; + sharedState->requiredProperties.insert( + {_qobject, propertyData}, + RequiredPropertyInfo { + 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); + auto postHocIt = postHocRequired.find(name); + if (postHocRequired.end() == postHocIt) + continue; + postHocRequired.erase(postHocIt); + + if (isContextObject) + sharedState->hadTopLevelRequiredProperties = true; + sharedState->requiredProperties.insert( + {_qobject, propertyData}, + RequiredPropertyInfo { + name, compilationUnit->finalUrl(), _compiledObject->location, {} }); + } + } + + if (!postHocRequired.isEmpty() && hadInheritedRequiredProperties) + recordError({}, QLatin1String("Property %1 was marked as required but does not exist").arg(*postHocRequired.begin())); + if (_compiledObject->nFunctions > 0) setupFunctions(); - setupBindings(); + setupBindings((binding && binding->hasFlag(QV4::CompiledData::Binding::IsDeferredBinding)) + ? BindingMode::ApplyAll + : BindingMode::ApplyImmediate); + + for (int aliasIndex = 0; aliasIndex != _compiledObject->aliasCount(); ++aliasIndex) { + const QV4::CompiledData::Alias* alias = _compiledObject->aliasesBegin() + aliasIndex; + const auto originalAlias = alias; + while (alias->isAliasToLocalAlias()) + alias = _compiledObject->aliasesBegin() + alias->localAliasIndex; + Q_ASSERT(alias->hasFlag(QV4::CompiledData::Alias::Resolved)); + if (!context->isIdValueSet(0)) // TODO: Do we really want 0 here? + continue; + QObject *target = context->idValue(alias->targetObjectId()); + if (!target) + continue; + QQmlData *targetDData = QQmlData::get(target, /*create*/false); + if (targetDData == nullptr || targetDData->propertyCache.isNull()) + continue; + int coreIndex = QQmlPropertyIndex::fromEncoded(alias->encodedMetaPropertyIndex).coreIndex(); + const QQmlPropertyData *const targetProperty = targetDData->propertyCache->property(coreIndex); + if (!targetProperty) + continue; + auto it = sharedState->requiredProperties.find({target, targetProperty}); + if (it != sharedState->requiredProperties.end()) + it->aliasesToRequired.push_back( + AliasToRequiredInfo { + compilationUnit->stringAt(originalAlias->nameIndex()), + compilationUnit->finalUrl() + }); + } qSwap(_vmeMetaObject, vmeMetaObject); qSwap(_bindingTarget, bindingTarget); @@ -1479,11 +1833,34 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * 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)->creationContext = context; + QQmlData::get(component, /*create*/ true); + return component; +} QQmlObjectCreatorRecursionWatcher::QQmlObjectCreatorRecursionWatcher(QQmlObjectCreator *creator) : sharedState(creator->sharedState) , watcher(creator->sharedState.data()) { } + +void ObjectInCreationGCAnchorList::trackObject(QV4::ExecutionEngine *engine, QObject *instance) +{ + *allJavaScriptObjects = 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, [this](QV4::MarkStack *ms) { + allJavaScriptObjects->heapObject()->mark(ms); + }); + ++allJavaScriptObjects; +} |