aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/qml/qqmlvmemetaobject.cpp34
-rw-r--r--src/qml/qml/v8/qv8engine.cpp12
-rw-r--r--tests/auto/qml/qqmlecmascript/data/HRMDPComponent.qml15
-rw-r--r--tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.2.qml31
-rw-r--r--tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.3.qml34
-rw-r--r--tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.qml31
-rw-r--r--tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.handle.2.qml5
-rw-r--r--tests/auto/qml/qqmlecmascript/data/signalEmitted.3.qml17
-rw-r--r--tests/auto/qml/qqmlecmascript/data/signalEmitted.4.qml15
-rw-r--r--tests/auto/qml/qqmlecmascript/testtypes.h2
-rw-r--r--tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp63
11 files changed, 230 insertions, 29 deletions
diff --git a/src/qml/qml/qqmlvmemetaobject.cpp b/src/qml/qml/qqmlvmemetaobject.cpp
index 50fda6db39..2434ef0a2b 100644
--- a/src/qml/qml/qqmlvmemetaobject.cpp
+++ b/src/qml/qml/qqmlvmemetaobject.cpp
@@ -515,6 +515,9 @@ QQmlVMEMetaObject::QQmlVMEMetaObject(QObject *obj,
aConnected.resize(metaData->aliasCount);
int list_type = qMetaTypeId<QQmlListProperty<QObject> >();
+ int qobject_type = qMetaTypeId<QObject*>();
+ int variant_type = qMetaTypeId<QVariant>();
+ bool needsGcCallback = (metaData->varPropertyCount > 0);
// ### Optimize
for (int ii = 0; ii < metaData->propertyCount - metaData->varPropertyCount; ++ii) {
@@ -522,12 +525,20 @@ QQmlVMEMetaObject::QQmlVMEMetaObject(QObject *obj,
if (t == list_type) {
listProperties.append(List(methodOffset() + ii, this));
data[ii].setValue(listProperties.count() - 1);
- }
+ } else if (!needsGcCallback && (t == qobject_type || t == variant_type)) {
+ needsGcCallback = true;
+ }
}
firstVarPropertyIndex = metaData->propertyCount - metaData->varPropertyCount;
- if (metaData->varPropertyCount)
+
+ // both var properties and variant properties can keep references to
+ // other QObjects, and var properties can also keep references to
+ // JavaScript objects. If we have any properties, we need to hook
+ // the gc() to ensure that references keep objects alive as needed.
+ if (needsGcCallback) {
QV8GCCallback::addGcCallbackNode(this);
+ }
}
QQmlVMEMetaObject::~QQmlVMEMetaObject()
@@ -1121,9 +1132,26 @@ void QQmlVMEMetaObject::GcPrologueCallback(QV8GCCallback::Node *node)
{
QQmlVMEMetaObject *vmemo = static_cast<QQmlVMEMetaObject*>(node);
Q_ASSERT(vmemo);
- if (!vmemo->varPropertiesInitialized || vmemo->varProperties.IsEmpty() || !vmemo->ctxt || !vmemo->ctxt->engine)
+
+ if (!vmemo->ctxt || !vmemo->ctxt->engine)
return;
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(vmemo->ctxt->engine);
+
+ // add references created by VMEVariant properties
+ int maxDataIdx = vmemo->metaData->propertyCount - vmemo->metaData->varPropertyCount;
+ for (int ii = 0; ii < maxDataIdx; ++ii) { // XXX TODO: optimise?
+ if (vmemo->data[ii].dataType() == QMetaType::QObjectStar) {
+ // possible QObject reference.
+ QObject *ref = vmemo->data[ii].asQObject();
+ if (ref) {
+ ep->v8engine()->addRelationshipForGC(vmemo->object, ref);
+ }
+ }
+ }
+
+ // add references created by var properties
+ if (!vmemo->varPropertiesInitialized || vmemo->varProperties.IsEmpty())
+ return;
ep->v8engine()->addRelationshipForGC(vmemo->object, vmemo->varProperties);
}
diff --git a/src/qml/qml/v8/qv8engine.cpp b/src/qml/qml/v8/qv8engine.cpp
index 85a2f1b379..732a04d437 100644
--- a/src/qml/qml/v8/qv8engine.cpp
+++ b/src/qml/qml/v8/qv8engine.cpp
@@ -836,7 +836,7 @@ v8::Persistent<v8::Object> *QV8Engine::findOwnerAndStrength(QObject *object, boo
void QV8Engine::addRelationshipForGC(QObject *object, v8::Persistent<v8::Value> handle)
{
- if (handle.IsEmpty())
+ if (!object || handle.IsEmpty())
return;
bool handleShouldBeStrong = false;
@@ -850,14 +850,18 @@ void QV8Engine::addRelationshipForGC(QObject *object, v8::Persistent<v8::Value>
void QV8Engine::addRelationshipForGC(QObject *object, QObject *other)
{
+ if (!object || !other)
+ return;
+
bool handleShouldBeStrong = false;
v8::Persistent<v8::Object> *implicitOwner = findOwnerAndStrength(object, &handleShouldBeStrong);
v8::Persistent<v8::Value> handle = QQmlData::get(other, true)->v8object;
- if (handleShouldBeStrong) {
+ if (handle.IsEmpty()) // no JS data to keep alive.
+ return;
+ else if (handleShouldBeStrong)
v8::V8::AddImplicitReferences(m_strongReferencer, &handle, 1);
- } else if (!implicitOwner->IsEmpty()) {
+ else if (!implicitOwner->IsEmpty())
v8::V8::AddImplicitReferences(*implicitOwner, &handle, 1);
- }
}
static QThreadStorage<QV8Engine::ThreadData*> perThreadEngineData;
diff --git a/tests/auto/qml/qqmlecmascript/data/HRMDPComponent.qml b/tests/auto/qml/qqmlecmascript/data/HRMDPComponent.qml
new file mode 100644
index 0000000000..897fd990ec
--- /dev/null
+++ b/tests/auto/qml/qqmlecmascript/data/HRMDPComponent.qml
@@ -0,0 +1,15 @@
+import QtQuick 2.0
+import Qt.test.qobjectApi 1.0 as QObjectApi
+
+Item {
+ property int variantCanary: 5
+ property var varCanary: 12
+
+ Component.onCompleted: {
+ QObjectApi.qobjectTestWritableProperty = 42;
+ }
+
+ Component.onDestruction: {
+ QObjectApi.qobjectTestWritableProperty = 43;
+ }
+}
diff --git a/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.2.qml b/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.2.qml
new file mode 100644
index 0000000000..00f09e159c
--- /dev/null
+++ b/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.2.qml
@@ -0,0 +1,31 @@
+import QtQuick 2.0
+import Qt.test.qobjectApi 1.0 as QObjectApi
+
+Item {
+ property bool success: true
+ property Item testProp: null
+
+ // first we create an object and reference it via a dynamic variant property
+ function createReference() {
+ var c = Qt.createComponent("HRMDPComponent.qml");
+ testProp = c.createObject(null); // QML ownership.
+ }
+
+ // after a gc, it should not have been collected.
+ function ensureReference() {
+ if (testProp == null) success = false; // should not have triggered delete notify / zeroed testProp value
+ if (testProp.variantCanary != 5) success = false; // should not have deleted vmemo of object referenced by testProp
+ if (testProp.varCanary != 12) success = false; // should not have collected vmemo vmeProperties
+ if (QObjectApi.qobjectTestWritableProperty != 42) success = false; // should not have been set to 43.
+ }
+
+ // then we remove the reference.
+ function removeReference() {
+ testProp = null; // allow original object to be released.
+ }
+
+ // after a gc (and deferred deletion process) the object should be gone
+ function ensureDeletion() {
+ if (QObjectApi.qobjectTestWritableProperty != 43) success = false; // should have been set to 43.
+ }
+}
diff --git a/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.3.qml b/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.3.qml
new file mode 100644
index 0000000000..57fee6384b
--- /dev/null
+++ b/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.3.qml
@@ -0,0 +1,34 @@
+import QtQuick 2.0
+import Qt.test.qobjectApi 1.0 as QObjectApi
+
+Item {
+ property bool success: true
+ property Item testProp: null
+
+ // first we create an object and reference it via a dynamic variant property
+ function createReference() {
+ var c = Qt.createComponent("HRMDPComponent.qml");
+ testProp = c.createObject(null); // QML ownership.
+ }
+
+ // after a gc, it should not have been collected.
+ function ensureReference() {
+ if (testProp == null) success = false; // should not have triggered delete notify / zeroed testProp value
+ if (testProp.variantCanary != 5) success = false; // should not have deleted vmemo of object referenced by testProp
+ if (testProp.varCanary != 12) success = false; // should not have collected vmemo vmeProperties
+ if (QObjectApi.qobjectTestWritableProperty != 42) success = false; // should not have been set to 43.
+ }
+
+ // then we manually delete the item being referenced
+ function manuallyDelete() {
+ QObjectApi.deleteQObject(testProp);
+ if (QObjectApi.qobjectTestWritableProperty != 43) success = false; // should have been set to 43.
+ }
+
+ // after a gc (and deferred deletion process) the object should be gone
+ function ensureDeleted() {
+ // a crash should not have occurred during the previous gc due to the
+ // VMEMO attempting to keep a previously deleted QObject alive.
+ if (testProp != null) success = false; // delete notify should have zeroed testProp value.
+ }
+}
diff --git a/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.qml b/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.qml
new file mode 100644
index 0000000000..30dd4bcaea
--- /dev/null
+++ b/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.dynprop.qml
@@ -0,0 +1,31 @@
+import QtQuick 2.0
+import Qt.test.qobjectApi 1.0 as QObjectApi
+
+Item {
+ property bool success: true
+ property variant testProp: null
+
+ // first we create an object and reference it via a dynamic variant property
+ function createReference() {
+ var c = Qt.createComponent("HRMDPComponent.qml");
+ testProp = c.createObject(null); // QML ownership.
+ }
+
+ // after a gc, it should not have been collected.
+ function ensureReference() {
+ if (testProp == null) success = false; // should not have triggered delete notify / zeroed testProp value
+ if (testProp.variantCanary != 5) success = false; // should not have deleted vmemo of object referenced by testProp
+ if (testProp.varCanary != 12) success = false; // should not have collected vmemo vmeProperties
+ if (QObjectApi.qobjectTestWritableProperty != 42) success = false; // should not have been set to 43.
+ }
+
+ // then we remove the reference.
+ function removeReference() {
+ testProp = null; // allow original object to be released.
+ }
+
+ // after a gc (and deferred deletion process) the object should be gone
+ function ensureDeletion() {
+ if (QObjectApi.qobjectTestWritableProperty != 43) success = false; // should have been set to 43.
+ }
+}
diff --git a/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.handle.2.qml b/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.handle.2.qml
index 91edc447e2..d0999134a8 100644
--- a/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.handle.2.qml
+++ b/tests/auto/qml/qqmlecmascript/data/handleReferenceManagement.handle.2.qml
@@ -17,6 +17,9 @@ Item {
first = crh.generate(crh);
second = crh.generate(crh);
// note: must manually reparent in unit test
- // after setting the handle references.
+ // after setting the handle references, and
+ // then set the "first" and "second" property
+ // values to null (removing references from obj
+ // to the generated objects).
}
}
diff --git a/tests/auto/qml/qqmlecmascript/data/signalEmitted.3.qml b/tests/auto/qml/qqmlecmascript/data/signalEmitted.3.qml
index 0cae5c02d8..8a09414478 100644
--- a/tests/auto/qml/qqmlecmascript/data/signalEmitted.3.qml
+++ b/tests/auto/qml/qqmlecmascript/data/signalEmitted.3.qml
@@ -7,16 +7,16 @@ Item {
property bool success: false
property bool c1HasBeenDestroyed: false
- property Item c1 // not a js reference, so won't keep it alive
-
SignalEmittedComponent {
id: c2
- property int c1a: if (root.c1) root.c1.a; else 0; // will change during onDestruction handler of c1.
function c1aChangedHandler() {
// this should still be called, after c1 has been destroyed by gc,
// because the onDestruction handler of c1 will be triggered prior
// to when c1 will be invalidated.
- if (root.c1HasBeenDestroyed && c1a == 20) root.c1.setSuccessPropertyOf(root, true);
+ if (root.c1HasBeenDestroyed)
+ root.success = true;
+ // note: cannot call c1::setSuccessPropertyOf(root, true), since any
+ // reference to c1 would have kept c1 alive. So, set it directly.
}
}
@@ -24,11 +24,10 @@ Item {
// dynamically construct sibling. When it goes out of scope, it should be gc'd.
// note that the gc() will call weakqobjectcallback which will set queued for
// deletion flag -- thus QQmlData::wasDeleted() will return true for that object..
- var c = Qt.createComponent("SignalEmittedComponent.qml", root);
- var o = c.createObject(null); // JS ownership
- o.onAChanged.connect(c2.c1aChangedHandler);
- c1 = o;
- c1HasBeenDestroyed = true;
+ var comp = Qt.createComponent("SignalEmittedComponent.qml", root);
+ var c1 = comp.createObject(null); // JS ownership
+ c1.onAChanged.connect(c2.c1aChangedHandler);
+ c1HasBeenDestroyed = true; // gc will collect c1.
// return to event loop.
}
}
diff --git a/tests/auto/qml/qqmlecmascript/data/signalEmitted.4.qml b/tests/auto/qml/qqmlecmascript/data/signalEmitted.4.qml
index e7dc0243fd..764ed6e6ca 100644
--- a/tests/auto/qml/qqmlecmascript/data/signalEmitted.4.qml
+++ b/tests/auto/qml/qqmlecmascript/data/signalEmitted.4.qml
@@ -7,16 +7,14 @@ Item {
property bool success: false
property bool c1HasBeenDestroyed: false
- property Item c1 // not a js reference, so won't keep it alive
-
SignalEmittedComponent {
id: c2
- property int c1a: if (root.c1) root.c1.a; else 0; // will change during onDestruction handler of c1.
function c1aChangedHandler() {
// this should still be called, after c1 has been destroyed by gc,
// because the onDestruction handler of c1 will be triggered prior
// to when c1 will be invalidated.
- if (root.c1HasBeenDestroyed && c1a == 20) root.c1.setSuccessPropertyOf(root, true);
+ if (root.c1HasBeenDestroyed)
+ root.success = true;
}
}
@@ -24,11 +22,10 @@ Item {
// dynamically construct sibling. When it goes out of scope, it should be gc'd.
// note that the gc() will call weakqobjectcallback which will set queued for
// deletion flag -- thus QQmlData::wasDeleted() will return true for that object..
- var c = Qt.createComponent("SignalEmittedComponent.qml", root);
- var o = c.createObject(null); // JS ownership
- o.onAChanged.connect(c2.c1aChangedHandler);
- c1 = o;
- c1HasBeenDestroyed = true;
+ var comp = Qt.createComponent("SignalEmittedComponent.qml", root);
+ var c1 = comp.createObject(null); // JS ownership
+ c1.onAChanged.connect(c2.c1aChangedHandler);
+ c1HasBeenDestroyed = true; // gc will collect c1.
// return to event loop.
}
diff --git a/tests/auto/qml/qqmlecmascript/testtypes.h b/tests/auto/qml/qqmlecmascript/testtypes.h
index 8e2b68f51c..3c12264852 100644
--- a/tests/auto/qml/qqmlecmascript/testtypes.h
+++ b/tests/auto/qml/qqmlecmascript/testtypes.h
@@ -1052,6 +1052,8 @@ public:
return ddata->indestructible?false:true;
}
+ Q_INVOKABLE void deleteQObject(QObject *obj) { delete obj; }
+
signals:
void qobjectTestPropertyChanged(int testProperty);
void qobjectTestWritablePropertyChanged(int testWritableProperty);
diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
index a460206113..ff2382659c 100644
--- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
+++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
@@ -4819,11 +4819,13 @@ void tst_qqmlecmascript::handleReferenceManagement()
QVERIFY(second != 0);
first->addReference(QQmlData::get(second)->v8object); // create circular reference
second->addReference(QQmlData::get(first)->v8object); // note: must be weak.
- // now we have to reparent and change ownership.
+ // now we have to reparent and change ownership, and unset the property references.
first->setParent(0);
second->setParent(0);
QQmlEngine::setObjectOwnership(first, QQmlEngine::JavaScriptOwnership);
QQmlEngine::setObjectOwnership(second, QQmlEngine::JavaScriptOwnership);
+ object->setProperty("first", QVariant::fromValue<QObject*>(0));
+ object->setProperty("second", QVariant::fromValue<QObject*>(0));
gc(engine);
QCOMPARE(dtorCount, 2); // despite circular references, both will be collected.
delete object;
@@ -4912,7 +4914,7 @@ void tst_qqmlecmascript::handleReferenceManagement()
second1->addReference(QQmlData::get(second2)->v8object); // create linear reference across engines
second2->addReference(QQmlData::get(first2)->v8object); // create linear reference within engine2
first2->addReference(QQmlData::get(first1)->v8object); // close the loop - circular ref across engines
- // now we have to reparent and change ownership to JS.
+ // now we have to reparent and change ownership to JS, and remove property references.
first1->setParent(0);
second1->setParent(0);
first2->setParent(0);
@@ -4921,6 +4923,10 @@ void tst_qqmlecmascript::handleReferenceManagement()
QQmlEngine::setObjectOwnership(second1, QQmlEngine::JavaScriptOwnership);
QQmlEngine::setObjectOwnership(first2, QQmlEngine::JavaScriptOwnership);
QQmlEngine::setObjectOwnership(second2, QQmlEngine::JavaScriptOwnership);
+ object1->setProperty("first", QVariant::fromValue<QObject*>(0));
+ object1->setProperty("second", QVariant::fromValue<QObject*>(0));
+ object2->setProperty("first", QVariant::fromValue<QObject*>(0));
+ object2->setProperty("second", QVariant::fromValue<QObject*>(0));
gc(engine);
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
QCoreApplication::processEvents();
@@ -4989,6 +4995,57 @@ void tst_qqmlecmascript::handleReferenceManagement()
QCoreApplication::processEvents();
QCOMPARE(dtorCount, 6); // all objects should have been cleaned up prior to deleting hrmEngine1.
}
+
+ {
+ // Dynamic variant property reference keeps target alive
+ QQmlEngine hrmEngine;
+ QQmlComponent component(&hrmEngine, testFileUrl("handleReferenceManagement.dynprop.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QMetaObject::invokeMethod(object, "createReference");
+ gc(engine);
+ QMetaObject::invokeMethod(object, "ensureReference");
+ gc(engine);
+ QMetaObject::invokeMethod(object, "removeReference");
+ gc(engine);
+ QMetaObject::invokeMethod(object, "ensureDeletion");
+ QCOMPARE(object->property("success").toBool(), true);
+ delete object;
+ }
+
+ {
+ // Dynamic Item property reference keeps target alive
+ QQmlEngine hrmEngine;
+ QQmlComponent component(&hrmEngine, testFileUrl("handleReferenceManagement.dynprop.2.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QMetaObject::invokeMethod(object, "createReference");
+ gc(engine);
+ QMetaObject::invokeMethod(object, "ensureReference");
+ gc(engine);
+ QMetaObject::invokeMethod(object, "removeReference");
+ gc(engine);
+ QMetaObject::invokeMethod(object, "ensureDeletion");
+ QCOMPARE(object->property("success").toBool(), true);
+ delete object;
+ }
+
+ {
+ // Item property reference to deleted item doesn't crash
+ QQmlEngine hrmEngine;
+ QQmlComponent component(&hrmEngine, testFileUrl("handleReferenceManagement.dynprop.3.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QMetaObject::invokeMethod(object, "createReference");
+ gc(engine);
+ QMetaObject::invokeMethod(object, "ensureReference");
+ gc(engine);
+ QMetaObject::invokeMethod(object, "manuallyDelete");
+ gc(engine);
+ QMetaObject::invokeMethod(object, "ensureDeleted");
+ QCOMPARE(object->property("success").toBool(), true);
+ delete object;
+ }
}
void tst_qqmlecmascript::stringArg()
@@ -6828,7 +6885,7 @@ void tst_qqmlecmascript::signalEmitted()
QVERIFY(obj != 0);
gc(engine); // should collect c1.
QMetaObject::invokeMethod(obj, "destroyC2");
- QTRY_VERIFY(obj->property("success").toBool());
+ QTRY_VERIFY(obj->property("success").toBool()); // handles events (incl. delete later).
delete obj;
}
}