QQmlListProperty ошибочные сбои с передачей прав собственности на JavaScript
Я испытываю некоторые серьезные носовые демоны с Qt5 (5.8, Arch Linux) и QML. По сути, код предоставляет QQmlListProperty с его членами, созданными на лету, и передающими свои права на JavaScript. Когда сигнал itemChanged() испускается тысячи раз (в моем реальном коде достаточно только пары раз), программа вызывает ошибку.
Согласно Valgrind + GDB, похоже, что 'A' освобождается QObject::event, а затем читается QML, вызывая segfault.
Как ни странно, даже если изменить имя переменной 'game' на что-то другое, то количество раз, когда мне приходится нажимать кнопку для сбоя программы, меняется. Удаление повторителя или прямоугольника или использование другого выражения для цвета прямоугольника влияет на зависание программы или нет. Кроме того, всякий раз, когда право собственности не передается JS, не возникает никаких проблем. К сожалению, это также вызывает утечки памяти, поскольку ничто не освобождает объекты "А".
Из-за этих деталей я считаю, что где-то в коде есть неопределенное поведение, но я понятия не имею, где.
Это самый короткий пример, который я мог бы создать:
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QObject>
#include <QQmlListProperty>
template <typename T>
T* make_js() {
T* t = new T();
QQmlEngine::setObjectOwnership(t, QQmlEngine::JavaScriptOwnership);
return t;
}
class A: public QObject {
Q_OBJECT
Q_PROPERTY(int id READ id CONSTANT)
public:
int id() const {return 1;}
};
class B: public QObject {
Q_OBJECT
Q_PROPERTY(A* item READ item NOTIFY itemChanged)
Q_PROPERTY(QQmlListProperty<A> list READ list CONSTANT)
public:
A *item() const { return make_js<A>(); }
Q_INVOKABLE void change() { emit itemChanged(); }
QQmlListProperty<A> list() {
return QQmlListProperty<A>(
this, nullptr,
[](QQmlListProperty<A> *) {return 1;},
[](QQmlListProperty<A> *, int ) {return make_js<A>();});
}
signals:
void itemChanged();
};
#include "main.moc"
int main(int argc, char *argv[]) {
QGuiApplication a(argc, argv);
B* game = new B();
QQmlApplicationEngine engine;
qmlRegisterType<B>("example", 1, 0, "B");
qmlRegisterType<A>("example", 1, 0, "A");
engine.rootContext()->setContextProperty("thing", game);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
a.exec();
delete game;
return 0;
}
main.qml:
import QtQuick 2.0
import QtQuick.Layouts 1.0
import QtQuick.Window 2.0
import QtQuick.Controls 2.0
import example 1.0
Window {
visible: true
width: 1280
height: 800
title: qsTr("Example")
ColumnLayout {
anchors.fill: parent
Repeater {
model: thing.list
Rectangle {
color: thing.item.id === 1 ? "red" : "blue"
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Button {
text: "Click me a bunch of times to crash!"
anchors.fill: parent
onClicked: {for(var i=0;i<1000;++i) thing.change();}
}
}
}
}
В чем причина такого поведения? Вы знаете об обходном пути для этого? Может ли быть так, что QQmlListProperty каким-то образом не совместим с владением JavaScript?
РЕДАКТИРОВАТЬ: я нашел обходной путь, который достаточно хорош для моего использования. изменения
Q_PROPERTY(QQmlListProperty<A> list READ list CONSTANT)
в
Q_PROPERTY(QQmlListProperty<A> list READ list NOTIFY listChanged)
и испуская listChanged() в change() вместе с itemChanged() исправляет это. При дополнительном тестировании я заметил, что Qt только один раз вызывает индексную лямбду из list(), а затем освобождает возвращаемый объект "A". Он больше не вызывает лямбду и, кажется, пытается повторно использовать уже освобожденную букву "А".
Тем не менее, я все еще хотел бы знать, что вызывает это в первую очередь.