diff options
-rw-r--r-- | src/qml/compiler/qqmltypecompiler.cpp | 342 | ||||
-rw-r--r-- | src/qml/compiler/qqmltypecompiler_p.h | 12 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata_p.h | 4 | ||||
-rw-r--r-- | src/qml/qml/qqmlvmemetaobject.cpp | 3 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/alias.14.errors.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/alias.14.qml | 17 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/invalidAlias.11.errors.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/invalidAlias.11.qml | 10 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 19 |
9 files changed, 300 insertions, 109 deletions
diff --git a/src/qml/compiler/qqmltypecompiler.cpp b/src/qml/compiler/qqmltypecompiler.cpp index 42e31ea7f8..83aaba791b 100644 --- a/src/qml/compiler/qqmltypecompiler.cpp +++ b/src/qml/compiler/qqmltypecompiler.cpp @@ -993,137 +993,271 @@ bool QQmlComponentAndAliasResolver::collectIdsAndAliases(int objectIndex) bool QQmlComponentAndAliasResolver::resolveAliases() { - foreach (int objectIndex, _objectsWithAliases) { - const QmlIR::Object *obj = qmlObjects->at(objectIndex); + if (_objectsWithAliases.isEmpty()) + return true; + bool atLeastOneAliasResolved; + do { + atLeastOneAliasResolved = false; + QVector<int> pendingObjects; - QQmlPropertyCache *propertyCache = propertyCaches.at(objectIndex); - Q_ASSERT(propertyCache); + for (int objectIndex: qAsConst(_objectsWithAliases)) { - int effectiveSignalIndex = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.count(); - int effectivePropertyIndex = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.count(); + QQmlCompileError error; + const auto result = resolveAliasesInObject(objectIndex, &error); - int aliasIndex = 0; - for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next, ++aliasIndex) { - const int idIndex = alias->idIndex; - const int targetObjectIndex = _idToObjectIndex.value(idIndex, -1); - if (targetObjectIndex == -1) { - recordError(alias->referenceLocation, tr("Invalid alias reference. Unable to find id \"%1\"").arg(stringAt(idIndex))); + if (error.isSet()) { + recordError(error); return false; } - Q_ASSERT(!(alias->flags & QV4::CompiledData::Alias::Resolved)); - alias->flags |= QV4::CompiledData::Alias::Resolved; - const QmlIR::Object *targetObject = qmlObjects->at(targetObjectIndex); - Q_ASSERT(targetObject->id >= 0); - alias->targetObjectId = targetObject->id; - - const QString aliasPropertyValue = stringAt(alias->propertyNameIndex); - - QStringRef property; - QStringRef subProperty; - - const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.')); - if (propertySeparator != -1) { - property = aliasPropertyValue.leftRef(propertySeparator); - subProperty = aliasPropertyValue.midRef(propertySeparator + 1); - } else - property = QStringRef(&aliasPropertyValue, 0, aliasPropertyValue.length()); - - int propIdx = -1; - int type = 0; - bool writable = false; - bool resettable = false; - - quint32 propertyFlags = QQmlPropertyData::IsAlias; - - if (property.isEmpty()) { - const QmlIR::Object *targetObject = qmlObjects->at(targetObjectIndex); - auto *typeRef = resolvedTypes->value(targetObject->inheritedTypeNameIndex); - Q_ASSERT(typeRef); - - if (typeRef->type) - type = typeRef->type->typeId(); - else - type = typeRef->compilationUnit->metaTypeId; - - alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; - propertyFlags |= QQmlPropertyData::IsQObjectDerived; + + if (result == AllAliasesResolved) { + addAliasesToPropertyCache(objectIndex); + atLeastOneAliasResolved = true; + } else if (result == SomeAliasesResolved) { + atLeastOneAliasResolved = true; + pendingObjects.append(objectIndex); } else { - QQmlPropertyCache *targetCache = propertyCaches.at(targetObjectIndex); - Q_ASSERT(targetCache); - QmlIR::PropertyResolver resolver(targetCache); + 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->flags & QV4::CompiledData::Alias::Resolved)) { + recordError(alias->location, tr("Circular alias reference detected")); + return false; + } + } + } - QQmlPropertyData *targetProperty = resolver.property(property.toString()); - if (!targetProperty || targetProperty->coreIndex > 0x0000FFFF) { - recordError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(property.toString())); - return false; - } + return true; +} - propIdx = targetProperty->coreIndex; - type = targetProperty->propType; +QQmlComponentAndAliasResolver::AliasResolutionResult QQmlComponentAndAliasResolver::resolveAliasesInObject(int objectIndex, QQmlCompileError *error) +{ + const QmlIR::Object * const obj = qmlObjects->at(objectIndex); + if (!obj->aliasCount()) + return AllAliasesResolved; - writable = targetProperty->isWritable(); - resettable = targetProperty->isResettable(); + int numResolvedAliases = 0; + bool seenUnresolvedAlias = false; - if (!subProperty.isEmpty()) { - const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(type); - if (!valueTypeMetaObject) { - recordError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(subProperty.toString())); - return false; - } + for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next) { + if (alias->flags & QV4::CompiledData::Alias::Resolved) + continue; - int valueTypeIndex = - valueTypeMetaObject->indexOfProperty(subProperty.toString().toUtf8().constData()); - if (valueTypeIndex == -1) { - recordError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(subProperty.toString())); - return false; - } - Q_ASSERT(valueTypeIndex <= 0x0000FFFF); + seenUnresolvedAlias = true; - propIdx = QQmlPropertyData::encodeValueTypePropertyIndex(propIdx, valueTypeIndex); - if (valueTypeMetaObject->property(valueTypeIndex).isEnumType()) - type = QVariant::Int; - else - type = valueTypeMetaObject->property(valueTypeIndex).userType(); + const int idIndex = alias->idIndex; + const int targetObjectIndex = _idToObjectIndex.value(idIndex, -1); + if (targetObjectIndex == -1) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias reference. Unable to find id \"%1\"").arg(stringAt(idIndex))); + break; + } - } else { - if (targetProperty->isEnum()) { - type = QVariant::Int; - } else { - // Copy type flags - propertyFlags |= targetProperty->getFlags() & QQmlPropertyData::PropTypeFlagMask; + const QmlIR::Object *targetObject = qmlObjects->at(targetObjectIndex); + Q_ASSERT(targetObject->id >= 0); + alias->targetObjectId = targetObject->id; + alias->aliasToLocalAlias = false; - if (targetProperty->isVarProperty()) - propertyFlags |= QQmlPropertyData::IsQVariant; + const QString aliasPropertyValue = stringAt(alias->propertyNameIndex); - if (targetProperty->isQObject()) - alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; + QStringRef property; + QStringRef subProperty; + + const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.')); + if (propertySeparator != -1) { + property = aliasPropertyValue.leftRef(propertySeparator); + subProperty = aliasPropertyValue.midRef(propertySeparator + 1); + } else + property = QStringRef(&aliasPropertyValue, 0, aliasPropertyValue.length()); + + int propIdx = -1; + + if (property.isEmpty()) { + alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; + } else { + QQmlPropertyCache *targetCache = propertyCaches.at(targetObjectIndex); + Q_ASSERT(targetCache); + QmlIR::PropertyResolver resolver(targetCache); + + QQmlPropertyData *targetProperty = resolver.property(property.toString()); + + // If it's an alias that we haven't resolved yet, try again later. + if (!targetProperty) { + bool aliasPointsToOtherAlias = false; + int localAliasIndex = 0; + for (auto targetAlias = targetObject->aliasesBegin(), end = targetObject->aliasesEnd(); targetAlias != end; ++targetAlias, ++localAliasIndex) { + if (stringAt(targetAlias->nameIndex) == property) { + aliasPointsToOtherAlias = true; + break; } } + if (aliasPointsToOtherAlias) { + if (targetObjectIndex == objectIndex) { + alias->localAliasIndex = localAliasIndex; + alias->aliasToLocalAlias = true; + alias->flags |= QV4::CompiledData::Alias::Resolved; + ++numResolvedAliases; + continue; + } + + // Try again later and resolve the target alias first. + _objectsWithAliases.append(objectIndex); + // restore + alias->idIndex = idIndex; + break; + } } - alias->encodedMetaPropertyIndex = propIdx; + if (!targetProperty || targetProperty->coreIndex > 0x0000FFFF) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(property.toString())); + break; + } - if (!(alias->flags & QV4::CompiledData::Property::IsReadOnly) && writable) - propertyFlags |= QQmlPropertyData::IsWritable; - else - propertyFlags &= ~QQmlPropertyData::IsWritable; + propIdx = targetProperty->coreIndex; - if (resettable) - propertyFlags |= QQmlPropertyData::IsResettable; - else - propertyFlags &= ~QQmlPropertyData::IsResettable; + if (!subProperty.isEmpty()) { + const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(targetProperty->propType); + if (!valueTypeMetaObject) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(subProperty.toString())); + break; + } - QString propertyName = stringAt(alias->nameIndex); + int valueTypeIndex = + valueTypeMetaObject->indexOfProperty(subProperty.toString().toUtf8().constData()); + if (valueTypeIndex == -1) { + *error = QQmlCompileError(alias->referenceLocation, tr("Invalid alias target location: %1").arg(subProperty.toString())); + break; + } + Q_ASSERT(valueTypeIndex <= 0x0000FFFF); - if (obj->defaultPropertyIsAlias && aliasIndex == obj->indexOfDefaultPropertyOrAlias) - propertyCache->_defaultPropertyName = propertyName; + propIdx = QQmlPropertyData::encodeValueTypePropertyIndex(propIdx, valueTypeIndex); + } else { + if (targetProperty->isQObject()) + alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; + } + } - propertyCache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, - type, effectiveSignalIndex++); + alias->encodedMetaPropertyIndex = propIdx; + alias->flags |= QV4::CompiledData::Alias::Resolved; + numResolvedAliases++; + } + if (numResolvedAliases == 0) + return seenUnresolvedAlias ? NoAliasResolved : AllAliasesResolved; + + return SomeAliasesResolved; +} + +void QQmlComponentAndAliasResolver::propertyDataForAlias(QmlIR::Alias *alias, int *type, quint32 *propertyFlags) +{ + const auto objectForId = [this](int objectId) { + for (auto idIt = _idToObjectIndex.constBegin(), end = _idToObjectIndex.constEnd(); idIt != end; ++idIt) { + const QmlIR::Object *obj = qmlObjects->at(*idIt); + if (obj->id == objectId) + return *idIt; + } + return -1; + }; + + const int targetObjectIndex = objectForId(alias->targetObjectId); + Q_ASSERT(targetObjectIndex >= 0); + + const QmlIR::Object *targetObject = qmlObjects->at(targetObjectIndex); + + *type = 0; + bool writable = false; + bool resettable = false; + + *propertyFlags = QQmlPropertyData::IsAlias; + + if (alias->aliasToLocalAlias) { + auto targetAlias = targetObject->firstAlias(); + for (uint i = 0; i < alias->localAliasIndex; ++i) + targetAlias = targetAlias->next; + propertyDataForAlias(targetAlias, type, propertyFlags); + return; + } else if (alias->encodedMetaPropertyIndex == -1) { + Q_ASSERT(alias->flags & QV4::CompiledData::Alias::AliasPointsToPointerObject); + auto *typeRef = resolvedTypes->value(targetObject->inheritedTypeNameIndex); + Q_ASSERT(typeRef); + + if (typeRef->type) + *type = typeRef->type->typeId(); + else + *type = typeRef->compilationUnit->metaTypeId; + + *propertyFlags |= QQmlPropertyData::IsQObjectDerived; + } else { + int coreIndex; + int valueTypeIndex = QQmlPropertyData::decodeValueTypePropertyIndex(alias->encodedMetaPropertyIndex, &coreIndex); + + QQmlPropertyCache *targetCache = propertyCaches.at(targetObjectIndex); + Q_ASSERT(targetCache); + QQmlPropertyData *targetProperty = targetCache->property(coreIndex); + Q_ASSERT(targetProperty); + + *type = targetProperty->propType; + + writable = targetProperty->isWritable(); + resettable = targetProperty->isResettable(); + + if (valueTypeIndex != -1) { + const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(*type); + if (valueTypeMetaObject->property(valueTypeIndex).isEnumType()) + *type = QVariant::Int; + else + *type = valueTypeMetaObject->property(valueTypeIndex).userType(); + } else { + if (targetProperty->isEnum()) { + *type = QVariant::Int; + } else { + // Copy type flags + *propertyFlags |= targetProperty->getFlags() & QQmlPropertyData::PropTypeFlagMask; + + if (targetProperty->isVarProperty()) + *propertyFlags |= QQmlPropertyData::IsQVariant; + } } } - return true; + + if (!(alias->flags & QV4::CompiledData::Property::IsReadOnly) && writable) + *propertyFlags |= QQmlPropertyData::IsWritable; + else + *propertyFlags &= ~QQmlPropertyData::IsWritable; + + if (resettable) + *propertyFlags |= QQmlPropertyData::IsResettable; + else + *propertyFlags &= ~QQmlPropertyData::IsResettable; +} + +void QQmlComponentAndAliasResolver::addAliasesToPropertyCache(int objectIndex) +{ + const QmlIR::Object * const obj = qmlObjects->at(objectIndex); + QQmlPropertyCache * const propertyCache = propertyCaches.at(objectIndex); + Q_ASSERT(propertyCache); + int effectiveSignalIndex = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.count(); + int effectivePropertyIndex = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.count(); + + int aliasIndex = 0; + for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next, ++aliasIndex) { + int type = 0; + quint32 propertyFlags = 0; + propertyDataForAlias(alias, &type, &propertyFlags); + + QString propertyName = stringAt(alias->nameIndex); + + if (obj->defaultPropertyIsAlias && aliasIndex == obj->indexOfDefaultPropertyOrAlias) + propertyCache->_defaultPropertyName = propertyName; + + propertyCache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, + type, effectiveSignalIndex++); + } } QQmlDeferredAndCustomParserBindingScanner::QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler) diff --git a/src/qml/compiler/qqmltypecompiler_p.h b/src/qml/compiler/qqmltypecompiler_p.h index 93ae7d1fad..0b078fd642 100644 --- a/src/qml/compiler/qqmltypecompiler_p.h +++ b/src/qml/compiler/qqmltypecompiler_p.h @@ -266,6 +266,16 @@ protected: void findAndRegisterImplicitComponents(const QmlIR::Object *obj, QQmlPropertyCache *propertyCache); bool collectIdsAndAliases(int objectIndex); bool resolveAliases(); + void propertyDataForAlias(QmlIR::Alias *alias, int *type, quint32 *propertyFlags); + + enum AliasResolutionResult { + NoAliasResolved, + SomeAliasesResolved, + AllAliasesResolved + }; + + AliasResolutionResult resolveAliasesInObject(int objectIndex, QQmlCompileError *error); + void addAliasesToPropertyCache(int objectIndex); QQmlEnginePrivate *enginePrivate; QQmlJS::MemoryPool *pool; @@ -278,7 +288,7 @@ protected: int _componentIndex; QHash<int, int> _idToObjectIndex; - QList<int> _objectsWithAliases; + QVector<int> _objectsWithAliases; QHash<int, QV4::CompiledData::CompilationUnit::ResolvedTypeReference*> *resolvedTypes; QQmlPropertyCacheVector propertyCaches; diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index 1e65be38da..f1cf365b25 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -446,11 +446,13 @@ struct Alias { }; union { LEUInt32 idIndex; // string index - LEUInt32 targetObjectId; // object id index (in QQmlContextData::idValues) + QJsonPrivate::qle_bitfield<0, 31> targetObjectId; // object id index (in QQmlContextData::idValues) + QJsonPrivate::qle_bitfield<31, 1> aliasToLocalAlias; }; union { LEUInt32 propertyNameIndex; // string index LEInt32 encodedMetaPropertyIndex; + LEUInt32 localAliasIndex; // index in list of aliases local to the object (if targetObjectId == objectId) }; Location location; Location referenceLocation; diff --git a/src/qml/qml/qqmlvmemetaobject.cpp b/src/qml/qml/qqmlvmemetaobject.cpp index 0ce43e9383..d7f6c5b3af 100644 --- a/src/qml/qml/qqmlvmemetaobject.cpp +++ b/src/qml/qml/qqmlvmemetaobject.cpp @@ -845,6 +845,9 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void * if (!ctxt) return -1; + while (aliasData->aliasToLocalAlias) + aliasData = &compiledObject->aliasTable()[aliasData->localAliasIndex]; + QQmlContext *context = ctxt->asQQmlContext(); QQmlContextPrivate *ctxtPriv = QQmlContextPrivate::get(context); diff --git a/tests/auto/qml/qqmllanguage/data/alias.14.errors.txt b/tests/auto/qml/qqmllanguage/data/alias.14.errors.txt new file mode 100644 index 0000000000..90a3ea4317 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/alias.14.errors.txt @@ -0,0 +1 @@ +10:34:References to other aliases within the same object are not supported at the moment diff --git a/tests/auto/qml/qqmllanguage/data/alias.14.qml b/tests/auto/qml/qqmllanguage/data/alias.14.qml new file mode 100644 index 0000000000..ff3c36d990 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/alias.14.qml @@ -0,0 +1,17 @@ +import QtQml 2.0 + +QtObject { + id: root + property bool targetProperty: true + + property QtObject foo: QtObject { + id: otherSubObject + property alias theAliasOrigin: root.targetProperty + property alias theAlias: otherSubObject.theAliasOrigin + } + + property QtObject referencingSubObject: QtObject { + property alias success: otherSubObject.theAlias + } + +} diff --git a/tests/auto/qml/qqmllanguage/data/invalidAlias.11.errors.txt b/tests/auto/qml/qqmllanguage/data/invalidAlias.11.errors.txt new file mode 100644 index 0000000000..b79b660c46 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/invalidAlias.11.errors.txt @@ -0,0 +1 @@ +5:5:Circular alias reference detected diff --git a/tests/auto/qml/qqmllanguage/data/invalidAlias.11.qml b/tests/auto/qml/qqmllanguage/data/invalidAlias.11.qml new file mode 100644 index 0000000000..9b50b48df8 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/invalidAlias.11.qml @@ -0,0 +1,10 @@ +import QtQml 2.0 + +QtObject { + id: root + property alias a: subObject.b + property QtObject foo: QtObject { + id: subObject + property alias b: root.a + } +} diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 1e8e92217c..1df7c4157e 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -494,6 +494,7 @@ void tst_qqmllanguage::errors_data() QTest::newRow("invalidAlias.8") << "invalidAlias.8.qml" << "invalidAlias.8.errors.txt" << false; QTest::newRow("invalidAlias.9") << "invalidAlias.9.qml" << "invalidAlias.9.errors.txt" << false; QTest::newRow("invalidAlias.10") << "invalidAlias.10.qml" << "invalidAlias.10.errors.txt" << false; + QTest::newRow("invalidAlias.11") << "invalidAlias.11.qml" << "invalidAlias.11.errors.txt" << false; QTest::newRow("invalidAttachedProperty.1") << "invalidAttachedProperty.1.qml" << "invalidAttachedProperty.1.errors.txt" << false; QTest::newRow("invalidAttachedProperty.2") << "invalidAttachedProperty.2.qml" << "invalidAttachedProperty.2.errors.txt" << false; @@ -1821,8 +1822,6 @@ void tst_qqmllanguage::aliasProperties() { // This is known to fail at the moment. QQmlComponent component(&engine, testFileUrl("alias.13.qml")); - QVERIFY(!component.errors().isEmpty()); -#if 0 VERIFY_ERRORS(0); QScopedPointer<QObject> object(component.create()); QVERIFY(!object.isNull()); @@ -1831,7 +1830,21 @@ void tst_qqmllanguage::aliasProperties() QVERIFY(!subObject.isNull()); QVERIFY(subObject->property("success").toBool()); -#endif + } + + // "Nested" aliases within an object that require iterative resolution + { + // This is known to fail at the moment. + QQmlComponent component(&engine, testFileUrl("alias.14.qml")); + VERIFY_ERRORS(0); + + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QPointer<QObject> subObject = qvariant_cast<QObject*>(object->property("referencingSubObject")); + QVERIFY(!subObject.isNull()); + + QVERIFY(subObject->property("success").toBool()); } } |