Add garbage collector prologue callback to qv8engine
authorChris Adams <christopher.adams@nokia.com>
Fri, 26 Aug 2011 03:54:00 +0000 (13:54 +1000)
committerQt by Nokia <qt-info@nokia.com>
Tue, 6 Sep 2011 06:29:53 +0000 (08:29 +0200)
This commit provides a generic way to manage persistent handles
created by QML so that circular references don't cause leaks, by
utilising v8's garbage collector callbacks.

Change-Id: Ia898197fdf5d86b90915b835ce3e532f7d400de4
Reviewed-on: http://codereview.qt.nokia.com/3688
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>
src/declarative/qml/v8/qv8engine.cpp
src/declarative/qml/v8/qv8gccallback_p.h [new file with mode: 0644]
src/declarative/qml/v8/v8.pri
tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.1.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.2.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.1.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.2.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp
tests/auto/declarative/qdeclarativeecmascript/testtypes.h
tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp

index 5b74f48..6f58417 100644 (file)
@@ -43,6 +43,7 @@
 
 #include "qv8contextwrapper_p.h"
 #include "qv8valuetypewrapper_p.h"
+#include "qv8gccallback_p.h"
 #include "qv8include_p.h"
 #include "../../../3rdparty/javascriptcore/DateMath.h"
 
@@ -134,6 +135,7 @@ QV8Engine::QV8Engine(QJSEngine* qq, QJSEngine::ContextOwnership ownership)
     v8::Context::Scope context_scope(m_context);
 
     v8::V8::SetUserObjectComparisonCallbackFunction(ObjectComparisonCallback);
+    QV8GCCallback::registerGcPrologueCallback();
 
     m_stringWrapper.init();
     m_contextWrapper.init(this);
@@ -2293,5 +2295,142 @@ void QV8Engine::emitSignalHandlerException()
     emit q->signalHandlerException(scriptValueFromInternal(uncaughtException()));
 }
 
