diff options
-rw-r--r-- | src/qml/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/qml/common/qv4compileddata_p.h | 1 | ||||
-rw-r--r-- | src/qml/compiler/qqmlirbuilder_p.h | 3 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4executablecompilationunit_p.h | 17 | ||||
-rw-r--r-- | src/qml/qml/qqmlcomponentandaliasresolver_p.h | 417 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertycache_p.h | 2 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertycachecreator_p.h | 146 | ||||
-rw-r--r-- | src/qml/qml/qqmltypecompiler.cpp | 416 | ||||
-rw-r--r-- | src/qml/qml/qqmltypecompiler_p.h | 56 | ||||
-rw-r--r-- | src/qml/qml/qqmltypedata.cpp | 114 | ||||
-rw-r--r-- | src/qml/qml/qqmltypedata_p.h | 4 | ||||
-rw-r--r-- | tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp | 100 |
12 files changed, 722 insertions, 555 deletions
diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt index 68d4c6841b..c004b105d3 100644 --- a/src/qml/CMakeLists.txt +++ b/src/qml/CMakeLists.txt @@ -267,6 +267,7 @@ qt_internal_add_qml_module(Qml qml/qqmlboundsignal.cpp qml/qqmlboundsignal_p.h qml/qqmlbuiltinfunctions.cpp qml/qqmlbuiltinfunctions_p.h qml/qqmlcomponent.cpp qml/qqmlcomponent.h qml/qqmlcomponent_p.h + qml/qqmlcomponentandaliasresolver_p.h qml/qqmlcomponentattached_p.h qml/qqmlcontext.cpp qml/qqmlcontext.h qml/qqmlcontext_p.h qml/qqmlcontextdata.cpp qml/qqmlcontextdata_p.h diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index c86f078894..9012c85ce7 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -1080,6 +1080,7 @@ public: const Binding *bindingsBegin() const { return bindingTable(); } const Binding *bindingsEnd() const { return bindingTable() + nBindings; } + int bindingCount() const { return nBindings; } const Property *propertiesBegin() const { return propertyTable(); } const Property *propertiesEnd() const { return propertyTable() + nProperties; } diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 47b871d07c..890c7f088a 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -167,6 +167,9 @@ struct PoolList bool operator!=(const Iterator &rhs) const { return ptr != rhs.ptr; } + + operator T *() { return ptr; } + operator const T *() const { return ptr; } }; Iterator begin() { return Iterator(first); } diff --git a/src/qml/jsruntime/qv4executablecompilationunit_p.h b/src/qml/jsruntime/qv4executablecompilationunit_p.h index 403b15158c..1d73154b80 100644 --- a/src/qml/jsruntime/qv4executablecompilationunit_p.h +++ b/src/qml/jsruntime/qv4executablecompilationunit_p.h @@ -154,9 +154,22 @@ public: std::unique_ptr<CompilationUnitMapper> backingFile; // --- interface for QQmlPropertyCacheCreator - using CompiledObject = CompiledData::Object; - using CompiledFunction = CompiledData::Function; + using CompiledObject = const CompiledData::Object; + using CompiledFunction = const CompiledData::Function; + using CompiledBinding = const CompiledData::Binding; enum class ListPropertyAssignBehavior { Append, Replace, ReplaceIfNotDefault }; + + // Empty dummy. We don't need to do this when loading from cache. + class IdToObjectMap + { + public: + void insert(int, int) {} + void clear() {} + + // We have already checked uniqueness of IDs when creating the CU + bool contains(int) { return false; } + }; + ListPropertyAssignBehavior listPropertyAssignBehavior() const { if (data->flags & CompiledData::Unit::ListPropertyAssignReplace) diff --git a/src/qml/qml/qqmlcomponentandaliasresolver_p.h b/src/qml/qml/qqmlcomponentandaliasresolver_p.h new file mode 100644 index 0000000000..4f4746acc3 --- /dev/null +++ b/src/qml/qml/qqmlcomponentandaliasresolver_p.h @@ -0,0 +1,417 @@ +// Copyright (C) 2023 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 +#ifndef QQMLCOMPONENTANDALIASRESOLVER_P_H +#define QQMLCOMPONENTANDALIASRESOLVER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlerror.h> + +#include <QtCore/qglobal.h> +#include <QtCore/qhash.h> + +#include <private/qqmltypeloader_p.h> +#include <private/qqmlpropertycachecreator_p.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcQmlTypeCompiler); + +template<typename ObjectContainer> +class QQmlComponentAndAliasResolver +{ + Q_DECLARE_TR_FUNCTIONS(QQmlComponentAndAliasResolver) +public: + using CompiledObject = typename ObjectContainer::CompiledObject; + using CompiledBinding = typename ObjectContainer::CompiledBinding; + + QQmlComponentAndAliasResolver( + ObjectContainer *compiler, + QQmlEnginePrivate *enginePrivate, + QQmlPropertyCacheVector *propertyCaches); + + [[nodiscard]] QQmlError resolve(int root = 0); + +private: + enum AliasResolutionResult { + NoAliasResolved, + SomeAliasesResolved, + AllAliasesResolved + }; + + // To be specialized for each container + void allocateNamedObjects(CompiledObject *object) const; + void setObjectId(int index) const; + [[nodiscard]] bool markAsComponent(int index) const; + [[nodiscard]] AliasResolutionResult resolveAliasesInObject(int objectIndex, QQmlError *error); + [[nodiscard]] bool wrapImplicitComponent(CompiledBinding *binding); + + [[nodiscard]] QQmlError findAndRegisterImplicitComponents( + const CompiledObject *obj, const QQmlPropertyCache::ConstPtr &propertyCache); + [[nodiscard]] QQmlError collectIdsAndAliases(int objectIndex); + [[nodiscard]] QQmlError resolveAliases(int componentIndex); + + QString stringAt(int idx) const { return m_compiler->stringAt(idx); } + QV4::ResolvedTypeReference *resolvedType(int id) const { return m_compiler->resolvedType(id); } + + [[nodiscard]] QQmlError error( + const QV4::CompiledData::Location &location, + const QString &description) + { + QQmlError error; + error.setLine(qmlConvertSourceCoordinate<quint32, int>(location.line())); + error.setColumn(qmlConvertSourceCoordinate<quint32, int>(location.column())); + error.setDescription(description); + error.setUrl(m_compiler->url()); + return error; + } + + template<typename Token> + [[nodiscard]] QQmlError error(Token token, const QString &description) + { + return error(token->location, description); + } + + static bool isUsableComponent(const QMetaObject *metaObject) + { + // The metaObject is a component we're interested in if it either is a QQmlComponent itself + // or if any of its parents is a QQmlAbstractDelegateComponent. We don't want to include + // qqmldelegatecomponent_p.h because it belongs to QtQmlModels. + + if (metaObject == &QQmlComponent::staticMetaObject) + return true; + + for (; metaObject; metaObject = metaObject->superClass()) { + if (qstrcmp(metaObject->className(), "QQmlAbstractDelegateComponent") == 0) + return true; + } + + return false; + } + + ObjectContainer *m_compiler = nullptr; + QQmlEnginePrivate *m_enginePrivate = nullptr; + + // Implicit component insertion may have added objects and thus we also need + // to extend the symmetric propertyCaches. Therefore, non-const propertyCaches. + QQmlPropertyCacheVector *m_propertyCaches = nullptr; + + // indices of the objects that are actually Component {} + QVector<quint32> m_componentRoots; + QVector<int> m_objectsWithAliases; + typename ObjectContainer::IdToObjectMap m_idToObjectIndex; +}; + +template<typename ObjectContainer> +QQmlComponentAndAliasResolver<ObjectContainer>::QQmlComponentAndAliasResolver( + ObjectContainer *compiler, + QQmlEnginePrivate *enginePrivate, + QQmlPropertyCacheVector *propertyCaches) + : m_compiler(compiler) + , m_enginePrivate(enginePrivate) + , m_propertyCaches(propertyCaches) +{ +} + +template<typename ObjectContainer> +QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::findAndRegisterImplicitComponents( + const CompiledObject *obj, const QQmlPropertyCache::ConstPtr &propertyCache) +{ + QQmlPropertyResolver propertyResolver(propertyCache); + + const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 + ? propertyCache->parent()->defaultProperty() + : propertyCache->defaultProperty(); + + for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) { + if (binding->type() != QV4::CompiledData::Binding::Type_Object) + continue; + if (binding->hasFlag(QV4::CompiledData::Binding::IsSignalHandlerObject)) + continue; + + auto targetObject = m_compiler->objectAt(binding->value.objectIndex); + auto typeReference = resolvedType(targetObject->inheritedTypeNameIndex); + Q_ASSERT(typeReference); + + const QMetaObject *firstMetaObject = nullptr; + const auto type = typeReference->type(); + if (type.isValid()) + firstMetaObject = type.metaObject(); + else if (const auto compilationUnit = typeReference->compilationUnit()) + firstMetaObject = compilationUnit->rootPropertyCache()->firstCppMetaObject(); + if (isUsableComponent(firstMetaObject)) + continue; + + // if here, not a QQmlComponent, so needs wrapping + const QQmlPropertyData *pd = nullptr; + if (binding->propertyNameIndex != quint32(0)) { + bool notInRevision = false; + pd = propertyResolver.property(stringAt(binding->propertyNameIndex), ¬InRevision); + } else { + pd = defaultProperty; + } + if (!pd || !pd->isQObject()) + continue; + + // If the version is given, use it and look up by QQmlType. + // Otherwise, make sure we look up by metaobject. + // TODO: Is this correct? + QQmlPropertyCache::ConstPtr pc = pd->typeVersion().hasMinorVersion() + ? QQmlMetaType::rawPropertyCacheForType(pd->propType(), pd->typeVersion()) + : QQmlMetaType::rawPropertyCacheForType(pd->propType()); + const QMetaObject *mo = pc ? pc->firstCppMetaObject() : nullptr; + while (mo) { + if (mo == &QQmlComponent::staticMetaObject) + break; + mo = mo->superClass(); + } + + if (!mo) + continue; + + if (!wrapImplicitComponent(binding)) + return error(binding, tr("Cannot wrap implicit component")); + } + + return QQmlError(); +} + +// Resolve ignores everything relating to inline components, except for implicit components. +template<typename ObjectContainer> +QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::resolve(int root) +{ + // Detect real Component {} objects as well as implicitly defined components, such as + // someItemDelegate: Item {} + // In the implicit case Item is surrounded by a synthetic Component {} because the property + // on the left hand side is of QQmlComponent type. + const int objCountWithoutSynthesizedComponents = m_compiler->objectCount(); + + // root+1, as ic root is handled at the end + const int startObjectIndex = root == 0 ? root : root+1; + + for (int i = startObjectIndex; i < objCountWithoutSynthesizedComponents; ++i) { + auto obj = m_compiler->objectAt(i); + const bool isInlineComponentRoot + = obj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot); + const bool isPartOfInlineComponent + = obj->hasFlag(QV4::CompiledData::Object::IsPartOfInlineComponent); + QQmlPropertyCache::ConstPtr cache = m_propertyCaches->at(i); + + bool isExplicitComponent = false; + if (obj->inheritedTypeNameIndex) { + auto *tref = resolvedType(obj->inheritedTypeNameIndex); + Q_ASSERT(tref); + if (tref->type().metaObject() == &QQmlComponent::staticMetaObject) + isExplicitComponent = true; + } + + if (isInlineComponentRoot && isExplicitComponent) { + qCWarning(lcQmlTypeCompiler).nospace().noquote() + << m_compiler->url().toString() << ":" << obj->location.line() << ":" + << obj->location.column() + << ": Using a Component as the root of an inline component is deprecated: " + "inline components are " + "automatically wrapped into Components when needed."; + } + + if (root == 0) { + // normal component root, skip over anything inline component related + if (isInlineComponentRoot || isPartOfInlineComponent) + continue; + } else if (!isPartOfInlineComponent || isInlineComponentRoot) { + // We've left the current inline component (potentially entered a new one), + // but we still need to resolve implicit components which are part of inline components. + if (cache && !isExplicitComponent) { + const QQmlError error = findAndRegisterImplicitComponents(obj, cache); + if (error.isValid()) + return error; + } + break; + } + + if (obj->inheritedTypeNameIndex == 0 && !cache) + continue; + + if (!isExplicitComponent) { + if (cache) { + const QQmlError error = findAndRegisterImplicitComponents(obj, cache); + if (error.isValid()) + return error; + } + continue; + } + + if (!markAsComponent(i)) + return error(obj, tr("Cannot mark object as component")); + + // check if this object is the root + if (i == 0) { + if (isExplicitComponent) + qCWarning(lcQmlTypeCompiler).nospace().noquote() + << m_compiler->url().toString() << ":" << obj->location.line() << ":" + << obj->location.column() + << ": Using a Component as the root of a qmldocument is deprecated: types " + "defined in qml documents are " + "automatically wrapped into Components when needed."; + } + + if (obj->functionCount() > 0) + return error(obj, tr("Component objects cannot declare new functions.")); + if (obj->propertyCount() > 0 || obj->aliasCount() > 0) + return error(obj, tr("Component objects cannot declare new properties.")); + if (obj->signalCount() > 0) + return error(obj, tr("Component objects cannot declare new signals.")); + + if (obj->bindingCount() == 0) + return error(obj, tr("Cannot create empty component specification")); + + const auto rootBinding = obj->bindingsBegin(); + const auto bindingsEnd = obj->bindingsEnd(); + + // Produce the more specific "no properties" error rather than the "invalid body" error + // where possible. + for (auto b = rootBinding; b != bindingsEnd; ++b) { + if (b->propertyNameIndex == 0) + continue; + + return error(b, tr("Component elements may not contain properties other than id")); + } + + if (auto b = rootBinding; + b->type() != QV4::CompiledData::Binding::Type_Object || ++b != bindingsEnd) { + return error(obj, tr("Invalid component body specification")); + } + + // For the root object, we are going to collect ids/aliases and resolve them for as a + // separate last pass. + if (i != 0) + m_componentRoots.append(i); + } + + for (int i = 0; i < m_componentRoots.size(); ++i) { + CompiledObject *component = m_compiler->objectAt(m_componentRoots.at(i)); + const auto rootBinding = component->bindingsBegin(); + + m_idToObjectIndex.clear(); + m_objectsWithAliases.clear(); + + if (const QQmlError error = collectIdsAndAliases(rootBinding->value.objectIndex); + error.isValid()) { + return error; + } + + allocateNamedObjects(component); + + if (const QQmlError error = resolveAliases(m_componentRoots.at(i)); error.isValid()) + return error; + } + + // Collect ids and aliases for root + m_idToObjectIndex.clear(); + m_objectsWithAliases.clear(); + + if (const QQmlError error = collectIdsAndAliases(root); error.isValid()) + return error; + + allocateNamedObjects(m_compiler->objectAt(root)); + return resolveAliases(root); +} + +template<typename ObjectContainer> +QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::collectIdsAndAliases(int objectIndex) +{ + auto obj = m_compiler->objectAt(objectIndex); + + if (obj->idNameIndex != 0) { + if (m_idToObjectIndex.contains(obj->idNameIndex)) + return error(obj->locationOfIdProperty, tr("id is not unique")); + setObjectId(objectIndex); + m_idToObjectIndex.insert(obj->idNameIndex, objectIndex); + } + + if (obj->aliasCount() > 0) + m_objectsWithAliases.append(objectIndex); + + // Stop at Component boundary + if (obj->hasFlag(QV4::CompiledData::Object::IsComponent) && objectIndex != /*root object*/0) + return QQmlError(); + + for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); + binding != end; ++binding) { + switch (binding->type()) { + case QV4::CompiledData::Binding::Type_Object: + case QV4::CompiledData::Binding::Type_AttachedProperty: + case QV4::CompiledData::Binding::Type_GroupProperty: + if (const QQmlError error = collectIdsAndAliases(binding->value.objectIndex); + error.isValid()) { + return error; + } + break; + default: + break; + } + } + + return QQmlError(); +} + +template<typename ObjectContainer> +QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::resolveAliases(int componentIndex) +{ + if (m_objectsWithAliases.isEmpty()) + return QQmlError(); + + QQmlPropertyCacheAliasCreator<ObjectContainer> aliasCacheCreator(m_propertyCaches, m_compiler); + + bool atLeastOneAliasResolved; + do { + atLeastOneAliasResolved = false; + QVector<int> pendingObjects; + + for (int objectIndex: std::as_const(m_objectsWithAliases)) { + + QQmlError error; + const auto result = resolveAliasesInObject(objectIndex, &error); + if (error.isValid()) + return error; + + if (result == AllAliasesResolved) { + QQmlError error = aliasCacheCreator.appendAliasesToPropertyCache( + *m_compiler->objectAt(componentIndex), objectIndex, m_enginePrivate); + if (error.isValid()) + return error; + atLeastOneAliasResolved = true; + } else if (result == SomeAliasesResolved) { + atLeastOneAliasResolved = true; + pendingObjects.append(objectIndex); + } else { + pendingObjects.append(objectIndex); + } + } + qSwap(m_objectsWithAliases, pendingObjects); + } while (!m_objectsWithAliases.isEmpty() && atLeastOneAliasResolved); + + if (!atLeastOneAliasResolved && !m_objectsWithAliases.isEmpty()) { + const CompiledObject *obj = m_compiler->objectAt(m_objectsWithAliases.first()); + for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) { + if (!alias->hasFlag(QV4::CompiledData::Alias::Resolved)) + return error(alias->location, tr("Circular alias reference detected")); + } + } + + return QQmlError(); +} + +QT_END_NAMESPACE + +#endif // QQMLCOMPONENTANDALIASRESOLVER_P_H diff --git a/src/qml/qml/qqmlpropertycache_p.h b/src/qml/qml/qqmlpropertycache_p.h index eb88cc43a2..058fea7366 100644 --- a/src/qml/qml/qqmlpropertycache_p.h +++ b/src/qml/qml/qqmlpropertycache_p.h @@ -232,7 +232,7 @@ private: friend class QQmlCompiler; template <typename T> friend class QQmlPropertyCacheCreator; template <typename T> friend class QQmlPropertyCacheAliasCreator; - friend class QQmlComponentAndAliasResolver; + template <typename T> friend class QQmlComponentAndAliasResolver; friend class QQmlMetaObject; QQmlPropertyCache(const QQmlMetaObjectPointer &metaObject) : _metaObject(metaObject) {} diff --git a/src/qml/qml/qqmlpropertycachecreator_p.h b/src/qml/qml/qqmlpropertycachecreator_p.h index 824260330b..99a64802c1 100644 --- a/src/qml/qml/qqmlpropertycachecreator_p.h +++ b/src/qml/qml/qqmlpropertycachecreator_p.h @@ -115,14 +115,6 @@ public: */ QQmlError verifyNoICCycle(); - /*! - \internal - Fills the property caches for the CompiledObjects by - calling buildMetaObjectsIncrementally until it can no - longer resume. - */ - QQmlError buildMetaObjects(); - enum class VMEMetaObjectIsRequired { Maybe, Always @@ -233,20 +225,6 @@ QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjectsIncrementally() } template <typename ObjectContainer> -inline QQmlError -QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjects() -{ - QQmlError error = verifyNoICCycle(); - if (error.isValid()) - return error; - QQmlPropertyCacheCreatorBase::IncrementalResult result; - do { - result = buildMetaObjectsIncrementally(); - } while (result.canResume); - return result.error; -} - -template <typename ObjectContainer> inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context, VMEMetaObjectIsRequired isVMERequired) { auto isAddressable = [](const QUrl &url) { @@ -753,18 +731,16 @@ class QQmlPropertyCacheAliasCreator public: typedef typename ObjectContainer::CompiledObject CompiledObject; - QQmlPropertyCacheAliasCreator(QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer); - - void appendAliasPropertiesToMetaObjects(QQmlEnginePrivate *enginePriv); - - QQmlError appendAliasesToPropertyCache(const CompiledObject &component, int objectIndex, QQmlEnginePrivate *enginePriv); + QQmlPropertyCacheAliasCreator( + QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer); + QQmlError appendAliasesToPropertyCache( + const CompiledObject &component, int objectIndex, QQmlEnginePrivate *enginePriv); private: - void appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex, QQmlEnginePrivate *enginePriv); - QQmlError propertyDataForAlias(const CompiledObject &component, const QV4::CompiledData::Alias &alias, QMetaType *type, QTypeRevision *version, QQmlPropertyData::Flags *propertyFlags, QQmlEnginePrivate *enginePriv); - - void collectObjectsWithAliasesRecursively(int objectIndex, QVector<int> *objectsWithAliases) const; - + QQmlError propertyDataForAlias( + const CompiledObject &component, const QV4::CompiledData::Alias &alias, QMetaType *type, + QTypeRevision *version, QQmlPropertyData::Flags *propertyFlags, + QQmlEnginePrivate *enginePriv); int objectForId(const CompiledObject &component, int id) const; QQmlPropertyCacheVector *propertyCaches; @@ -772,107 +748,11 @@ private: }; template <typename ObjectContainer> -inline QQmlPropertyCacheAliasCreator<ObjectContainer>::QQmlPropertyCacheAliasCreator(QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer) +inline QQmlPropertyCacheAliasCreator<ObjectContainer>::QQmlPropertyCacheAliasCreator( + QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer) : propertyCaches(propertyCaches) , objectContainer(objectContainer) { - -} - -template <typename ObjectContainer> -inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasPropertiesToMetaObjects(QQmlEnginePrivate *enginePriv) -{ - // skip the root object (index 0) as that one does not have a first object index originating - // from a binding. - for (int i = 1; i < objectContainer->objectCount(); ++i) { - const CompiledObject &component = *objectContainer->objectAt(i); - if (!component.hasFlag(QV4::CompiledData::Object::IsComponent)) - continue; - - const auto rootBinding = component.bindingsBegin(); - appendAliasPropertiesInMetaObjectsWithinComponent(component, rootBinding->value.objectIndex, enginePriv); - } - - const int rootObjectIndex = 0; - appendAliasPropertiesInMetaObjectsWithinComponent(*objectContainer->objectAt(rootObjectIndex), rootObjectIndex, enginePriv); -} - -template <typename ObjectContainer> -inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex, QQmlEnginePrivate *enginePriv) -{ - QVector<int> objectsWithAliases; - collectObjectsWithAliasesRecursively(firstObjectIndex, &objectsWithAliases); - if (objectsWithAliases.isEmpty()) - return; - - const auto allAliasTargetsExist = [this, &component](const CompiledObject &object) { - auto alias = object.aliasesBegin(); - auto end = object.aliasesEnd(); - for ( ; alias != end; ++alias) { - Q_ASSERT(alias->hasFlag(QV4::CompiledData::Alias::Resolved)); - - const int targetObjectIndex = objectForId(component, alias->targetObjectId()); - Q_ASSERT(targetObjectIndex >= 0); - - if (alias->isAliasToLocalAlias()) - continue; - - if (alias->encodedMetaPropertyIndex == -1) - continue; - - const QQmlPropertyCache::ConstPtr targetCache - = propertyCaches->at(targetObjectIndex); - Q_ASSERT(targetCache); - - int coreIndex = QQmlPropertyIndex::fromEncoded(alias->encodedMetaPropertyIndex).coreIndex(); - const QQmlPropertyData *targetProperty = targetCache->property(coreIndex); - if (!targetProperty) - return false; - } - return true; - }; - - do { - QVector<int> pendingObjects; - - for (int objectIndex: std::as_const(objectsWithAliases)) { - const CompiledObject &object = *objectContainer->objectAt(objectIndex); - - if (allAliasTargetsExist(object)) { - appendAliasesToPropertyCache(component, objectIndex, enginePriv); - } else { - pendingObjects.append(objectIndex); - } - - } - objectsWithAliases = std::move(pendingObjects); - } while (!objectsWithAliases.isEmpty()); -} - -template <typename ObjectContainer> -inline void QQmlPropertyCacheAliasCreator<ObjectContainer>::collectObjectsWithAliasesRecursively(int objectIndex, QVector<int> *objectsWithAliases) const -{ - const CompiledObject &object = *objectContainer->objectAt(objectIndex); - if (object.aliasCount() > 0) - objectsWithAliases->append(objectIndex); - - // Stop at Component boundary - if (object.hasFlag(QV4::CompiledData::Object::IsComponent) && objectIndex != /*root object*/0) - return; - - auto binding = object.bindingsBegin(); - auto end = object.bindingsEnd(); - for (; binding != end; ++binding) { - switch (binding->type()) { - case QV4::CompiledData::Binding::Type_Object: - case QV4::CompiledData::Binding::Type_AttachedProperty: - case QV4::CompiledData::Binding::Type_GroupProperty: - collectObjectsWithAliasesRecursively(binding->value.objectIndex, objectsWithAliases); - break; - default: - break; - } - } } template <typename ObjectContainer> @@ -912,7 +792,8 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor lastAlias = targetAlias; } while (lastAlias->isAliasToLocalAlias()); - return propertyDataForAlias(component, *lastAlias, type, version, propertyFlags, enginePriv); + return propertyDataForAlias( + component, *lastAlias, type, version, propertyFlags, enginePriv); } const int targetObjectIndex = objectForId(component, alias.targetObjectId()); @@ -947,7 +828,8 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor propertyFlags->type = QQmlPropertyData::Flags::QObjectDerivedType; } else { int coreIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).coreIndex(); - int valueTypeIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).valueTypeIndex(); + int valueTypeIndex = QQmlPropertyIndex::fromEncoded( + alias.encodedMetaPropertyIndex).valueTypeIndex(); QQmlPropertyCache::ConstPtr targetCache = propertyCaches->at(targetObjectIndex); Q_ASSERT(targetCache); diff --git a/src/qml/qml/qqmltypecompiler.cpp b/src/qml/qml/qqmltypecompiler.cpp index 203a004765..0e328c68f9 100644 --- a/src/qml/qml/qqmltypecompiler.cpp +++ b/src/qml/qml/qqmltypecompiler.cpp @@ -8,6 +8,7 @@ #include <private/qqmlvmemetaobject_p.h> #include <private/qqmlcomponent_p.h> #include <private/qqmlpropertyresolver_p.h> +#include <private/qqmlcomponentandaliasresolver_p.h> #define COMPILE_EXCEPTION(token, desc) \ { \ @@ -63,9 +64,11 @@ QQmlRefPointer<QV4::ExecutableCompilationUnit> QQmlTypeCompiler::compile() } else { // Resolve component boundaries and aliases - QQmlComponentAndAliasResolver resolver(this); - if (!resolver.resolve(result.processedRoot)) + QQmlComponentAndAliasResolver resolver(this, enginePrivate(), &m_propertyCaches); + if (QQmlError error = resolver.resolve(result.processedRoot); error.isValid()) { + recordError(error); return nullptr; + } pendingGroupPropertyBindings.resolveMissingPropertyCaches(&m_propertyCaches); pendingGroupPropertyBindings.clear(); // anything that can be processed is now processed } @@ -202,10 +205,9 @@ QVector<QmlIR::Object *> *QQmlTypeCompiler::qmlObjects() const return &document->objects; } -void QQmlTypeCompiler::setPropertyCaches(QQmlPropertyCacheVector &&caches) +QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() { - m_propertyCaches = std::move(caches); - Q_ASSERT(m_propertyCaches.count() > 0); + return &m_propertyCaches; } const QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() const @@ -213,11 +215,6 @@ const QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() const return &m_propertyCaches; } -QQmlPropertyCacheVector &&QQmlTypeCompiler::takePropertyCaches() -{ - return std::move(m_propertyCaches); -} - QQmlJS::MemoryPool *QQmlTypeCompiler::memoryPool() { return document->jsParserEngine.pool(); @@ -730,357 +727,86 @@ void QQmlScriptStringScanner::scan() } } -QQmlComponentAndAliasResolver::QQmlComponentAndAliasResolver(QQmlTypeCompiler *typeCompiler) - : QQmlCompilePass(typeCompiler) - , enginePrivate(typeCompiler->enginePrivate()) - , pool(typeCompiler->memoryPool()) - , qmlObjects(typeCompiler->qmlObjects()) - , propertyCaches(std::move(typeCompiler->takePropertyCaches())) -{ -} - -static bool isUsableComponent(const QMetaObject *metaObject) +template<> +void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::allocateNamedObjects( + QmlIR::Object *object) const { - // The metaObject is a component we're interested in if it either is a QQmlComponent itself - // or if any of its parents is a QQmlAbstractDelegateComponent. We don't want to include - // qqmldelegatecomponent_p.h because it belongs to QtQmlModels. - - if (metaObject == &QQmlComponent::staticMetaObject) - return true; - - for (; metaObject; metaObject = metaObject->superClass()) { - if (qstrcmp(metaObject->className(), "QQmlAbstractDelegateComponent") == 0) - return true; - } - - return false; -} - -void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents( - const QmlIR::Object *obj, const QQmlPropertyCache::ConstPtr &propertyCache) -{ - QQmlPropertyResolver propertyResolver(propertyCache); - - const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); - - for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { - if (binding->type() != QV4::CompiledData::Binding::Type_Object) - continue; - if (binding->hasFlag(QV4::CompiledData::Binding::IsSignalHandlerObject)) - continue; - - const QmlIR::Object *targetObject = qmlObjects->at(binding->value.objectIndex); - auto *tr = resolvedType(targetObject->inheritedTypeNameIndex); - Q_ASSERT(tr); - - const QMetaObject *firstMetaObject = nullptr; - const auto type = tr->type(); - if (type.isValid()) - firstMetaObject = type.metaObject(); - else if (const auto compilationUnit = tr->compilationUnit()) - firstMetaObject = compilationUnit->rootPropertyCache()->firstCppMetaObject(); - if (isUsableComponent(firstMetaObject)) - continue; - // if here, not a QQmlComponent, so needs wrapping - - const QQmlPropertyData *pd = nullptr; - if (binding->propertyNameIndex != quint32(0)) { - bool notInRevision = false; - pd = propertyResolver.property(stringAt(binding->propertyNameIndex), ¬InRevision); - } else { - pd = defaultProperty; - } - if (!pd || !pd->isQObject()) - continue; - - // If the version is given, use it and look up by QQmlType. - // Otherwise, make sure we look up by metaobject. - // TODO: Is this correct? - QQmlPropertyCache::ConstPtr pc = pd->typeVersion().hasMinorVersion() - ? QQmlMetaType::rawPropertyCacheForType(pd->propType(), pd->typeVersion()) - : QQmlMetaType::rawPropertyCacheForType(pd->propType()); - const QMetaObject *mo = pc ? pc->firstCppMetaObject() : nullptr; - while (mo) { - if (mo == &QQmlComponent::staticMetaObject) - break; - mo = mo->superClass(); - } - - if (!mo) - continue; - - // emulate "import QML 1.0" and then wrap the component in "QML.Component {}" - QQmlType componentType = QQmlMetaType::qmlType( - &QQmlComponent::staticMetaObject, QStringLiteral("QML"), - QTypeRevision::fromVersion(1, 0)); - Q_ASSERT(componentType.isValid()); - const QString qualifier = QStringLiteral("QML"); - - compiler->addImport(componentType.module(), qualifier, componentType.version()); - - QmlIR::Object *syntheticComponent = pool->New<QmlIR::Object>(); - syntheticComponent->init( - pool, - compiler->registerString(qualifier + QLatin1Char('.') + componentType.elementName()), - compiler->registerString(QString()), binding->valueLocation); - syntheticComponent->flags |= QV4::CompiledData::Object::IsComponent; - - if (!containsResolvedType(syntheticComponent->inheritedTypeNameIndex)) { - auto typeRef = new QV4::ResolvedTypeReference; - typeRef->setType(componentType); - typeRef->setVersion(componentType.version()); - insertResolvedType(syntheticComponent->inheritedTypeNameIndex, typeRef); - } - - qmlObjects->append(syntheticComponent); - const int componentIndex = qmlObjects->size() - 1; - // Keep property caches symmetric - QQmlPropertyCache::ConstPtr componentCache - = QQmlMetaType::propertyCache(&QQmlComponent::staticMetaObject); - propertyCaches.append(componentCache); - - QmlIR::Binding *syntheticBinding = pool->New<QmlIR::Binding>(); - *syntheticBinding = *binding; - - // The synthetic binding inside Component has no name. It's just "Component { Foo {} }". - syntheticBinding->propertyNameIndex = 0; - - syntheticBinding->setType(QV4::CompiledData::Binding::Type_Object); - QString error = syntheticComponent->appendBinding(syntheticBinding, /*isListBinding*/false); - Q_ASSERT(error.isEmpty()); - Q_UNUSED(error); - - binding->value.objectIndex = componentIndex; - - componentRoots.append(componentIndex); - } + object->namedObjectsInComponent.allocate(m_compiler->memoryPool(), m_idToObjectIndex); } -// Resolve ignores everything relating to inline components, except for implicit components. -bool QQmlComponentAndAliasResolver::resolve(int root) +template<> +bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::markAsComponent(int index) const { - // Detect real Component {} objects as well as implicitly defined components, such as - // someItemDelegate: Item {} - // In the implicit case Item is surrounded by a synthetic Component {} because the property - // on the left hand side is of QQmlComponent type. - const int objCountWithoutSynthesizedComponents = qmlObjects->size(); - const int startObjectIndex = root == 0 ? root : root+1; // root+1, as ic root is handled at the end - for (int i = startObjectIndex; i < objCountWithoutSynthesizedComponents; ++i) { - QmlIR::Object *obj = qmlObjects->at(i); - const bool isInlineComponentRoot - = obj->flags & QV4::CompiledData::Object::IsInlineComponentRoot; - const bool isPartOfInlineComponent - = obj->flags & QV4::CompiledData::Object::IsPartOfInlineComponent; - QQmlPropertyCache::ConstPtr cache = propertyCaches.at(i); - - bool isExplicitComponent = false; - if (obj->inheritedTypeNameIndex) { - auto *tref = resolvedType(obj->inheritedTypeNameIndex); - Q_ASSERT(tref); - if (tref->type().metaObject() == &QQmlComponent::staticMetaObject) - isExplicitComponent = true; - } - - if (isInlineComponentRoot && isExplicitComponent) { - qCWarning(lcQmlTypeCompiler).nospace().noquote() - << compiler->url().toString() << ":" << obj->location.line() << ":" - << obj->location.column() - << ": Using a Component as the root of an inline component is deprecated: " - "inline components are " - "automatically wrapped into Components when needed."; - } - - if (root == 0) { - // normal component root, skip over anything inline component related - if (isInlineComponentRoot || isPartOfInlineComponent) - continue; - } else if (!isPartOfInlineComponent || isInlineComponentRoot) { - // We've left the current inline component (potentially entered a new one), - // but we still need to resolve implicit components which are part of inline components. - if (cache && !isExplicitComponent) - findAndRegisterImplicitComponents(obj, cache); - break; - } - - if (obj->inheritedTypeNameIndex == 0 && !cache) - continue; - - if (!isExplicitComponent) { - if (cache) - findAndRegisterImplicitComponents(obj, cache); - continue; - } - - obj->flags |= QV4::CompiledData::Object::IsComponent; - - // check if this object is the root - if (i == 0) { - if (isExplicitComponent) - qCWarning(lcQmlTypeCompiler).nospace().noquote() - << compiler->url().toString() << ":" << obj->location.line() << ":" - << obj->location.column() - << ": Using a Component as the root of a qmldocument is deprecated: types " - "defined in qml documents are " - "automatically wrapped into Components when needed."; - } - - if (obj->functionCount() > 0) - COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new functions.")); - if (obj->propertyCount() > 0 || obj->aliasCount() > 0) - COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new properties.")); - if (obj->signalCount() > 0) - COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new signals.")); - - if (obj->bindingCount() == 0) - COMPILE_EXCEPTION(obj, tr("Cannot create empty component specification")); - - const QmlIR::Binding *rootBinding = obj->firstBinding(); - - for (const QmlIR::Binding *b = rootBinding; b; b = b->next) { - if (b->propertyNameIndex != 0) - COMPILE_EXCEPTION(rootBinding, tr("Component elements may not contain properties other than id")); - } - - if (rootBinding->next || rootBinding->type() != QV4::CompiledData::Binding::Type_Object) - COMPILE_EXCEPTION(obj, tr("Invalid component body specification")); - - // For the root object, we are going to collect ids/aliases and resolve them for as a separate - // last pass. - if (i != 0) - componentRoots.append(i); - - } - - for (int i = 0; i < componentRoots.size(); ++i) { - QmlIR::Object *component = qmlObjects->at(componentRoots.at(i)); - const QmlIR::Binding *rootBinding = component->firstBinding(); - - _idToObjectIndex.clear(); - - _objectsWithAliases.clear(); - - if (!collectIdsAndAliases(rootBinding->value.objectIndex)) - return false; - - component->namedObjectsInComponent.allocate(pool, _idToObjectIndex); - - if (!resolveAliases(componentRoots.at(i))) - return false; - } - - // Collect ids and aliases for root - _idToObjectIndex.clear(); - _objectsWithAliases.clear(); - - collectIdsAndAliases(root); - - QmlIR::Object *rootComponent = qmlObjects->at(root); - rootComponent->namedObjectsInComponent.allocate(pool, _idToObjectIndex); - - if (!resolveAliases(root)) - return false; - - // Implicit component insertion may have added objects and thus we also need - // to extend the symmetric propertyCaches. - compiler->setPropertyCaches(std::move(propertyCaches)); - compiler->setComponentRoots(componentRoots); - + m_compiler->qmlObjects()->at(index)->flags |= QV4::CompiledData::Object::IsComponent; return true; } -bool QQmlComponentAndAliasResolver::collectIdsAndAliases(int objectIndex) +template<> +void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::setObjectId(int index) const { - QmlIR::Object *obj = qmlObjects->at(objectIndex); - - if (obj->idNameIndex != 0) { - if (_idToObjectIndex.contains(obj->idNameIndex)) { - recordError(obj->locationOfIdProperty, tr("id is not unique")); - return false; - } - obj->id = _idToObjectIndex.size(); - _idToObjectIndex.insert(obj->idNameIndex, objectIndex); - } - - if (obj->aliasCount() > 0) - _objectsWithAliases.append(objectIndex); - - // Stop at Component boundary - if (obj->flags & QV4::CompiledData::Object::IsComponent && objectIndex != /*root object*/0) - return true; - - for (const QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { - switch (binding->type()) { - case QV4::CompiledData::Binding::Type_Object: - case QV4::CompiledData::Binding::Type_AttachedProperty: - case QV4::CompiledData::Binding::Type_GroupProperty: - if (!collectIdsAndAliases(binding->value.objectIndex)) - return false; - break; - default: - break; - } - } - - return true; + m_compiler->qmlObjects()->at(index)->id = m_idToObjectIndex.size(); } -bool QQmlComponentAndAliasResolver::resolveAliases(int componentIndex) +template<> +bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::wrapImplicitComponent(QmlIR::Binding *binding) { - if (_objectsWithAliases.isEmpty()) - return true; - - QQmlPropertyCacheAliasCreator<QQmlTypeCompiler> aliasCacheCreator(&propertyCaches, compiler); + QQmlJS::MemoryPool *pool = m_compiler->memoryPool(); + QVector<QmlIR::Object *> *qmlObjects = m_compiler->qmlObjects(); + + // emulate "import QML 1.0" and then wrap the component in "QML.Component {}" + QQmlType componentType = QQmlMetaType::qmlType( + &QQmlComponent::staticMetaObject, QStringLiteral("QML"), + QTypeRevision::fromVersion(1, 0)); + Q_ASSERT(componentType.isValid()); + const QString qualifier = QStringLiteral("QML"); + + m_compiler->addImport(componentType.module(), qualifier, componentType.version()); + + QmlIR::Object *syntheticComponent = pool->New<QmlIR::Object>(); + syntheticComponent->init( + pool, + m_compiler->registerString( + qualifier + QLatin1Char('.') + componentType.elementName()), + m_compiler->registerString(QString()), binding->valueLocation); + syntheticComponent->flags |= QV4::CompiledData::Object::IsComponent; + + if (!m_compiler->resolvedTypes->contains(syntheticComponent->inheritedTypeNameIndex)) { + auto typeRef = new QV4::ResolvedTypeReference; + typeRef->setType(componentType); + typeRef->setVersion(componentType.version()); + m_compiler->resolvedTypes->insert(syntheticComponent->inheritedTypeNameIndex, typeRef); + } - bool atLeastOneAliasResolved; - do { - atLeastOneAliasResolved = false; - QVector<int> pendingObjects; + qmlObjects->append(syntheticComponent); + const int componentIndex = qmlObjects->size() - 1; + // Keep property caches symmetric + QQmlPropertyCache::ConstPtr componentCache + = QQmlMetaType::propertyCache(&QQmlComponent::staticMetaObject); + m_propertyCaches->append(componentCache); - for (int objectIndex: std::as_const(_objectsWithAliases)) { + QmlIR::Binding *syntheticBinding = pool->New<QmlIR::Binding>(); + *syntheticBinding = *binding; - QQmlError error; - const auto result = resolveAliasesInObject(objectIndex, &error); + // The synthetic binding inside Component has no name. It's just "Component { Foo {} }". + syntheticBinding->propertyNameIndex = 0; - if (error.isValid()) { - recordError(error); - return false; - } + syntheticBinding->setType(QV4::CompiledData::Binding::Type_Object); + QString error = syntheticComponent->appendBinding(syntheticBinding, /*isListBinding*/false); + Q_ASSERT(error.isEmpty()); + Q_UNUSED(error); - if (result == AllAliasesResolved) { - QQmlError error = aliasCacheCreator.appendAliasesToPropertyCache(*qmlObjects->at(componentIndex), objectIndex, enginePrivate); - if (error.isValid()) { - recordError(error); - return false; - } - atLeastOneAliasResolved = true; - } else if (result == SomeAliasesResolved) { - atLeastOneAliasResolved = true; - pendingObjects.append(objectIndex); - } else { - pendingObjects.append(objectIndex); - } - } - qSwap(_objectsWithAliases, pendingObjects); - } while (!_objectsWithAliases.isEmpty() && atLeastOneAliasResolved); - - if (!atLeastOneAliasResolved && !_objectsWithAliases.isEmpty()) { - const QmlIR::Object *obj = qmlObjects->at(_objectsWithAliases.first()); - for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) { - if (!alias->hasFlag(QV4::CompiledData::Alias::Resolved)) { - recordError(alias->location, tr("Circular alias reference detected")); - return false; - } - } - } + binding->value.objectIndex = componentIndex; + m_componentRoots.append(componentIndex); return true; } -QQmlComponentAndAliasResolver::AliasResolutionResult -QQmlComponentAndAliasResolver::resolveAliasesInObject(int objectIndex, - QQmlError *error) +template<> +typename QQmlComponentAndAliasResolver<QQmlTypeCompiler>::AliasResolutionResult +QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveAliasesInObject( + int objectIndex, QQmlError *error) { - const QmlIR::Object * const obj = qmlObjects->at(objectIndex); + const QmlIR::Object * const obj = m_compiler->objectAt(objectIndex); if (!obj->aliasCount()) return AllAliasesResolved; @@ -1094,7 +820,7 @@ QQmlComponentAndAliasResolver::resolveAliasesInObject(int objectIndex, seenUnresolvedAlias = true; const int idIndex = alias->idIndex(); - const int targetObjectIndex = _idToObjectIndex.value(idIndex, -1); + const int targetObjectIndex = m_idToObjectIndex.value(idIndex, -1); if (targetObjectIndex == -1) { *error = qQmlCompileError( alias->referenceLocation, @@ -1102,7 +828,7 @@ QQmlComponentAndAliasResolver::resolveAliasesInObject(int objectIndex, break; } - const QmlIR::Object *targetObject = qmlObjects->at(targetObjectIndex); + const QmlIR::Object *targetObject = m_compiler->objectAt(targetObjectIndex); Q_ASSERT(targetObject->id >= 0); alias->setTargetObjectId(targetObject->id); alias->setIsAliasToLocalAlias(false); @@ -1124,7 +850,7 @@ QQmlComponentAndAliasResolver::resolveAliasesInObject(int objectIndex, if (property.isEmpty()) { alias->setFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject); } else { - QQmlPropertyCache::ConstPtr targetCache = propertyCaches.at(targetObjectIndex); + QQmlPropertyCache::ConstPtr targetCache = m_propertyCaches->at(targetObjectIndex); if (!targetCache) { *error = qQmlCompileError( alias->referenceLocation, @@ -1180,8 +906,8 @@ QQmlComponentAndAliasResolver::resolveAliasesInObject(int objectIndex, isDeepAlias = false; for (auto it = targetObject->bindingsBegin(); it != targetObject->bindingsEnd(); ++it) { auto binding = *it; - if (compiler->stringAt(binding.propertyNameIndex) == property) { - resolver = QQmlPropertyResolver(propertyCaches.at(binding.value.objectIndex)); + if (m_compiler->stringAt(binding.propertyNameIndex) == property) { + resolver = QQmlPropertyResolver(m_propertyCaches->at(binding.value.objectIndex)); const QQmlPropertyData *actualProperty = resolver.property(subProperty.toString()); if (actualProperty) { propIdx = QQmlPropertyIndex(propIdx.coreIndex(), actualProperty->coreIndex()); diff --git a/src/qml/qml/qqmltypecompiler_p.h b/src/qml/qml/qqmltypecompiler_p.h index 02edbb0c08..d07f42fb47 100644 --- a/src/qml/qml/qqmltypecompiler_p.h +++ b/src/qml/qml/qqmltypecompiler_p.h @@ -52,9 +52,14 @@ public: // --- interface used by QQmlPropertyCacheCreator typedef QmlIR::Object CompiledObject; + typedef QmlIR::Binding CompiledBinding; using ListPropertyAssignBehavior = QmlIR::Pragma::ListPropertyAssignBehaviorValue; + // Deliberate choice of map over hash here to ensure stable generated output. + using IdToObjectMap = QMap<int, int>; + const QmlIR::Object *objectAt(int index) const { return document->objects.at(index); } + QmlIR::Object *objectAt(int index) { return document->objects.at(index); } int objectCount() const { return document->objects.size(); } QString stringAt(int idx) const; QmlIR::PoolList<QmlIR::Function>::Iterator objectFunctionsBegin(const QmlIR::Object *object) const { return object->functionsBegin(); } @@ -86,11 +91,8 @@ public: QQmlEnginePrivate *enginePrivate() const { return engine; } const QQmlImports *imports() const; QVector<QmlIR::Object *> *qmlObjects() const; - void setPropertyCaches(QQmlPropertyCacheVector &&caches); + QQmlPropertyCacheVector *propertyCaches(); const QQmlPropertyCacheVector *propertyCaches() const; - QQmlPropertyCacheVector &&takePropertyCaches(); - void setComponentRoots(const QVector<quint32> &roots) { m_componentRoots = roots; } - const QVector<quint32> &componentRoots() const { return m_componentRoots; } QQmlJS::MemoryPool *memoryPool(); QStringView newStringRef(const QString &string); const QV4::Compiler::StringTableGenerator *stringPool() const; @@ -117,7 +119,6 @@ private: QHash<int, QQmlCustomParser*> customParsers; // index in first hash is component index, vector inside contains object indices of objects with id property - QVector<quint32> m_componentRoots; QQmlPropertyCacheVector m_propertyCaches; QQmlRefPointer<QQmlTypeNameCache> typeNameCache; @@ -132,16 +133,9 @@ struct QQmlCompilePass protected: void recordError(const QV4::CompiledData::Location &location, const QString &description) const { compiler->recordError(location, description); } - void recordError(const QQmlError &error) - { compiler->recordError(error); } QV4::ResolvedTypeReference *resolvedType(int id) const { return compiler->resolvedType(id); } - bool containsResolvedType(int id) const - { return compiler->resolvedTypes->contains(id); } - QV4::ResolvedTypeReferenceMap::iterator insertResolvedType( - int id, QV4::ResolvedTypeReference *value) - { return compiler->resolvedTypes->insert(id, value); } QQmlTypeCompiler *compiler; }; @@ -235,44 +229,6 @@ private: const QQmlPropertyCacheVector * const propertyCaches; }; -class QQmlComponentAndAliasResolver : public QQmlCompilePass -{ - Q_DECLARE_TR_FUNCTIONS(QQmlAnonymousComponentResolver) -public: - QQmlComponentAndAliasResolver(QQmlTypeCompiler *typeCompiler); - - bool resolve(int root = 0); - -protected: - void findAndRegisterImplicitComponents( - const QmlIR::Object *obj, const QQmlPropertyCache::ConstPtr &propertyCache); - bool collectIdsAndAliases(int objectIndex); - bool resolveAliases(int componentIndex); - void propertyDataForAlias(QmlIR::Alias *alias, int *type, quint32 *propertyFlags); - - enum AliasResolutionResult { - NoAliasResolved, - SomeAliasesResolved, - AllAliasesResolved - }; - - AliasResolutionResult resolveAliasesInObject(int objectIndex, QQmlError *error); - - QQmlEnginePrivate *enginePrivate; - QQmlJS::MemoryPool *pool; - - QVector<QmlIR::Object*> *qmlObjects; - - // indices of the objects that are actually Component {} - QVector<quint32> componentRoots; - - // Deliberate choice of map over hash here to ensure stable generated output. - QMap<int, int> _idToObjectIndex; - QVector<int> _objectsWithAliases; - - QQmlPropertyCacheVector propertyCaches; -}; - class QQmlDeferredAndCustomParserBindingScanner : public QQmlCompilePass { Q_DECLARE_TR_FUNCTIONS(QQmlDeferredAndCustomParserBindingScanner) diff --git a/src/qml/qml/qqmltypedata.cpp b/src/qml/qml/qqmltypedata.cpp index ddfbccbddf..0e94de34cd 100644 --- a/src/qml/qml/qqmltypedata.cpp +++ b/src/qml/qml/qqmltypedata.cpp @@ -1,15 +1,16 @@ // 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 <private/qqmltypedata_p.h> +#include <private/qqmlcomponentandaliasresolver_p.h> #include <private/qqmlengine_p.h> -#include <private/qqmlpropertycachecreator_p.h> -#include <private/qqmlpropertyvalidator_p.h> #include <private/qqmlirbuilder_p.h> #include <private/qqmlirloader_p.h> +#include <private/qqmlpropertycachecreator_p.h> +#include <private/qqmlpropertyvalidator_p.h> #include <private/qqmlscriptblob_p.h> #include <private/qqmlscriptdata_p.h> #include <private/qqmltypecompiler_p.h> +#include <private/qqmltypedata_p.h> #include <private/qqmltypeloaderqmldircontent_p.h> #include <QtCore/qloggingcategory.h> @@ -173,7 +174,53 @@ bool QQmlTypeData::tryLoadFromDiskCache() return true; } -void QQmlTypeData::createTypeAndPropertyCaches( +template<> +void QQmlComponentAndAliasResolver<QV4::ExecutableCompilationUnit>::allocateNamedObjects( + const QV4::CompiledData::Object *object) const +{ + Q_UNUSED(object); +} + +template<> +bool QQmlComponentAndAliasResolver<QV4::ExecutableCompilationUnit>::markAsComponent(int index) const +{ + return m_compiler->objectAt(index)->hasFlag(QV4::CompiledData::Object::IsComponent); +} + +template<> +void QQmlComponentAndAliasResolver<QV4::ExecutableCompilationUnit>::setObjectId(int index) const +{ + Q_UNUSED(index) + // we cannot sanity-check the index here because bindings are sorted in a different order + // in the CU vs the IR. +} + +template<> +typename QQmlComponentAndAliasResolver<QV4::ExecutableCompilationUnit>::AliasResolutionResult +QQmlComponentAndAliasResolver<QV4::ExecutableCompilationUnit>::resolveAliasesInObject( + int objectIndex, QQmlError *error) +{ + const CompiledObject *obj = m_compiler->objectAt(objectIndex); + for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) { + if (!alias->hasFlag(QV4::CompiledData::Alias::Resolved)) { + *error = qQmlCompileError( alias->referenceLocation, tr("Unresolved alias found")); + return NoAliasResolved; + } + } + + return AllAliasesResolved; +} + +template<> +bool QQmlComponentAndAliasResolver<QV4::ExecutableCompilationUnit>::wrapImplicitComponent( + const QV4::CompiledData::Binding *binding) +{ + // This should have been done when creating the CU. + Q_UNUSED(binding); + return false; +} + +QQmlError QQmlTypeData::createTypeAndPropertyCaches( const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, const QV4::ResolvedTypeReferenceMap &resolvedTypeCache) { @@ -190,18 +237,32 @@ void QQmlTypeData::createTypeAndPropertyCaches( QQmlPropertyCacheCreator<QV4::ExecutableCompilationUnit> propertyCacheCreator( &m_compiledData->propertyCaches, &pendingGroupPropertyBindings, engine, m_compiledData.data(), m_importCache.data(), typeClassName()); - QQmlError error = propertyCacheCreator.buildMetaObjects(); - if (error.isValid()) { - setError(error); - return; - } - QQmlPropertyCacheAliasCreator<QV4::ExecutableCompilationUnit> aliasCreator( - &m_compiledData->propertyCaches, m_compiledData.data()); - aliasCreator.appendAliasPropertiesToMetaObjects(engine); + QQmlError error = propertyCacheCreator.verifyNoICCycle(); + if (error.isValid()) + return error; + + QQmlPropertyCacheCreatorBase::IncrementalResult result; + do { + result = propertyCacheCreator.buildMetaObjectsIncrementally(); + if (result.error.isValid()) { + return result.error; + } else { + QQmlComponentAndAliasResolver resolver( + m_compiledData.data(), engine, &m_compiledData->propertyCaches); + if (const QQmlError error = resolver.resolve(result.processedRoot); + error.isValid()) { + return error; + } + pendingGroupPropertyBindings.resolveMissingPropertyCaches(&m_compiledData->propertyCaches); + pendingGroupPropertyBindings.clear(); // anything that can be processed is now processed + } + + } while (result.canResume); } pendingGroupPropertyBindings.resolveMissingPropertyCaches(&m_compiledData->propertyCaches); + return QQmlError(); } static bool addTypeReferenceChecksumsToHash( @@ -370,23 +431,34 @@ void QQmlTypeData::done() // verify if any dependencies changed if we're using a cache if (m_document.isNull()) { - createTypeAndPropertyCaches(typeNameCache, resolvedTypeCache); - if (isError()) { - return; - } - - if (m_compiledData->verifyChecksum(dependencyHasher)) { + const QQmlError error = createTypeAndPropertyCaches(typeNameCache, resolvedTypeCache); + if (!error.isValid() && m_compiledData->verifyChecksum(dependencyHasher)) { setCompileUnit(m_compiledData); } else { - qCDebug(DBG_DISK_CACHE) << "Checksum mismatch for cached version of" << m_compiledData->fileName(); + + if (error.isValid()) { + qCDebug(DBG_DISK_CACHE) + << "Failed to create property caches for" + << m_compiledData->fileName() + << "because" << error.description(); + } else { + qCDebug(DBG_DISK_CACHE) + << "Checksum mismatch for cached version of" + << m_compiledData->fileName(); + } + if (!loadFromSource()) return; // We want to keep our resolve types ... m_compiledData->resolvedTypes.clear(); - // ... but we don't want their property caches. - for (QV4::ResolvedTypeReference *ref: std::as_const(resolvedTypeCache)) + // ... but we don't want the property caches we've created for the broken CU. + for (QV4::ResolvedTypeReference *ref: std::as_const(resolvedTypeCache)) { + if (ref->compilationUnit() != m_compiledData) + continue; ref->setTypePropertyCache(QQmlPropertyCache::ConstPtr()); + ref->setCompilationUnit(QQmlRefPointer<QV4::ExecutableCompilationUnit>()); + } m_compiledData.reset(); } diff --git a/src/qml/qml/qqmltypedata_p.h b/src/qml/qml/qqmltypedata_p.h index eb39e99348..9e4f85974b 100644 --- a/src/qml/qml/qqmltypedata_p.h +++ b/src/qml/qml/qqmltypedata_p.h @@ -95,8 +95,8 @@ private: void compile(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, QV4::ResolvedTypeReferenceMap *resolvedTypeCache, const QV4::CompiledData::DependentTypesHasher &dependencyHasher); - void createTypeAndPropertyCaches(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, - const QV4::ResolvedTypeReferenceMap &resolvedTypeCache); + QQmlError createTypeAndPropertyCaches(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, + const QV4::ResolvedTypeReferenceMap &resolvedTypeCache); bool resolveType(const QString &typeName, QTypeRevision &version, TypeReference &ref, int lineNumber = -1, int columnNumber = -1, bool reportErrors = true, diff --git a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp index cec24a5fc5..9fc27c899b 100644 --- a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp +++ b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp @@ -41,6 +41,8 @@ private slots: void cacheModuleScripts(); void reuseStaticMappings(); void invalidateSaveLoadCache(); + + void inlineComponentDoesNotCauseConstantInvalidation_data(); void inlineComponentDoesNotCauseConstantInvalidation(); private: @@ -1118,29 +1120,123 @@ void tst_qmldiskcache::invalidateSaveLoadCache() QVERIFY(unit->unitData() != oldUnit->unitData()); } +void tst_qmldiskcache::inlineComponentDoesNotCauseConstantInvalidation_data() +{ + QTest::addColumn<QByteArray>("code"); + + QTest::addRow("simple") << QByteArray(R"( + import QtQml + QtObject { + component Test: QtObject { + property int i: 28 + } + property Test test: Test { + objectName: "foobar" + } + property int k: test.i + } + )"); + + QTest::addRow("with function") << QByteArray(R"( + import QtQml + QtObject { + component Test : QtObject { + id: self + property int i: 2 + property alias j: self.i + } + property Test test: Test { + function updateValue() {} + objectName: 'foobar' + j: 28 + } + property int k: test.j + } + )"); + + QTest::addRow("in nested") << QByteArray(R"( + import QtQuick + Item { + Item { + component Line: Item { + property alias endY: pathLine.y + Item { + Item { + id: pathLine + } + } + } + } + Line { + id: primaryLine + endY: 28 + } + property int k: primaryLine.endY + } + )"); + + QTest::addRow("with revision") << QByteArray(R"( + import QtQuick + ListView { + Item { + id: scrollBar + } + delegate: Image { + mipmap: true + } + Item { + id: refreshNodesIndicator + } + property int k: delegate.createObject().mipmap ? 28 : 4 + } + )"); +} + void tst_qmldiskcache::inlineComponentDoesNotCauseConstantInvalidation() { + QFETCH(QByteArray, code); + QQmlEngine engine; TestCompiler testCompiler(&engine); QVERIFY(testCompiler.tempDir.isValid()); + auto check = [&](){ + QQmlComponent c(&engine, QUrl::fromLocalFile(testCompiler.testFilePath)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("k"), QVariant::fromValue<int>(28)); + }; + testCompiler.reset(); - QVERIFY(testCompiler.writeTestFile("import QtQml\nQtObject { component Test : QtObject { property int i: 2 }\n property Test test: Test { objectName: 'foobar' } }\n")); + QVERIFY(testCompiler.writeTestFile(code)); + QVERIFY(testCompiler.loadTestFile()); const quintptr data1 = testCompiler.unitData(); QVERIFY(data1 != 0); QCOMPARE(testCompiler.unitData(), data1); + check(); engine.clearComponentCache(); // inline component does not invalidate cache QVERIFY(testCompiler.loadTestFile()); QCOMPARE(testCompiler.unitData(), data1); + check(); testCompiler.reset(); - QVERIFY(testCompiler.writeTestFile("import QtQml\nQtObject { component Test : QtObject { property double d: 2 }\n property Test test: Test { objectName: 'foobar' } }\n")); + QVERIFY(testCompiler.writeTestFile(R"( + import QtQml + QtObject { + component Test : QtObject { + property double d: 2 + } + property Test test: Test { + objectName: 'foobar' + } + })")); QVERIFY(testCompiler.loadTestFile()); const quintptr data2 = testCompiler.unitData(); QVERIFY(data2); |