aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2026-08-06 11:37:20 +0200
committerFabian Kosmale <fabian.kosmale@qt.io>2026-09-10 13:14:07 +0000
commit7e43d0f6174333d419d96f7f227e1826b13be87a (patch)
treeea8a4e80443c80152c96626b1e07758ac0ab2800
parent9cec1431edd4467fa61c2db71a145403125ea32f (diff)
QQmlAnyBinding::installOn: Fix IgnoreInterceptors with dynamic metaobjects6.11
If installOn was called on an object with an alias to a BINDABLE property (e.g. via PropertyChanges), we would fail to resolve the UntypedBindable. The reason is that we were attempting a direct call of the static meta-objects function (via qt_metacall). This certainly bypasses interceptors, but it also bypasses the QQmlVMEMetaObject logic which resolves aliases. After resolution, we now always use QMetaObject::metacall, but set a flag on any potential interceptor meta-object. We need to set that flag recursively on the meta-object parent chain, as we can't know where the relevant interceptor sits in the parent chain. We have to resolve any aliases first, as we'd otherwise set the flag on the wrong dynamic meta-object if the alias points to another object. Alternatively, we could modify the metacall for BINDABLE to also pass an (optional) flags argument, but that change requires larger coordination between qtbase and declarative, and should at least not be backported. Fixes: QTBUG-148815 Pick-to: 6.8 Change-Id: I75836c8402c1c9df4e28c61e0199c5e1d359b8b6 Reviewed-by: Olivier De Cannière <olivier.decanniere@qt.io> (cherry picked from commit 6cd3898790dbf005a0ebfc90c7ac0b607a37f919) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> (cherry picked from commit 2900cbb439a496679d731f65cf0453e6e668b5a2) Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
-rw-r--r--src/qml/qml/qqmlanybinding_p.h22
-rw-r--r--src/qml/qml/qqmlvmemetaobject.cpp60
-rw-r--r--src/qml/qml/qqmlvmemetaobject_p.h21
-rw-r--r--tests/auto/qml/qqmlanybinding/data/BaseWithInterceptor.qml6
-rw-r--r--tests/auto/qml/qqmlanybinding/data/aliasOfBindable.qml8
-rw-r--r--tests/auto/qml/qqmlanybinding/data/aliasOfInterceptedBindable.qml12
-rw-r--r--tests/auto/qml/qqmlanybinding/data/derivedOfInterceptedBindable.qml7
-rw-r--r--tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp109
-rw-r--r--tests/auto/qml/qqmlanybinding/withbindable.h34
9 files changed, 245 insertions, 34 deletions
diff --git a/src/qml/qml/qqmlanybinding_p.h b/src/qml/qml/qqmlanybinding_p.h
index cd00ed9011..f8d7f1dbfa 100644
--- a/src/qml/qml/qqmlanybinding_p.h
+++ b/src/qml/qml/qqmlanybinding_p.h
@@ -15,10 +15,12 @@
//
// We mean it.
//
-
+#include "qqmlvmemetaobject_p.h"
#include <qqmlproperty.h>
#include <private/qqmlpropertybinding_p.h>
#include <private/qqmlbinding_p.h>
+#include <private/qqmlvmemetaobject_p.h>
+#include <utility>
QT_BEGIN_NAMESPACE
@@ -256,8 +258,22 @@ public:
Q_ASSERT(target.isBindable());
QUntypedBindable bindable;
void *argv[] = {&bindable};
- if (mode == IgnoreInterceptors) {
- target.object()->qt_metacall(QMetaObject::BindableProperty, target.index(), argv);
+ if (mode == IgnoreInterceptors && QObjectPrivate::get(target.object())->metaObject) {
+ // we want to tell any interceptor to avoid interception, but if there's an alias,
+ // we'd end up querying the wrong object
+ auto targetObject = target.object();
+ const QQmlPropertyData *targetProp = &QQmlPropertyPrivate::get(target)->core;
+ QQmlPropertyIndex originalIndex(targetProp->coreIndex());
+ QQmlPropertyIndex propIndex = originalIndex;
+ if (targetProp->isAlias()) {
+ QQmlPropertyPrivate::findAliasTarget(targetObject, originalIndex, &targetObject, &propIndex);
+ // we currently don't support bindable on value types, we would need to handle valueTypeIndex
+ // if that were to change in the future
+ Q_ASSERT(propIndex.valueTypeIndex() == -1);
+ }
+ QQmlInterceptorMetaObject::setInterceptionMode(targetObject, QQmlInterceptorMetaObject::SkipInterception);
+ QMetaObject::metacall(targetObject, QMetaObject::BindableProperty, propIndex.coreIndex(), argv);
+ QQmlInterceptorMetaObject::setInterceptionMode(targetObject, QQmlInterceptorMetaObject::Intercept);
} else {
QMetaObject::metacall(target.object(), QMetaObject::BindableProperty, target.index(), argv);
}
diff --git a/src/qml/qml/qqmlvmemetaobject.cpp b/src/qml/qml/qqmlvmemetaobject.cpp
index 29365ce7db..7865eef5f6 100644
--- a/src/qml/qml/qqmlvmemetaobject.cpp
+++ b/src/qml/qml/qqmlvmemetaobject.cpp
@@ -48,7 +48,7 @@ QQmlVMEResolvedList::QQmlVMEResolvedList(QQmlListProperty<QObject> *prop)
->property(m_metaObject->propOffset() + m_id)
.typeName(),
"QQmlListProperty"));
- Q_ASSERT(m_metaObject->object == prop->object);
+ Q_ASSERT(m_metaObject->object.data() == prop->object);
// readPropertyAsList() with checks transformed into Q_ASSERT
// and without allocation.
@@ -109,7 +109,7 @@ QQmlVMEResolvedList::~QQmlVMEResolvedList() = default;
void QQmlVMEResolvedList::activateSignal() const
{
- m_metaObject->activate(m_metaObject->object, int(m_id), nullptr);
+ m_metaObject->activate(m_metaObject->object.data(), int(m_id), nullptr);
}
void QQmlVMEMetaObject::list_append(QQmlListProperty<QObject> *prop, QObject *o)
@@ -188,7 +188,7 @@ QQmlVMEVariantQObjectPtr::QQmlVMEVariantQObjectPtr()
void QQmlVMEVariantQObjectPtr::objectDestroyedImpl(QQmlGuardImpl *guard)
{
auto This = static_cast<QQmlVMEVariantQObjectPtr *>(guard);
- if (!This->m_target || QQmlData::wasDeleted(This->m_target->object))
+ if (!This->m_target || QQmlData::wasDeleted(This->m_target->object.data()))
return;
if (This->m_index >= 0) {
@@ -202,7 +202,7 @@ void QQmlVMEVariantQObjectPtr::objectDestroyedImpl(QQmlGuardImpl *guard)
}
}
- This->m_target->activate(This->m_target->object, This->m_index, nullptr);
+ This->m_target->activate(This->m_target->object.data(), This->m_index, nullptr);
}
}
@@ -246,7 +246,7 @@ void QQmlVMEMetaObjectEndpoint::tryConnect()
// This is actually notify. Aliases are after regular properties in the MetaObject.
// So add propCount() to get the signal index.
int sigIdx = aliasId + metaObject->propCount();
- metaObject->activate(metaObject->object, sigIdx, nullptr);
+ metaObject->activate(metaObject->object.data(), sigIdx, nullptr);
} else if (metaObject->findCompiledObject()) {
const QQmlPropertyData *aliasProperty
= metaObject->cache->property(metaObject->aliasOffset() + aliasId);
@@ -337,7 +337,7 @@ void QQmlInterceptorMetaObject::registerInterceptor(QQmlPropertyIndex index, QQm
int QQmlInterceptorMetaObject::metaCall(QObject *o, QMetaObject::Call c, int id, void **a)
{
- Q_ASSERT(o == object);
+ Q_ASSERT(o == object.data());
Q_UNUSED(o);
if (intercept(c, id, a))
@@ -352,7 +352,7 @@ bool QQmlInterceptorMetaObject::doIntercept(QMetaObject::Call c, int id, void **
continue;
const int valueIndex = vi->m_propertyIndex.valueTypeIndex();
- const QQmlData *data = QQmlData::get(object);
+ const QQmlData *data = QQmlData::get(object.data());
const QMetaType metaType = data->propertyCache->property(id)->propType();
if (metaType.isValid()) {
@@ -406,7 +406,7 @@ bool QQmlInterceptorMetaObject::doIntercept(QMetaObject::Call c, int id, void **
QMetaProperty valueProp = valueType->property(valueIndex);
QVariant newValue(metaType, a[0]);
- valueType->read(object, id);
+ valueType->read(object.data(), id);
QVariant prevComponentValue = valueType->readOnGadget(valueProp);
valueType->setValue(newValue);
@@ -419,7 +419,7 @@ bool QQmlInterceptorMetaObject::doIntercept(QMetaObject::Call c, int id, void **
// So, we cannot return here if prevComponentValue == newComponentValue.
valueType->writeOnGadget(valueProp, std::move(prevComponentValue));
valueType->write(
- object, id,
+ object.data(), id,
QQmlPropertyData::DontRemoveBinding | QQmlPropertyData::BypassInterceptor,
QV4::ReferenceObject::AllProperties);
@@ -515,7 +515,7 @@ QQmlVMEMetaObject::QQmlVMEMetaObject(QV4::ExecutionEngine *engine,
QQmlVMEMetaObject::~QQmlVMEMetaObject()
{
- if (parent.isT1()) parent.asT1()->objectDestroyed(object);
+ if (parent.isT1()) parent.asT1()->objectDestroyed(object.data());
delete [] aliasEndpoints;
qDeleteAll(varObjectGuards);
@@ -780,7 +780,7 @@ QRectF QQmlVMEMetaObject::readPropertyAsRectF(int id) const
int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void **a)
{
- Q_ASSERT(o == object);
+ Q_ASSERT(o == object.data());
Q_UNUSED(o);
int id = _id;
@@ -828,7 +828,7 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void *
break;
}
} else {
- qmlWarning(object) << "Cannot find member data";
+ qmlWarning(object.data()) << "Cannot find member data";
}
} else {
const QV4::CompiledData::CommonType t
@@ -910,7 +910,7 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void *
propType.construct(a[0], data);
}
} else {
- qmlWarning(object) << "Cannot find member data";
+ qmlWarning(object.data()) << "Cannot find member data";
}
}
}
@@ -980,7 +980,7 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void *
}
md->set(engine, id, sequence);
if (sequence->isUndefined()) {
- qmlWarning(object)
+ qmlWarning(object.data())
<< "Could not create a QML sequence object for "
<< propType.name();
}
@@ -989,7 +989,7 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void *
break;
}
} else {
- qmlWarning(object) << "Cannot find member data";
+ qmlWarning(object.data()) << "Cannot find member data";
}
} else {
const QV4::CompiledData::CommonType t
@@ -1088,13 +1088,13 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void *
}
}
} else {
- qmlWarning(object) << "Cannot find member data";
+ qmlWarning(object.data()) << "Cannot find member data";
}
}
}
if (needActivate)
- activate(object, id, nullptr);
+ activate(object.data(), id, nullptr);
}
return -1;
@@ -1203,7 +1203,7 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void *
id -= signalOffset();
if (id < signalCount()) {
- activate(object, id, a);
+ activate(object.data(), id, a);
return -1;
}
@@ -1240,11 +1240,11 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void *
if (arguments && arguments->names) {
const quint32 parameterCount = arguments->names->size();
Q_ASSERT(parameterCount == function->formalParameterCount());
- function->call(object, a, arguments->types, parameterCount);
+ function->call(object.data(), a, arguments->types, parameterCount);
} else {
Q_ASSERT(function->formalParameterCount() == 0);
const QMetaType returnType = methodData->propType();
- function->call(object, a, &returnType, 0);
+ function->call(object.data(), a, &returnType, 0);
}
if (scope.hasException()) {
@@ -1261,7 +1261,7 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void *
}
if (parent.isT1())
- return parent.asT1()->metaCall(object, c, _id, a);
+ return parent.asT1()->metaCall(object.data(), c, _id, a);
else
return object->qt_metacall(c, _id, a);
}
@@ -1277,7 +1277,7 @@ bool QQmlVMEMetaObject::getListProperty(int id, QQmlListProperty<QObject> *targe
// and the second half storing the property id
auto mo = static_cast<QQmlVMEMetaObject *>(
- QObjectPrivate::get(object)->metaObject);
+ QObjectPrivate::get(object.data())->metaObject);
quintptr inheritanceDepth = 0u;
while (mo && mo != this) {
mo = mo->parentVMEMetaObject();
@@ -1285,20 +1285,20 @@ bool QQmlVMEMetaObject::getListProperty(int id, QQmlListProperty<QObject> *targe
}
constexpr quintptr idBits = sizeof(quintptr) * CHAR_BIT / 2u;
if (Q_UNLIKELY(inheritanceDepth >= (quintptr(1) << idBits))) {
- qmlWarning(object) << "Too many objects in inheritance hierarchy "
- "for list property";
+ qmlWarning(object.data()) << "Too many objects in inheritance hierarchy "
+ "for list property";
return false;
}
if (Q_UNLIKELY(quintptr(id) >= (quintptr(1) << idBits))) {
- qmlWarning(object) << "Too many properties in object "
- "for list property";
+ qmlWarning(object.data()) << "Too many properties in object "
+ "for list property";
return false;
}
quintptr encodedIndex = (inheritanceDepth << idBits) + id;
initPropertyAsList(id);
*target = QQmlListProperty<QObject>(
- object, reinterpret_cast<void *>(quintptr(encodedIndex)),
+ object.data(), reinterpret_cast<void *>(quintptr(encodedIndex)),
list_append, list_count, list_at,
list_clear, list_replace, list_removeLast);
return true;
@@ -1399,7 +1399,7 @@ void QQmlVMEMetaObject::writeVarProperty(int id, const QV4::Value &value)
guard->setGuardedValue(valueObject, this, id);
// Emit change signal as appropriate.
- activate(object, id, nullptr);
+ activate(object.data(), id, nullptr);
}
void QQmlVMEMetaObject::writeKnownVarProperty(int id, const QVariant &value)
@@ -1430,7 +1430,7 @@ void QQmlVMEMetaObject::writeKnownVarProperty(int id, const QVariant &value)
QVariant currentValue = readPropertyAsVariant(id);
md->set(engine, id, newv);
if ((currentValue.userType() != value.userType() || currentValue != value))
- activate(object, id, nullptr);
+ activate(object.data(), id, nullptr);
}
QV4::ReturnedValue QQmlVMEMetaObject::vmeMethod(int index) const
@@ -1483,7 +1483,7 @@ void QQmlVMEMetaObject::setVMEProperty(int index, const QV4::Value &v)
void QQmlVMEMetaObject::ensureQObjectWrapper()
{
Q_ASSERT(cache);
- QV4::QObjectWrapper::ensureWrapper(engine, object);
+ QV4::QObjectWrapper::ensureWrapper(engine, object.data());
}
void QQmlVMEMetaObject::mark(QV4::MarkStack *markStack)
diff --git a/src/qml/qml/qqmlvmemetaobject_p.h b/src/qml/qml/qqmlvmemetaobject_p.h
index 28afd6312c..db2803633d 100644
--- a/src/qml/qml/qqmlvmemetaobject_p.h
+++ b/src/qml/qml/qqmlvmemetaobject_p.h
@@ -17,6 +17,7 @@
// We mean it.
//
+#include "qqmldata_p.h"
#include <private/qbipointer_p.h>
#include <private/qqmlguard_p.h>
#include <private/qqmlguardedcontextdata_p.h>
@@ -142,7 +143,11 @@ public:
void invalidate() { metaObject.setTag(MetaObjectInvalid); }
- QObject *object = nullptr;
+ // only used for Bindable so far; Write instead passes a flag via the metacall
+ enum InterceptionBehavior { Intercept, SkipInterception };
+ static void setInterceptionMode(QObject *object, InterceptionBehavior behavior);
+
+ QTaggedPointer<QObject> object = nullptr;
QQmlPropertyCache::ConstPtr cache;
protected:
@@ -158,6 +163,9 @@ protected:
return false;
break;
case QMetaObject::BindableProperty:
+ if (object.tag() == SkipInterception) {
+ return false;
+ }
break;
default:
return false;
@@ -188,6 +196,17 @@ inline QQmlInterceptorMetaObject *QQmlInterceptorMetaObject::get(QObject *obj)
return nullptr;
}
+inline void QQmlInterceptorMetaObject::setInterceptionMode(
+ QObject *object, QQmlInterceptorMetaObject::InterceptionBehavior behavior)
+{
+ QQmlInterceptorMetaObject *interceptorMetaObject = get(object);
+ while (interceptorMetaObject) {
+ interceptorMetaObject->object.setTag(behavior);
+ auto parent = interceptorMetaObject->parent;
+ interceptorMetaObject = ((parent.isT1() && parent.flag()) ? static_cast<QQmlInterceptorMetaObject *>(parent.asT1()) : nullptr);
+ }
+}
+
class QQmlVMEMetaObjectEndpoint;
class Q_QML_EXPORT QQmlVMEMetaObject : public QQmlInterceptorMetaObject
{
diff --git a/tests/auto/qml/qqmlanybinding/data/BaseWithInterceptor.qml b/tests/auto/qml/qqmlanybinding/data/BaseWithInterceptor.qml
new file mode 100644
index 0000000000..45557d323b
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/data/BaseWithInterceptor.qml
@@ -0,0 +1,6 @@
+import bindable 1.0
+
+WithBinding {
+ property int baseProp: 0
+ BindableInterceptor on prop {}
+}
diff --git a/tests/auto/qml/qqmlanybinding/data/aliasOfBindable.qml b/tests/auto/qml/qqmlanybinding/data/aliasOfBindable.qml
new file mode 100644
index 0000000000..63f4e022e1
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/data/aliasOfBindable.qml
@@ -0,0 +1,8 @@
+import QtQml
+import bindable 1.0
+
+WithBinding {
+ id: root
+ property int trigger: 1
+ property alias aliasProp: root.prop
+}
diff --git a/tests/auto/qml/qqmlanybinding/data/aliasOfInterceptedBindable.qml b/tests/auto/qml/qqmlanybinding/data/aliasOfInterceptedBindable.qml
new file mode 100644
index 0000000000..bda4fa231f
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/data/aliasOfInterceptedBindable.qml
@@ -0,0 +1,12 @@
+import QtQml
+import bindable 1.0
+
+QtObject {
+ id: root
+ property int trigger: 1
+ property QtObject inner: WithBinding {
+ id: innerObject
+ BindableInterceptor on prop {}
+ }
+ property alias aliasProp: innerObject.prop
+}
diff --git a/tests/auto/qml/qqmlanybinding/data/derivedOfInterceptedBindable.qml b/tests/auto/qml/qqmlanybinding/data/derivedOfInterceptedBindable.qml
new file mode 100644
index 0000000000..690551ec46
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/data/derivedOfInterceptedBindable.qml
@@ -0,0 +1,7 @@
+import QtQml
+
+BaseWithInterceptor {
+ id: root
+ property int trigger: 1
+ property alias aliasProp: root.prop
+}
diff --git a/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp b/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp
index 66aae90591..5eef85e885 100644
--- a/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp
+++ b/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp
@@ -4,6 +4,7 @@
#include <QtQml/QQmlComponent>
#include <QtCore/QScopedPointer>
#include <QtQml/private/qqmlanybinding_p.h>
+#include <QtQml/private/qqmlcontextdata_p.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtCore/private/qobject_p.h>
#include "withbindable.h"
@@ -20,6 +21,9 @@ private slots:
void basicActions();
void unboundQQmlPropertyBindingDoesNotCrash();
void ofDynamicMetaObject();
+ void installOnAliasOfBindable();
+ void installOnAliasOfInterceptedBindable();
+ void installOnBindableInterceptedInBaseType();
};
tst_qqmlanybinding::tst_qqmlanybinding()
@@ -170,6 +174,111 @@ void tst_qqmlanybinding::ofDynamicMetaObject()
QPropertyBindingPrivate::get(binding));
}
+void tst_qqmlanybinding::installOnAliasOfBindable()
+{
+ QQmlEngine engine;
+ QQmlComponent comp(&engine, testFileUrl("aliasOfBindable.qml"));
+ QScopedPointer<QObject> root(comp.create());
+ QVERIFY2(root, qPrintable(comp.errorString()));
+
+ QQmlProperty aliasProp(root.get(), "aliasProp");
+ QVERIFY(aliasProp.isValid());
+ QVERIFY(aliasProp.isBindable());
+
+ QQmlAnyBinding binding = QQmlAnyBinding::createFromCodeString(
+ aliasProp, QStringLiteral("trigger + 1"), root.get(),
+ QQmlContextData::get(qmlContext(root.get())), QString(), 0);
+ QVERIFY(binding.isUntypedPropertyBinding());
+
+ binding.installOn(aliasProp);
+ QCOMPARE(aliasProp.read().toInt(), 2);
+
+ QQmlProperty trigger(root.get(), "trigger");
+ trigger.write(41);
+ QCOMPARE(aliasProp.read().toInt(), 42);
+}
+
+void tst_qqmlanybinding::installOnAliasOfInterceptedBindable()
+{
+ QQmlEngine engine;
+ QQmlComponent comp(&engine, testFileUrl("aliasOfInterceptedBindable.qml"));
+ QScopedPointer<QObject> root(comp.create());
+ QVERIFY2(root, qPrintable(comp.errorString()));
+
+ QObject *inner = qvariant_cast<QObject *>(root->property("inner"));
+ QVERIFY(inner);
+ auto *interceptor = inner->findChild<BindableInterceptor *>();
+ QVERIFY(interceptor);
+
+ QQmlProperty aliasProp(root.get(), "aliasProp");
+ QVERIFY(aliasProp.isBindable());
+
+ QQmlAnyBinding binding = QQmlAnyBinding::createFromCodeString(
+ aliasProp, QStringLiteral("trigger + 1"), root.get(),
+ QQmlContextData::get(qmlContext(root.get())), QString(), 0);
+ QVERIFY(binding.isUntypedPropertyBinding());
+
+ binding.installOn(aliasProp);
+ QCOMPARE(interceptor->bindableCount, 0);
+ QCOMPARE(aliasProp.read().toInt(), 2);
+
+ QQmlProperty trigger(root.get(), "trigger");
+ trigger.write(41);
+ QCOMPARE(aliasProp.read().toInt(), 42);
+
+ // RespectInterceptors still routes through the interceptor.
+ QQmlAnyBinding intercepted = QQmlAnyBinding::createFromCodeString(
+ aliasProp, QStringLiteral("trigger + 2"), root.get(),
+ QQmlContextData::get(qmlContext(root.get())), QString(), 0);
+ intercepted.installOn(aliasProp, QQmlAnyBinding::RespectInterceptors);
+ QCOMPARE(interceptor->bindableCount, 1);
+}
+
+void tst_qqmlanybinding::installOnBindableInterceptedInBaseType()
+{
+ QQmlEngine engine;
+ QQmlComponent comp(&engine, testFileUrl("derivedOfInterceptedBindable.qml"));
+ QScopedPointer<QObject> root(comp.create());
+ QVERIFY2(root, qPrintable(comp.errorString()));
+
+ auto *interceptor = root->findChild<BindableInterceptor *>();
+ QVERIFY(interceptor);
+
+ QQmlProperty prop(root.get(), "prop");
+ QVERIFY(prop.isBindable());
+
+ QQmlAnyBinding binding = QQmlAnyBinding::createFromCodeString(
+ prop, QStringLiteral("trigger + 1"), root.get(),
+ QQmlContextData::get(qmlContext(root.get())), QString(), 0);
+ QVERIFY(binding.isUntypedPropertyBinding());
+
+ binding.installOn(prop);
+ QCOMPARE(interceptor->bindableCount, 0);
+ QCOMPARE(prop.read().toInt(), 2);
+
+ // Same, but reached through an alias, so that the alias resolution and the
+ // parent chain walk have to cooperate.
+ QQmlProperty aliasProp(root.get(), "aliasProp");
+ QVERIFY(aliasProp.isBindable());
+ QQmlAnyBinding aliasBinding = QQmlAnyBinding::createFromCodeString(
+ aliasProp, QStringLiteral("trigger + 2"), root.get(),
+ QQmlContextData::get(qmlContext(root.get())), QString(), 0);
+ aliasBinding.installOn(aliasProp);
+ QCOMPARE(interceptor->bindableCount, 0);
+ QCOMPARE(prop.read().toInt(), 3);
+
+ QQmlProperty trigger(root.get(), "trigger");
+ trigger.write(41);
+ QCOMPARE(prop.read().toInt(), 43);
+
+ // RespectInterceptors still routes through the interceptor.
+ QQmlAnyBinding intercepted = QQmlAnyBinding::createFromCodeString(
+ prop, QStringLiteral("trigger + 3"), root.get(),
+ QQmlContextData::get(qmlContext(root.get())), QString(), 0);
+ intercepted.installOn(prop, QQmlAnyBinding::RespectInterceptors);
+ QCOMPARE(interceptor->bindableCount, 1);
+}
+
QTEST_MAIN(tst_qqmlanybinding)
#include "tst_qqmlanybinding.moc"
diff --git a/tests/auto/qml/qqmlanybinding/withbindable.h b/tests/auto/qml/qqmlanybinding/withbindable.h
index 322459f991..9d0cb56457 100644
--- a/tests/auto/qml/qqmlanybinding/withbindable.h
+++ b/tests/auto/qml/qqmlanybinding/withbindable.h
@@ -6,6 +6,8 @@
#include <QObject>
#include <QtCore/qproperty.h>
#include <qqml.h>
+#include <QtQml/private/qqmlproperty_p.h>
+#include <QtQml/private/qqmlpropertyvalueinterceptor_p.h>
class WithBinding : public QObject {
Q_OBJECT
@@ -20,5 +22,37 @@ private:
QProperty<int> m_prop;
};
+// Minimal stand-in for Behavior: redirects bindings to its own property.
+class BindableInterceptor : public QObject, public QQmlPropertyValueInterceptor
+{
+ Q_OBJECT
+ QML_ELEMENT
+ Q_INTERFACES(QQmlPropertyValueInterceptor)
+
+public:
+ void setTarget(const QQmlProperty &property) override { m_property = property; }
+
+ void write(const QVariant &value) override
+ {
+ ++writeCount;
+ QQmlPropertyPrivate::write(m_property, value, QQmlPropertyData::BypassInterceptor);
+ }
+
+ bool bindable(QUntypedBindable *bindable, QUntypedBindable target) override
+ {
+ Q_UNUSED(target);
+ ++bindableCount;
+ *bindable = QBindable<int>(&m_intercepted);
+ return true;
+ }
+
+ int writeCount = 0;
+ int bindableCount = 0;
+
+private:
+ QQmlProperty m_property;
+ QProperty<int> m_intercepted;
+};
+
#endif