aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2025-09-09 15:45:18 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2025-09-13 11:15:06 +0000
commitdd41fe2fae61e6b9d1096832fc408b65dbd011f5 (patch)
treee2bdebed02d17f7464e478759970805bf7c9813a
parent4300d3d031d12b1e123dbb2daa3875922d744386 (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.cpp9
-rw-r--r--tests/auto/qml/qjsengine/tst_qjsengine.cpp30
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"