Fix crash caused by dereferencing collected v8 data

If a var property of a QObject is read after the v8 data associated
with the qobject has been deleted but prior to the DeferredDelete
event being processed, the varProperties array will be null and
a crash will occur.

This patch ensures that we check for this condition in both the
access and set codepaths for var properties, and also ensures
that an object which has previously been queued for deletion cannot
be referenced in JS.

Finally, it adds a unit test to ensure that we don't regress.

Task-number: QTBUG-24748
Change-Id: Idde384ca01e18f4dcf9e376e9379f2c5eb410e14
Reviewed-by: Michael Brasser <michael.brasser@nokia.com>
This commit is contained in:
Chris Adams 2012-03-13 13:30:39 +10:00 committed by Qt by Nokia
parent 147247a31a
commit 25793276e5
8 changed files with 79 additions and 14 deletions

View File

@ -78,8 +78,8 @@ class Q_QML_EXPORT QQmlData : public QAbstractDeclarativeData
public:
QQmlData()
: ownMemory(true), ownContext(false), indestructible(true), explicitIndestructibleSet(false),
hasTaintedV8Object(false), notifyList(0), context(0), outerContext(0), bindings(0),
nextContextObject(0), prevContextObject(0), bindingBitsSize(0), bindingBits(0),
hasTaintedV8Object(false), isQueuedForDeletion(false), notifyList(0), context(0), outerContext(0),
bindings(0), nextContextObject(0), prevContextObject(0), bindingBitsSize(0), bindingBits(0),
lineNumber(0), columnNumber(0), deferredComponent(0), deferredIdx(0), v8objectid(0),
propertyCache(0), guards(0), extendedData(0) {
init();
@ -110,7 +110,8 @@ public:
quint32 indestructible:1;
quint32 explicitIndestructibleSet:1;
quint32 hasTaintedV8Object:1;
quint32 dummy:27;
quint32 isQueuedForDeletion:1;
quint32 dummy:26;
struct NotifyList {
quint64 connectionMask;

View File

@ -832,15 +832,17 @@ v8::Handle<v8::Value> QQmlVMEMetaObject::readVarProperty(int id)
{
Q_ASSERT(id >= firstVarPropertyIndex);
ensureVarPropertiesAllocated();
return varProperties->Get(id - firstVarPropertyIndex);
if (ensureVarPropertiesAllocated())
return varProperties->Get(id - firstVarPropertyIndex);
return v8::Handle<v8::Value>();
}
QVariant QQmlVMEMetaObject::readPropertyAsVariant(int id)
{
if (id >= firstVarPropertyIndex) {
ensureVarPropertiesAllocated();
return QQmlEnginePrivate::get(ctxt->engine)->v8engine()->toVariant(varProperties->Get(id - firstVarPropertyIndex), -1);
if (ensureVarPropertiesAllocated())
return QQmlEnginePrivate::get(ctxt->engine)->v8engine()->toVariant(varProperties->Get(id - firstVarPropertyIndex), -1);
return QVariant();
} else {
if (data[id].dataType() == QMetaType::QObjectStar) {
return QVariant::fromValue(data[id].asQObject());
@ -853,7 +855,8 @@ QVariant QQmlVMEMetaObject::readPropertyAsVariant(int id)
void QQmlVMEMetaObject::writeVarProperty(int id, v8::Handle<v8::Value> value)
{
Q_ASSERT(id >= firstVarPropertyIndex);
ensureVarPropertiesAllocated();
if (!ensureVarPropertiesAllocated())
return;
// Importantly, if the current value is a scarce resource, we need to ensure that it
// gets automatically released by the engine if no other references to it exist.
@ -882,7 +885,8 @@ void QQmlVMEMetaObject::writeVarProperty(int id, v8::Handle<v8::Value> value)
void QQmlVMEMetaObject::writeProperty(int id, const QVariant &value)
{
if (id >= firstVarPropertyIndex) {
ensureVarPropertiesAllocated();
if (!ensureVarPropertiesAllocated())
return;
// Importantly, if the current value is a scarce resource, we need to ensure that it
// gets automatically released by the engine if no other references to it exist.
@ -1029,10 +1033,17 @@ void QQmlVMEMetaObject::setVMEProperty(int index, v8::Handle<v8::Value> v)
return writeVarProperty(index - propOffset, v);
}
void QQmlVMEMetaObject::ensureVarPropertiesAllocated()
bool QQmlVMEMetaObject::ensureVarPropertiesAllocated()
{
if (!varPropertiesInitialized)
allocateVarPropertiesArray();
// in some situations, the QObject's v8object (and associated v8 data,
// such as the varProperties array) will have been cleaned up, but the
// QObject ptr will not yet have been deleted (eg, waiting on deleteLater).
// In this situation, the varProperties handle will be (and should remain)
// empty.
return !varProperties.IsEmpty();
}
// see also: QV8GCCallback::garbageCollectorPrologueCallback()

View File

@ -195,7 +195,7 @@ private:
static void VarPropertiesWeakReferenceCallback(v8::Persistent<v8::Value> object, void* parameter);
static void GcPrologueCallback(QV8GCCallback::Node *node);
inline void allocateVarPropertiesArray();
inline void ensureVarPropertiesAllocated();
inline bool ensureVarPropertiesAllocated();
void connectAlias(int aliasId);
QBitArray aConnected;

View File

@ -883,8 +883,10 @@ static void WeakQObjectReferenceCallback(v8::Persistent<v8::Value> handle, void
QQmlData *ddata = QQmlData::get(object, false);
if (ddata) {
ddata->v8object.Clear();
if (!object->parent() && !ddata->indestructible)
if (!object->parent() && !ddata->indestructible) {
ddata->isQueuedForDeletion = true;
object->deleteLater();
}
}
}
@ -1043,12 +1045,15 @@ v8::Handle<v8::Value> QV8QObjectWrapper::newQObject(QObject *object)
if (QObjectPrivate::get(object)->wasDeleted)
return v8::Null();
QQmlData *ddata = QQmlData::get(object, true);
if (!ddata)
return v8::Undefined();
if (ddata->isQueuedForDeletion)
return v8::Null();
if (ddata->v8objectid == m_id && !ddata->v8object.IsEmpty()) {
// We own the v8object
return v8::Local<v8::Object>::New(ddata->v8object);

View File

@ -0,0 +1,5 @@
import QtQuick 2.0
Item {
property var varprop: true
}

View File

@ -0,0 +1,28 @@
import QtQuick 2.0
import Qt.test 1.0 as ModuleApi
Item {
id: testOwnership
property bool test: false
function runTest() {
var o;
var c = Qt.createComponent("ComponentWithVarProp.qml");
if (c.status == Component.Ready) {
o = c.createObject();
} else {
return; // failed to create component.
}
o.varprop = true; // causes initialization of varProperties.
ModuleApi.trackObject(o); // stores QObject ptr
if (ModuleApi.trackedObject() == null) return; // is still valid, should have a valid v8object.
o = new Date(); // causes object to be gc-able.
gc(); // collect object's v8object + varProperties, queues deleteLater.
if (ModuleApi.trackedObject() != null) return; // v8object was previously collected.
ModuleApi.setTrackedObjectProperty("varprop"); // deferences varProperties of object.
test = !(ModuleApi.trackedObjectProperty("varprop")); // deferences varProperties of object.
// if we didn't crash, success.
}
}

View File

@ -982,7 +982,7 @@ class testQObjectApi : public QObject
public:
testQObjectApi(QObject* parent = 0)
: QObject(parent), m_testProperty(0), m_testWritableProperty(0), m_testWritableFinalProperty(0), m_methodCallCount(0)
: QObject(parent), m_testProperty(0), m_testWritableProperty(0), m_testWritableFinalProperty(0), m_methodCallCount(0), m_trackedObject(0)
{
}
@ -992,6 +992,11 @@ public:
Q_INVOKABLE int qobjectEnumTestMethod(MyEnum val) { return (static_cast<int>(val) + 5); }
Q_INVOKABLE int qobjectTestMethod(int increment = 1) { m_methodCallCount += increment; return m_methodCallCount; }
Q_INVOKABLE void trackObject(QObject *obj) { m_trackedObject = obj; }
Q_INVOKABLE QObject *trackedObject() const { return m_trackedObject; }
Q_INVOKABLE void setTrackedObjectProperty(const QString &propName) const { m_trackedObject->setProperty(qPrintable(propName), QVariant(5)); }
Q_INVOKABLE QVariant trackedObjectProperty(const QString &propName) const { return m_trackedObject->property(qPrintable(propName)); }
int qobjectTestProperty() const { return m_testProperty; }
void setQObjectTestProperty(int tp) { m_testProperty = tp; emit qobjectTestPropertyChanged(tp); }
@ -1011,6 +1016,7 @@ private:
int m_testWritableProperty;
int m_testWritableFinalProperty;
int m_methodCallCount;
QObject *m_trackedObject;
};
class CircularReferenceObject : public QObject,

View File

@ -3906,6 +3906,15 @@ void tst_qqmlecmascript::propertyVarOwnership()
delete object;
}
// Garbage collection cannot result in attempted dereference of empty handle
{
QQmlComponent component(&engine, testFileUrl("propertyVarOwnership.5.qml"));
QObject *object = component.create();
QVERIFY(object != 0);
QMetaObject::invokeMethod(object, "runTest");
QCOMPARE(object->property("test").toBool(), true);
delete object;
}
}
void tst_qqmlecmascript::propertyVarImplicitOwnership()