aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2023-03-07 14:39:52 +0100
committerUlf Hermann <ulf.hermann@qt.io>2023-03-15 01:08:43 +0100
commit2d7fe23b41aa3fd719b7bc8aa585ab799e4a0c39 (patch)
tree4d5720b2713ffbce0fc00769c02ed676f078f879
parent4c6d0b2bf09329dd8b036761f8f2924f54af3a07 (diff)
QML: Insert aliases in inline components when loading from disk cache
So far we did not completely restore aliases in inline components. This was masked by the fact that until recently we failed to load inline components from the disk cache and always loaded them from source instead. To fix this, refactor QQmlComponentAndAliasResolver to work for both, QmlIR and QV4::CompiledData. With QmlIR, it populates the relevant data structures. With QV4::CompiledData, it sanity-checks them. The sanity-checks do incur some overhead, but given recent events, we should err on the side of caution here. Since QQmlComponentAndAliasResolver has received all the fixes we've applied to make inline components work, this should lead to inline components loaded from cache files to work the same way as those compiled from source. In turn, we can drop some methods of QQmlPropertyCacheAliasCreator. Amends commit 131db085a752469e8f19974c2edb3a138d900249 Pick-to: 6.5 Fixes: QTBUG-111766 Fixes: QTBUG-111857 Change-Id: I9cc75e700a5fe5810a866e9aa930b9811368b1b4 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qml/CMakeLists.txt1
-rw-r--r--src/qml/common/qv4compileddata_p.h1
-rw-r--r--src/qml/compiler/qqmlirbuilder_p.h3
-rw-r--r--src/qml/jsruntime/qv4executablecompilationunit_p.h17
-rw-r--r--src/qml/qml/qqmlcomponentandaliasresolver_p.h417
-rw-r--r--src/qml/qml/qqmlpropertycache_p.h2
-rw-r--r--src/qml/qml/qqmlpropertycachecreator_p.h146
-rw-r--r--src/qml/qml/qqmltypecompiler.cpp416
-rw-r--r--src/qml/qml/qqmltypecompiler_p.h56
-rw-r--r--src/qml/qml/qqmltypedata.cpp114
-rw-r--r--src/qml/qml/qqmltypedata_p.h4
-rw-r--r--tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp100
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), &notInRevision);
+ } 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), &notInRevision);
- } 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);