diff options
| author | Ulf Hermann <ulf.hermann@qt.io> | 2025-09-09 15:45:18 +0200 |
|---|---|---|
| committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2025-09-13 11:15:06 +0000 |
| commit | dd41fe2fae61e6b9d1096832fc408b65dbd011f5 (patch) | |
| tree | e2bdebed02d17f7464e478759970805bf7c9813a | |
| parent | 4300d3d031d12b1e123dbb2daa3875922d744386 (diff) | |
QtQml: Empty SimpleArrayData vacant space when truncating
Without this we effectively soft-leak the contents of any
SimpleArrayData whenever we truncate it. Only when the array was either
completely dropped or re-filled would the extra objects be reclaimed.
Task-number: QTBUG-139025
Pick-to: 6.8
Change-Id: I88e9dc3ea8ec57c1de71b7b5417ebcfbaa75bb61
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit e0f65fe66f0cc17eaf4c6c41d1b2f65ab2737e3c)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 04f069e4d60921ef5422f51a2ed47b7786de61f3)
| -rw-r--r-- | src/qml/jsruntime/qv4arraydata.cpp | 9 | ||||
| -rw-r--r-- | tests/auto/qml/qjsengine/tst_qjsengine.cpp | 30 |
2 files changed, 37 insertions, 2 deletions
diff --git a/src/qml/jsruntime/qv4arraydata.cpp b/src/qml/jsruntime/qv4arraydata.cpp index 724f6fbfa3..f691fe6beb 100644 --- a/src/qml/jsruntime/qv4arraydata.cpp +++ b/src/qml/jsruntime/qv4arraydata.cpp @@ -264,14 +264,19 @@ uint SimpleArrayData::truncate(Object *o, uint newLen) return newLen; if (!dd->attrs) { + for (uint i = newLen; i < dd->values.size; ++i) + dd->setData(dd->internalClass->engine, i, Value::emptyValue()); dd->values.size = newLen; return newLen; } while (dd->values.size > newLen) { - if (!dd->data(dd->values.size - 1).isEmpty() && !dd->attrs[dd->values.size - 1].isConfigurable()) + const uint lastIndex = dd->values.size - 1; + if (!dd->data(lastIndex).isEmpty() && !dd->attrs[lastIndex].isConfigurable()) return dd->values.size; - --dd->values.size; + + dd->setData(dd->internalClass->engine, lastIndex, Value::emptyValue()); + dd->values.size = lastIndex; } return dd->values.size; } diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 60b5cfbf46..e0dfc0afeb 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -355,6 +355,7 @@ private slots: #endif void evalInGlobalContext(); + void truncateArrayData(); public: Q_INVOKABLE QJSValue throwingCppMethod1(); @@ -6899,6 +6900,35 @@ void tst_QJSEngine::evalInGlobalContext() QCOMPARE(ret.toString(), QLatin1String("99")); } +void tst_QJSEngine::truncateArrayData() +{ + QJSEngine engine; + + QJSValue array = engine.newArray(); + array.setProperty(0, QJSValue::NullValue); + array.setProperty(1, QJSValue(14)); + array.setProperty(2, QJSValue(QLatin1String("aaa"))); + + // Append a JavaScript-owned object to the array and don't keep a local reference. + QJSValue object = engine.newQObject(new QObject()); + QSignalSpy spy(object.toQObject(), &QObject::destroyed); + // std::move won't do here because setProperty() doesn't accept rvalue refs + array.setProperty(3, std::exchange(object, QJSValue())); + QVERIFY(object.isUndefined()); + + QCOMPARE(array.property("length").toInt(), 4); + + gc(*engine.handle()); + QCOMPARE(spy.count(), 0); + QCOMPARE(array.property("length").toInt(), 4); + + // Truncating the array allows the GC to collect the QObject, which results in its deletion. + array.setProperty("length", 3); + QCOMPARE(array.property("length").toInt(), 3); + gc(*engine.handle()); + QCOMPARE(spy.count(), 1); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" |
