/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QQMLPROPERTYCACHECREATOR_P_H #define QQMLPROPERTYCACHECREATOR_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 #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE inline QQmlError qQmlCompileError(const QV4::CompiledData::Location &location, const QString &description) { QQmlError error; error.setLine(qmlConvertSourceCoordinate(location.line)); error.setColumn(qmlConvertSourceCoordinate(location.column)); error.setDescription(description); return error; } struct QQmlBindingInstantiationContext { QQmlBindingInstantiationContext() {} QQmlBindingInstantiationContext(int referencingObjectIndex, const QV4::CompiledData::Binding *instantiatingBinding, const QString &instantiatingPropertyName, QQmlPropertyCache *referencingObjectPropertyCache); bool resolveInstantiatingProperty(); QQmlRefPointer instantiatingPropertyCache(QQmlEnginePrivate *enginePrivate) const; int referencingObjectIndex = -1; const QV4::CompiledData::Binding *instantiatingBinding = nullptr; QString instantiatingPropertyName; QQmlRefPointer referencingObjectPropertyCache; QQmlPropertyData *instantiatingProperty = nullptr; }; struct QQmlPendingGroupPropertyBindings : public QVector { void resolveMissingPropertyCaches(QQmlEnginePrivate *enginePrivate, QQmlPropertyCacheVector *propertyCaches) const; }; struct QQmlPropertyCacheCreatorBase { Q_DECLARE_TR_FUNCTIONS(QQmlPropertyCacheCreatorBase) public: static QAtomicInt classIndexCounter; static int metaTypeForPropertyType(QV4::CompiledData::BuiltinType type); static QByteArray createClassNameTypeByUrl(const QUrl &url); }; template class QQmlPropertyCacheCreator : public QQmlPropertyCacheCreatorBase { public: typedef typename ObjectContainer::CompiledObject CompiledObject; QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings, QQmlEnginePrivate *enginePrivate, const ObjectContainer *objectContainer, const QQmlImports *imports, const QByteArray &typeClassName); QQmlError buildMetaObjects(); enum class VMEMetaObjectIsRequired { Maybe, Always }; protected: QQmlError buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context, VMEMetaObjectIsRequired isVMERequired); QQmlRefPointer propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlError *error) const; QQmlError createMetaObject(int objectIndex, const CompiledObject *obj, const QQmlRefPointer &baseTypeCache); int metaTypeForParameter(const QV4::CompiledData::ParameterType ¶m, QString *customTypeName = nullptr); QString stringAt(int index) const { return objectContainer->stringAt(index); } QQmlEnginePrivate * const enginePrivate; const ObjectContainer * const objectContainer; const QQmlImports * const imports; QQmlPropertyCacheVector *propertyCaches; QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings; QByteArray typeClassName; // not const as we temporarily chang it for inline components unsigned int currentRoot; // set to objectID of inline component root when handling inline components }; template inline QQmlPropertyCacheCreator::QQmlPropertyCacheCreator(QQmlPropertyCacheVector *propertyCaches, QQmlPendingGroupPropertyBindings *pendingGroupPropertyBindings, QQmlEnginePrivate *enginePrivate, const ObjectContainer *objectContainer, const QQmlImports *imports, const QByteArray &typeClassName) : enginePrivate(enginePrivate) , objectContainer(objectContainer) , imports(imports) , propertyCaches(propertyCaches) , pendingGroupPropertyBindings(pendingGroupPropertyBindings) , typeClassName(typeClassName) , currentRoot(-1) { propertyCaches->resize(objectContainer->objectCount()); } template inline QQmlError QQmlPropertyCacheCreator::buildMetaObjects() { using namespace icutils; QQmlBindingInstantiationContext context; // get a list of all inline components using InlineComponent = typename std::remove_reference().inlineComponentsBegin()))>::type; std::vector allICs {}; for (int i=0; i != objectContainer->objectCount(); ++i) { const CompiledObject *obj = objectContainer->objectAt(i); for (auto it = obj->inlineComponentsBegin(); it != obj->inlineComponentsEnd(); ++it) { allICs.push_back(*it); } } // create a graph on inline components referencing inline components std::vector nodes; nodes.resize(allICs.size()); std::iota(nodes.begin(), nodes.end(), 0); AdjacencyList adjacencyList; adjacencyList.resize(nodes.size()); fillAdjacencyListForInlineComponents(objectContainer, adjacencyList, nodes, allICs); bool hasCycle = false; auto nodesSorted = topoSort(nodes, adjacencyList, hasCycle); if (hasCycle) { QQmlError diag; diag.setDescription(QLatin1String("Inline components form a cycle!")); return diag; } // create meta objects for inline components before compiling actual root component for (auto nodeIt = nodesSorted.rbegin(); nodeIt != nodesSorted.rend(); ++nodeIt) { const auto &ic = allICs[nodeIt->index]; QV4::ResolvedTypeReference *typeRef = objectContainer->resolvedType(ic.nameIndex); Q_ASSERT(propertyCaches->at(ic.objectIndex) == nullptr); Q_ASSERT(typeRef->typePropertyCache().isNull()); // not set yet QByteArray icTypeName { objectContainer->stringAt(ic.nameIndex).toUtf8() }; QScopedValueRollback nameChange {typeClassName, icTypeName}; QScopedValueRollback rootChange {currentRoot, ic.objectIndex}; QQmlError diag = buildMetaObjectRecursively(ic.objectIndex, context, VMEMetaObjectIsRequired::Always); if (diag.isValid()) { return diag; } typeRef->setTypePropertyCache(propertyCaches->at(ic.objectIndex)); Q_ASSERT(!typeRef->typePropertyCache().isNull()); } return buildMetaObjectRecursively(/*root object*/0, context, VMEMetaObjectIsRequired::Maybe); } template inline QQmlError QQmlPropertyCacheCreator::buildMetaObjectRecursively(int objectIndex, const QQmlBindingInstantiationContext &context, VMEMetaObjectIsRequired isVMERequired) { auto isAddressable = [](const QUrl &url) { const QString fileName = url.fileName(); return !fileName.isEmpty() && fileName.front().isUpper(); }; const CompiledObject *obj = objectContainer->objectAt(objectIndex); bool needVMEMetaObject = isVMERequired == VMEMetaObjectIsRequired::Always || obj->propertyCount() != 0 || obj->aliasCount() != 0 || obj->signalCount() != 0 || obj->functionCount() != 0 || obj->enumCount() != 0 || (((obj->flags & QV4::CompiledData::Object::IsComponent) || (objectIndex == 0 && isAddressable(objectContainer->url()))) && !objectContainer->resolvedType(obj->inheritedTypeNameIndex)->isFullyDynamicType()); if (!needVMEMetaObject) { auto binding = obj->bindingsBegin(); auto end = obj->bindingsEnd(); for ( ; binding != end; ++binding) { if (binding->type == QV4::CompiledData::Binding::Type_Object && (binding->flags & QV4::CompiledData::Binding::IsOnAssignment)) { // If the on assignment is inside a group property, we need to distinguish between QObject based // group properties and value type group properties. For the former the base type is derived from // the property that references us, for the latter we only need a meta-object on the referencing object // because interceptors can't go to the shared value type instances. if (context.instantiatingProperty && QQmlValueTypeFactory::isValueType(context.instantiatingProperty->propType())) { if (!propertyCaches->needsVMEMetaObject(context.referencingObjectIndex)) { const CompiledObject *obj = objectContainer->objectAt(context.referencingObjectIndex); auto *typeRef = objectContainer->resolvedType(obj->inheritedTypeNameIndex); Q_ASSERT(typeRef); QQmlRefPointer baseTypeCache = typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); QQmlError error = createMetaObject(context.referencingObjectIndex, obj, baseTypeCache); if (error.isValid()) return error; } } else { // On assignments are implemented using value interceptors, which require a VME meta object. needVMEMetaObject = true; } break; } } } QQmlRefPointer baseTypeCache; { QQmlError error; baseTypeCache = propertyCacheForObject(obj, context, &error); if (error.isValid()) return error; } if (baseTypeCache) { if (needVMEMetaObject) { QQmlError error = createMetaObject(objectIndex, obj, baseTypeCache); if (error.isValid()) return error; } else { propertyCaches->set(objectIndex, baseTypeCache); } } if (QQmlPropertyCache *thisCache = propertyCaches->at(objectIndex)) { auto binding = obj->bindingsBegin(); auto end = obj->bindingsEnd(); for ( ; binding != end; ++binding) if (binding->type >= QV4::CompiledData::Binding::Type_Object) { QQmlBindingInstantiationContext context(objectIndex, &(*binding), stringAt(binding->propertyNameIndex), thisCache); // Binding to group property where we failed to look up the type of the // property? Possibly a group property that is an alias that's not resolved yet. // Let's attempt to resolve it after we're done with the aliases and fill in the // propertyCaches entry then. if (!context.resolveInstantiatingProperty()) pendingGroupPropertyBindings->append(context); QQmlError error = buildMetaObjectRecursively(binding->value.objectIndex, context, VMEMetaObjectIsRequired::Maybe); if (error.isValid()) return error; } } QQmlError noError; return noError; } template inline QQmlRefPointer QQmlPropertyCacheCreator::propertyCacheForObject(const CompiledObject *obj, const QQmlBindingInstantiationContext &context, QQmlError *error) const { if (context.instantiatingProperty) { return context.instantiatingPropertyCache(enginePrivate); } else if (obj->inheritedTypeNameIndex != 0) { auto *typeRef = objectContainer->resolvedType(obj->inheritedTypeNameIndex); Q_ASSERT(typeRef); if (typeRef->isFullyDynamicType()) { if (obj->propertyCount() > 0 || obj->aliasCount() > 0) { *error = qQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully dynamic types cannot declare new properties.")); return nullptr; } if (obj->signalCount() > 0) { *error = qQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully dynamic types cannot declare new signals.")); return nullptr; } if (obj->functionCount() > 0) { *error = qQmlCompileError(obj->location, QQmlPropertyCacheCreatorBase::tr("Fully Dynamic types cannot declare new functions.")); return nullptr; } } return typeRef->createPropertyCache(QQmlEnginePrivate::get(enginePrivate)); } else if (context.instantiatingBinding && context.instantiatingBinding->isAttachedProperty()) { auto *typeRef = objectContainer->resolvedType( context.instantiatingBinding->propertyNameIndex); Q_ASSERT(typeRef); QQmlType qmltype = typeRef->type(); if (!qmltype.isValid()) { imports->resolveType(stringAt(context.instantiatingBinding->propertyNameIndex), &qmltype, nullptr, nullptr, nullptr); } const QMetaObject *attachedMo = qmltype.attachedPropertiesType(enginePrivate); if (!attachedMo) { *error = qQmlCompileError(context.instantiatingBinding->location, QQmlPropertyCacheCreatorBase::tr("Non-existent attached object")); return nullptr; } return enginePrivate->cache(attachedMo); } return nullptr; } template inline QQmlError QQmlPropertyCacheCreator::createMetaObject(int objectIndex, const CompiledObject *obj, const QQmlRefPointer &baseTypeCache) { QQmlRefPointer cache; cache.adopt(baseTypeCache->copyAndReserve(obj->propertyCount() + obj->aliasCount(), obj->functionCount() + obj->propertyCount() + obj->aliasCount() + obj->signalCount(), obj->signalCount() + obj->propertyCount() + obj->aliasCount(), obj->enumCount())); propertyCaches->set(objectIndex, cache); propertyCaches->setNeedsVMEMetaObject(objectIndex); QByteArray newClassName; if (objectIndex == /*root object*/0 || int(currentRoot) == objectIndex) { newClassName = typeClassName; } if (newClassName.isEmpty()) { newClassName = QQmlMetaObject(baseTypeCache.data()).className(); newClassName.append("_QML_"); newClassName.append(QByteArray::number(classIndexCounter.fetchAndAddRelaxed(1))); } cache->_dynamicClassName = newClassName; QQmlPropertyResolver resolver(baseTypeCache); auto p = obj->propertiesBegin(); auto pend = obj->propertiesEnd(); for ( ; p != pend; ++p) { bool notInRevision = false; QQmlPropertyData *d = resolver.property(stringAt(p->nameIndex), ¬InRevision); if (d && d->isFinal()) return qQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr("Cannot override FINAL property")); } auto a = obj->aliasesBegin(); auto aend = obj->aliasesEnd(); for ( ; a != aend; ++a) { bool notInRevision = false; QQmlPropertyData *d = resolver.property(stringAt(a->nameIndex), ¬InRevision); if (d && d->isFinal()) return qQmlCompileError(a->location, QQmlPropertyCacheCreatorBase::tr("Cannot override FINAL property")); } int effectivePropertyIndex = cache->propertyIndexCacheStart; int effectiveMethodIndex = cache->methodIndexCacheStart; // For property change signal override detection. // We prepopulate a set of signal names which already exist in the object, // and throw an error if there is a signal/method defined as an override. QSet seenSignals; seenSignals << QStringLiteral("destroyed") << QStringLiteral("parentChanged") << QStringLiteral("objectNameChanged"); QQmlPropertyCache *parentCache = cache.data(); while ((parentCache = parentCache->parent())) { if (int pSigCount = parentCache->signalCount()) { int pSigOffset = parentCache->signalOffset(); for (int i = pSigOffset; i < pSigCount; ++i) { QQmlPropertyData *currPSig = parentCache->signal(i); // XXX TODO: find a better way to get signal name from the property data :-/ for (QQmlPropertyCache::StringCache::ConstIterator iter = parentCache->stringCache.begin(); iter != parentCache->stringCache.end(); ++iter) { if (currPSig == (*iter).second) { seenSignals.insert(iter.key()); break; } } } } } // Set up notify signals for properties - first normal, then alias p = obj->propertiesBegin(); pend = obj->propertiesEnd(); for ( ; p != pend; ++p) { auto flags = QQmlPropertyData::defaultSignalFlags(); QString changedSigName = stringAt(p->nameIndex) + QLatin1String("Changed"); seenSignals.insert(changedSigName); cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); } a = obj->aliasesBegin(); aend = obj->aliasesEnd(); for ( ; a != aend; ++a) { auto flags = QQmlPropertyData::defaultSignalFlags(); QString changedSigName = stringAt(a->nameIndex) + QLatin1String("Changed"); seenSignals.insert(changedSigName); cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); } auto e = obj->enumsBegin(); auto eend = obj->enumsEnd(); for ( ; e != eend; ++e) { const int enumValueCount = e->enumValueCount(); QVector values; values.reserve(enumValueCount); auto enumValue = e->enumValuesBegin(); auto end = e->enumValuesEnd(); for ( ; enumValue != end; ++enumValue) values.append(QQmlEnumValue(stringAt(enumValue->nameIndex), enumValue->value)); cache->appendEnum(stringAt(e->nameIndex), values); } // Dynamic signals auto s = obj->signalsBegin(); auto send = obj->signalsEnd(); for ( ; s != send; ++s) { const int paramCount = s->parameterCount(); QList names; names.reserve(paramCount); QVarLengthArray paramTypes(paramCount?(paramCount + 1):0); if (paramCount) { paramTypes[0] = paramCount; int i = 0; auto param = s->parametersBegin(); auto end = s->parametersEnd(); for ( ; param != end; ++param, ++i) { names.append(stringAt(param->nameIndex).toUtf8()); QString customTypeName; auto type = metaTypeForParameter(param->type, &customTypeName); if (type == QMetaType::UnknownType) return qQmlCompileError(s->location, QQmlPropertyCacheCreatorBase::tr("Invalid signal parameter type: %1").arg(customTypeName)); paramTypes[i + 1] = type; } } auto flags = QQmlPropertyData::defaultSignalFlags(); if (paramCount) flags.setHasArguments(true); QString signalName = stringAt(s->nameIndex); if (seenSignals.contains(signalName)) return qQmlCompileError(s->location, QQmlPropertyCacheCreatorBase::tr("Duplicate signal name: invalid override of property change signal or superclass signal")); seenSignals.insert(signalName); cache->appendSignal(signalName, flags, effectiveMethodIndex++, paramCount?paramTypes.constData():nullptr, names); } // Dynamic slots auto function = objectContainer->objectFunctionsBegin(obj); auto fend = objectContainer->objectFunctionsEnd(obj); for ( ; function != fend; ++function) { auto flags = QQmlPropertyData::defaultSlotFlags(); const QString slotName = stringAt(function->nameIndex); if (seenSignals.contains(slotName)) return qQmlCompileError(function->location, QQmlPropertyCacheCreatorBase::tr("Duplicate method name: invalid override of property change signal or superclass signal")); // Note: we don't append slotName to the seenSignals list, since we don't // protect against overriding change signals or methods with properties. QList parameterNames; QVector parameterTypes; auto formal = function->formalsBegin(); auto end = function->formalsEnd(); for ( ; formal != end; ++formal) { flags.setHasArguments(true); parameterNames << stringAt(formal->nameIndex).toUtf8(); int type = metaTypeForParameter(formal->type); if (type == QMetaType::UnknownType) type = QMetaType::QVariant; parameterTypes << type; } int returnType = metaTypeForParameter(function->returnType); if (returnType == QMetaType::UnknownType) returnType = QMetaType::QVariant; cache->appendMethod(slotName, flags, effectiveMethodIndex++, returnType, parameterNames, parameterTypes); } // Dynamic properties int effectiveSignalIndex = cache->signalHandlerIndexCacheStart; int propertyIdx = 0; p = obj->propertiesBegin(); pend = obj->propertiesEnd(); for ( ; p != pend; ++p, ++propertyIdx) { int propertyType = 0; QTypeRevision propertyTypeVersion = QTypeRevision::zero(); QQmlPropertyData::Flags propertyFlags; const QV4::CompiledData::BuiltinType type = p->builtinType(); if (type == QV4::CompiledData::BuiltinType::Var) propertyFlags.type = QQmlPropertyData::Flags::VarPropertyType; if (type != QV4::CompiledData::BuiltinType::InvalidBuiltin) { propertyType = metaTypeForPropertyType(type); } else { Q_ASSERT(!p->isBuiltinType); QQmlType qmltype; bool selfReference = false; if (!imports->resolveType(stringAt(p->builtinTypeOrTypeNameIndex), &qmltype, nullptr, nullptr, nullptr, QQmlType::AnyRegistrationType, &selfReference)) { return qQmlCompileError(p->location, QQmlPropertyCacheCreatorBase::tr("Invalid property type")); } // inline components are not necessarily valid yet Q_ASSERT(qmltype.isValid() || qmltype.isInlineComponentType()); if (qmltype.isComposite() || qmltype.isInlineComponentType()) { CompositeMetaTypeIds typeIds; if (qmltype.isInlineComponentType()) { auto objectId = qmltype.inlineComponentId(); auto containingType = qmltype.containingType(); if (containingType.isValid()) { auto icType = containingType.lookupInlineComponentById(objectId); typeIds = {icType.typeId(), icType.qListTypeId()}; } else { typeIds = {}; } if (!typeIds.isValid()) // type has not been registered yet, we must be in containing type typeIds = objectContainer->typeIdsForComponent(objectId); Q_ASSERT(typeIds.isValid()); } else if (selfReference) { typeIds = objectContainer->typeIdsForComponent(); } else { QQmlRefPointer tdata = enginePrivate->typeLoader.getType(qmltype.sourceUrl()); Q_ASSERT(tdata); Q_ASSERT(tdata->isComplete()); auto compilationUnit = tdata->compilationUnit(); typeIds = compilationUnit->typeIdsForComponent(); } if (p->isList) { propertyType = typeIds.listId.id(); } else { propertyType = typeIds.id.id(); } } else { if (p->isList) { propertyType = qmltype.qListTypeId().id(); } else { propertyType = qmltype.typeId().id(); propertyTypeVersion = qmltype.version(); } } if (p->isList) propertyFlags.type = QQmlPropertyData::Flags::QListType; else propertyFlags.type = QQmlPropertyData::Flags::QObjectDerivedType; } if (!p->isReadOnly && !p->isList) propertyFlags.setIsWritable(true); QString propertyName = stringAt(p->nameIndex); if (!obj->defaultPropertyIsAlias && propertyIdx == obj->indexOfDefaultPropertyOrAlias) cache->_defaultPropertyName = propertyName; cache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, propertyType, propertyTypeVersion, effectiveSignalIndex); effectiveSignalIndex++; } QQmlError noError; return noError; } template inline int QQmlPropertyCacheCreator::metaTypeForParameter(const QV4::CompiledData::ParameterType ¶m, QString *customTypeName) { if (param.indexIsBuiltinType) { // built-in type return metaTypeForPropertyType(static_cast(int(param.typeNameIndexOrBuiltinType))); } // lazily resolved type const QString typeName = stringAt(param.typeNameIndexOrBuiltinType); if (customTypeName) *customTypeName = typeName; QQmlType qmltype; bool selfReference = false; if (!imports->resolveType(typeName, &qmltype, nullptr, nullptr, nullptr, QQmlType::AnyRegistrationType, &selfReference)) return QMetaType::UnknownType; if (!qmltype.isComposite()) return qmltype.typeId().id(); if (selfReference) return objectContainer->typeIdsForComponent().id.id(); QQmlRefPointer tdata = enginePrivate->typeLoader.getType(qmltype.sourceUrl()); Q_ASSERT(tdata); Q_ASSERT(tdata->isComplete()); auto compilationUnit = tdata->compilationUnit(); return compilationUnit->typeIds.id.id(); } template 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); private: void appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex, QQmlEnginePrivate *enginePriv); QQmlError propertyDataForAlias(const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, QTypeRevision *version, QQmlPropertyData::Flags *propertyFlags, QQmlEnginePrivate *enginePriv); void collectObjectsWithAliasesRecursively(int objectIndex, QVector *objectsWithAliases) const; int objectForId(const CompiledObject &component, int id) const; QQmlPropertyCacheVector *propertyCaches; const ObjectContainer *objectContainer; }; template inline QQmlPropertyCacheAliasCreator::QQmlPropertyCacheAliasCreator(QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer) : propertyCaches(propertyCaches) , objectContainer(objectContainer) { } template inline void QQmlPropertyCacheAliasCreator::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.flags & 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 inline void QQmlPropertyCacheAliasCreator::appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex, QQmlEnginePrivate *enginePriv) { QVector 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->flags & QV4::CompiledData::Alias::Resolved); const int targetObjectIndex = objectForId(component, alias->targetObjectId); Q_ASSERT(targetObjectIndex >= 0); if (alias->aliasToLocalAlias) continue; if (alias->encodedMetaPropertyIndex == -1) continue; const QQmlPropertyCache *targetCache = propertyCaches->at(targetObjectIndex); Q_ASSERT(targetCache); int coreIndex = QQmlPropertyIndex::fromEncoded(alias->encodedMetaPropertyIndex).coreIndex(); QQmlPropertyData *targetProperty = targetCache->property(coreIndex); if (!targetProperty) return false; } return true; }; do { QVector pendingObjects; for (int objectIndex: qAsConst(objectsWithAliases)) { const CompiledObject &object = *objectContainer->objectAt(objectIndex); if (allAliasTargetsExist(object)) { appendAliasesToPropertyCache(component, objectIndex, enginePriv); } else { pendingObjects.append(objectIndex); } } qSwap(objectsWithAliases, pendingObjects); } while (!objectsWithAliases.isEmpty()); } template inline void QQmlPropertyCacheAliasCreator::collectObjectsWithAliasesRecursively(int objectIndex, QVector *objectsWithAliases) const { const CompiledObject &object = *objectContainer->objectAt(objectIndex); if (object.aliasCount() > 0) objectsWithAliases->append(objectIndex); // Stop at Component boundary if (object.flags & QV4::CompiledData::Object::IsComponent && objectIndex != /*root object*/0) return; auto binding = object.bindingsBegin(); auto end = object.bindingsEnd(); for (; binding != end; ++binding) { if (binding->type != QV4::CompiledData::Binding::Type_Object && binding->type != QV4::CompiledData::Binding::Type_AttachedProperty && binding->type != QV4::CompiledData::Binding::Type_GroupProperty) continue; collectObjectsWithAliasesRecursively(binding->value.objectIndex, objectsWithAliases); } } template inline QQmlError QQmlPropertyCacheAliasCreator::propertyDataForAlias( const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, QTypeRevision *version, QQmlPropertyData::Flags *propertyFlags, QQmlEnginePrivate *enginePriv) { *type = 0; bool writable = false; bool resettable = false; bool bindable = false; propertyFlags->setIsAlias(true); if (alias.aliasToLocalAlias) { const QV4::CompiledData::Alias *lastAlias = &alias; QVarLengthArray seenAliases({lastAlias}); do { const int targetObjectIndex = objectForId(component, lastAlias->targetObjectId); Q_ASSERT(targetObjectIndex >= 0); const CompiledObject *targetObject = objectContainer->objectAt(targetObjectIndex); Q_ASSERT(targetObject); auto nextAlias = targetObject->aliasesBegin(); for (uint i = 0; i < lastAlias->localAliasIndex; ++i) ++nextAlias; const QV4::CompiledData::Alias *targetAlias = &(*nextAlias); if (seenAliases.contains(targetAlias)) { return qQmlCompileError(targetAlias->location, QQmlPropertyCacheCreatorBase::tr("Cyclic alias")); } seenAliases.append(targetAlias); lastAlias = targetAlias; } while (lastAlias->aliasToLocalAlias); return propertyDataForAlias(component, *lastAlias, type, version, propertyFlags, enginePriv); } const int targetObjectIndex = objectForId(component, alias.targetObjectId); Q_ASSERT(targetObjectIndex >= 0); const CompiledObject &targetObject = *objectContainer->objectAt(targetObjectIndex); if (alias.encodedMetaPropertyIndex == -1) { Q_ASSERT(alias.flags & QV4::CompiledData::Alias::AliasPointsToPointerObject); auto *typeRef = objectContainer->resolvedType(targetObject.inheritedTypeNameIndex); if (!typeRef) { // Can be caused by the alias target not being a valid id or property. E.g.: // property alias dataValue: dataVal // invalidAliasComponent { id: dataVal } return qQmlCompileError(targetObject.location, QQmlPropertyCacheCreatorBase::tr("Invalid alias target")); } const auto referencedType = typeRef->type(); if (referencedType.isValid()) *type = referencedType.typeId().id(); else *type = typeRef->compilationUnit()->typeIds.id.id(); *version = typeRef->version(); propertyFlags->type = QQmlPropertyData::Flags::QObjectDerivedType; } else { int coreIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).coreIndex(); int valueTypeIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).valueTypeIndex(); QQmlPropertyCache *targetCache = propertyCaches->at(targetObjectIndex); Q_ASSERT(targetCache); QQmlPropertyData *targetProperty = targetCache->property(coreIndex); Q_ASSERT(targetProperty); // for deep aliases, valueTypeIndex is always set if (!QQmlValueTypeFactory::isValueType(targetProperty->propType()) && valueTypeIndex != -1) { // deep alias property *type = targetProperty->propType(); targetCache = enginePriv->propertyCacheForType(*type); Q_ASSERT(targetCache); targetProperty = targetCache->property(valueTypeIndex); *type = targetProperty->propType(); writable = targetProperty->isWritable(); resettable = targetProperty->isResettable(); bindable = targetProperty->isBindable(); } else { // value type or primitive type or enum *type = targetProperty->propType(); writable = targetProperty->isWritable(); resettable = targetProperty->isResettable(); bindable = targetProperty->isBindable(); if (valueTypeIndex != -1) { const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(*type); if (valueTypeMetaObject->property(valueTypeIndex).isEnumType()) *type = QMetaType::Int; else *type = valueTypeMetaObject->property(valueTypeIndex).userType(); } else { if (targetProperty->isEnum()) { *type = QMetaType::Int; } else { // Copy type flags propertyFlags->copyPropertyTypeFlags(targetProperty->flags()); if (targetProperty->isVarProperty()) propertyFlags->type = QQmlPropertyData::Flags::QVariantType; } } } } propertyFlags->setIsWritable(!(alias.flags & QV4::CompiledData::Alias::IsReadOnly) && writable); propertyFlags->setIsResettable(resettable); propertyFlags->setIsBindable(bindable); return QQmlError(); } template inline QQmlError QQmlPropertyCacheAliasCreator::appendAliasesToPropertyCache( const CompiledObject &component, int objectIndex, QQmlEnginePrivate *enginePriv) { const CompiledObject &object = *objectContainer->objectAt(objectIndex); if (!object.aliasCount()) return QQmlError(); QQmlPropertyCache *propertyCache = propertyCaches->at(objectIndex); Q_ASSERT(propertyCache); int effectiveSignalIndex = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.count(); int effectivePropertyIndex = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.count(); int aliasIndex = 0; auto alias = object.aliasesBegin(); auto end = object.aliasesEnd(); for ( ; alias != end; ++alias, ++aliasIndex) { Q_ASSERT(alias->flags & QV4::CompiledData::Alias::Resolved); int type = 0; QTypeRevision version = QTypeRevision::zero(); QQmlPropertyData::Flags propertyFlags; QQmlError error = propertyDataForAlias(component, *alias, &type, &version, &propertyFlags, enginePriv); if (error.isValid()) return error; const QString propertyName = objectContainer->stringAt(alias->nameIndex); if (object.defaultPropertyIsAlias && aliasIndex == object.indexOfDefaultPropertyOrAlias) propertyCache->_defaultPropertyName = propertyName; propertyCache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, type, version, effectiveSignalIndex++); } return QQmlError(); } template inline int QQmlPropertyCacheAliasCreator::objectForId(const CompiledObject &component, int id) const { for (quint32 i = 0, count = component.namedObjectsInComponentCount(); i < count; ++i) { const int candidateIndex = component.namedObjectsInComponentTable()[i]; const CompiledObject &candidate = *objectContainer->objectAt(candidateIndex); if (candidate.id == id) return candidateIndex; } return -1; } QT_END_NAMESPACE #endif // QQMLPROPERTYCACHECREATOR_P_H