+QThreadStorage<QV8GCCallback::ThreadData *> QV8GCCallback::threadData;
+void QV8GCCallback::initializeThreadData()
+{
+    QV8GCCallback::ThreadData *newThreadData = new QV8GCCallback::ThreadData;
+    threadData.setLocalData(newThreadData);
+}
+
+void QV8GCCallback::registerGcPrologueCallback()
+{
+    if (!threadData.hasLocalData())
+        initializeThreadData();
+
+    QV8GCCallback::ThreadData *td = threadData.localData();
+    if (!td->gcPrologueCallbackRegistered) {
+        td->gcPrologueCallbackRegistered = true;
+        v8::V8::AddGCPrologueCallback(QV8GCCallback::garbageCollectorPrologueCallback, v8::kGCTypeMarkSweepCompact);
+    }
+}
+
+QV8GCCallback::Node::Node(PrologueCallback callback)
+    : prologueCallback(callback)
+{
+}
+
+QV8GCCallback::Node::~Node()
+{
+    node.remove();
+}
+
+QV8GCCallback::Referencer::Referencer()
+{
+    v8::HandleScope handleScope;
+    v8::Handle<v8::Context> context = v8::Context::New();
+    v8::Context::Scope contextScope(context);
+    strongReferencer = qPersistentNew(v8::Object::New());
+}
+
+void QV8GCCallback::Referencer::addRelationship(QObject *object, QObject *other)
+{
+    bool handleShouldBeStrong = false;
+    v8::Persistent<v8::Object> *implicitOwner = findOwnerAndStrength(object, &handleShouldBeStrong);
+    v8::Persistent<v8::Value> handle = QDeclarativeData::get(other, true)->v8object;
+    if (handleShouldBeStrong) {
+        v8::V8::AddImplicitReferences(strongReferencer, &handle, 1);
+    } else if (!implicitOwner->IsEmpty()) {
+        v8::V8::AddImplicitReferences(*implicitOwner, &handle, 1);
+    }
+}
+
+void QV8GCCallback::Referencer::addRelationship(QObject *object, v8::Persistent<v8::Value> handle)
+{
+    if (handle.IsEmpty())
+        return;
+
+    bool handleShouldBeStrong = false;
+    v8::Persistent<v8::Object> *implicitOwner = findOwnerAndStrength(object, &handleShouldBeStrong);
+    if (handleShouldBeStrong) {
+        v8::V8::AddImplicitReferences(strongReferencer, &handle, 1);
+    } else if (!implicitOwner->IsEmpty()) {
+        v8::V8::AddImplicitReferences(*implicitOwner, &handle, 1);
+    }
+}
+
+v8::Persistent<v8::Object> *QV8GCCallback::Referencer::findOwnerAndStrength(QObject *object, bool *shouldBeStrong)
+{
+    QObject *parent = object->parent();
+    if (!parent) {
+        // if the object has JS ownership, the object's v8object owns the lifetime of the persistent value.
+        if (QDeclarativeEngine::objectOwnership(object) == QDeclarativeEngine::JavaScriptOwnership) {
+            *shouldBeStrong = false;
+            return &(QDeclarativeData::get(object)->v8object);
+        }
+
+        // no parent, and has CPP ownership - doesn't have an implicit parent.
+        *shouldBeStrong = true;
+        return 0;
+    }
+
+    // if it is owned by CPP, it's root parent may still be owned by JS.
+    // in that case, the owner of the persistent handle is the root parent's v8object.
+    while (parent->parent())
+        parent = parent->parent();
+
+    if (QDeclarativeEngine::objectOwnership(parent) == QDeclarativeEngine::JavaScriptOwnership) {
+        // root parent is owned by JS.  It's v8object owns the persistent value in question.
+        *shouldBeStrong = false;
+        return &(QDeclarativeData::get(parent)->v8object);
+    } else {
+        // root parent has CPP ownership.  The persistent value should not be made weak.
+        *shouldBeStrong = true;
+        return 0;
+    }
+}
+
+/*
+   Ensure that each persistent handle is strong if it has CPP ownership
+   and has no implicitly JS owned object owner in its parent chain, and
+   weak otherwise.
+
+   Any weak handle whose parent object is still alive will have an implicit
+   reference (between the parent and the handle) added, so that it will
+   not be collected.
+
+   Note that this callback is registered only for kGCTypeMarkSweepCompact
+   collection cycles, as it is during collection cycles of that type
+   in which weak persistent handle callbacks are called when required.
+ */
+void QV8GCCallback::garbageCollectorPrologueCallback(v8::GCType, v8::GCCallbackFlags)
+{
+    if (!threadData.hasLocalData())
+        return;
+
+    QV8GCCallback::ThreadData *td = threadData.localData();
+    QV8GCCallback::Node *currNode = td->gcCallbackNodes.first();
+
+    while (currNode) {
+        // The client which adds itself to the list is responsible
+        // for maintaining the correct implicit references in the
+        // specified callback.
+        currNode->prologueCallback(&td->referencer, currNode);
+        currNode = td->gcCallbackNodes.next(currNode);
+    }
+}
+
+void QV8GCCallback::addGcCallbackNode(QV8GCCallback::Node *node)
+{
+    if (!threadData.hasLocalData())
+        initializeThreadData();
+
+    QV8GCCallback::ThreadData *td = threadData.localData();
+    td->gcCallbackNodes.insert(node);
+}
+
+QV8GCCallback::ThreadData::~ThreadData()
+{
+}
+
 QT_END_NAMESPACE
 
diff --git a/src/declarative/qml/v8/qv8gccallback_p.h b/src/declarative/qml/v8/qv8gccallback_p.h
new file mode 100644 (file)
index 0000000..095c0e5
--- /dev/null
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QV8GCCALLBACK_P_H
+#define QV8GCCALLBACK_P_H
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API.  It exists purely as an
+// implementation detail.  This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qobject.h>
+#include <QtCore/qthreadstorage.h>
+#include <private/qv8_p.h>
+#include <private/qintrusivelist_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_AUTOTEST_EXPORT QV8GCCallback
+{
+private:
+    class ThreadData;
+public:
+    static void garbageCollectorPrologueCallback(v8::GCType, v8::GCCallbackFlags);
+    static void registerGcPrologueCallback();
+
+    class Referencer {
+    public:
+        ~Referencer() {}
+        void addRelationship(QObject *object, v8::Persistent<v8::Value> handle);
+        void addRelationship(QObject *object, QObject *other);
+    private:
+        Referencer();
+        static v8::Persistent<v8::Object> *findOwnerAndStrength(QObject *qobjectOwner, bool *shouldBeStrong);
+        v8::Persistent<v8::Object> strongReferencer;
+        friend class QV8GCCallback::ThreadData;
+    };
+
+    class Node {
+    public:
+        typedef void (*PrologueCallback)(Referencer *r, Node *node);
+        Node(PrologueCallback callback);
+        ~Node();
+
+        QIntrusiveListNode node;
+        PrologueCallback prologueCallback;
+    };
+
+    static void addGcCallbackNode(Node *node);
+
+private:
+    class ThreadData {
+    public:
+        ThreadData() : gcPrologueCallbackRegistered(false) { }
+        ~ThreadData();
+        Referencer referencer;
+        bool gcPrologueCallbackRegistered;
+        QIntrusiveList<Node, &Node::node> gcCallbackNodes;
+    };
+
+    static void initializeThreadData();
+    static QThreadStorage<ThreadData *> threadData;
+};
+
+QT_END_NAMESPACE
+
+#endif // QV8GCCALLBACK_P_H
+
index 175efd6..4b64217 100644 (file)
@@ -8,6 +8,7 @@ HEADERS += \
     $$PWD/qv8debug_p.h \
     $$PWD/qv8stringwrapper_p.h \
     $$PWD/qv8engine_p.h \
