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