Ensure binding target has not been deleted
authorMatthew Vogt <matthew.vogt@nokia.com>
Thu, 26 Apr 2012 23:15:11 +0000 (09:15 +1000)
committerQt by Nokia <qt-info@nokia.com>
Fri, 4 May 2012 03:51:29 +0000 (05:51 +0200)
Prevent the evaluation of bindings if the target has been deleted.
Also, mark an item as queued for deletion at the beginning of the
destructor call chain, so that bindings triggered by the operation
of the destructor itself are not evaluated (after the context is
destructed, if necessary).

Task-number: QTBUG-25516
Change-Id: I587ef7923eb749eb7980156ad73822c1fb7c1ff3
Reviewed-by: Michael Brasser <michael.brasser@nokia.com>
src/qml/qml/qqmlbinding.cpp
src/qml/qml/qqmldata_p.h
src/qml/qml/qqmlengine.cpp
src/qml/qml/v4/qv4bindings.cpp
src/qml/qml/v8/qv8bindings.cpp
tests/auto/qml/qqmlecmascript/data/ContainerComponent.qml [new file with mode: 0644]
tests/auto/qml/qqmlecmascript/data/ContentComponent.qml [new file with mode: 0644]
tests/auto/qml/qqmlecmascript/data/bindingSuppression.qml [new file with mode: 0644]
tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp

index d2a02dc..45c2273 100644 (file)
@@ -186,6 +186,10 @@ void QQmlBinding::update(QQmlPropertyPrivate::WriteFlags flags)
     if (!enabledFlag() || !context() || !context()->isValid())
         return;
 
+    // Check that the target has not been deleted
+    if (QQmlData::wasDeleted(object()))
+        return;
+
     QQmlTrace trace("General Binding Update");
     trace.addDetail("URL", m_url);
     trace.addDetail("Line", m_lineNumber);
index e97bbb1..4849db0 100644 (file)
@@ -195,6 +195,10 @@ public:
     QHash<int, QObject *> *attachedProperties() const;
 
     static inline bool wasDeleted(QObject *);
+
+    static void markAsDeleted(QObject *);
+    static inline void setQueuedForDeletion(QObject *);
+
 private:
     // For objectNameNotifier and attachedProperties
     mutable QQmlDataExtended *extendedData;
@@ -205,7 +209,7 @@ bool QQmlData::wasDeleted(QObject *object)
     if (!object)
         return true;
 
-    QObjectPrivate *priv = QObjectPrivate::get(const_cast<QObject *>(object));
+    QObjectPrivate *priv = QObjectPrivate::get(object);
     if (priv->wasDeleted)
         return true;
 
@@ -213,6 +217,17 @@ bool QQmlData::wasDeleted(QObject *object)
            static_cast<QQmlData *>(priv->declarativeData)->isQueuedForDeletion;
 }
 
+void QQmlData::setQueuedForDeletion(QObject *object)
+{
+    if (object) {
+        if (QObjectPrivate *priv = QObjectPrivate::get(object)) {
+            if (!priv->wasDeleted && priv->declarativeData) {
+                static_cast<QQmlData *>(priv->declarativeData)->isQueuedForDeletion = true;
+            }
+        }
+    }
+}
+
 QQmlNotifierEndpoint *QQmlData::notify(int index)
 {
     Q_ASSERT(index <= 0xFFFF);
index 2717ed3..91c86a2 100644 (file)
@@ -422,6 +422,10 @@ void QQmlPrivate::qdeclarativeelement_destructor(QObject *o)
             d->context->destroy();
             d->context = 0;
         }
+
+        // Mark this object as in the process of deletion to
+        // prevent it resolving in bindings
+        QQmlData::markAsDeleted(o);
     }
 }
 
@@ -468,6 +472,16 @@ int QQmlData::endpointCount(int index)
     return count;
 }
 
