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
7
QT_BEGIN_NAMESPACE
8
9
DEFINE_OBJECT_VTABLE
(
QV4
::
ReferenceObject
);
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
487
void
QQmlDirtyReferenceObject_callback
(QQmlNotifierEndpoint *e,
void
**) {
488
static_cast
<QV4::Heap::ReferenceObjectEndpoint*>(e)->reference->setDirty(
true
);
489
}
490
491
QT_END_NAMESPACE
QQmlDirtyReferenceObject_callback
void QQmlDirtyReferenceObject_callback(QQmlNotifierEndpoint *e, void **)
Definition
qv4referenceobject.cpp:487
DEFINE_OBJECT_VTABLE
QT_BEGIN_NAMESPACE DEFINE_OBJECT_VTABLE(QV4::ReferenceObject)
qtdeclarative
src
qml
jsruntime
qv4referenceobject.cpp
Generated on
for Qt by
1.14.0