Details
-
Bug
-
Resolution: Unresolved
-
P1: Critical
-
6.5.1, 6.6.1
-
None
-
Windows, but should occur on any platform.
Description
Consider the following basic example of a C++ model exposed to QML:
// main.cpp #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QAbstractListModel> #include <QQmlContext> #include <QDebug> #include "RootData.h" class CppModel : public QAbstractListModel { public: virtual int rowCount(const QModelIndex &parent) const override {return 1;} virtual QVariant data(const QModelIndex &index, int role) const override {qDebug () << "CppModel::data"; return "test";} void Reset () {beginResetModel(); endResetModel();} }; int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); CppModel *model = new CppModel(); RootData *rootData = new RootData(); QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("cppModel", model); engine.rootContext()->setContextProperty("loaderActive", true); engine.rootContext()->setContextProperty("rootData", rootData); engine.load(QStringLiteral("qrc:/main.qml")); qDebug () << "\nDisabling loader..."; engine.rootContext()->setContextProperty("loaderActive", false); // correctly destroys the Repeater, but after destruction some bindings will still be evaluated. qDebug () << "loader disabled"; qDebug () << "\nDelete rootData..."; rootData->DeleteObject(); // should never be used anymore, as loader is inactive qDebug () << "\nReset model..."; model->Reset(); // trigger the re-evaluation of destroyed bindings return app.exec(); }
// RootData.h #include "QObject" class RootData : public QObject { Q_OBJECT public: RootData() : object(new QObject()) {object->setObjectName("objectName");} ~RootData() {delete object;} Q_INVOKABLE QString getValue () {return object->objectName();} void DeleteObject() {delete object; object = nullptr;} QObject *object; };
// main.qml ApplicationWindow { visible: true Loader { active: loaderActive sourceComponent: comp } Component { id: comp Column { Repeater { id: repeater model: cppModel Component.onCompleted: console.log("Repeater constructed"); Component.onDestruction: console.log("Repeater destroyed"); delegate: Text {text: {console.log("updating text"); return display + rootData.getValue();}} } } } }
The output of this code is:
qml: updating text CppModel::data qml: Repeater constructed Disabling loader... qml: Repeater destroyed loader disabled Delete rootData... Reset model... qml: updating text // this should not occur CppModel::data // this should not occur // application crashes due to accessing destructed RootData::object (inside RootData::getValue called from QML): RootData::getValue should not have been called.
The main issue is that some bindings are evaluated due to the Repeater being repopulated as a result of a model reset, even after the Repeater itself is already destructed. This results in unexpected function calls, potentially accessing destructed objects (with C++ ownership) as it the case in this example (i.e. QML calls RootData::getValue() which access RootData::object after it is destructed).