Do not execute overwritten bindings
authorMatthew Vogt <matthew.vogt@nokia.com>
Tue, 20 Mar 2012 23:17:25 +0000 (09:17 +1000)
committerQt by Nokia <qt-info@nokia.com>
Wed, 4 Apr 2012 05:15:44 +0000 (07:15 +0200)
During the construction of an object, internal bindings can be
overwritten by the initialization of objects with outer scope.

To yield the expected result, suppress the evaluation of inner
bindings that are overwritten by outer bindings, during the
completion phase of object creation.

Task-number: QTBUG-23138
Change-Id: I679309543a9b64c774bb99798ad5ccf518726d10
Reviewed-by: Chris Adams <christopher.adams@nokia.com>
Reviewed-by: J-P Nurmi <j-p.nurmi@nokia.com>
src/qml/qml/qqmlvme.cpp
tests/auto/qml/qquickbinding/data/InnerObject.qml [new file with mode: 0644]
tests/auto/qml/qquickbinding/data/OuterObject.qml [new file with mode: 0644]
tests/auto/qml/qquickbinding/data/replaceBinding.qml [new file with mode: 0644]
tests/auto/qml/qquickbinding/tst_qquickbinding.cpp

index 21f07d96883116d3cab99f68e7f7f7c1256feec1..5534583dfa072b1ce480aab8d83fb453c30f4049 100644 (file)
 #include <QtCore/qvarlengtharray.h>
 #include <QtQml/qjsvalue.h>
 
+#include <utility>
+
 QT_BEGIN_NAMESPACE
 
+template <typename T1, typename T2>
+uint qHash(const std::pair<T1, T2> &p)
+{
+    return qHash(p.first) ^ qHash(p.second);
+}
+
 using namespace QQmlVMETypes;
 
 #define VME_EXCEPTION(desc, line) \
@@ -1216,18 +1224,40 @@ QQmlContextData *QQmlVME::complete(const Interrupt &interrupt)
     {
     QQmlTrace trace("VME Binding Enable");
     trace.event("begin binding eval");
-    while (!bindValues.isEmpty()) {
-        QQmlAbstractBinding *b = bindValues.pop();
 
-        if(b) {
-            b->m_mePtr = 0;
-            b->setEnabled(true, QQmlPropertyPrivate::BypassInterceptor | 
-                                QQmlPropertyPrivate::DontRemoveBinding);
-        }
+    size_t bindValuesRemaining = bindValues.count();
+    if (bindValuesRemaining > 0) {
+        typedef std::pair<QObject *, int> TargetProperty;
 
-        if (watcher.hasRecursed() || interrupt.shouldInterrupt())
-            return 0;
+        QSet<TargetProperty> boundProperties;
+        boundProperties.reserve(bindValuesRemaining - 1);
+
+        while (bindValuesRemaining > 0) {
+            QQmlAbstractBinding *b = bindValues.pop();
+            --bindValuesRemaining;
+
+            if (b) {
+                b->m_mePtr = 0;
+
+                TargetProperty property(std::make_pair(b->object(), b->propertyIndex()));
+                if (!boundProperties.contains(property)) {
+                    // We have not assigned a binding to this property yet
+                    b->setEnabled(true, QQmlPropertyPrivate::BypassInterceptor |
+                                        QQmlPropertyPrivate::DontRemoveBinding);
+
+                    if (bindValuesRemaining > 0) {
+                        boundProperties.insert(property);
+                    }
+                } else {
+                    b->destroy();
+                }
+            }
+
+            if (watcher.hasRecursed() || interrupt.shouldInterrupt())
+                return 0;
+        }
     }
+
     bindValues.deallocate();
     }
 
diff --git a/tests/auto/qml/qquickbinding/data/InnerObject.qml b/tests/auto/qml/qquickbinding/data/InnerObject.qml
new file mode 100644 (file)
index 0000000..a8ed959
--- /dev/null
@@ -0,0 +1,13 @@
+import QtQuick 2.0
+
+QtObject {
+    property int foo1: 100
+    property int foo2: 100
+    property int foo3: { return 100; }
+    property int foo4: { return 100; }
+
+    property string bar1: 'Hello'
+    property string bar2: 'Hello'
+    property string bar3: { return 'Hello'; }
+    property string bar4: { return 'Hello'; }
+}
diff --git a/tests/auto/qml/qquickbinding/data/OuterObject.qml b/tests/auto/qml/qquickbinding/data/OuterObject.qml
new file mode 100644 (file)
index 0000000..da571a9
--- /dev/null
@@ -0,0 +1,5 @@
+import QtQuick 2.0
+
+Item {
+    property InnerObject inner: InnerObject {}
+}
diff --git a/tests/auto/qml/qquickbinding/data/replaceBinding.qml b/tests/auto/qml/qquickbinding/data/replaceBinding.qml
new file mode 100644 (file)
index 0000000..670231a
--- /dev/null
@@ -0,0 +1,26 @@
+import QtQuick 2.0
+
+OuterObject {
+    property bool success: false
+
+    inner.foo1: 200
+    inner.foo2: { return 200; }
+    inner.foo3: 200
+    inner.foo4: { return 200; }
+
+    inner.bar1: 'Goodbye'
+    inner.bar2: { return 'Goodbye' }
+    inner.bar3: 'Goodbye'
+    inner.bar4: { return 'Goodbye' }
+
+    Component.onCompleted: {
+        success = (inner.foo1 == 200 &&
+                   inner.foo2 == 200 &&
+                   inner.foo3 == 200 &&
+                   inner.foo4 == 200 &&
+                   inner.bar1 == 'Goodbye' &&
+                   inner.bar2 == 'Goodbye' &&
+                   inner.bar3 == 'Goodbye' &&
+                   inner.bar4 == 'Goodbye');
+    }
+}
index 939c4a9e3d6f78e9cb78fa4bf786f6a43c181da6..0aef837e3becade02e53442b8c0d427a6ab73c90 100644 (file)
@@ -57,6 +57,7 @@ private slots:
     void restoreBinding();
     void restoreBindingWithLoop();
     void deletedObject();
+    void replaceBinding();
 
 private:
     QQmlEngine engine;
@@ -192,6 +193,17 @@ void tst_qquickbinding::deletedObject()
     delete rect;
 }
 
+void tst_qquickbinding::replaceBinding()
+{
+    QQmlEngine engine;
+    QQmlComponent c(&engine, testFileUrl("replaceBinding.qml"));
+    QObject *obj = c.create();
+    QVERIFY(obj != 0);
+
+    QVERIFY(obj->property("success").toBool());
+    delete obj;
+}
+
 QTEST_MAIN(tst_qquickbinding)
 
 #include "tst_qquickbinding.moc"