1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QV4REFERENCEOBJECT_P_H
5#define QV4REFERENCEOBJECT_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <private/qv4object_p.h>
19#include <private/qv4stackframe_p.h>
20#include <private/qqmlnotifier_p.h>
21#include <private/qv4qobjectwrapper_p.h>
22
23QT_BEGIN_NAMESPACE
24
25namespace QV4 {
26namespace Heap {
27
28struct ReferenceObject;
29struct ReferenceObjectEndpoint : QQmlNotifierEndpoint {
30 ReferenceObjectEndpoint(ReferenceObject* reference)
31 : QQmlNotifierEndpoint(QQmlDirtyReferenceObject),
32 reference(reference)
33 {}
34
35 ReferenceObject* reference;
36};
37
38#define ReferenceObjectMembers(class, Member) \
39 Member(class, Pointer, Object *, m_object)
40
41DECLARE_HEAP_OBJECT(ReferenceObject, Object) {
42 DECLARE_MARKOBJECTS(ReferenceObject);
43
44 enum Flag : quint8 {
45 NoFlag = 0,
46 CanWriteBack = 1 << 0,
47 IsVariant = 1 << 1,
48 EnforcesLocation = 1 << 2,
49 IsDirty = 1 << 3,
50 };
51 Q_DECLARE_FLAGS(Flags, Flag);
52
53 void init(Object *object, int property, Flags flags)
54 {
55 auto connectToNotifySignal = [this](QObject* obj, int property, QQmlEngine* engine) {
56 Q_ASSERT(obj);
57 Q_ASSERT(engine);
58
59 Q_ASSERT(!referenceEndpoint);
60 Q_ASSERT(!bindableNotifier);
61
62 referenceEndpoint = new ReferenceObjectEndpoint(this);
63 referenceEndpoint->connect(
64 source: obj,
65 // Connect and signal emission work on "signal
66 // indexe"s. Those are different from "method
67 // indexes".
68 // The public MetaObject interface can, generally,
69 // give us the "method index" of the notify
70 // signal.
71 // Quite unintuitively, this is true for
72 // "notifySignalIndex".
73 // As the "method index" and the "signal index"
74 // can be different, connecting the "method index"
75 // of the notify signal can incur in issues when
76 // the signal is being emitted and checking for
77 // connected endpoints.
78 // For example, we might be connected to the
79 // "method index" of the notify signal for the
80 // property and end up checking for the
81 // subscribers of a different signal when the
82 // notify signal is emitted, due to the different
83 // meaning of the same index.
84 // Thus we pass by the private interface to ensure
85 // that we are connecting based on the "signal
86 // index" instead.
87 sourceSignal: QMetaObjectPrivate::signalIndex(m: obj->metaObject()->property(index: property).notifySignal()),
88 engine);
89 // When the object that is being referenced is destroyed, we
90 // need to ensure that one additional read is performed to
91 // invalidate the data we hold.
92 // As the object might be destroyed in a way that doesn't
93 // trigger the notify signal for the relevant property, we react
94 // directly to the destruction itself.
95 // We use a plain connection instead of a QQmlNotifierEndpoint
96 // based connection as, currently, declarative-side signals are
97 // always discarded during destruction (see
98 // QQmlData::signalEmitted).
99 // In theory it should be possible to relax that condition for
100 // the destroy signal specifically, which should allow a more
101 // optimized way of connecting.
102 // Nonetheless this seems to be the only place where we have
103 // this kind of need, and thus go for the simpler solution,
104 // which can be changed later if the need arise.
105 new(onDelete) QMetaObject::Connection(QObject::connect(sender: obj, signal: &QObject::destroyed, slot: [this](){ setDirty(true); }));
106 };
107
108 auto connectToBindable = [this](QObject* obj, int property, QQmlEngine* engine) {
109 Q_ASSERT(obj);
110 Q_ASSERT(engine);
111
112 Q_ASSERT(!referenceEndpoint);
113 Q_ASSERT(!bindableNotifier);
114
115 bindableNotifier = new QPropertyNotifier(obj->metaObject()->property(index: property).bindable(object: obj).addNotifier(f: [this](){ setDirty(true); }));
116 new(onDelete) QMetaObject::Connection(QObject::connect(sender: obj, signal: &QObject::destroyed, slot: [this](){ setDirty(true); }));
117 };
118
119 setObject(object);
120 m_property = property;
121 m_flags = flags;
122
123 while (object &&
124 object->internalClass->vtable->type != Managed::Type_V4QObjectWrapper &&
125 object->internalClass->vtable->type != Managed::Type_QMLTypeWrapper)
126 {
127 if (!(object->internalClass->vtable->type == Managed::Type_V4ReferenceObject) &&
128 !(object->internalClass->vtable->type == Managed::Type_V4Sequence) &&
129 !(object->internalClass->vtable->type == Managed::Type_DateObject) &&
130 !(object->internalClass->vtable->type == Managed::Type_QMLValueTypeWrapper))
131 {
132 break;
133 }
134
135 property = static_cast<QV4::Heap::ReferenceObject*>(object)->property();
136 object = static_cast<QV4::Heap::ReferenceObject*>(object)->object();
137 }
138
139 if (object && object->internalClass->vtable->type == Managed::Type_V4QObjectWrapper)
140 {
141 auto wrapper = static_cast<QV4::Heap::QObjectWrapper*>(object);
142 QObject* obj = wrapper->object();
143
144 if (obj->metaObject()->property(index: property).isBindable() && internalClass->engine->qmlEngine())
145 connectToBindable(obj, property, internalClass->engine->qmlEngine());
146 else if (obj->metaObject()->property(index: property).hasNotifySignal() && internalClass->engine->qmlEngine())
147 connectToNotifySignal(obj, property, internalClass->engine->qmlEngine());
148 }
149
150 if (object && object->internalClass->vtable->type == Managed::Type_QMLTypeWrapper) {
151 auto wrapper = static_cast<QV4::Heap::QQmlTypeWrapper*>(object);
152
153 Scope scope(internalClass->engine);
154 Scoped<QV4::QQmlTypeWrapper> scopedWrapper(scope, wrapper);
155 QObject* obj = scopedWrapper->object();
156
157 if (obj->metaObject()->property(index: property).isBindable() && internalClass->engine->qmlEngine())
158 connectToBindable(obj, property, internalClass->engine->qmlEngine());
159 else if (obj->metaObject()->property(index: property).hasNotifySignal() && internalClass->engine->qmlEngine())
160 connectToNotifySignal(obj, property, internalClass->engine->qmlEngine());
161 }
162
163 // If we could not connect to anything we don't have a way to
164 // dirty on-demand and thus should be in an always dirty state
165 // to ensure that reads go through.
166 if (!isConnected())
167 setDirty(true);
168
169 Object::init();
170 }
171
172 Flags flags() const { return Flags(m_flags); }
173
174 Object *object() const { return m_object.get(); }
175 void setObject(Object *object) { m_object.set(e: internalClass->engine, newVal: object); }
176
177 int property() const { return m_property; }
178
179 bool canWriteBack() const { return hasFlag(flag: CanWriteBack); }
180 bool isVariant() const { return hasFlag(flag: IsVariant); }
181 bool enforcesLocation() const { return hasFlag(flag: EnforcesLocation); }
182
183 void setLocation(const Function *function, quint16 statement)
184 {
185 m_function = function;
186 m_statementIndex = statement;
187 }
188
189 const Function *function() const { return m_function; }
190 quint16 statementIndex() const { return m_statementIndex; }
191
192 bool isAttachedToProperty() const
193 {
194 if (enforcesLocation()) {
195 if (CppStackFrame *frame = internalClass->engine->currentStackFrame) {
196 if (frame->v4Function != function() || frame->statementNumber() != statementIndex())
197 return false;
198 } else {
199 return false;
200 }
201 }
202
203 return true;
204 }
205
206 bool isReference() const { return m_object; }
207
208 bool isDirty() const { return hasFlag(flag: IsDirty); }
209 void setDirty(bool dirty) { setFlag(flag: IsDirty, set: dirty); }
210 bool isConnected() {
211 return (referenceEndpoint && referenceEndpoint->isConnected()) || bindableNotifier;
212 }
213
214 void destroy() {
215 // If we allocated any connection then we must have connected
216 // to the destroyed signal too, and we should clean it up.
217 if (referenceEndpoint || bindableNotifier) {
218 QObject::disconnect(*reinterpret_cast<QMetaObject::Connection*>(&onDelete));
219 std::destroy_at(location: reinterpret_cast<QMetaObject::Connection*>(&onDelete));
220 }
221
222 if (referenceEndpoint)
223 delete referenceEndpoint;
224
225 if (bindableNotifier)
226 delete bindableNotifier;
227 }
228
229 private:
230
231 bool hasFlag(Flag flag) const
232 {
233 return m_flags & quint8(flag);
234 }
235
236 void setFlag(Flag flag, bool set)
237 {
238 m_flags = set ? (m_flags | quint8(flag)) : (m_flags & ~quint8(flag));
239 }
240
241 const Function *m_function;
242 int m_property;
243 quint16 m_statementIndex;
244 quint8 m_flags;
245 ReferenceObjectEndpoint* referenceEndpoint;
246 QPropertyNotifier* bindableNotifier;
247 // We need to store an handle if we connect to the destroyed
248 // signal so that we can disconnect from it. To avoid yet
249 // another allocation, considering that
250 // QMetaObject::Connection is not trivial, we store it in
251 // block memory.
252 alignas(alignof(QMetaObject::Connection))
253 std::byte onDelete[sizeof(QMetaObject::Connection)];
254 };
255
256 Q_DECLARE_OPERATORS_FOR_FLAGS(ReferenceObject::Flags)
257
258 } // namespace Heap
259
260
261 struct ReferenceObject : public Object
262 {
263 V4_OBJECT2(ReferenceObject, Object)
264 Q_MANAGED_TYPE(V4ReferenceObject)
265 V4_NEEDS_DESTROY
266
267 public:
268 static constexpr const int AllProperties = -1;
269
270 template<typename HeapObject>
271 static bool readReference(HeapObject *ref)
272 {
273 if (!ref->object())
274 return false;
275
276 if (!ref->isDirty())
277 return true;
278
279 QV4::Scope scope(ref->internalClass->engine);
280 QV4::ScopedObject object(scope, ref->object());
281
282 bool wasRead = false;
283 if (ref->isVariant()) {
284 QVariant variant;
285 void *a[] = { &variant };
286 wasRead = object->metacall(call: QMetaObject::ReadProperty, index: ref->property(), a)
287 && ref->setVariant(variant);
288 } else {
289 void *a[] = { ref->storagePointer() };
290 wasRead = object->metacall(call: QMetaObject::ReadProperty, index: ref->property(), a);
291 }
292
293 ref->setDirty(!ref->isConnected() || !wasRead);
294 return wasRead;
295 }
296
297 template<typename HeapObject>
298 static bool writeBack(HeapObject *ref, int internalIndex = AllProperties)
299 {
300 if (!ref->object() || !ref->canWriteBack())
301 return false;
302
303 QV4::Scope scope(ref->internalClass->engine);
304 QV4::ScopedObject object(scope, ref->object());
305
306 int flags = QQmlPropertyData::HasInternalIndex;
307 int status = -1;
308 if (ref->isVariant()) {
309 QVariant variant = ref->toVariant();
310 void *a[] = { &variant, nullptr, &status, &flags, &internalIndex };
311 return object->metacall(call: QMetaObject::WriteProperty, index: ref->property(), a);
312 }
313
314 void *a[] = { ref->storagePointer(), nullptr, &status, &flags, &internalIndex };
315 return object->metacall(call: QMetaObject::WriteProperty, index: ref->property(), a);
316 }
317
318 template<typename HeapObject>
319 static HeapObject *detached(HeapObject *ref)
320 {
321 if (ref->object() && !ref->enforcesLocation() && !readReference(ref))
322 return ref; // It's dead. No point in detaching it anymore
323
324 return ref->detached();
325 }
326};
327
328} // namespace QV4
329
330QT_END_NAMESPACE
331
332#endif // QV4REFERENCEOBJECT_P_H
333

source code of qtdeclarative/src/qml/jsruntime/qv4referenceobject_p.h