+void QQmlData::markAsDeleted(QObject *o)
+{
+    QQmlData::setQueuedForDeletion(o);
+
+    QObjectPrivate *p = QObjectPrivate::get(o);
+    for (QList<QObject *>::iterator it = p->children.begin(), end = p->children.end(); it != end; ++it) {
+        QQmlData::markAsDeleted(*it);
+    }
+}
+
 void QQmlEnginePrivate::init()
 {
     Q_Q(QQmlEngine);
index b41fb28..dbbaa23 100644 (file)
@@ -358,6 +358,10 @@ void QV4Bindings::run(Binding *binding, QQmlPropertyPrivate::WriteFlags flags)
     if (!context || !context->isValid()) 
         return;
 
+    // Check that the target has not been deleted
+    if (QQmlData::wasDeleted(binding->target))
+        return;
+
     QQmlTrace trace("V4 Binding Update");
     trace.addDetail("URL", context->url);
     trace.addDetail("Line", binding->line);
index 65c395e..7cb14fb 100644 (file)
@@ -106,6 +106,14 @@ void QV8Bindings::Binding::update(QQmlPropertyPrivate::WriteFlags flags)
     if (!enabledFlag())
         return;
 
+    QQmlContextData *context = parent->context();
+    if (!context || !context->isValid())
+        return;
+
+    // Check that the target has not been deleted
+    if (QQmlData::wasDeleted(object()))
+        return;
+
     QQmlTrace trace("V8 Binding Update");
     trace.addDetail("URL", parent->url());
     trace.addDetail("Line", instruction->line);
@@ -113,12 +121,9 @@ void QV8Bindings::Binding::update(QQmlPropertyPrivate::WriteFlags flags)
 
     QQmlBindingProfiler prof(parent->urlString(), instruction->line, instruction->column);
 
-    QQmlContextData *context = parent->context();
-    if (!context || !context->isValid())
-        return;
-
     if (!updatingFlag()) {
         setUpdatingFlag(true);
+
         QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context->engine);
 
         bool isUndefined = false;
diff --git a/tests/auto/qml/qqmlecmascript/data/ContainerComponent.qml b/tests/auto/qml/qqmlecmascript/data/ContainerComponent.qml
new file mode 100644 (file)
index 0000000..459c82a
--- /dev/null
@@ -0,0 +1,4 @@
+import QtQuick 2.0
+
+Item {
+}
diff --git a/tests/auto/qml/qqmlecmascript/data/ContentComponent.qml b/tests/auto/qml/qqmlecmascript/data/ContentComponent.qml
new file mode 100644 (file)
index 0000000..7710624
--- /dev/null
@@ -0,0 +1,9 @@
+import QtQuick 2.0
+
+Item {
+    property bool validParentChildCount: parent && (parent.children.length > 0)
+
+    onValidParentChildCountChanged: {
+        if (!validParentChildCount) console.warn('WARNING: Invalid parent child count')
+    }
+}
diff --git a/tests/auto/qml/qqmlecmascript/data/bindingSuppression.qml b/tests/auto/qml/qqmlecmascript/data/bindingSuppression.qml
new file mode 100644 (file)
index 0000000..2d3bfa1
--- /dev/null
@@ -0,0 +1,17 @@
+import QtQuick 2.0
+
+Item {
+    id: root
+
+    Component.onCompleted: {
+        var container = containerComponent.createObject(root)
+        contentComponent.createObject(container)
+        container.destroy();
+
+        pendingEvents.process()
+    }
+
+    property Component containerComponent: ContainerComponent {}
+    property Component contentComponent: ContentComponent {}
+}
+
index 11819c5..781d594 100644 (file)
@@ -258,6 +258,7 @@ private slots:
     void replaceBinding();
     void deleteRootObjectInCreation();
     void onDestruction();
+    void bindingSuppression();
 
 private:
     static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter);
@@ -6658,6 +6659,35 @@ void tst_qqmlecmascript::onDestruction()
     }
 }
 
+struct EventProcessor : public QObject
+{
+    Q_OBJECT
+public:
+    Q_INVOKABLE void process()
+    {
+        QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
+        QCoreApplication::processEvents();
+    }
+};
+
+void tst_qqmlecmascript::bindingSuppression()
+{
+    QQmlEngine engine;
+    EventProcessor processor;
+    engine.rootContext()->setContextProperty("pendingEvents", &processor);
+
+    transientErrorsMsgCount = 0;
+    QtMsgHandler old = qInstallMsgHandler(transientErrorsMsgHandler);
+
+    QQmlComponent c(&engine, testFileUrl("bindingSuppression.qml"));
+    QObject *obj = c.create();
+    QVERIFY(obj != 0);
+    delete obj;
+
+    qInstallMsgHandler(old);
+    QCOMPARE(transientErrorsMsgCount, 0);
+}
+
 QTEST_MAIN(tst_qqmlecmascript)
 
 #include "tst_qqmlecmascript.moc"