for (; i != m_javaScriptOwnedWeakQObjects.end(); ++i) {
QV8QObjectResource *resource = *i;
Q_ASSERT(resource);
- deleteWeakQObject(resource);
+ deleteWeakQObject(resource, true);
}
}
Q_ASSERT(resource);
static_cast<QV8QObjectWrapper*>(wrapper)->unregisterWeakQObjectReference(resource);
- static_cast<QV8QObjectWrapper*>(wrapper)->deleteWeakQObject(resource);
-
- qPersistentDispose(handle);
+ if (static_cast<QV8QObjectWrapper*>(wrapper)->deleteWeakQObject(resource, false)) {
+ qPersistentDispose(handle); // dispose.
+ } else {
+ handle.MakeWeak(0, WeakQObjectReferenceCallback); // revive.
+ }
}
static void WeakQObjectInstanceCallback(v8::Persistent<v8::Value> handle, void *data)
return v8::Local<v8::Object>::New((*iter)->v8object);
}
}
-void QV8QObjectWrapper::deleteWeakQObject(QV8QObjectResource *resource)
+
+// returns true if the object's qqmldata v8object handle should
+// be disposed by the caller, false if it should not be (due to
+// creation status, etc).
+bool QV8QObjectWrapper::deleteWeakQObject(QV8QObjectResource *resource, bool calledFromEngineDtor)
{
QObject *object = resource->object;
if (object) {
QQmlData *ddata = QQmlData::get(object, false);
if (ddata) {
- if (ddata->rootObjectInCreation) {
- ddata->v8object.MakeWeak(0, WeakQObjectReferenceCallback);
- return;
+ if (!calledFromEngineDtor && ddata->rootObjectInCreation) {
+ // if weak ref callback is triggered (by gc) for a root object
+ // prior to completion of creation, we should NOT delete it.
+ return false;
}
ddata->v8object.Clear();
}
}
}
+
+ return true;
}
QPair<QObject *, int> QV8QObjectWrapper::ExtractQtSignal(QV8Engine *engine, v8::Handle<v8::Object> object)
friend class QV8QObjectInstance;
v8::Local<v8::Object> newQObject(QObject *, QQmlData *, QV8Engine *);
- void deleteWeakQObject(QV8QObjectResource *resource);
+ bool deleteWeakQObject(QV8QObjectResource *resource, bool calledFromEngineDtor = false);
static v8::Handle<v8::Value> GetProperty(QV8Engine *, QObject *, v8::Handle<v8::Value> *,
const QHashedV8String &, QV8QObjectWrapper::RevisionMode);
static bool SetProperty(QV8Engine *, QObject *, const QHashedV8String &,
--- /dev/null
+import QtQuick 2.0
+import Qt.test.qobjectApi 1.0 as ModApi
+
+Rectangle {
+ id: base
+ color: "red"
+
+ function flipOwnership() {
+ ModApi.trackObject(base);
+ ModApi.trackedObject(); // flip the ownership.
+ if (!ModApi.trackedObjectHasJsOwnership())
+ derived.testConditionsMet = false;
+ else
+ derived.testConditionsMet = true;
+ }
+
+ onColorChanged: {
+ // will be triggered during beginCreate of derived
+ flipOwnership();
+ gc();
+ gc();
+ gc();
+ }
+}
--- /dev/null
+import QtQuick 2.0
+
+DeleteRootObjectInCreationComponentBase {
+ id: derived
+ color: "black" // will trigger change signal during beginCreate.
+
+ property bool testConditionsMet: false // will be set by base
+ function setTestConditionsMet(obj) {
+ obj.testConditionsMet = derived.testConditionsMet;
+ }
+}
--- /dev/null
+import QtQuick 2.0
+
+Item {
+ property int a: 50
+}
--- /dev/null
+import QtQuick 2.0
+import Qt.test.qobjectApi 1.0 as ModApi
+
+Rectangle {
+ id: base
+ x: 1
+ color: "red"
+ property bool testConditionsMet: false
+
+ onXChanged: {
+ ModApi.trackObject(base);
+ ModApi.trackedObject(); // flip the ownership.
+ if (!ModApi.trackedObjectHasJsOwnership())
+ testConditionsMet = false;
+ else
+ testConditionsMet = true;
+ }
+
+ onColorChanged: {
+ // will be triggered during beginCreate of derived
+ test.testConditionsMet = testConditionsMet;
+ gc();
+ gc();
+ gc();
+ }
+}
--- /dev/null
+import QtQuick 2.0
+
+QQmlDataDestroyedComponent2Base {
+ id: derived
+ color: "black" // will trigger change signal during beginCreate.
+ x: 2 // flip ownership of base
+}
--- /dev/null
+import QtQuick 2.0
+
+Item {
+ id: test
+ property bool testConditionsMet: false
+ Component.onCompleted: {
+ var c = Qt.createComponent("DeleteRootObjectInCreationComponentDerived.qml")
+ c.createObject(null).setTestConditionsMet(test); // JS ownership, but it will be a RootObjectInCreation until finished beginCreate.
+ }
+}
--- /dev/null
+import QtQuick 2.0
+
+Item {
+ id: test
+ property bool testConditionsMet: false
+ Component.onCompleted: {
+ var c = Qt.createComponent("QQmlDataDestroyedComponent2Derived.qml")
+ c.createObject(test); // Cpp ownership, but it will be a RootObjectInCreation until finished beginCreate.
+ }
+}
--- /dev/null
+import QtQuick 2.0
+
+Item {
+ Component.onCompleted: {
+ var c = Qt.createComponent("QQmlDataDestroyedComponent.qml")
+ var o = c.createObject(null); // JS ownership
+ }
+}
int qobjectTestWritableFinalProperty() const { return m_testWritableFinalProperty; }
void setQObjectTestWritableFinalProperty(int tp) { m_testWritableFinalProperty = tp; emit qobjectTestWritableFinalPropertyChanged(); }
+ Q_INVOKABLE bool trackedObjectHasJsOwnership() {
+ QObject * object = m_trackedObject;
+
+ if (!object)
+ return false;
+
+ QQmlData *ddata = QQmlData::get(object, false);
+ if (!ddata)
+ return false;
+ else
+ return ddata->indestructible?false:true;
+ }
+
signals:
void qobjectTestPropertyChanged(int testProperty);
void qobjectTestWritablePropertyChanged(int testWritableProperty);
void bindingSuppression();
void signalEmitted();
void threadSignal();
+ void qqmldataDestroyed();
private:
static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter);
void tst_qqmlecmascript::deleteRootObjectInCreation()
{
+ {
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("deleteRootObjectInCreation.qml"));
QObject *obj = c.create();
QTest::qWait(1);
QVERIFY(obj->property("childDestructible").toBool());
delete obj;
+ }
+
+ {
+ QQmlComponent c(&engine, testFileUrl("deleteRootObjectInCreation.2.qml"));
+ QObject *object = c.create();
+ QVERIFY(object != 0);
+ QVERIFY(object->property("testConditionsMet").toBool());
+ delete object;
+ }
}
void tst_qqmlecmascript::onDestruction()
delete object;
}
+// ensure that the qqmldata::destroyed() handler doesn't cause problems
+void tst_qqmlecmascript::qqmldataDestroyed()
+{
+ // gc cleans up a qobject, later the qqmldata destroyed handler will run.
+ {
+ QQmlComponent c(&engine, testFileUrl("qqmldataDestroyed.qml"));
+ QObject *object = c.create();
+ QVERIFY(object != 0);
+ // now gc causing the collection of the dynamically constructed object.
+ engine.collectGarbage();
+ engine.collectGarbage();
+ // now process events to allow deletion (calling qqmldata::destroyed())
+ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
+ QCoreApplication::processEvents();
+ // shouldn't crash.
+ delete object;
+ }
+
+ // in this case, the object has CPP ownership, and the gc will
+ // be triggered during its beginCreate stage.
+ {
+ QQmlComponent c(&engine, testFileUrl("qqmldataDestroyed.2.qml"));
+ QObject *object = c.create();
+ QVERIFY(object != 0);
+ QVERIFY(object->property("testConditionsMet").toBool());
+ // the gc() within the handler will have triggered the weak
+ // qobject reference callback. If that incorrectly disposes
+ // the handle, when the qqmldata::destroyed() handler is
+ // called due to object deletion we will see a crash.
+ delete object;
+ // shouldn't have crashed.
+ }
+}
+
QTEST_MAIN(tst_qqmlecmascript)
#include "tst_qqmlecmascript.moc"