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
qqmlpreviewobjectpatch.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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 reason:default
4
6
7#include <private/qqmlcontextdata_p.h>
8#include <private/qqmldata_p.h>
9#include <private/qqmljavascriptexpression_p.h>
10#include <private/qqmlobjectcreator_p.h>
11#include <private/qqmlpreviewbindingpatchcontext_p.h>
12#include <private/qqmlscriptdata_p.h>
13#include <private/qqmlvme_p.h>
14#include <private/qqmlvmemetaobject_p.h>
15#include <private/qv4resolvedtypereference_p.h>
16
17#include <QtCore/qset.h>
18
20
21namespace QQmlPreview {
22
28
30 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, int objectIndex,
31 QVarLengthArray<int, 4> &indices)
32{
33 const QV4::CompiledData::Object *obj = unit->objectAt(objectIndex);
34 const QV4::CompiledData::Binding *binding = obj->bindingTable();
35 for (quint32 i = 0; i < obj->nBindings; ++i, ++binding) {
36 switch (binding->type()) {
37 case QV4::CompiledData::Binding::Type_AttachedProperty:
38 case QV4::CompiledData::Binding::Type_GroupProperty: {
39 const int subIndex = binding->value.objectIndex;
40 indices.push_back(subIndex);
41 collectGroupAndAttachedPropertySubObjects(unit, subIndex, indices);
42 break;
43 }
44 default:
45 break;
46 }
47 }
48}
49
50static QVarLengthArray<int, 4>
51objectIndices(QObject *object, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit)
52{
53 QVarLengthArray<int, 4> objectIndices;
54
55 QQmlData *ddata = QQmlData::get(object);
56 if (ddata->compilationUnit == oldUnit)
57 objectIndices.push_back(ddata->cuObjectIndex);
58 if (ddata->hasVMEMetaObject) {
59 for (QQmlVMEMetaObject *vme =
60 static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(object)->metaObject);
61 vme; vme = vme->parentVMEMetaObject()) {
62 if (vme->compilationUnit() == oldUnit)
63 objectIndices.push_back(vme->qmlObjectId());
64 }
65 }
66
67 // Include indices of group property sub-objects (value types like font, anchors margins, etc.)
68 // These are virtual objects in the CU that don't correspond to real QObjects.
69 const qsizetype count = objectIndices.size();
70 for (qsizetype i = 0; i < count; ++i)
71 collectGroupAndAttachedPropertySubObjects(oldUnit, objectIndices[i], objectIndices);
72
73 return objectIndices;
74}
75
77nonCompositeBaseType(const QQmlPropertyCache::ConstPtr &propertyCache)
78{
79 for (QQmlPropertyCache::ConstPtr parent = propertyCache; parent; parent = parent->parent()) {
80 if (!parent->isComposite())
81 return parent;
82 }
83
84 return QQmlPropertyCache::ConstPtr();
85}
86
87static bool
88hasChangedNonCompositeBaseType(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
89 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
90 int objectIndex)
91{
92 const QV4::CompiledData::Object *oldObj = oldUnit->objectAt(objectIndex);
93 const auto *oldTypeRef = oldUnit->resolvedType(oldObj->inheritedTypeNameIndex);
94 if (!oldTypeRef)
95 return false; // Group property sub-objects have no inherited type.
96
97 const QV4::CompiledData::Object *newObj = newUnit->objectAt(objectIndex);
98 const auto *newTypeRef = newUnit->resolvedType(newObj->inheritedTypeNameIndex);
99 if (!newTypeRef)
100 return true; // Type disappeared — definitely changed.
101
102 return nonCompositeBaseType(oldTypeRef->typePropertyCache())
103 != nonCompositeBaseType(newTypeRef->typePropertyCache());
104}
105
106static Severity objectAffectedByDiff(const QVarLengthArray<int, 4> &objectIndices,
107 const QV4::CompiledData::CompilationUnitDiff &diff,
108 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
109 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
110{
111 Severity result = Unaffected;
112
113 for (const QV4::CompiledData::Change &change : diff.changes) {
114 switch (change.type) {
115 case QV4::CompiledData::ChangeType::RequiredPropertyExtraDataAdded:
116 case QV4::CompiledData::ChangeType::RequiredPropertyExtraDataChanged:
117 case QV4::CompiledData::ChangeType::RequiredPropertyExtraDataRemoved:
118 case QV4::CompiledData::ChangeType::AliasAdded:
119 case QV4::CompiledData::ChangeType::AliasChanged:
120 case QV4::CompiledData::ChangeType::AliasRemoved:
121 case QV4::CompiledData::ChangeType::BindingAdded:
122 case QV4::CompiledData::ChangeType::BindingChanged:
123 case QV4::CompiledData::ChangeType::BindingRemoved:
124 case QV4::CompiledData::ChangeType::PropertyAdded:
125 case QV4::CompiledData::ChangeType::PropertyChanged:
126 case QV4::CompiledData::ChangeType::PropertyRemoved:
127 case QV4::CompiledData::ChangeType::SignalAdded:
128 case QV4::CompiledData::ChangeType::SignalChanged:
129 case QV4::CompiledData::ChangeType::SignalRemoved:
130 // These are object-specific. Only the object in question needs rebuilding.
131 if (!objectIndices.contains(change.objectIndex))
132 continue;
133 if (result == Unaffected)
134 result = Rebuild;
135 break;
136 case QV4::CompiledData::ChangeType::EnumAdded:
137 case QV4::CompiledData::ChangeType::EnumChanged:
138 case QV4::CompiledData::ChangeType::EnumRemoved:
139 case QV4::CompiledData::ChangeType::FunctionAdded:
140 case QV4::CompiledData::ChangeType::FunctionChanged:
141 case QV4::CompiledData::ChangeType::FunctionRemoved:
142 // These are global. All objects created from the CU need rebuilding
143 // TODO: Enum changes actually propagate outside the CU. We'd need to rebuild
144 // absolutely everything that can reach the enum.
145 if (result == Unaffected)
146 result = Rebuild;
147 break;
148 case QV4::CompiledData::ChangeType::ImportAdded:
149 case QV4::CompiledData::ChangeType::ImportChanged:
150 case QV4::CompiledData::ChangeType::ImportRemoved:
151 // Changing imports can change everything or nothing. Most of the time you have no
152 // conflicting type names. Let's assume it's OK.
153 // TODO: To get this right, we need to compare all type resolutions in the whole CU
154 // to see if they're still the same.
155 continue;
156 case QV4::CompiledData::ChangeType::InlineComponentAdded:
157 case QV4::CompiledData::ChangeType::InlineComponentRemoved:
158 case QV4::CompiledData::ChangeType::InlineComponentChanged:
159 // Adding, removing, or changing an inline component cannot change anything by itself.
160 // What we care about is the CU object the inline component refers to.
161 continue;
162 case QV4::CompiledData::ChangeType::ObjectChanged: {
163 if (!objectIndices.contains(int(change.index)))
164 continue;
165
166 // If the non-composite base type changes, we need to replace the object
167 if (hasChangedNonCompositeBaseType(oldUnit, newUnit, change.index))
168 return Replace;
169
170 // If only some flags change, rebuilding is enough.
171 if (result == Unaffected)
172 result = Rebuild;
173
174 break;
175 }
176 case QV4::CompiledData::ChangeType::ObjectAdded:
177 case QV4::CompiledData::ChangeType::ObjectRemoved:
178 // Adding or removing an object only takes effect if you also add or remove
179 // a binding to that object, and that causes the outer object to be rebuilt.
180 continue;
181 default:
182 continue;
183 }
184 }
185
186 return result;
187}
188
190{
191 QObject *object = nullptr;
192 int index = -1;
193};
194
203
204// Walk the type resolution chain starting from the object at cuIndex in unit,
205// collecting all composite (QML-defined) base type levels. Returns them ordered
206// deepest-first (e.g., grandparent before parent).
209 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
210 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
211{
212 std::vector<CompositeLevel> levels;
213
214 auto currentUnit = instanceLevel.cu;
215 const QV4::CompiledData::Object *obj = currentUnit->objectAt(instanceLevel.objectIndex);
216 const QV4::ResolvedTypeReference *typeRef =
217 currentUnit->resolvedType(obj->inheritedTypeNameIndex);
218
219 while (typeRef && (typeRef->type().isComposite() || typeRef->type().isInlineComponentType())) {
220 QQmlRefPointer<QV4::ExecutableCompilationUnit> cu = typeRef->isSelfReference()
221 ? currentUnit
222 : currentUnit->engine->executableCompilationUnit(typeRef->compilationUnit());
223 Q_ASSERT(cu);
224
225 // Replace the old unit with the new one wherever it occurs
226 if (cu == oldUnit)
227 cu = newUnit;
228
229 int rootIndex;
230 QString icName;
231 if (typeRef->type().isInlineComponentType()) {
232 icName = typeRef->type().elementName();
233 rootIndex = cu->inlineComponentId(icName);
234 } else {
235 rootIndex = 0;
236 }
237
238 levels.push_back({ cu, rootIndex, icName, nullptr });
239
240 if (rootIndex >= cu->objectCount())
241 break;
242
243 // Walk deeper into the base type
244 const QV4::CompiledData::Object *rootObj = cu->objectAt(rootIndex);
245 typeRef = cu->resolvedType(rootObj->inheritedTypeNameIndex);
246 currentUnit = cu;
247 }
248
249 return levels;
250}
251
252// cuIndex is not necessarily the "outermost" index. There may be levels above oldUnit.
253// Those have to be preserved/rebuilt.
254static void rebuildObject(QObject *object, int cuIndex,
255 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
256 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
257{
258 // If the object's index doesn't exist in the new CU, it's obsolete.
259 if (cuIndex >= newUnit->objectCount())
260 return;
261
262 QQmlData *ddata = QQmlData::get(object);
263 Q_ASSERT(ddata);
264
265 QQmlRefPointer<QQmlContextData> outerContext =
266 QQmlRefPointer<QQmlContextData>(ddata->outerContext);
267
268 // If the object has no context, or is scheduled for deletion, it's half-dead already.
269 if (!ddata->context || !outerContext || ddata->isQueuedForDeletion)
270 return;
271
272 CompositeLevel instanceLevel{ ddata->compilationUnit == oldUnit ? newUnit
273 : ddata->compilationUnit,
274 ddata->cuObjectIndex, QString(), outerContext };
275
276 // If the object doesn't exist anymore in the new CU it will be deleted via GC.
277 // Nothing to do here.
278 if (instanceLevel.objectIndex >= instanceLevel.cu->objectCount())
279 return;
280
281 std::vector<CompositeLevel> levels = collectCompositeLevels(instanceLevel, oldUnit, newUnit);
282
283 BindingPatchContext(object).reset();
284
285 QV4::ExecutionEngine *v4 = newUnit->engine;
286 Q_ASSERT(v4);
287 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(v4);
288 Q_ASSERT(enginePrivate);
289
290 if (outerContext->contextObject() == object) {
291 outerContext->setContextObject(nullptr);
292 outerContext = enginePrivate->createComponentRootContext(
293 instanceLevel.cu, outerContext->parent(), instanceLevel.objectIndex);
294 outerContext->setContextObject(object);
295 instanceLevel.context = outerContext;
296 }
297
298 for (QQmlRefPointer<QQmlContextData> ctx(ddata->context); ctx; ctx = ctx->linkedContext()) {
299 if (ctx->contextObject() == object)
300 ctx->setContextObject(nullptr);
301 }
302
303 ddata->clear();
304
305 QObjectPrivate *objectPrivate = QObjectPrivate::get(object);
306 delete std::exchange(objectPrivate->metaObject, nullptr);
307
308 QQmlRefPointer<QQmlContextData> levelContext = outerContext;
309 for (qsizetype i = 0, end = levels.size(); i < end; ++i) {
310 CompositeLevel &level = levels[i];
311 levelContext = level.context = enginePrivate->createComponentRootContext(
312 level.cu, levelContext, level.objectIndex);
313 levelContext->setContextObject(object);
314 }
315
316 if (outerContext->contextObject() == object) {
317 ddata->ownContext = outerContext;
318 ddata->context = outerContext.data();
319 if (!levels.empty())
320 ddata->context->setLinkedContext(levels.front().context);
321 } else if (!levels.empty()) {
322 ddata->ownContext = levels.back().context;
323 ddata->context = ddata->ownContext.data();
324 if (levels.size() > 1)
325 ddata->context->setLinkedContext(levels.front().context);
326 } else {
327 // Non-root child: doesn't own a context. Re-link to the parent context.
328 ddata->context = outerContext.data();
329 }
330
331 for (auto it = levels.crbegin(), end = levels.crend(); it != end; ++it) {
332 it->context->addOwnedObject(ddata);
333 QQmlPropertyCache::ConstPtr cache = it->cu->propertyCachesPtr()->at(it->objectIndex);
334 new QQmlVMEMetaObject(v4, object, cache, it->cu, it->objectIndex);
335 }
336
337 outerContext->addOwnedObject(ddata);
338 if (QQmlPropertyCacheVector *caches = instanceLevel.cu->propertyCachesPtr();
339 caches->count() > instanceLevel.objectIndex
340 && caches->needsVMEMetaObject(instanceLevel.objectIndex)) {
341 QQmlPropertyCache::ConstPtr cache = newUnit->propertyCachesPtr()->at(cuIndex);
342 new QQmlVMEMetaObject(v4, object, cache, newUnit, cuIndex);
343 }
344
345 // Repopulate bindings at each composite level (deepest first).
346 // This sets up functions, evaluates bindings (creating child objects), etc.
347 // The object may get queued for deletion as result of some bindings.
348 // In that case we have to abort.
349 for (auto it = levels.crbegin(), end = levels.crend(); it != end && !ddata->isQueuedForDeletion;
350 ++it) {
351 QQmlObjectCreator creator(it->context, it->cu, outerContext, it->icName, nullptr);
352 creator.repopulateBindings(it->objectIndex, object, it->context,
353 QQmlObjectCreator::InitFlag::IsContextObject
354 | QQmlObjectCreator::InitFlag::IsDocumentRoot);
355
356 QQmlInstantiationInterrupt interrupt;
357 creator.finalize(interrupt);
358 }
359
360 // The object may get queued for deletion as result of some bindings.
361 // In that case don't touch it any further.
362 if (!ddata->isQueuedForDeletion) {
363 // Repopulate bindings at the instance level in the parent CU.
364 QQmlObjectCreator creator(instanceLevel.context, instanceLevel.cu, outerContext, QString(),
365 nullptr);
366 creator.repopulateBindings(instanceLevel.objectIndex, object, outerContext,
367 QQmlObjectCreator::InitFlag::None);
368 QQmlInstantiationInterrupt interrupt;
369 creator.finalize(interrupt);
370 }
371}
372
373bool applyDiff(std::vector<QObject *> &objects, const QV4::CompiledData::CompilationUnitDiff &diff,
374 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
375 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
376{
377 // Ensure the new CU's runtime data (strings, lookups, functions) is populated.
378 if (!newUnit->runtimeStrings)
379 newUnit->populate();
380
381 std::vector<ObjectAndIndex> rebuild;
382 std::vector<ObjectAndIndex> componentRoots;
383 bool rebuildOuter = false;
384
385 for (QObject *object : objects) {
386 bool isComponentRoot = false;
387 const QVarLengthArray<int, 4> indices = objectIndices(object, oldUnit);
388 for (int index : indices) {
389 if (index >= oldUnit->objectCount()) {
390 // Implicit component detected. Rebuild the component around it.
391 rebuildOuter = true;
392 continue;
393 }
394
395 const auto flags = oldUnit->objectAt(index)->flags();
396 if (flags & QV4::CompiledData::Object::IsComponent) {
397 // Explicit component object. Rebuild the component around it.
398 rebuildOuter = true;
399 continue;
400 }
401
402 if (index == 0 || flags & QV4::CompiledData::Object::IsInlineComponentRoot) {
403 componentRoots.push_back({ object, indices.first() });
404 isComponentRoot = true;
405 break;
406 }
407 }
408
409 switch (objectAffectedByDiff(indices, diff, oldUnit, newUnit)) {
410 case Unaffected:
411 // TODO: We don't actually need to rebuild here. We only need to update the context,
412 // ddata, etc. to point to the new compilation unit.
413 if (isComponentRoot)
414 rebuild.push_back({ object, indices.first() });
415 continue;
416 case Rebuild:
417 rebuild.push_back({ object, indices.first() });
418 break;
419 case Replace:
420 // Can't replace the root object of a component
421 // TODO: In some cases we can, by rebuilding the object that instantiated it instead.
422 if (isComponentRoot)
423 return false;
424 rebuildOuter = true;
425 break;
426 }
427 }
428
429 if (rebuildOuter)
430 rebuild = std::move(componentRoots);
431
432 QQmlRefPointer<QQmlContextData> rootContext;
433 for (const auto &[object, cuIndex] : rebuild) {
434 rebuildObject(object, cuIndex, oldUnit, newUnit);
435 QQmlData *rootData = QQmlData::get(object);
436 if (rootData)
437 rootContext = rootData->ownContext;
438 }
439
440 for (QObject *object : objects) {
441 QQmlData *ddata = QQmlData::get(object);
442 if (ddata->compilationUnit == newUnit)
443 continue;
444 if (ddata->compilationUnit != oldUnit)
445 continue;
446
447 // Only remap objects whose cuObjectIndex is still valid in the new CU.
448 // Objects whose index is out of range are obsolete (they belonged to
449 // the old type definition and have no counterpart in the new one).
450 if (ddata->cuObjectIndex >= newUnit->objectCount()) {
451 // Null the VME's compilation unit so stale alias lookups
452 // (triggered by refreshBindings) safely return nullptr from
453 // findCompiledObject() instead of asserting.
454 if (ddata->hasVMEMetaObject) {
455 for (auto *vmeMeta = static_cast<QQmlVMEMetaObject *>(
456 QObjectPrivate::get(object)->metaObject);
457 vmeMeta; vmeMeta = vmeMeta->parentVMEMetaObject()) {
458 if (vmeMeta->compilationUnit() == oldUnit)
459 vmeMeta->setCompilationUnit(nullptr);
460 }
461 }
462 continue;
463 }
464
465 ddata->compilationUnit = newUnit;
466 if (!ddata->hasVMEMetaObject)
467 continue;
468
469 for (QQmlVMEMetaObject *vmeMeta =
470 static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(object)->metaObject);
471 vmeMeta; vmeMeta = vmeMeta->parentVMEMetaObject()) {
472 if (vmeMeta->compilationUnit() == oldUnit
473 && vmeMeta->qmlObjectId() < newUnit->objectCount()) {
474 vmeMeta->setCompilationUnit(newUnit);
475 }
476 }
477 }
478 return true;
479}
480
481// Update the m_v4Function of a QQmlJavaScriptExpression from the old CU to the
482// corresponding function in the new CU.
483static void updateExpressionFunction(QQmlJavaScriptExpression *expr,
484 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
485 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
486{
487 QV4::Function *f = expr->function();
488 if (!f || f->executableCompilationUnit() != oldUnit.data())
489 return;
490
491 const auto &oldFunctions = oldUnit->runtimeFunctions;
492 const int index = oldFunctions.indexOf(f);
493 Q_ASSERT(index >= 0);
494
495 expr->setFunction(index < newUnit->runtimeFunctions.size() ? newUnit->runtimeFunctions[index]
496 : nullptr);
497}
498
499static void
500updateAndRefreshExpressionsRecursive(const QQmlRefPointer<QQmlContextData> &context,
501 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
502 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
503{
504 for (auto child = context->childContexts(); child; child = child->nextChild())
505 updateAndRefreshExpressionsRecursive(child, oldUnit, newUnit);
506
507 for (auto *expr = context->expressions(); expr; expr = expr->nextExpression()) {
508 updateExpressionFunction(expr, oldUnit, newUnit);
509 expr->refresh();
510 }
511}
512
513void refreshBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
514 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
515{
516 updateAndRefreshExpressionsRecursive(
517 QQmlContextData::get(newUnit->engine->qmlEngine()->rootContext()), oldUnit, newUnit);
518}
519
520} // namespace QQmlPreview
521
522QT_END_NAMESPACE
static std::vector< CompositeLevel > collectCompositeLevels(const CompositeLevel &instanceLevel, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
void refreshBindings(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static void collectGroupAndAttachedPropertySubObjects(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &unit, int objectIndex, QVarLengthArray< int, 4 > &indices)
static bool hasChangedNonCompositeBaseType(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit, int objectIndex)
static QVarLengthArray< int, 4 > objectIndices(QObject *object, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit)
static void updateAndRefreshExpressionsRecursive(const QQmlRefPointer< QQmlContextData > &context, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static void rebuildObject(QObject *object, int cuIndex, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static QQmlPropertyCache::ConstPtr nonCompositeBaseType(const QQmlPropertyCache::ConstPtr &propertyCache)
bool applyDiff(std::vector< QObject * > &objects, const QV4::CompiledData::CompilationUnitDiff &diff, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static void updateExpressionFunction(QQmlJavaScriptExpression *expr, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static Severity objectAffectedByDiff(const QVarLengthArray< int, 4 > &objectIndices, const QV4::CompiledData::CompilationUnitDiff &diff, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
Combined button and popup list for selecting options.
QQmlRefPointer< QV4::ExecutableCompilationUnit > cu
QQmlRefPointer< QQmlContextData > context