+    $$PWD/qv8gccallback_p.h \
     $$PWD/qv8contextwrapper_p.h \
     $$PWD/qv8qobjectwrapper_p.h \
     $$PWD/qv8typewrapper_p.h \
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.1.qml b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.1.qml
new file mode 100644 (file)
index 0000000..9c27653
--- /dev/null
@@ -0,0 +1,25 @@
+import QtQuick 2.0
+import Qt.test 1.0
+
+Item {
+    id: obj
+    objectName: "obj"
+    property CircularReferenceHandle first
+    property CircularReferenceHandle second
+
+    CircularReferenceHandle {
+        id: crh
+        objectName: "crh"
+    }
+
+    function createReference() {
+        first = crh.generate(crh);
+        second = crh.generate(crh);
+        // NOTE: manually add reference from first to second
+        // in unit test prior reparenting and gc.
+    }
+
+    function performGc() {
+        gc();
+    }
+}
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.2.qml
new file mode 100644 (file)
index 0000000..dc19626
--- /dev/null
@@ -0,0 +1,26 @@
+import QtQuick 2.0
+import Qt.test 1.0
+
+Item {
+    id: obj
+    objectName: "obj"
+    property CircularReferenceHandle first
+    property CircularReferenceHandle second
+
+    CircularReferenceHandle {
+        id: crh
+        objectName: "crh"
+    }
+
+    function circularReference() {
+        // generate the circularly referential pair
+        first = crh.generate(crh);
+        second = crh.generate(crh);
+        // note: must manually reparent in unit test
+        // after setting the handle references.
+    }
+
+    function performGc() {
+        gc();
+    }
+}
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.1.qml b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.1.qml
new file mode 100644 (file)
index 0000000..4fd1311
--- /dev/null
@@ -0,0 +1,31 @@
+import QtQuick 2.0
+import Qt.test 1.0
+
+Item {
+    id: obj
+    objectName: "obj"
+
+    property CircularReferenceObject first
+    property CircularReferenceObject second
+
+
+    CircularReferenceObject {
+        id: cro
+        objectName: "cro"
+    }
+
+    function createReference() {
+        // generate the objects
+        first = cro.generate(cro); // has parent, so won't be collected
+        second = cro.generate();   // no parent, but will be kept alive by first's reference
+        first.addReference(second);
+
+        // remove top level references
+        first = cro;
+        second = cro;
+    }
+
+    function performGc() {
+        gc();
+    }
+}
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.2.qml
new file mode 100644 (file)
index 0000000..3f8415c
--- /dev/null
@@ -0,0 +1,32 @@
+import QtQuick 2.0
+import Qt.test 1.0
+
+Item {
+    id: obj
+    objectName: "obj"
+
+    property CircularReferenceObject first
+    property CircularReferenceObject second
+
+
+    CircularReferenceObject {
+        id: cro
+        objectName: "cro"
+    }
+
+    function circularReference() {
+        // generate the circularly referential pair - they should still be collected
+        first = cro.generate();  // no parent, so should be collected
+        second = cro.generate(); // no parent, so should be collected
+        first.addReference(second);
+        second.addReference(first);
+
+        // remove top level references
+        first = cro;
+        second = cro;
+    }
+
+    function performGc() {
+        gc();
+    }
+}
index 05d5510..0057c11 100644 (file)
@@ -189,6 +189,9 @@ void registerTypes()
 
     qRegisterMetaType<MyQmlObject::MyType>("MyEnum2");
     qRegisterMetaType<Qt::MouseButtons>("Qt::MouseButtons");
