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
42class tst_qv4mm : public QQmlDataTest
43{
44 Q_OBJECT
45
46private slots:
47 void gcStats();
48 void multiWrappedQObjects();
49 void accessParentOnDestruction();
50 void clearICParent();
51};
52
53void tst_qv4mm::gcStats()
54{
55 QLoggingCategory::setFilterRules("qt.qml.gc.*=true");
56 QQmlEngine engine;
57 engine.collectGarbage();
58}
59
60void 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
98void 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
113void 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
151QTEST_MAIN(tst_qv4mm)
152
153#include "tst_qv4mm.moc"
154

source code of qtdeclarative/tests/auto/qml/qv4mm/tst_qv4mm.cpp