| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 basysKom GmbH. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the test suite of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | #include <qtest.h> |
| 30 | #include <QQmlEngine> |
| 31 | #include <QLoggingCategory> |
| 32 | #include <QQmlComponent> |
| 33 | |
| 34 | #include <private/qv4mm_p.h> |
| 35 | #include <private/qv4qobjectwrapper_p.h> |
| 36 | #include <private/qjsvalue_p.h> |
| 37 | |
| 38 | #include "../../shared/util.h" |
| 39 | |
| 40 | #include <memory> |
| 41 | |
| 42 | class tst_qv4mm : public QQmlDataTest |
| 43 | { |
| 44 | Q_OBJECT |
| 45 | |
| 46 | private slots: |
| 47 | void gcStats(); |
| 48 | void multiWrappedQObjects(); |
| 49 | void accessParentOnDestruction(); |
| 50 | void clearICParent(); |
| 51 | }; |
| 52 | |
| 53 | void tst_qv4mm::gcStats() |
| 54 | { |
| 55 | QLoggingCategory::setFilterRules("qt.qml.gc.*=true" ); |
| 56 | QQmlEngine engine; |
| 57 | engine.collectGarbage(); |
| 58 | } |
| 59 | |
| 60 | void tst_qv4mm::multiWrappedQObjects() |
| 61 | { |
| 62 | QV4::ExecutionEngine engine1; |
| 63 | QV4::ExecutionEngine engine2; |
| 64 | { |
| 65 | QObject object; |
| 66 | for (int i = 0; i < 10; ++i) |
| 67 | QV4::QObjectWrapper::wrap(engine: i % 2 ? &engine1 : &engine2, object: &object); |
| 68 | |
| 69 | QCOMPARE(engine1.memoryManager->m_pendingFreedObjectWrapperValue.size(), 0); |
| 70 | QCOMPARE(engine2.memoryManager->m_pendingFreedObjectWrapperValue.size(), 0); |
| 71 | { |
| 72 | QV4::WeakValue value; |
| 73 | value.set(engine: &engine1, value: QV4::QObjectWrapper::wrap(engine: &engine1, object: &object)); |
| 74 | } |
| 75 | |
| 76 | QCOMPARE(engine1.memoryManager->m_pendingFreedObjectWrapperValue.size(), 1); |
| 77 | QCOMPARE(engine2.memoryManager->m_pendingFreedObjectWrapperValue.size(), 0); |
| 78 | |
| 79 | // The additional WeakValue from m_multiplyWrappedQObjects hasn't been moved |
| 80 | // to m_pendingFreedObjectWrapperValue yet. It's still alive after all. |
| 81 | engine1.memoryManager->runGC(); |
| 82 | QCOMPARE(engine1.memoryManager->m_pendingFreedObjectWrapperValue.size(), 1); |
| 83 | |
| 84 | // engine2 doesn't own the object as engine1 was the first to wrap it above. |
| 85 | // Therefore, no effect here. |
| 86 | engine2.memoryManager->runGC(); |
| 87 | QCOMPARE(engine2.memoryManager->m_pendingFreedObjectWrapperValue.size(), 0); |
| 88 | } |
| 89 | |
| 90 | // Clears m_pendingFreedObjectWrapperValue. Now it's really dead. |
| 91 | engine1.memoryManager->runGC(); |
| 92 | QCOMPARE(engine1.memoryManager->m_pendingFreedObjectWrapperValue.size(), 0); |
| 93 | |
| 94 | engine2.memoryManager->runGC(); |
| 95 | QCOMPARE(engine2.memoryManager->m_pendingFreedObjectWrapperValue.size(), 0); |
| 96 | } |
| 97 | |
| 98 | void tst_qv4mm::accessParentOnDestruction() |
| 99 | { |
| 100 | QLoggingCategory::setFilterRules("qt.qml.gc.*=false" ); |
| 101 | QQmlEngine engine; |
| 102 | QQmlComponent component(&engine, testFileUrl(fileName: "createdestroy.qml" )); |
| 103 | std::unique_ptr<QObject> obj(component.create()); |
| 104 | QVERIFY(obj); |
| 105 | QPointer<QObject> timer = qvariant_cast<QObject *>(v: obj->property(name: "timer" )); |
| 106 | QVERIFY(timer); |
| 107 | QTRY_VERIFY(!timer->property("running" ).toBool()); |
| 108 | QCOMPARE(obj->property("iterations" ).toInt(), 100); |
| 109 | QCOMPARE(obj->property("creations" ).toInt(), 100); |
| 110 | QCOMPARE(obj->property("destructions" ).toInt(), 100); |
| 111 | } |
| 112 | |
| 113 | void tst_qv4mm::clearICParent() |
| 114 | { |
| 115 | QV4::ExecutionEngine engine; |
| 116 | QV4::Scope scope(engine.rootContext()); |
| 117 | QV4::ScopedObject object(scope, engine.newObject()); |
| 118 | |
| 119 | // Keep identifiers in a separate array so that we don't have to allocate them in the loop that |
| 120 | // should test the GC on InternalClass allocations. |
| 121 | QV4::ScopedArrayObject identifiers(scope, engine.newArrayObject()); |
| 122 | for (uint i = 0; i < 16 * 1024; ++i) { |
| 123 | QV4::Scope scope(&engine); |
| 124 | QV4::ScopedString s(scope); |
| 125 | s = engine.newIdentifier(text: QString::fromLatin1(str: "key%1" ).arg(a: i)); |
| 126 | identifiers->push_back(v: s); |
| 127 | |
| 128 | QV4::ScopedValue v(scope); |
| 129 | v->setDouble(i); |
| 130 | object->insertMember(s, v); |
| 131 | } |
| 132 | |
| 133 | // When allocating the InternalClass objects required for deleting properties, the GC should |
| 134 | // eventually run and remove all but the last two. |
| 135 | // If we ever manage to avoid allocating the InternalClasses in the first place we will need |
| 136 | // to change this test. |
| 137 | for (uint i = 0; i < 16 * 1024; ++i) { |
| 138 | QV4::Scope scope(&engine); |
| 139 | QV4::ScopedString s(scope, identifiers->get(idx: i)); |
| 140 | QV4::Scoped<QV4::InternalClass> ic(scope, object->internalClass()); |
| 141 | QVERIFY(ic->d()->parent != nullptr); |
| 142 | object->deleteProperty(id: s->toPropertyKey()); |
| 143 | QVERIFY(object->internalClass() != ic->d()); |
| 144 | QCOMPARE(object->internalClass()->parent, ic->d()); |
| 145 | if (ic->d()->parent == nullptr) |
| 146 | return; |
| 147 | } |
| 148 | QFAIL("Garbage collector was not triggered by large amount of InternalClasses" ); |
| 149 | } |
| 150 | |
| 151 | QTEST_MAIN(tst_qv4mm) |
| 152 | |
| 153 | #include "tst_qv4mm.moc" |
| 154 | |