diff options
| author | Ulf Hermann <ulf.hermann@qt.io> | 2025-05-23 12:31:46 +0200 |
|---|---|---|
| committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2025-06-09 07:00:17 +0000 |
| commit | 05bf1bcb5067287644e7a41067bd4d779c56f4b9 (patch) | |
| tree | bfe5d568d5276c6c1ff665c653d29540f101aee2 | |
| parent | 5cecb800c184e35583620bccd7c83c284dd8642a (diff) | |
QQmlBind: Store all values as QV4::PersistentValue
We can't store them as QObject because that may result in dangling
pointers. We need the heap wrappers to track deletions.
Pick-to: 6.9 6.8 6.5
Fixes: QTBUG-135295
Change-Id: Id53932aec834ad59104bd867c0056fb8da94d827
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit db7a74d7dc3097c4464c22cbfc189bc180b80fd6)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
| -rw-r--r-- | src/qmlmeta/types/qqmlbind.cpp | 75 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlbinding/data/deleteStashedObject.qml | 51 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp | 19 |
3 files changed, 119 insertions, 26 deletions
diff --git a/src/qmlmeta/types/qqmlbind.cpp b/src/qmlmeta/types/qqmlbind.cpp index 8ca11064ec..a66ace6ddd 100644 --- a/src/qmlmeta/types/qqmlbind.cpp +++ b/src/qmlmeta/types/qqmlbind.cpp @@ -59,10 +59,8 @@ public: silentDestroy(oldKind); switch (newKind) { case QQmlBindEntryKind::V4Value: - new (&v4Value) QV4::PersistentValue(std::move(other.v4Value)); - break; case QQmlBindEntryKind::Variant: - new (&variant) QVariant(std::move(other.variant)); + new (&v4Value) QV4::PersistentValue(std::move(other.v4Value)); break; case QQmlBindEntryKind::Binding: new (&binding) QQmlAnyBinding(std::move(other.binding)); @@ -79,10 +77,8 @@ public: silentDestroy(oldKind); switch (newKind) { case QQmlBindEntryKind::V4Value: - new (&v4Value) QV4::PersistentValue(other.v4Value); - break; case QQmlBindEntryKind::Variant: - new (&variant) QVariant(other.variant); + new (&v4Value) QV4::PersistentValue(other.v4Value); break; case QQmlBindEntryKind::Binding: new (&binding) QQmlAnyBinding(other.binding); @@ -97,10 +93,8 @@ public: { switch (kind) { case QQmlBindEntryKind::V4Value: - v4Value.~PersistentValue(); - break; case QQmlBindEntryKind::Variant: - variant.~QVariant(); + v4Value.~PersistentValue(); break; case QQmlBindEntryKind::Binding: binding.~QQmlAnyBinding(); @@ -111,10 +105,11 @@ public: return QQmlBindEntryKind::None; } - [[nodiscard]] QQmlBindEntryKind set(QVariant v, QQmlBindEntryKind oldKind) + [[nodiscard]] QQmlBindEntryKind set( + QV4::ExecutionEngine *engine, const QVariant &v, QQmlBindEntryKind oldKind) { silentDestroy(oldKind); - new (&variant) QVariant(std::move(v)); + new (&v4Value) QV4::PersistentValue(engine, engine->fromVariant(v)); return QQmlBindEntryKind::Variant; } @@ -125,6 +120,13 @@ public: return QQmlBindEntryKind::V4Value; } + [[nodiscard]] QQmlBindEntryKind setVariant(QV4::PersistentValue v, QQmlBindEntryKind oldKind) + { + silentDestroy(oldKind); + new (&v4Value) QV4::PersistentValue(std::move(v)); + return QQmlBindEntryKind::Variant; + } + [[nodiscard]] QQmlBindEntryKind set(QQmlAnyBinding v, QQmlBindEntryKind oldKind) { silentDestroy(oldKind); @@ -133,7 +135,6 @@ public: } QV4::PersistentValue v4Value; - QVariant variant; QQmlAnyBinding binding; private: @@ -730,7 +731,10 @@ QVariant QQmlBind::value() const Q_D(const QQmlBind); if (d->mode == QQmlBindPrivate::ObjectPropertyValue) { Q_ASSERT(d->objectPropertyValueData.entry.currentKind == QQmlBindEntryKind::Variant); - return d->objectPropertyValueData.entry.current.variant; + QV4::ExecutionEngine *engine = d->objectPropertyValueData.entry.current.v4Value.engine(); + return engine->toVariant( + *d->objectPropertyValueData.entry.current.v4Value.valueRef(), QMetaType()); + } return QVariant(); } @@ -751,7 +755,13 @@ void QQmlBind::setValue(const QVariant &v) Q_FALLTHROUGH(); case QQmlBindPrivate::ObjectPropertyValue: { QQmlBindEntry *targetEntry = &d->objectPropertyValueData.entry; - targetEntry->currentKind = targetEntry->current.set(v, targetEntry->currentKind); + QQmlEngine *engine = qmlEngine(this); + if (!engine) { + qWarning() << "QQmlBind must be created in a QML context"; + return; + } + targetEntry->currentKind + = targetEntry->current.set(engine->handle(), v, targetEntry->currentKind); prepareEval(); break; } @@ -1054,8 +1064,8 @@ void QQmlBindPrivate::decodeBinding( break; } - const auto setVariant = [&entry](QVariant var) { - entry.currentKind = entry.current.set(std::move(var), entry.currentKind); + const auto setVariant = [&entry](QV4::PersistentValue value) { + entry.currentKind = entry.current.setVariant(value, entry.currentKind); }; const auto setBinding = [&entry](QQmlAnyBinding binding) { @@ -1086,16 +1096,20 @@ void QQmlBindPrivate::decodeBinding( } break; case QV4::CompiledData::Binding::Type_String: - setVariant(compilationUnit->bindingValueAsString(binding)); + setVariant(QV4::PersistentValue( + compilationUnit->engine, + compilationUnit->runtimeStrings[binding->stringIndex]->asReturnedValue())); break; case QV4::CompiledData::Binding::Type_Number: - setVariant(compilationUnit->bindingValueAsNumber(binding)); + setVariant(QV4::PersistentValue( + compilationUnit->engine, + compilationUnit->constants[binding->value.constantValueIndex].asReturnedValue())); break; case QV4::CompiledData::Binding::Type_Boolean: - setVariant(binding->valueAsBoolean()); + setVariant(QV4::PersistentValue(compilationUnit->engine, QV4::Encode(binding->value.b))); break; case QV4::CompiledData::Binding::Type_Null: - setVariant(QVariant::fromValue(nullptr)); + setVariant(QV4::PersistentValue(compilationUnit->engine, QV4::Encode::null())); break; case QV4::CompiledData::Binding::Type_Object: case QV4::CompiledData::Binding::Type_Invalid: @@ -1236,8 +1250,11 @@ bool QQmlBindPrivate::isCurrent(QQmlBindEntry *entry) const QV4::Value::fromReturnedValue(vmemo->vmeProperty(propPriv->core.coreIndex())), *entry->current.v4Value.valueRef()); } - case QQmlBindEntryKind::Variant: - return entry->current.variant == entry->prop.read(); + case QQmlBindEntryKind::Variant: { + const QV4::PersistentValue &v4Value = entry->current.v4Value; + return v4Value.engine()->toVariant(*v4Value.valueRef(), entry->prop.propertyMetaType()) + == entry->prop.read(); + } case QQmlBindEntryKind::Binding: return entry->current.binding == QQmlAnyBinding::ofProperty(entry->prop); case QQmlBindEntryKind::None: @@ -1283,7 +1300,9 @@ void QQmlBindPrivate::preEvalEntry(QQmlBindEntry *entry) case QQmlBindEntryKind::Variant: if (restoreValue) { QQmlAnyBinding::takeFrom(entry->prop); // we don't want to have a binding active - entry->prop.write(entry->previous.variant); + const QV4::PersistentValue &v4Value = entry->previous.v4Value; + entry->prop.write(v4Value.engine()->toVariant( + *v4Value.valueRef(), entry->prop.propertyMetaType())); entry->clearPrev(); } break; @@ -1311,7 +1330,8 @@ void QQmlBindPrivate::preEvalEntry(QQmlBindEntry *entry) QV4::PersistentValue(vmemo->engine, retVal), entry->previousKind); } else { // nope, use the meta object to get a QVariant - entry->previousKind = entry->previous.set(entry->prop.read(), entry->previousKind); + entry->previousKind = entry->previous.set( + propPriv->engine->handle(), entry->prop.read(), entry->previousKind); } } } @@ -1326,9 +1346,12 @@ void QQmlBindPrivate::postEvalEntry(QQmlBindEntry *entry) if (!entry->prop.isValid()) return; switch (entry->currentKind) { - case QQmlBindEntryKind::Variant: - entry->prop.write(entry->current.variant); + case QQmlBindEntryKind::Variant: { + const QV4::PersistentValue &v4Value = entry->current.v4Value; + entry->prop.write( + v4Value.engine()->toVariant(*v4Value.valueRef(), entry->prop.propertyMetaType())); break; + } case QQmlBindEntryKind::Binding: Q_ASSERT(!delayed); entry->current.binding.installOn(entry->prop); diff --git a/tests/auto/qml/qqmlbinding/data/deleteStashedObject.qml b/tests/auto/qml/qqmlbinding/data/deleteStashedObject.qml new file mode 100644 index 0000000000..fb95305791 --- /dev/null +++ b/tests/auto/qml/qqmlbinding/data/deleteStashedObject.qml @@ -0,0 +1,51 @@ +pragma ComponentBehavior: Bound +import QtQml + +QtObject { + id: root + + property Component c: Component { + QtObject { + Component.onDestruction: { + console.log("dead") + timer.start() + } + } + } + + property QtObject stashed: c.createObject() + property QtObject replacement: QtObject {} + + onStashedChanged: { + if (stashed) { + page = stashed + console.log("alive") + binding.when = true + } + } + + property QtObject page + + property Binding binding: Binding { + id: binding + target: root + property: "page" + when: false + value: root.replacement + } + + property Timer timer: Timer { + id: timer + interval: 10 + onTriggered: { + console.log("before") + binding.when = false + console.log("after") + } + } + + Component.onCompleted: { + console.log("destroy") + stashed.destroy(); + } +} diff --git a/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp b/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp index 42f5cfc1dc..48f437387d 100644 --- a/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp +++ b/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp @@ -53,6 +53,7 @@ private slots: void qQmlPropertyToPropertyBinding(); void qQmlPropertyToPropertyBindingReverse(); void delayedBindingDestruction(); + void deleteStashedObject(); private: QQmlEngine engine; @@ -856,6 +857,24 @@ void tst_qqmlbinding::delayedBindingDestruction() verifyDelegate(QLatin1String("foo")); } +void tst_qqmlbinding::deleteStashedObject() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("deleteStashedObject.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + + QTest::ignoreMessage(QtDebugMsg, "alive"); + QTest::ignoreMessage(QtDebugMsg, "destroy"); + QScopedPointer<QObject> object(component.create()); + QVERIFY(object); + QVERIFY(object->property("page").value<QObject *>() != nullptr); + + QTest::ignoreMessage(QtDebugMsg, "dead"); + QTest::ignoreMessage(QtDebugMsg, "before"); + QTest::ignoreMessage(QtDebugMsg, "after"); + QTRY_VERIFY(object->property("page").value<QObject *>() == nullptr); +} + QTEST_MAIN(tst_qqmlbinding) #include "tst_qqmlbinding.moc" |