+
+    qmlRegisterType<CircularReferenceObject>("Qt.test", 1, 0, "CircularReferenceObject");
+    qmlRegisterType<CircularReferenceHandle>("Qt.test", 1, 0, "CircularReferenceHandle");
 }
 
 #include "testtypes.moc"
index 2738ee3..52b74af 100644 (file)
@@ -57,6 +57,9 @@
 #include <QtDeclarative/qdeclarativescriptstring.h>
 #include <QtDeclarative/qdeclarativecomponent.h>
 
+#include <private/qv8gccallback_p.h>
+#include <private/qdeclarativeengine_p.h>
+
 class MyQmlAttachedObject : public QObject
 {
     Q_OBJECT
@@ -966,6 +969,108 @@ private:
     int m_methodCallCount;
 };
 
+class CircularReferenceObject : public QObject,
+                                public QV8GCCallback::Node
+{
+    Q_OBJECT
+
+public:
+    CircularReferenceObject(QObject *parent = 0)
+        : QObject(parent), QV8GCCallback::Node(callback), m_referenced(0), m_dtorCount(0)
+    {
+        QV8GCCallback::addGcCallbackNode(this);
+    }
+
+    ~CircularReferenceObject()
+    {
+        if (m_dtorCount) *m_dtorCount = *m_dtorCount + 1;
+    }
+
+    Q_INVOKABLE void setDtorCount(int *dtorCount)
+    {
+        m_dtorCount = dtorCount;
+    }
+
+    Q_INVOKABLE CircularReferenceObject *generate(QObject *parent = 0)
+    {
+        CircularReferenceObject *retn = new CircularReferenceObject(parent);
+        retn->m_dtorCount = m_dtorCount;
+        return retn;
+    }
+
+    Q_INVOKABLE void addReference(QObject *other)
+    {
+        m_referenced = other;
+    }
+
+    static void callback(QV8GCCallback::Referencer *r, QV8GCCallback::Node *n)
+    {
+        CircularReferenceObject *cro = static_cast<CircularReferenceObject*>(n);
+        if (cro->m_referenced) {
+            r->addRelationship(cro, cro->m_referenced);
+        }
+    }
+
+private:
+    QObject *m_referenced;
+    int *m_dtorCount;
+};
+Q_DECLARE_METATYPE(CircularReferenceObject*)
+
+class CircularReferenceHandle : public QObject,
+                                public QV8GCCallback::Node
+{
+    Q_OBJECT
+
+public:
+    CircularReferenceHandle(QObject *parent = 0)
+        : QObject(parent), QV8GCCallback::Node(gccallback), m_dtorCount(0)
+    {
+        QV8GCCallback::addGcCallbackNode(this);
+    }
+
+    ~CircularReferenceHandle()
+    {
+        if (m_dtorCount) *m_dtorCount = *m_dtorCount + 1;
+    }
+
+    Q_INVOKABLE void setDtorCount(int *dtorCount)
+    {
+        m_dtorCount = dtorCount;
+    }
+
+    Q_INVOKABLE CircularReferenceHandle *generate(QObject *parent = 0)
+    {
+        CircularReferenceHandle *retn = new CircularReferenceHandle(parent);
+        retn->m_dtorCount = m_dtorCount;
+        return retn;
+    }
+
+    Q_INVOKABLE void addReference(v8::Persistent<v8::Value> handle)
+    {
+        m_referenced = qPersistentNew(handle);
+        m_referenced.MakeWeak(static_cast<void*>(this), wrcallback);
+    }
+
+    static void wrcallback(v8::Persistent<v8::Value> handle, void *params)
+    {
+        CircularReferenceHandle *crh = static_cast<CircularReferenceHandle*>(params);
+        qPersistentDispose(handle);
+        crh->m_referenced.Clear();
+    }
+
+    static void gccallback(QV8GCCallback::Referencer *r, QV8GCCallback::Node *n)
+    {
+        CircularReferenceHandle *crh = static_cast<CircularReferenceHandle*>(n);
+        r->addRelationship(crh, crh->m_referenced);
+    }
+
+private:
+    v8::Persistent<v8::Value> m_referenced;
+    int *m_dtorCount;
+};
+Q_DECLARE_METATYPE(CircularReferenceHandle*)
+
 void registerTypes();
 
 #endif // TESTTYPES_H
