QmlCompiler: Handle non-resettable undefined assignment

We need to generate an exception if undefined is assigned to a property
that can't be reset. We don't want to reject everything that can
potentially be undefined. Therefore, we use the QVariant fallback and
examine the value for undefined at run time.

Pick-to: 6.5 6.2
Change-Id: I0a034032f4522f017b452690d93319eb4bfedb1c
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit afbf7b6990)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit ed50111eeb)
This commit is contained in:
Ulf Hermann 2024-01-25 11:18:17 +01:00
parent 49a62c4102
commit 1b0a03639c
9 changed files with 140 additions and 50 deletions

View File

@ -986,10 +986,17 @@ static ObjectPropertyResult changeObjectProperty(QV4::Lookup *l, QObject *object
}
template<bool StrictType = false>
static ObjectPropertyResult resetObjectProperty(QV4::Lookup *l, QObject *object)
static ObjectPropertyResult resetObjectProperty(
QV4::Lookup *l, QObject *object, QV4::ExecutionEngine *v4)
{
return changeObjectProperty<StrictType>(l, object, [&](const QQmlPropertyData *property) {
property->resetProperty(object, {});
if (property->isResettable()) {
property->resetProperty(object, {});
} else {
v4->throwError(
QLatin1String("Cannot assign [undefined] to ") +
QLatin1String(property->propType().name()));
}
});
}
@ -1029,11 +1036,18 @@ static ObjectPropertyResult storeFallbackProperty(QV4::Lookup *l, QObject *objec
});
}
static ObjectPropertyResult resetFallbackProperty(QV4::Lookup *l, QObject *object)
static ObjectPropertyResult resetFallbackProperty(
QV4::Lookup *l, QObject *object, const QMetaProperty *property, QV4::ExecutionEngine *v4)
{
return changeFallbackProperty(l, object, [&](const QMetaObject *metaObject, int coreIndex) {
void *args[] = { nullptr };
metaObject->metacall(object, QMetaObject::ResetProperty, coreIndex, args);
if (property->isResettable()) {
void *args[] = { nullptr };
metaObject->metacall(object, QMetaObject::ResetProperty, coreIndex, args);
} else {
v4->throwError(
QLatin1String("Cannot assign [undefined] to ") +
QLatin1String(property->typeName()));
}
});
}
@ -1110,7 +1124,7 @@ static ObjectPropertyResult storeObjectAsVariant(
return storeObjectProperty<true>(l, object, variant);
if (!variant->isValid())
return resetObjectProperty<true>(l, object);
return resetObjectProperty<true>(l, object, v4);
if (isTypeCompatible(variant->metaType(), propType))
return storeObjectProperty<true>(l, object, variant->data());
@ -1129,12 +1143,13 @@ static ObjectPropertyResult storeFallbackAsVariant(
= reinterpret_cast<const QMetaObject *>(l->qobjectFallbackLookup.metaObject - 1);
Q_ASSERT(metaObject);
const QMetaType propType = metaObject->property(l->qobjectFallbackLookup.coreIndex).metaType();
const QMetaProperty property = metaObject->property(l->qobjectFallbackLookup.coreIndex);
const QMetaType propType = property.metaType();
if (propType == QMetaType::fromType<QVariant>())
return storeFallbackProperty(l, object, variant);
if (!propType.isValid())
return resetFallbackProperty(l, object);
if (!variant->isValid())
return resetFallbackProperty(l, object, &property, v4);
if (isTypeCompatible(variant->metaType(), propType))
return storeFallbackProperty(l, object, variant->data());
@ -1387,7 +1402,7 @@ void AOTCompiledContext::storeNameSloppy(uint nameIndex, void *value, QMetaType
if (isTypeCompatible(type, propType)) {
storeResult = storeObjectProperty(&l, qmlScopeObject, value);
} else if (isUndefined(value, type)) {
storeResult = resetObjectProperty(&l, qmlScopeObject);
storeResult = resetObjectProperty(&l, qmlScopeObject, engine->handle());
} else {
QVariant var(propType);
QV4::ExecutionEngine *v4 = engine->handle();
@ -1402,12 +1417,12 @@ void AOTCompiledContext::storeNameSloppy(uint nameIndex, void *value, QMetaType
case ObjectLookupResult::Fallback: {
const QMetaObject *metaObject
= reinterpret_cast<const QMetaObject *>(l.qobjectFallbackLookup.metaObject - 1);
const QMetaType propType
= metaObject->property(l.qobjectFallbackLookup.coreIndex).metaType();
const QMetaProperty property = metaObject->property(l.qobjectFallbackLookup.coreIndex);
const QMetaType propType = property.metaType();
if (isTypeCompatible(type, propType)) {
storeResult = storeFallbackProperty(&l, qmlScopeObject, value);
} else if (isUndefined(value, type)) {
storeResult = resetFallbackProperty(&l, qmlScopeObject);
storeResult = resetFallbackProperty(&l, qmlScopeObject, &property, engine->handle());
} else {
QVariant var(propType);
QV4::ExecutionEngine *v4 = engine->handle();

View File

@ -87,11 +87,11 @@ void QQmlJSShadowCheck::handleStore(int base, const QString &memberName)
if (checkShadowing(baseType, memberName, base) == Shadowable)
return;
// If the property isn't shadowable but resettable, we have to turn the read register into
// If the property isn't shadowable, we have to turn the read register into
// var if the accumulator can hold undefined. This has to be done in a second pass
// because the accumulator may still turn into var due to its own shadowing.
const QQmlJSRegisterContent member = m_typeResolver->memberType(baseType, memberName);
if (member.isProperty() && !member.property().reset().isEmpty())
if (member.isProperty())
m_resettableStores.append({m_state.accumulatorIn(), instructionOffset});
}

View File

@ -647,9 +647,7 @@ void QQmlJSTypePropagator::generate_StoreNameCommon(int nameIndex)
m_state.setHasSideEffects(true);
if (m_typeResolver->canHoldUndefined(in) && !m_typeResolver->canHoldUndefined(type)) {
if (type.property().reset().isEmpty())
setError(u"Cannot assign potential undefined to %1"_s.arg(type.descriptiveName()));
else if (m_typeResolver->registerIsStoredIn(in, m_typeResolver->voidType()))
if (m_typeResolver->registerIsStoredIn(in, m_typeResolver->voidType()))
addReadAccumulator(m_typeResolver->globalType(m_typeResolver->varType()));
else
addReadAccumulator(in);
@ -972,19 +970,19 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
getCurrentBindingSourceLocation()));
}
// If the property is resettable we must not coerce the input to the property type
// If the input can hold undefined we must not coerce it to the property type
// as that might eliminate an undefined value. For example, undefined -> string
// becomes "undefined".
// Therefore we explicitly require the value to be given as QVariant. This triggers
// the QVariant fallback path that also used for shadowable properties. QVariant can
// hold undefined and the lookup functions will handle that appropriately.
// We need the undefined value for either resetting the property if that is supported
// or generating an exception otherwise. Therefore we explicitly require the value to
// be given as QVariant. This triggers the QVariant fallback path that's also used for
// shadowable properties. QVariant can hold undefined and the lookup functions will
// handle that appropriately.
const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
const QQmlJSRegisterContent readType
= (property.property().reset().isEmpty()
|| !m_typeResolver->canHoldUndefined(m_state.accumulatorIn()))
? property
: property.storedIn(varType).castTo(varType);
const QQmlJSRegisterContent readType = m_typeResolver->canHoldUndefined(m_state.accumulatorIn())
? property.storedIn(varType).castTo(varType)
: property;
addReadAccumulator(readType);
addReadRegister(base, callBase);
m_state.setHasSideEffects(true);

View File

@ -122,6 +122,7 @@ set(qml_files
extendedTypes.qml
failures.qml
fallbacklookups.qml
fallbackresettable.qml
fileDialog.qml
flagEnum.qml
fromBoolValue.qml

View File

@ -5,34 +5,44 @@
#define DYNAMICMETA_H
#include <private/qobject_p.h>
#include <private/qmetaobjectbuilder_p.h>
#include <QtQmlIntegration/qqmlintegration.h>
struct FreeDeleter {
void operator()(QMetaObject *meta) { free(meta); }
};
template<typename T>
class MetaObjectData : public QDynamicMetaObjectData
{
Q_DISABLE_COPY_MOVE(MetaObjectData)
public:
MetaObjectData() = default;
~MetaObjectData() = default;
MetaObjectData()
{
QMetaObjectBuilder builder;
builder.setSuperClass(&T::staticMetaObject);
builder.setFlags(builder.flags() | DynamicMetaObject);
metaObject = builder.toMetaObject();
};
~MetaObjectData() {
free(metaObject);
};
QMetaObject *toDynamicMetaObject(QObject *) override
{
return const_cast<QMetaObject *>(&T::staticMetaObject);
return metaObject;
}
int metaCall(QObject *o, QMetaObject::Call call, int idx, void **argv) override
{
return o->qt_metacall(call, idx, argv);
}
QMetaObject *metaObject = nullptr;
};
class DynamicMeta : public QObject
{
Q_OBJECT
Q_PROPERTY(int foo READ foo WRITE setFoo NOTIFY fooChanged FINAL)
Q_PROPERTY(qreal value READ value WRITE setValue RESET resetValue NOTIFY valueChanged FINAL)
Q_PROPERTY(qreal shadowable READ shadowable CONSTANT)
QML_ELEMENT
public:
@ -54,11 +64,26 @@ public:
Q_INVOKABLE int bar(int baz) { return baz + 12; }
qreal value() const { return m_value; }
qreal shadowable() const { return 25; }
public slots:
void resetValue() { setValue(0); }
void setValue(qreal value)
{
if (m_value == value)
return;
m_value = value;
emit valueChanged();
}
Q_SIGNALS:
void fooChanged();
void valueChanged();
private:
int m_foo = 0;
qreal m_value = 0;
};
class DynamicMetaSingleton : public DynamicMeta

View File

@ -35,12 +35,6 @@ QtObject {
onPartyStarted: (foozle) => { objectName = foozle }
}
signal foo()
signal bar()
// Cannot assign potential undefined
onFoo: objectName = self.bar()
property int enumFromGadget1: GadgetWithEnum.CONNECTED + 1
property int enumFromGadget2: TT2.GadgetWithEnum.CONNECTED + 1

View File

@ -0,0 +1,23 @@
pragma Strict
import QtQml
import TestTypes
DynamicMeta {
id: self
value: 999
property double notResettable: 10
property double notResettable2: { return undefined }
property DynamicMeta shadowing: DynamicMeta {
property var shadowable: undefined
}
function doReset() { self.value = undefined }
function doReset2() { self.value = shadowing.shadowable }
function doNotReset() { self.notResettable = undefined }
signal aaa()
signal bbb()
onAaa: objectName = self.bbb()
}

View File

@ -6,10 +6,18 @@ Resettable {
id: self
value: 999
property double notResettable: 10
property double notResettable2: { return undefined }
property Resettable shadowing: Resettable {
property var shadowable: undefined
}
function doReset() { self.value = undefined }
function doReset2() { self.value = shadowing.shadowable }
function doNotReset() { self.notResettable = undefined }
signal aaa()
signal bbb()
onAaa: objectName = self.bbb()
}

View File

@ -165,7 +165,10 @@ private slots:
void registerElimination();
void registerPropagation();
void renameAdjust();
void resettableProperty();
void resettableProperty_data();
void revisions();
void scopeIdLookup();
void scopeObjectDestruction();
@ -3466,23 +3469,46 @@ void tst_QmlCppCodegen::renameAdjust()
void tst_QmlCppCodegen::resettableProperty()
{
QFETCH(QString, url);
QQmlEngine engine;
QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/resettable.qml"_s));
QQmlComponent c(&engine, QUrl(url));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QTest::ignoreMessage(
QtWarningMsg, qPrintable(url + u":10:5: Unable to assign [undefined] to double"_s));
QScopedPointer<QObject> o(c.create());
QVERIFY(o);
ResettableProperty *resettable = qobject_cast<ResettableProperty *>(o.data());
QVERIFY(resettable);
QCOMPARE(o->property("value").toDouble(), 999);
QMetaObject::invokeMethod(o.data(), "doReset");
QCOMPARE(o->property("value").toDouble(), 0);
QCOMPARE(resettable->value(), 999);
QMetaObject::invokeMethod(resettable, "doReset");
QCOMPARE(resettable->value(), 0);
o->setProperty("value", double(82));
QCOMPARE(o->property("value").toDouble(), 82);
QMetaObject::invokeMethod(o.data(), "doReset2");
QCOMPARE(o->property("value").toDouble(), 0);
resettable->setValue(82);
QCOMPARE(resettable->value(), 82);
QMetaObject::invokeMethod(resettable, "doReset2");
QCOMPARE(resettable->value(), 0);
QTest::ignoreMessage(
QtWarningMsg, qPrintable(url + u":18: Error: Cannot assign [undefined] to double"_s));
QCOMPARE(o->property("notResettable").toDouble(), 10);
QMetaObject::invokeMethod(o.data(), "doNotReset");
QCOMPARE(o->property("notResettable").toDouble(), 10);
QCOMPARE(o->property("notResettable2").toDouble(), 0); // not NaN
o->setObjectName(u"namename"_s);
QTest::ignoreMessage(
QtWarningMsg, qPrintable(url + u":22: Error: Cannot assign [undefined] to QString"_s));
QMetaObject::invokeMethod(o.data(), "aaa");
QCOMPARE(o->objectName(), u"namename"_s);
}
void tst_QmlCppCodegen::resettableProperty_data()
{
QTest::addColumn<QString>("url");
QTest::addRow("object lookups") << u"qrc:/qt/qml/TestTypes/resettable.qml"_s;
QTest::addRow("fallback lookups") << u"qrc:/qt/qml/TestTypes/fallbackresettable.qml"_s;
}
void tst_QmlCppCodegen::revisions()