Ensure binding target has not been deleted

Prevent the evaluation of bindings if the target has been deleted.
Also, mark an item as queued for deletion at the beginning of the
destructor call chain, so that bindings triggered by the operation
of the destructor itself are not evaluated (after the context is
destructed, if necessary).

Task-number: QTBUG-25516
Change-Id: I587ef7923eb749eb7980156ad73822c1fb7c1ff3
Reviewed-by: Michael Brasser <michael.brasser@nokia.com>
This commit is contained in:
Matthew Vogt 2012-04-27 09:15:11 +10:00 committed by Qt by Nokia
parent 9ae289f259
commit 186abc1e08
9 changed files with 107 additions and 5 deletions

View File

@ -186,6 +186,10 @@ void QQmlBinding::update(QQmlPropertyPrivate::WriteFlags flags)
if (!enabledFlag() || !context() || !context()->isValid())
return;
// Check that the target has not been deleted
if (QQmlData::wasDeleted(object()))
return;
QQmlTrace trace("General Binding Update");
trace.addDetail("URL", m_url);
trace.addDetail("Line", m_lineNumber);

View File

@ -195,6 +195,10 @@ public:
QHash<int, QObject *> *attachedProperties() const;
static inline bool wasDeleted(QObject *);
static void markAsDeleted(QObject *);
static inline void setQueuedForDeletion(QObject *);
private:
// For objectNameNotifier and attachedProperties
mutable QQmlDataExtended *extendedData;
@ -205,7 +209,7 @@ bool QQmlData::wasDeleted(QObject *object)
if (!object)
return true;
QObjectPrivate *priv = QObjectPrivate::get(const_cast<QObject *>(object));
QObjectPrivate *priv = QObjectPrivate::get(object);
if (priv->wasDeleted)
return true;
@ -213,6 +217,17 @@ bool QQmlData::wasDeleted(QObject *object)
static_cast<QQmlData *>(priv->declarativeData)->isQueuedForDeletion;
}
void QQmlData::setQueuedForDeletion(QObject *object)
{
if (object) {
if (QObjectPrivate *priv = QObjectPrivate::get(object)) {
if (!priv->wasDeleted && priv->declarativeData) {
static_cast<QQmlData *>(priv->declarativeData)->isQueuedForDeletion = true;
}
}
}
}
QQmlNotifierEndpoint *QQmlData::notify(int index)
{
Q_ASSERT(index <= 0xFFFF);

View File

@ -422,6 +422,10 @@ void QQmlPrivate::qdeclarativeelement_destructor(QObject *o)
d->context->destroy();
d->context = 0;
}
// Mark this object as in the process of deletion to
// prevent it resolving in bindings
QQmlData::markAsDeleted(o);
}
}
@ -468,6 +472,16 @@ int QQmlData::endpointCount(int index)
return count;
}
void QQmlData::markAsDeleted(QObject *o)
{
QQmlData::setQueuedForDeletion(o);
QObjectPrivate *p = QObjectPrivate::get(o);
for (QList<QObject *>::iterator it = p->children.begin(), end = p->children.end(); it != end; ++it) {
QQmlData::markAsDeleted(*it);
}
}
void QQmlEnginePrivate::init()
{
Q_Q(QQmlEngine);

View File

@ -358,6 +358,10 @@ void QV4Bindings::run(Binding *binding, QQmlPropertyPrivate::WriteFlags flags)
if (!context || !context->isValid())
return;
// Check that the target has not been deleted
if (QQmlData::wasDeleted(binding->target))
return;
QQmlTrace trace("V4 Binding Update");
trace.addDetail("URL", context->url);
trace.addDetail("Line", binding->line);

View File

@ -106,6 +106,14 @@ void QV8Bindings::Binding::update(QQmlPropertyPrivate::WriteFlags flags)
if (!enabledFlag())
return;
QQmlContextData *context = parent->context();
if (!context || !context->isValid())
return;
// Check that the target has not been deleted
if (QQmlData::wasDeleted(object()))
return;
QQmlTrace trace("V8 Binding Update");
trace.addDetail("URL", parent->url());
trace.addDetail("Line", instruction->line);
@ -113,12 +121,9 @@ void QV8Bindings::Binding::update(QQmlPropertyPrivate::WriteFlags flags)
QQmlBindingProfiler prof(parent->urlString(), instruction->line, instruction->column);
QQmlContextData *context = parent->context();
if (!context || !context->isValid())
return;
if (!updatingFlag()) {
setUpdatingFlag(true);
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context->engine);
bool isUndefined = false;

View File

@ -0,0 +1,4 @@
import QtQuick 2.0
Item {
}

View File

@ -0,0 +1,9 @@
import QtQuick 2.0
Item {
property bool validParentChildCount: parent && (parent.children.length > 0)
onValidParentChildCountChanged: {
if (!validParentChildCount) console.warn('WARNING: Invalid parent child count')
}
}

View File

@ -0,0 +1,17 @@
import QtQuick 2.0
Item {
id: root
Component.onCompleted: {
var container = containerComponent.createObject(root)
contentComponent.createObject(container)
container.destroy();
pendingEvents.process()
}
property Component containerComponent: ContainerComponent {}
property Component contentComponent: ContentComponent {}
}

View File

@ -258,6 +258,7 @@ private slots:
void replaceBinding();
void deleteRootObjectInCreation();
void onDestruction();
void bindingSuppression();
private:
static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter);
@ -6658,6 +6659,35 @@ void tst_qqmlecmascript::onDestruction()
}
}
struct EventProcessor : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE void process()
{
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
QCoreApplication::processEvents();
}
};
void tst_qqmlecmascript::bindingSuppression()
{
QQmlEngine engine;
EventProcessor processor;
engine.rootContext()->setContextProperty("pendingEvents", &processor);
transientErrorsMsgCount = 0;
QtMsgHandler old = qInstallMsgHandler(transientErrorsMsgHandler);
QQmlComponent c(&engine, testFileUrl("bindingSuppression.qml"));
QObject *obj = c.create();
QVERIFY(obj != 0);
delete obj;
qInstallMsgHandler(old);
QCOMPARE(transientErrorsMsgCount, 0);
}
QTEST_MAIN(tst_qqmlecmascript)
#include "tst_qqmlecmascript.moc"