Only signal list variable assignment once

Lists are internally cleared and each element is pushed to the back
from the source list in QML. Use nonsignaling operations for clear
and push and activate manually having performed the operations.

Fixes: QTBUG-112208
Change-Id: I1a995905f3fa758e4cc8c39b8576db668d84a067
Reviewed-by: Mikołaj Boc <Mikolaj.Boc@qt.io>
This commit is contained in:
Mikolaj Boc 2023-04-17 16:25:40 +02:00 committed by Mikołaj Boc
parent 528807e80c
commit 7656d4922c
5 changed files with 126 additions and 64 deletions

View File

@ -1549,18 +1549,26 @@ bool QQmlPropertyPrivate::write(
if (valueMetaObject.isNull())
return false;
QQmlListProperty<void> prop;
QQmlListProperty<QObject> prop;
property.readProperty(object, &prop);
if (!prop.clear)
if (!prop.clear || !prop.append)
return false;
prop.clear(&prop);
const bool useNonsignalingListOps = prop.clear == &QQmlVMEMetaObject::list_clear
&& prop.append == &QQmlVMEMetaObject::list_append;
auto propClear =
useNonsignalingListOps ? &QQmlVMEMetaObject::list_clear_nosignal : prop.clear;
auto propAppend =
useNonsignalingListOps ? &QQmlVMEMetaObject::list_append_nosignal : prop.append;
propClear(&prop);
const auto doAppend = [&](QObject *o) {
if (o && !QQmlMetaObject::canConvert(o, valueMetaObject))
o = nullptr;
prop.append(&prop, o);
propAppend(&prop, o);
};
if (variantMetaType == QMetaType::fromType<QQmlListReference>()) {
@ -1574,6 +1582,10 @@ bool QQmlPropertyPrivate::write(
} else if (!iterateQObjectContainer(variantMetaType, value.data(), doAppend)) {
doAppend(QQmlMetaType::toQObject(value));
}
if (useNonsignalingListOps) {
Q_ASSERT(QQmlVMEMetaObject::get(object));
QQmlVMEResolvedList(&prop).activateSignal();
}
} else if (variantMetaType == propertyMetaType) {
QVariant v = value;
property.writeProperty(object, v.data(), flags);

View File

@ -25,100 +25,96 @@
QT_BEGIN_NAMESPACE
class ResolvedList
QQmlVMEResolvedList::QQmlVMEResolvedList(QQmlListProperty<QObject> *prop)
{
Q_DISABLE_COPY_MOVE(ResolvedList)
// see QQmlVMEMetaObject::metaCall for how this was constructed
auto encodedIndex = quintptr(prop->data);
constexpr quintptr usableBits = sizeof(quintptr) * CHAR_BIT;
quintptr inheritanceDepth = encodedIndex >> (usableBits / 2);
m_id = encodedIndex & ((quintptr(1) << (usableBits / 2)) - 1);
public:
ResolvedList(QQmlListProperty<QObject> *prop)
{
// see QQmlVMEMetaObject::metaCall for how this was constructed
auto encodedIndex = quintptr(prop->data);
constexpr quintptr usableBits = sizeof(quintptr) * CHAR_BIT;
quintptr inheritanceDepth = encodedIndex >> (usableBits / 2);
m_id = encodedIndex & ((quintptr(1) << (usableBits / 2)) - 1);
// walk up to the correct meta object if necessary
auto mo = static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(prop->object)->metaObject);
while (inheritanceDepth--)
mo = mo->parentVMEMetaObject();
m_metaObject = mo;
Q_ASSERT(m_metaObject);
Q_ASSERT(::strstr(m_metaObject->toDynamicMetaObject(prop->object)
->property(m_metaObject->propOffset() + m_id)
.typeName(),
"QQmlListProperty"));
Q_ASSERT(m_metaObject->object == prop->object);
// walk up to the correct meta object if necessary
auto mo = static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(prop->object)->metaObject);
while (inheritanceDepth--)
mo = mo->parentVMEMetaObject();
m_metaObject = mo;
Q_ASSERT(m_metaObject);
Q_ASSERT(::strstr(m_metaObject->toDynamicMetaObject(prop->object)->property(
m_metaObject->propOffset() + m_id).typeName(), "QQmlListProperty") );
Q_ASSERT(m_metaObject->object == prop->object);
// readPropertyAsList() with checks transformed into Q_ASSERT
// and without allocation.
if (m_metaObject->propertyAndMethodStorage.isUndefined() &&
m_metaObject->propertyAndMethodStorage.valueRef()) {
return;
}
if (auto *md = static_cast<QV4::MemberData *>(
m_metaObject->propertyAndMethodStorage.asManaged())) {
const auto *v = (md->data() + m_id)->as<QV4::VariantObject>();
Q_ASSERT(v);
Q_ASSERT(v->d());
QVariant &data = v->d()->data();
Q_ASSERT(data.userType() == qMetaTypeId<QVector<QQmlGuard<QObject>>>());
m_list = static_cast<QVector<QQmlGuard<QObject>> *>(data.data());
Q_ASSERT(m_list);
}
// readPropertyAsList() with checks transformed into Q_ASSERT
// and without allocation.
if (m_metaObject->propertyAndMethodStorage.isUndefined()
&& m_metaObject->propertyAndMethodStorage.valueRef()) {
return;
}
~ResolvedList() = default;
QQmlVMEMetaObject *metaObject() const { return m_metaObject; }
QVector<QQmlGuard<QObject>> *list() const { return m_list; }
quintptr id() const { return m_id; }
void activateSignal() const
{
m_metaObject->activate(m_metaObject->object, int(m_id + m_metaObject->methodOffset()),
nullptr);
if (auto *md = static_cast<QV4::MemberData *>(
m_metaObject->propertyAndMethodStorage.asManaged())) {
const auto *v = (md->data() + m_id)->as<QV4::VariantObject>();
Q_ASSERT(v);
Q_ASSERT(v->d());
QVariant &data = v->d()->data();
Q_ASSERT(data.userType() == qMetaTypeId<QVector<QQmlGuard<QObject>>>());
m_list = static_cast<QVector<QQmlGuard<QObject>> *>(data.data());
Q_ASSERT(m_list);
}
}
private:
QQmlVMEMetaObject *m_metaObject = nullptr;
QVector<QQmlGuard<QObject>> *m_list = nullptr;
quintptr m_id = 0;
};
QQmlVMEResolvedList::~QQmlVMEResolvedList() = default;
static void list_append(QQmlListProperty<QObject> *prop, QObject *o)
void QQmlVMEResolvedList::activateSignal() const
{
const ResolvedList resolved(prop);
m_metaObject->activate(m_metaObject->object, int(m_id + m_metaObject->methodOffset()), nullptr);
}
void QQmlVMEMetaObject::list_append(QQmlListProperty<QObject> *prop, QObject *o)
{
const QQmlVMEResolvedList resolved(prop);
resolved.list()->append(o);
resolved.activateSignal();
}
void QQmlVMEMetaObject::list_append_nosignal(QQmlListProperty<QObject> *prop, QObject *o)
{
QQmlVMEResolvedList(prop).list()->append(o);
}
static qsizetype list_count(QQmlListProperty<QObject> *prop)
{
return ResolvedList(prop).list()->size();
return QQmlVMEResolvedList(prop).list()->size();
}
static QObject *list_at(QQmlListProperty<QObject> *prop, qsizetype index)
{
return ResolvedList(prop).list()->at(index);
return QQmlVMEResolvedList(prop).list()->at(index);
}
static void list_clear(QQmlListProperty<QObject> *prop)
void QQmlVMEMetaObject::list_clear(QQmlListProperty<QObject> *prop)
{
const ResolvedList resolved(prop);
const QQmlVMEResolvedList resolved(prop);
resolved.list()->clear();
resolved.activateSignal();
}
void QQmlVMEMetaObject::list_clear_nosignal(QQmlListProperty<QObject> *prop)
{
QQmlVMEResolvedList(prop).list()->clear();
}
static void list_replace(QQmlListProperty<QObject> *prop, qsizetype index, QObject *o)
{
const ResolvedList resolved(prop);
const QQmlVMEResolvedList resolved(prop);
resolved.list()->replace(index, o);
resolved.activateSignal();
}
static void list_removeLast(QQmlListProperty<QObject> *prop)
{
const ResolvedList resolved(prop);
const QQmlVMEResolvedList resolved(prop);
resolved.list()->removeLast();
resolved.activateSignal();
}

View File

@ -37,6 +37,26 @@
QT_BEGIN_NAMESPACE
class QQmlVMEMetaObject;
class QQmlVMEResolvedList
{
Q_DISABLE_COPY_MOVE(QQmlVMEResolvedList)
public:
QQmlVMEResolvedList(QQmlListProperty<QObject> *prop);
~QQmlVMEResolvedList();
QQmlVMEMetaObject *metaObject() const { return m_metaObject; }
QVector<QQmlGuard<QObject>> *list() const { return m_list; }
quintptr id() const { return m_id; }
void activateSignal() const;
private:
QQmlVMEMetaObject *m_metaObject = nullptr;
QVector<QQmlGuard<QObject>> *m_list = nullptr;
quintptr m_id = 0;
};
class QQmlVMEVariantQObjectPtr : public QQmlGuard<QObject>
{
public:
@ -149,6 +169,11 @@ public:
static QQmlVMEMetaObject *getForMethod(QObject *o, int coreIndex);
static QQmlVMEMetaObject *getForSignal(QObject *o, int coreIndex);
static void list_append(QQmlListProperty<QObject> *prop, QObject *o);
static void list_clear(QQmlListProperty<QObject> *prop);
static void list_append_nosignal(QQmlListProperty<QObject> *prop, QObject *o);
static void list_clear_nosignal(QQmlListProperty<QObject> *prop);
protected:
int metaCall(QObject *o, QMetaObject::Call _c, int _id, void **_a) override;

View File

@ -0,0 +1,13 @@
import QtQuick 2.0
Item {
property int signalCounter: 0
property list<QtObject> sourceList: [ QtObject{}, QtObject{}, QtObject{} ]
property list<QtObject> targetList1: sourceList
onTargetList1Changed: signalCounter++
function assignList() {
targetList1 = sourceList
}
}

View File

@ -217,6 +217,9 @@ private slots:
void bindToNonQObjectTarget();
void assignVariantList();
void listAssignmentSignals();
private:
QQmlEngine engine;
};
@ -2544,6 +2547,19 @@ void tst_qqmlproperty::assignVariantList()
QCOMPARE(holder->doubleList(), doubleList);
}
void tst_qqmlproperty::listAssignmentSignals()
{
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl("listAssignmentSignals.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> root(component.create());
QVERIFY(!root.isNull());
QCOMPARE(root->property("signalCounter").toInt(), 1);
QMetaObject::invokeMethod(root.get(), "assignList");
QCOMPARE(root->property("signalCounter").toInt(), 2);
}
QTEST_MAIN(tst_qqmlproperty)
#include "tst_qqmlproperty.moc"