index d04b38b..b732cd8 100644 (file)
@@ -153,6 +153,7 @@ private slots:
     void elementAssign();
     void objectPassThroughSignals();
     void booleanConversion();
+    void handleReferenceManagement();
 
     void bug1();
     void bug2();
@@ -3210,6 +3211,256 @@ void tst_qdeclarativeecmascript::booleanConversion()
     delete object;
 }
 
+void tst_qdeclarativeecmascript::handleReferenceManagement()
+{
+
+    int dtorCount = 0;
+    {
+        // Linear QObject reference
+        QDeclarativeEngine hrmEngine;
+        QDeclarativeComponent component(&hrmEngine, TEST_FILE("handleReferenceManagement.object.1.qml"));
+        QObject *object = component.create();
+        QVERIFY(object != 0);
+        CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>("cro");
+        cro->setDtorCount(&dtorCount);
+        QMetaObject::invokeMethod(object, "createReference");
+        QMetaObject::invokeMethod(object, "performGc");
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 0); // second has JS ownership, kept alive by first's reference
+        delete object;
+        hrmEngine.collectGarbage();
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 3);
+    }
+
+    dtorCount = 0;
+    {
+        // Circular QObject reference
+        QDeclarativeEngine hrmEngine;
+        QDeclarativeComponent component(&hrmEngine, TEST_FILE("handleReferenceManagement.object.2.qml"));
+        QObject *object = component.create();
+        QVERIFY(object != 0);
+        CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>("cro");
+        cro->setDtorCount(&dtorCount);
+        QMetaObject::invokeMethod(object, "circularReference");
+        QMetaObject::invokeMethod(object, "performGc");
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 2); // both should be cleaned up, since circular references shouldn't keep alive.
+        delete object;
+        hrmEngine.collectGarbage();
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 3);
+    }
+
+    dtorCount = 0;
+    {
+        // Linear handle reference
+        QDeclarativeEngine hrmEngine;
+        QDeclarativeComponent component(&hrmEngine, TEST_FILE("handleReferenceManagement.handle.1.qml"));
+        QObject *object = component.create();
+        QVERIFY(object != 0);
+        CircularReferenceHandle *crh = object->findChild<CircularReferenceHandle*>("crh");
+        QVERIFY(crh != 0);
+        crh->setDtorCount(&dtorCount);
+        QMetaObject::invokeMethod(object, "createReference");
+        CircularReferenceHandle *first = object->property("first").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *second = object->property("second").value<CircularReferenceHandle*>();
+        QVERIFY(first != 0);
+        QVERIFY(second != 0);
+        first->addReference(QDeclarativeData::get(second)->v8object); // create reference
+        // now we have to reparent second and make second owned by JS.
+        second->setParent(0);
+        QDeclarativeEngine::setObjectOwnership(second, QDeclarativeEngine::JavaScriptOwnership);
+        QMetaObject::invokeMethod(object, "performGc");
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 0); // due to reference from first to second, second shouldn't be collected.
+        delete object;
+        hrmEngine.collectGarbage();
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 3);
+    }
+
+    dtorCount = 0;
+    {
+        // Circular handle reference
+        QDeclarativeEngine hrmEngine;
+        QDeclarativeComponent component(&hrmEngine, TEST_FILE("handleReferenceManagement.handle.2.qml"));
+        QObject *object = component.create();
+        QVERIFY(object != 0);
+        CircularReferenceHandle *crh = object->findChild<CircularReferenceHandle*>("crh");
+        QVERIFY(crh != 0);
+        crh->setDtorCount(&dtorCount);
+        QMetaObject::invokeMethod(object, "circularReference");
+        CircularReferenceHandle *first = object->property("first").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *second = object->property("second").value<CircularReferenceHandle*>();
+        QVERIFY(first != 0);
+        QVERIFY(second != 0);
+        first->addReference(QDeclarativeData::get(second)->v8object); // create circular reference
+        second->addReference(QDeclarativeData::get(first)->v8object); // note: must be weak.
+        // now we have to reparent and change ownership.
+        first->setParent(0);
+        second->setParent(0);
+        QDeclarativeEngine::setObjectOwnership(first, QDeclarativeEngine::JavaScriptOwnership);
+        QDeclarativeEngine::setObjectOwnership(second, QDeclarativeEngine::JavaScriptOwnership);
+        QMetaObject::invokeMethod(object, "performGc");
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 2); // despite circular references, both will be collected.
+        delete object;
+        hrmEngine.collectGarbage();
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 3);
+    }
+
+    dtorCount = 0;
+    {
+        // multiple engine interaction - linear reference
+        QDeclarativeEngine hrmEngine1;
+        QDeclarativeEngine hrmEngine2;
+        QDeclarativeComponent component1(&hrmEngine1, TEST_FILE("handleReferenceManagement.handle.1.qml"));
+        QDeclarativeComponent component2(&hrmEngine2, TEST_FILE("handleReferenceManagement.handle.1.qml"));
+        QObject *object1 = component1.create();
+        QObject *object2 = component2.create();
+        QVERIFY(object1 != 0);
+        QVERIFY(object2 != 0);
+        CircularReferenceHandle *crh1 = object1->findChild<CircularReferenceHandle*>("crh");
+        CircularReferenceHandle *crh2 = object2->findChild<CircularReferenceHandle*>("crh");
+        QVERIFY(crh1 != 0);
+        QVERIFY(crh2 != 0);
+        crh1->setDtorCount(&dtorCount);
+        crh2->setDtorCount(&dtorCount);
+        QMetaObject::invokeMethod(object1, "createReference");
+        QMetaObject::invokeMethod(object2, "createReference");
+        CircularReferenceHandle *first1 = object1->property("first").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *second1 = object1->property("second").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *first2 = object2->property("first").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *second2 = object2->property("second").value<CircularReferenceHandle*>();
+        QVERIFY(first1 != 0);
+        QVERIFY(second1 != 0);
+        QVERIFY(first2 != 0);
+        QVERIFY(second2 != 0);
+        first1->addReference(QDeclarativeData::get(second2)->v8object); // create reference across engines
+        // now we have to reparent second2 and make second2 owned by JS.
+        second2->setParent(0);
+        QDeclarativeEngine::setObjectOwnership(second2, QDeclarativeEngine::JavaScriptOwnership);
+        QMetaObject::invokeMethod(object1, "performGc");
+        QMetaObject::invokeMethod(object2, "performGc");
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 0); // due to reference from first1 to second2, second2 shouldn't be collected.
+        delete object1;
+        delete object2;
+        hrmEngine1.collectGarbage();
+        hrmEngine2.collectGarbage();
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 6);
+    }
+
+    dtorCount = 0;
+    {
+        // multiple engine interaction - circular reference
+        QDeclarativeEngine hrmEngine1;
+        QDeclarativeEngine hrmEngine2;
+        QDeclarativeComponent component1(&hrmEngine1, TEST_FILE("handleReferenceManagement.handle.1.qml"));
+        QDeclarativeComponent component2(&hrmEngine2, TEST_FILE("handleReferenceManagement.handle.1.qml"));
+        QObject *object1 = component1.create();
+        QObject *object2 = component2.create();
+        QVERIFY(object1 != 0);
+        QVERIFY(object2 != 0);
+        CircularReferenceHandle *crh1 = object1->findChild<CircularReferenceHandle*>("crh");
+        CircularReferenceHandle *crh2 = object2->findChild<CircularReferenceHandle*>("crh");
+        QVERIFY(crh1 != 0);
+        QVERIFY(crh2 != 0);
+        crh1->setDtorCount(&dtorCount);
+        crh2->setDtorCount(&dtorCount);
+        QMetaObject::invokeMethod(object1, "createReference");
+        QMetaObject::invokeMethod(object2, "createReference");
+        CircularReferenceHandle *first1 = object1->property("first").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *second1 = object1->property("second").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *first2 = object2->property("first").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *second2 = object2->property("second").value<CircularReferenceHandle*>();
+        QVERIFY(first1 != 0);
+        QVERIFY(second1 != 0);
+        QVERIFY(first2 != 0);
+        QVERIFY(second2 != 0);
+        first1->addReference(QDeclarativeData::get(second1)->v8object);  // create linear reference within engine1
+        second1->addReference(QDeclarativeData::get(second2)->v8object); // create linear reference across engines
+        second2->addReference(QDeclarativeData::get(first2)->v8object);  // create linear reference within engine2
+        first2->addReference(QDeclarativeData::get(first1)->v8object);   // close the loop - circular ref across engines
+        // now we have to reparent and change ownership to JS.
+        first1->setParent(0);
+        second1->setParent(0);
+        first2->setParent(0);
+        second2->setParent(0);
+        QDeclarativeEngine::setObjectOwnership(first1, QDeclarativeEngine::JavaScriptOwnership);
+        QDeclarativeEngine::setObjectOwnership(second1, QDeclarativeEngine::JavaScriptOwnership);
+        QDeclarativeEngine::setObjectOwnership(first2, QDeclarativeEngine::JavaScriptOwnership);
+        QDeclarativeEngine::setObjectOwnership(second2, QDeclarativeEngine::JavaScriptOwnership);
+        QMetaObject::invokeMethod(object1, "performGc");
+        QMetaObject::invokeMethod(object2, "performGc");
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 4); // circular references shouldn't keep them alive.
+        delete object1;
+        delete object2;
+        hrmEngine1.collectGarbage();
+        hrmEngine2.collectGarbage();
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 6);
+    }
+
+    dtorCount = 0;
+    {
+        // multiple engine interaction - linear reference with engine deletion
+        QDeclarativeEngine *hrmEngine1 = new QDeclarativeEngine;
+        QDeclarativeEngine *hrmEngine2 = new QDeclarativeEngine;
+        QDeclarativeComponent component1(hrmEngine1, TEST_FILE("handleReferenceManagement.handle.1.qml"));
+        QDeclarativeComponent component2(hrmEngine2, TEST_FILE("handleReferenceManagement.handle.1.qml"));
+        QObject *object1 = component1.create();
+        QObject *object2 = component2.create();
+        QVERIFY(object1 != 0);
+        QVERIFY(object2 != 0);
+        CircularReferenceHandle *crh1 = object1->findChild<CircularReferenceHandle*>("crh");
+        CircularReferenceHandle *crh2 = object2->findChild<CircularReferenceHandle*>("crh");
+        QVERIFY(crh1 != 0);
+        QVERIFY(crh2 != 0);
+        crh1->setDtorCount(&dtorCount);
+        crh2->setDtorCount(&dtorCount);
+        QMetaObject::invokeMethod(object1, "createReference");
+        QMetaObject::invokeMethod(object2, "createReference");
+        CircularReferenceHandle *first1 = object1->property("first").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *second1 = object1->property("second").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *first2 = object2->property("first").value<CircularReferenceHandle*>();
+        CircularReferenceHandle *second2 = object2->property("second").value<CircularReferenceHandle*>();
+        QVERIFY(first1 != 0);
+        QVERIFY(second1 != 0);
+        QVERIFY(first2 != 0);
+        QVERIFY(second2 != 0);
+        first1->addReference(QDeclarativeData::get(second1)->v8object);  // create linear reference within engine1
+        second1->addReference(QDeclarativeData::get(second2)->v8object); // create linear reference across engines
+        second2->addReference(QDeclarativeData::get(first2)->v8object);  // create linear reference within engine2
+        // now we have to reparent and change ownership to JS.
+        first1->setParent(crh1);
+        second1->setParent(0);
+        first2->setParent(0);
+        second2->setParent(0);
+        QDeclarativeEngine::setObjectOwnership(second1, QDeclarativeEngine::JavaScriptOwnership);
+        QDeclarativeEngine::setObjectOwnership(first2, QDeclarativeEngine::JavaScriptOwnership);
+        QDeclarativeEngine::setObjectOwnership(second2, QDeclarativeEngine::JavaScriptOwnership);
+        QMetaObject::invokeMethod(object1, "performGc");
+        QMetaObject::invokeMethod(object2, "performGc");
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 0);
+        delete hrmEngine2;
+        QMetaObject::invokeMethod(object1, "performGc");
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 0);
+        delete object1;
+        delete object2;
+        hrmEngine1->collectGarbage();
+        QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+        QCOMPARE(dtorCount, 6);
+        delete hrmEngine1;
+    }
+}
+
 // Test that assigning a null object works 
 // Regressed with: df1788b4dbbb2826ae63f26bdf166342595343f4
 void tst_qdeclarativeecmascript::nullObjectBinding()