QtQml: Fix some problems with deep aliases

We cannot get the property cache for an inline component the usual way
during type compilation. We have to ask the compilation unit for it.
This will usually not work in 6.6 or earlier since the compilation unit
does not know the IC's metatypes. However, it shouldn't crash.

Guard against still not being able to retrieve the property cache for
any reason. Abort the compilation rather than crashing.

Also, unify the setting of property attributes. Those should really work
the same way everywhere.

Finally, disallow writing aliases to value type properties where the
property holding the value type itself is not writable.

Pick-to: 6.2
Task-number: QTBUG-115579
Change-Id: I029eb56a9a390085d0c696a787a64c48acf0d620
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit 3ea55bf398)
(cherry picked from commit 7326d41bef)
This commit is contained in:
Ulf Hermann 2023-11-07 15:16:32 +01:00
parent cebfaea1d8
commit dc4bd4fb3f
8 changed files with 140 additions and 35 deletions

View File

@ -480,6 +480,15 @@ int ExecutableCompilationUnit::totalObjectCount() const {
return inlineComponentData[icRoot].totalObjectCount; return inlineComponentData[icRoot].totalObjectCount;
} }
ResolvedTypeReference *ExecutableCompilationUnit::resolvedType(QMetaType type) const
{
for (ResolvedTypeReference *ref : std::as_const(resolvedTypes)) {
if (ref->type().typeId() == type)
return ref;
}
return nullptr;
}
int ExecutableCompilationUnit::totalParserStatusCount() const { int ExecutableCompilationUnit::totalParserStatusCount() const {
if (icRoot == -1) if (icRoot == -1)
return m_totalParserStatusCount; return m_totalParserStatusCount;

View File

@ -141,6 +141,7 @@ public:
QVector<QQmlRefPointer<QQmlScriptData>> dependentScripts; QVector<QQmlRefPointer<QQmlScriptData>> dependentScripts;
ResolvedTypeReferenceMap resolvedTypes; ResolvedTypeReferenceMap resolvedTypes;
ResolvedTypeReference *resolvedType(int id) const { return resolvedTypes.value(id); } ResolvedTypeReference *resolvedType(int id) const { return resolvedTypes.value(id); }
ResolvedTypeReference *resolvedType(QMetaType type) const;
bool verifyChecksum(const CompiledData::DependentTypesHasher &dependencyHasher) const; bool verifyChecksum(const CompiledData::DependentTypesHasher &dependencyHasher) const;

View File

@ -849,53 +849,74 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor
const QQmlPropertyData *targetProperty = targetCache->property(coreIndex); const QQmlPropertyData *targetProperty = targetCache->property(coreIndex);
Q_ASSERT(targetProperty); Q_ASSERT(targetProperty);
// for deep aliases, valueTypeIndex is always set const QMetaType targetPropType = targetProperty->propType();
if (!QQmlMetaType::isValueType(targetProperty->propType()) && valueTypeIndex != -1) {
// deep alias property
*type = targetProperty->propType();
QQmlPropertyCache::ConstPtr typeCache = QQmlMetaType::propertyCacheForType(*type);
Q_ASSERT(typeCache);
const QQmlPropertyData *typeProperty = typeCache->property(valueTypeIndex);
if (typeProperty == nullptr) { const auto resolveType = [](QMetaType targetPropType) {
return qQmlCompileError(alias.referenceLocation, if (targetPropType.flags() & QMetaType::IsEnumeration)
QQmlPropertyCacheCreatorBase::tr("Invalid alias target")); return QMetaType::fromType<int>();
else
return targetPropType;
};
const auto populateWithPropertyData = [&](const QQmlPropertyData *property) {
*type = resolveType(property->propType());
writable = property->isWritable();
resettable = property->isResettable();
bindable = property->isBindable();
// Copy type flags
propertyFlags->copyPropertyTypeFlags(property->flags());
if (property->isVarProperty())
propertyFlags->type = QQmlPropertyData::Flags::QVariantType;
};
// for deep aliases, valueTypeIndex is always set
if (!QQmlMetaType::isValueType(targetPropType) && valueTypeIndex != -1) {
// deep alias property
QQmlPropertyCache::ConstPtr typeCache
= QQmlMetaType::propertyCacheForType(targetPropType);
if (!typeCache) {
// See if it's a half-resolved composite type
if (const QV4::ResolvedTypeReference *typeRef
= objectContainer->resolvedType(targetPropType)) {
typeCache = typeRef->typePropertyCache();
}
} }
*type = typeProperty->propType(); const QQmlPropertyData *typeProperty = typeCache
writable = typeProperty->isWritable(); ? typeCache->property(valueTypeIndex)
resettable = typeProperty->isResettable(); : nullptr;
bindable = typeProperty->isBindable(); if (typeProperty == nullptr) {
return qQmlCompileError(
alias.referenceLocation,
QQmlPropertyCacheCreatorBase::tr("Invalid alias target"));
}
populateWithPropertyData(typeProperty);
} else { } else {
// value type or primitive type or enum // value type or primitive type or enum
*type = targetProperty->propType(); populateWithPropertyData(targetProperty);
writable = targetProperty->isWritable();
resettable = targetProperty->isResettable();
bindable = targetProperty->isBindable();
if (valueTypeIndex != -1) { if (valueTypeIndex != -1) {
const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(*type); const QMetaObject *valueTypeMetaObject
if (valueTypeMetaObject->property(valueTypeIndex).isEnumType()) = QQmlMetaType::metaObjectForValueType(*type);
*type = QMetaType::fromType<int>(); const QMetaProperty valueTypeMetaProperty
else = valueTypeMetaObject->property(valueTypeIndex);
*type = valueTypeMetaObject->property(valueTypeIndex).metaType(); *type = resolveType(valueTypeMetaProperty.metaType());
} else {
if (targetProperty->isEnum()) {
*type = QMetaType::fromType<int>();
} else {
// Copy type flags
propertyFlags->copyPropertyTypeFlags(targetProperty->flags());
if (targetProperty->isVarProperty()) // We can only write or reset the value type property if we can write
propertyFlags->type = QQmlPropertyData::Flags::QVariantType; // the value type itself.
} resettable = writable && valueTypeMetaProperty.isResettable();
writable = writable && valueTypeMetaProperty.isWritable();
bindable = valueTypeMetaProperty.isBindable();
} }
} }
} }
propertyFlags->setIsWritable(!(alias.hasFlag(QV4::CompiledData::Alias::IsReadOnly)) propertyFlags->setIsWritable(
&& writable); writable && !alias.hasFlag(QV4::CompiledData::Alias::IsReadOnly));
propertyFlags->setIsResettable(resettable); propertyFlags->setIsResettable(resettable);
propertyFlags->setIsBindable(bindable); propertyFlags->setIsBindable(bindable);
return QQmlError(); return QQmlError();

