[Regression] Fix lazy binding evaluation

Commit 04774bb14c long time ago introduced the
concept of fixed order binding initialization with lazy evaluation, where a bit
is reserved for each binding that indicates whether it's been initialized the
first time or not. When reading a property on a QObject, we'd check if the
corresponding binding for the property has been initialized or not and flush
(i.e. execute) the binding if necessary.

As part of the V4/V8 clean-up, commit 1eb4120094
removed the StoreV8Binding instruction, which made the call for setting the
this-binding-is-not-evaluated-yet bit. Nowadays we only use StoreBinding, for
which this optimization was never implemented (and not needed really). Now that
we have a unified JS code path, we need to set the pending binding bit and also
make sure that we call flushPendingBinding for any JS side property access
(accelerated or not).

Also flushPendingBindingImpl had two bugs:

   * In an attempt of trying to find the binding to flush, it could happen that
     we'd try to flush a previously destroyed binding (m_mePtr is null), so
     the b variable would remain the first binding in the object and we'd flush
     the wrong one (instead of none). Added a missing check to verify that the
     property index matches.
   * Also resetting the mePtr must be done through clear(), to ensure that the
     pointer in bindValues in the VME is also cleared, to avoid re-enabling the
     same binding again in complete();

Task-number: QTBUG-36441

Change-Id: Icdb0c8fb036051fd5d6c4d33b10cd0c0ed9a9d5c
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
This commit is contained in:
Simon Hausmann 2014-01-28 14:04:58 +01:00 committed by The Qt Project
parent 44b16b0698
commit 5dc7649f5a
6 changed files with 43 additions and 4 deletions

View File

@ -317,7 +317,6 @@ ReturnedValue QObjectWrapper::getQmlProperty(ExecutionContext *ctx, QQmlContextD
return QV4::Object::get(this, name, hasProperty); return QV4::Object::get(this, name, hasProperty);
} }
QQmlData::flushPendingBinding(m_object, result->coreIndex);
QQmlData *ddata = QQmlData::get(m_object, false); QQmlData *ddata = QQmlData::get(m_object, false);
if (revisionMode == QV4::QObjectWrapper::CheckRevision && result->hasRevision()) { if (revisionMode == QV4::QObjectWrapper::CheckRevision && result->hasRevision()) {
@ -338,6 +337,8 @@ ReturnedValue QObjectWrapper::getProperty(QObject *object, ExecutionContext *ctx
{ {
QV4::Scope scope(ctx); QV4::Scope scope(ctx);
QQmlData::flushPendingBinding(object, property->coreIndex);
if (property->isFunction() && !property->isVarProperty()) { if (property->isFunction() && !property->isVarProperty()) {
if (property->isVMEFunction()) { if (property->isVMEFunction()) {
QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(object); QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(object);

View File

@ -777,8 +777,8 @@ void QQmlData::flushPendingBindingImpl(int coreIndex)
while (b && *b->m_mePtr && b->propertyIndex() != coreIndex) while (b && *b->m_mePtr && b->propertyIndex() != coreIndex)
b = b->nextBinding(); b = b->nextBinding();
if (b) { if (b && b->propertyIndex() == coreIndex) {
b->m_mePtr = 0; b->clear();
b->setEnabled(true, QQmlPropertyPrivate::BypassInterceptor | b->setEnabled(true, QQmlPropertyPrivate::BypassInterceptor |
QQmlPropertyPrivate::DontRemoveBinding); QQmlPropertyPrivate::DontRemoveBinding);
} }

View File

@ -881,6 +881,12 @@ QObject *QQmlVME::run(QList<QQmlError> *errors,
CLEAN_PROPERTY(target, QDPP::bindingIndex(instr.property)); CLEAN_PROPERTY(target, QDPP::bindingIndex(instr.property));
bind->addToObject(); bind->addToObject();
if (!instr.property.isValueTypeVirtual()) {
QQmlData *data = QQmlData::get(target);
Q_ASSERT(data);
data->setPendingBindingBit(target, instr.property.coreIndex);
}
} }
QML_END_INSTR(StoreBinding) QML_END_INSTR(StoreBinding)

View File

@ -0,0 +1,13 @@
import QtQuick 2.0
Item
{
property int someInt: 4
property var variantArray: [1, 2]
property int arrayLength: 0
onSomeIntChanged:
{
arrayLength = variantArray.length
}
}

View File

@ -0,0 +1,6 @@
import QtQuick 2.1
LazyBindingComponent
{
someInt: 5
}

View File

@ -321,6 +321,7 @@ private slots:
void qtbug_34792(); void qtbug_34792();
void noCaptureWhenWritingProperty(); void noCaptureWhenWritingProperty();
void singletonWithEnum(); void singletonWithEnum();
void lazyBindingEvaluation();
private: private:
// static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter); // static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter);
@ -667,7 +668,7 @@ void tst_qqmlecmascript::methods()
void tst_qqmlecmascript::bindingLoop() void tst_qqmlecmascript::bindingLoop()
{ {
QQmlComponent component(&engine, testFileUrl("bindingLoop.qml")); QQmlComponent component(&engine, testFileUrl("bindingLoop.qml"));
QString warning = component.url().toString() + ":5:9: QML MyQmlObject: Binding loop detected for property \"stringProperty\""; QString warning = component.url().toString() + ":9:9: QML MyQmlObject: Binding loop detected for property \"stringProperty\"";
QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());
QObject *object = component.create(); QObject *object = component.create();
QVERIFY(object != 0); QVERIFY(object != 0);
@ -7520,6 +7521,18 @@ void tst_qqmlecmascript::singletonWithEnum()
QCOMPARE(prop.toInt(), int(SingletonWithEnum::TestValue)); QCOMPARE(prop.toInt(), int(SingletonWithEnum::TestValue));
} }
void tst_qqmlecmascript::lazyBindingEvaluation()
{
QQmlComponent component(&engine, testFileUrl("lazyBindingEvaluation.qml"));
QScopedPointer<QObject> obj(component.create());
if (obj.isNull())
qDebug() << component.errors().first().toString();
QVERIFY(!obj.isNull());
QVariant prop = obj->property("arrayLength");
QVERIFY(prop.type() == QVariant::Int);
QCOMPARE(prop.toInt(), 2);
}
QTEST_MAIN(tst_qqmlecmascript) QTEST_MAIN(tst_qqmlecmascript)
#include "tst_qqmlecmascript.moc" #include "tst_qqmlecmascript.moc"