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#include <private/qv4referenceobject_p.h>
5
6QT_BEGIN_NAMESPACE
7
8DEFINE_OBJECT_VTABLE(QV4::ReferenceObject);
9
10/*!
11 \class QV4::ReferenceObject
12 \brief An object that keeps track of the provenance of its owned
13 value, allowing to reflect mutations on the original instance.
14
15 \internal
16
17 \section1 Copied Types and Mutations
18
19 In QML, certain types are conceptually passed by value.
20 Instances of those types are always copied when they are accessed
21 or passed around.
22
23 Let those be "Copied Type"s.
24
25 For example, suppose that \c{foo} is an instance of a Copied Type
26 that has a member \c{bar} that has some value \c{X}.
27
28 Consider the following example:
29
30 \qml
31 import QtQuick
32
33 Item {
34 Component.onCompleted: {
35 foo.bar = Y
36 console.log(foo.bar)
37 }
38 }
39 \endqml
40
41 Where \c{Y} is some value that can inhabit \c{foo.bar} and whose
42 stringified representation is distinguishable from \c{X}.
43
44 One might expect that a stringified representation of \c{Y} should be logged.
45 Nonetheless, as \c{foo} is a Copied Type, accessing it creates a copy.
46 The access to the \c{bar} member and its further mutation is
47 performed on the copy that was created, and thus is not retained by
48 the object that \c{foo} refers to.
49
50 If \c{copy} is an operation that performs a deep-copy of an object
51 and returns it, the above snippet can be considered implicitly
52 equivalent to the following:
53
54 \qml
55 import QtQuick
56
57 Item {
58 Component.onCompleted: {
59 copy(foo).bar = Y
60 console.log(copy(foo).bar)
61 }
62 }
63 \endqml
64
65 This can generally be surprising as it stands in contrast to the
66 effect that the above assignment would have if \c{foo} wasn't a
67 Copied Type. Similarly, it stands in contrast to what one could
68 expect from the outcome of the same assignment in a Javascript
69 environment, where the mutation might be expected to generally be
70 visible in later steps no matter the type of \c{foo}.
71
72 A ReferenceObject can be used to avoid this inconsistency by
73 wrapping a value and providing a "write-back" mechanism that can
74 reflect mutations back on the original value.
75
76 Furthermore, a ReferenceObject can be used to load the data from
77 the original value to ensure that the two values remain in sync, as
78 the value might have been mutated while the copy is still alive,
79 conceptually allowing for an "inverted write-back".
80
81 \section1 Setting Up a ReferenceObject
82
83 ReferenceObject is intended to be extended by inheritance.
84
85 An object that is used to wrap a value that is copied around but
86 has a provenance that requires a write-back, can inherit from
87 ReferenceObject to plug into the write-back behavior.
88
89 The heap part of the object should subclass
90 QV4::Heap::ReferenceObject while the object part should subclass
91 QV4::ReferenceObject.
92
93 When initializing the heap part of the subclass,
94 QV4::Heap::ReferenceObject::init should be called to set up the
95 write-back mechanism.
96
97 The write-back mechanism stores a reference to an object and,
98 potentially, a property index to write-back at.
99
100 Furthermore, a series of flags can be used to condition the
101 behavior of the write-back.
102
103 For example:
104
105 \code
106 void QV4::Heap::Foo::init(Heap::Object *object) {
107 ReferenceObject::init(object, 1, ReferenceObject::Flag::CanWriteBack);
108 // Some further initialization code
109 ...
110 }
111 \endcode
112
113 The snippet is an example of some sub-class \c{Foo} of
114 ReferenceObject setting up for a write-back to the property at index
115 1 of some object \c{object}.
116
117 Generally, a sub-class of ReferenceObject will be used to wrap one
118 or more Copied Types and provide a certain behavior.
119
120 In certain situations there is no need to setup a write-back.
121 For example, we might have certain cases where there is no original
122 value to be wrapped while still in need of providing an object of
123 the sub-classing type.
124
125 One example of such a behavior is that of returning an instance from
126 a C++ method to QML.
127
128 When that is the case, the following initialization can be provided:
129
130 \code
131 void QV4::Heap::Foo::init(Heap::Object *object) {
132 ReferenceObject::init(nullptr, -1, NoFlag);
133 // Some further initialization code
134 ...
135 }
136 \endcode
137
138 \section2 Intitialization and the IsDirty flag
139
140 In certain cases, we try to avoid read-backs when we know that we
141 have the latest data available already, see \l{Limiting reads on a
142 QObject property}.
143
144 Certain implementation of ReferenceObject, might want to lazily load
145 the data on the first read, rather than on the initialization of the
146 reference. One such example would be `QQmlValueTypeWrapper`.
147
148 When that is the case, the IsDirty flag should be passed at
149 initialiazation time.
150 For example:
151
152 \code
153 void QV4::Heap::Foo::init(Heap::Object *object) {
154 ReferenceObject::init(object, 1, ReferenceObject::Flag::CanWriteBack | ReferenceObject::Flag::IsDirty);
155 // Some further initialization code
156 ...
157 }
158 \endcode
159
160 If the flag is not passed there the first read might be elided,
161 leaving the object in an incorrect state.
162
163 \section1 Providing the Required Infrastructure for a Default Write-back
164
165 Generally, to use the base implementation of write and read backs,
166 as provided by QV4::ReferenceObject::readReference and
167 QV4::ReferenceObject::writeBack, a sub-class should provide the
168 following interface:
169
170 \code
171 void *storagePointer();
172 const void *storagePointer();
173
174
175 QVariant toVariant() const;
176 bool setVariant(const QVariant &variant);
177 \endcode
178
179 The two overloads of \c{storagePointer} should provide access to
180 the internal backing storage of the ReferenceObject.
181
182 Generally, a ReferenceObject will be wrapping some instance of a C++
183 type, owning a copy of the original instance used as a storage for
184 writes and reads.
185 An implementation of \c{storagePointer} should give access to this
186 backing storage, which is used to know what value to write-back and
187 where to write when reading back from the original value.
188
189 For example, a Sequence type that wraps a QVariantList will own its
190 own instance of a QVariantList. The implementation for
191 \c{storagePointer} should give access to that owned instance.
192
193 \c{toVariant} should provide a QVariant wrapped representation of
194 the internal storage that the ReferenceObject uses.
195 This is used during the write-back of a ReferenceObject whose
196 original value was a QVariant backed instance.
197
198 Do remember that instances of a ReferenceObject that are backing
199 QVariant values should further pass the
200 QV4::Heap::ReferenceObject::Flag::IsVariant flag at initialization
201 time.
202
203 On the opposite side, \c{setVariant} should switch the value that
204 the ReferenceObject stores with the provided variant.
205 This is used when a QVariant backed ReferenceObject performs a read
206 of its original value, to allow for synchronization.
207
208 It is still possible to use ReferenceObject without necessarily
209 providing the above interface.
210 QV4::DateObject is an example of a ReferenceObject sub-class that
211 provides its own writeBack implementation and doesn't abide by the
212 above.
213
214 \section1 Performing a Write-back
215
216 With a sub-class of ReferenceObject that was set-up as above, a
217 write-back can be performed by calling
218 QV4::ReferenceObject::writeBack.
219
220 For example, some ReferenceObject subclass \c{Foo} might be
221 backing, say, some map-like object.
222
223 Internally, insertion of an element might pass by some method, say
224 \c{insertElement}. An implementation might, for example, look like
225 the following:
226
227 \code
228 bool Foo::insertElement(const QString& key, const Value& value) {
229 // Insert the element if possible
230 ...
231 QV4::ReferenceObject::writeBack(d());
232 ...
233 // Some further handling
234 }
235 \endcode
236
237 The call to writeBack will ensure that the newly inserted element
238 will be reflected on the original value where the object originates
239 from.
240
241 Here \c{d()}, with \c{Foo} being a sub-class of ReferenceObject and
242 thus of Object, is the heap part of \c{Foo} that should provide the
243 interface specificed above and owns the actual storage of the stored
244 value.
245
246 \section1 Synchronizing with the Original Value
247
248 QV4::ReferenceObject::readReference provides a way to obtain the
249 current state of the value that the ReferenceObject refers to.
250
251 When this read is performed, the obtained value will be stored back
252 into the backing storage for the ReferenceObject.
253
254 This allows the ReferenceObject to lazily load the latest data on
255 demand and correctly reflect the original value.
256
257 For example, say that \c{Foo} is a sub-class of ReferenceObject that
258 is used to back array-like values.
259
260 \c{Foo} should provide the usual \c{virtualGetLength} method to
261 support the \c{length} property that we expect an array to have.
262
263 In a possible implementation of \c{virtualGetLength}, \c{Foo} should
264 ensure that readReference is called before providing a value for the
265 length, to ensure that the latest data is available.
266
267 For example:
268
269 \code
270 qint64 Foo::virtualGetLength(const Managed *m)
271 {
272 const Foo *foo = static_cast<const Foo *>(m);
273 foo->readReference(d());
274 // Return the length from the owned storage
275 }
276 \endcode
277
278 \section2 Limiting reads on a QObject property
279
280 In most cases we cannot know whether the original data was modified between
281 read accesses. This generally forces a read to be performed each time we
282 require the latest data, even if we might have it already.
283
284 This can have surprising results, as certain procedure might require reading
285 the data multiple times to be performed, which sometimes can be very
286 expensive.
287
288 When the original data comes from the property of a \c{QObject}, and the
289 property has a \tt{NOTIFY} signal or is \tt{BINDABLE}, we can subscribe to the
290 signal to know when the data is actually modified outside our control, so that
291 we need to fetch it again.
292
293 A ReferenceObject can take advantage of this to reduce the number of reads
294 that are required when dealing with a \c{QObject}'s property provening data.
295
296 When a property has a \tt{NOTIFY} signal and is a \tt{BINDABLE} at
297 the same time, we only need to use one such connection.
298 Currently, the \tt{BINDABLE} subscription will take predecedence.
299
300 ReferenceObjects that are part of a \l{Reference object chains}{chain}, will
301 traverse the chain up until a QOjbect holding root is found, and connect based
302 on that object.
303 As read/write backs in a chain are always propagated up the chain, this allow
304 ReferenceObjects that are not directly parented to relevant element to still
305 avoid unnecesary reads.
306
307 For example, the property of a value type exposed by a Q_GADGET, cannot have a
308 \tt{NOTIFY} signal.
309 Nonetheless, if a change were to occur to the parent value type or the
310 property itself, that change would be propagated up the chain, possibly
311 triggering a \tt{NOTIFY} signal that is part of the chain.
312 Thus, by connecting to that upper \tt{NOTIFY} signal, we can still reliably know
313 if a change was performed on the property itself and thus avoid reduce the
314 number of reads.
315
316 As changes in the chain that do not really invalidate the data of that
317 property will still trigger that same \tt{NOTIFY} signal, sometimes we will
318 perform a read that is unnecessary due to granularity at which we are working.
319 This is the case, returning to the example above, when a different
320 property of that same value type will be changed.
321
322 This should still be a win, as we still expect to cut off multiple reads that
323 would be performed without the optimization.
324
325 The default implementation for QV4::ReferenceObject::readReference will take
326 care of performing this optimization already.
327
328 Derived objects that provide their own readReference implementation can plug
329 into QV4::Heap::ReferenceObject::isDirty, QV4::Heap::ReferenceObject::setDirty
330 and QV4::Heap::ReferenceObject::isConnected to provide the same optimization.
331
332 A ReferenceObject uses a "dirty" flag to track whether the data should be read
333 again.
334 If the ReferenceObject refers to a \c{QObject}'s property that has a
335 \tt{NOTIFY} signal or is \tt{BINDABLE}, it will set the flag each time the
336 \tt{NOTIFY} signal is emitted or the \tt{BINDABLE} is changed.
337
338 isDirty returns whether the flag is set and, thus, a readReference
339 implementation should avoid performing the read itself when the method
340 returns true.
341
342 After a read is performed, the "dirty" flag should be set again if the read
343 was unsuccessful.
344 The flag can be modified by usages of `setDirty`.
345
346 Generally, this only applies to instances of ReferenceObject that provene from
347 a \c{QObject}'s property that has a notify signal, as that is the case that
348 allows us to know when a read is required.
349
350 In all other cases, a ReferenceObject should always be "dirty" and perform a
351 read, as it cannot know if the data was modified since its last read.
352 This case will initially be managed by the base constructor for
353 ReferenceObject, nonetheless derived objects with a custom readReference
354 implementation need to take it into accoutn when setting the "dirty" flag
355 after a read.
356
357 isConnected can be used to discern between the two cases, as it will only
358 return true when the ReferenceObject is connected to a NOTIFY signal that can
359 modify the "dirty" flag.
360 When isConnected is false, a read implementation should always keep the
361 ReferenceObject in a permanent "dirty" state, to ensure that the correct data
362 is fetched when required.
363
364 \section1 Limiting Write-backs Based on Source Location
365
366 \note We generally consider location-aware write-backs to be a
367 mistake and expect to generally avoid further uses of them.
368 Due to backward compatibility promises they cannot be universally
369 enforced, possibly creating discrepancies in certain behaviors.
370 If at some point possible, the feature might be backtracked on and
371 removed, albeit this has shown to be difficult due to certain
372 existing cross-dependencies.
373
374 Consider the following code:
375
376 \qml
377 import QtQuick
378
379 Text {
380 Component.onCompleted: {
381 var f = font // font is a value type and f is a value type reference
382 f.bold = true;
383 otherText.font = f
384 }
385 }
386 \endqml
387
388 Remembering that \c{font} is a Copied Type in QtQuick, \c{f} will be a
389 Copied Type reference, internally backed by a ReferenceObject.
390 Changing the \c{bold} property of \c{f} would internally perform a
391 write-back which will reflect on the original \c{font} property of
392 the \c{Text} component, as we would expect from the definition we
393 gave above.
394
395 Nonetheless, we could generally expect that the intention behind the
396 code wasn't to update the original \c{font} property, but to pass a
397 slightly modified version of its value to \c{otherText}.
398
399 To avoid introducing this kind of possibly unintended behavior while
400 still supporting a solution to the original problem, ReferenceObject
401 allows limiting the availability of write-backs to a specific source
402 location.
403
404 For example, by limiting the availability of write-backs to the
405 single statement where a Copied Type reference is created, we can
406 address the most common cases of the original issue while avoiding
407 the unintuitive behavior of the above.
408
409 To enable source location enforcement,
410 QV4::Heap::ReferenceObject::Flag::EnforcesLocation should be set
411 when the ReferenceObject is initialized.
412
413 A reference location should be set by calling
414 QV4::Heap::ReferenceObject::setLocation.
415 For example, during initialization, one might be set to limit
416 write-backs to the currently processed statement, where the
417 ReferenceObject was created, as follows:
418
419 \code
420 void Heap::Sequence::Foo::init(..., Heap::ReferenceObject::Flags flags) {
421 ...
422 ReferenceObject::init(..., flags & ReferenceObject::Flag::EnforcesLocation);
423 ...
424 if (CppStackFrame *frame = internalClass->engine->currentStackFrame)
425 setLocation(frame->v4Function, frame->statementNumber());
426 ...
427 }
428 \endcode
429
430 Do note that calls to QV4::ReferenceObject::writeBack and
431 QV4::ReferenceObject::readReference do not directly take into
432 account location enforcement.
433
434 This should generally be handled by the sub-class.
435 QV4::Heap::ReferenceObject::isAttachedToProperty can be used to
436 recognize whether the reference is still suitable for write-backs in
437 a location-enforcement-aware way.
438
439 \section1 Reference object chains
440
441 ReferenceObject can be nested.
442
443 For example, consider:
444
445 \code
446 a.b.c
447 \endcode
448
449 Where \c{a} is some object exposed to QML, \c{b} is a property of \c{a} and \c{c} is a property of \c{b}.
450
451 Based on what each of \c{a}, \c{b} and \c{c} is, multiple ReferenceObject
452 instances, parented to one another, might be introduced.
453
454 For example, if \c{a} is a Q_OBJECT, \c{b} is a value type and \c{c} is a type
455 that will be converted to a QV4::Sequence, \c{a} will be wrapped by a
456 QObjectWrapper, \c{b} will be wrapped by a QQmlValueTypeWrapper which is parented
457 to the QObjectWrapper wrapping \c{a} and \c{c} will be a Sequence that is
458 parented to the QQmlValueTypeWrapper wrapping \c{b}.
459
460 This parenting chain is used to enable recursive read/write backs, ensuring
461 that a read/write back travels up the chain as required so that the latest
462 data is available on every relevant element.
463
464 At certain points in the chain, it is possible that a non-reference object is
465 introduced.
466
467 For example, this is always the case when a Q_OBJECT is retrieved, as it will
468 be wrapped in a QObjectWrapper which is not a reference object.
469
470 This breaks the chain of parenting and introduces the start of a new chain.
471 As a QObjectWrapper directly stores a pointer to the original object, it
472 doesn't need to perform the same read/write backs that reference objects do.
473 Similarly, child reference objects only need to read up to the innermost
474 QObjectWrapper in a chain to obtain the latest data.
475
476 Returning to the example above, if \c{b} is a Q_OBJECT instead of a value
477 type, then it will be the root of the reference chain that has \c{c} has its
478 child, without the need to be related to the QObjectWrapper that has been
479 built by accessing \c{a}.
480
481 QQmlTypeWrapper, that can wrap a QObject pointer that represents a
482 singleton or an attached property, behaves as chain root in the
483 exact same way that QObjectWrapper does.
484 */
485
486void QQmlDirtyReferenceObject_callback(QQmlNotifierEndpoint *e, void **) {
487 static_cast<QV4::Heap::ReferenceObjectEndpoint*>(e)->reference->setDirty(true);
488}
489
490QT_END_NAMESPACE
491

source code of qtdeclarative/src/qml/jsruntime/qv4referenceobject.cpp