View File

@ -108,6 +108,15 @@ public:
return resolvedTypes->value(id); return resolvedTypes->value(id);
} }
QV4::ResolvedTypeReference *resolvedType(QMetaType type) const
{
for (QV4::ResolvedTypeReference *ref : std::as_const(*resolvedTypes)) {
if (ref->type().typeId() == type)
return ref;
}
return nullptr;
}
CompositeMetaTypeIds typeIdsForComponent(int objectId = 0) const; CompositeMetaTypeIds typeIdsForComponent(int objectId = 0) const;
private: private:

View File

@ -0,0 +1,25 @@
import QtQml
QtObject {
id: root
component ObjectWithColor: QtObject {
property string color
property var varvar
}
property ObjectWithColor border: ObjectWithColor {
id: border
color: root.trueBorderColor
varvar: root.trueBorderVarvar
}
readonly property rect readonlyRect: ({x: 12, y: 13, width: 14, height: 15})
property alias borderColor: border.color
property alias borderVarvar: border.varvar
property alias readonlyRectX: root.readonlyRect.x
property string trueBorderColor: "green"
property var trueBorderVarvar: 1234
}

View File

@ -0,0 +1,8 @@
import QtQml
DeepAliasOnIC {
borderColor: "black"
borderVarvar: "mauve"
}

View File

@ -0,0 +1,5 @@
import QtQml
DeepAliasOnIC {
readonlyRectX: 55
}

View File

@ -422,6 +422,7 @@ private slots:
void typeAnnotationCycle(); void typeAnnotationCycle();
void objectInQmlListAndGc(); void objectInQmlListAndGc();
void deepAliasOnICOrReadonly();
private: private:
QQmlEngine engine; QQmlEngine engine;
@ -8116,6 +8117,32 @@ void tst_qqmllanguage::objectInQmlListAndGc()
QCOMPARE(child->objectName(), QLatin1String("child")); QCOMPARE(child->objectName(), QLatin1String("child"));
} }
void tst_qqmllanguage::deepAliasOnICOrReadonly()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("deepAliasOnICUser.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> o(c.create());
QVERIFY(!o.isNull());
// We are mostly testing that it doesn't crash here. The actual bug is fixed separately.
QEXPECT_FAIL("", "QTBUG-115579 is not fixed yet", Continue);
QCOMPARE(o->property("borderColor").toString(), QLatin1String("black"));
const QVariant var = o->property("borderVarvar");
QEXPECT_FAIL("", "QTBUG-115579 is not fixed yet", Continue);
QCOMPARE(var.metaType(), QMetaType::fromType<QString>());
QEXPECT_FAIL("", "QTBUG-115579 is not fixed yet", Continue);
QCOMPARE(var.toString(), QLatin1String("mauve"));
QQmlComponent c2(&engine, testFileUrl("deepAliasOnReadonly.qml"));
QVERIFY(c2.isError());
QVERIFY(c2.errorString().contains(
QLatin1String(
"Invalid property assignment: \"readonlyRectX\" is a read-only property")));
}
QTEST_MAIN(tst_qqmllanguage) QTEST_MAIN(tst_qqmllanguage)
#include "tst_qqmllanguage.moc" #include "tst_qqmllanguage.moc"