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/qqmlcomponent_p.h>
8#include <private/qqmlcontextdata_p.h>
9#include <private/qqmldata_p.h>
10#include <private/qqmljavascriptexpression_p.h>
11#include <private/qqmlobjectcreator_p.h>
12#include <private/qqmlpreviewbindingpatchcontext_p.h>
13#include <private/qqmlpreviewdiff_p.h>
14#include <private/qqmlpropertyresolver_p.h>
15#include <private/qqmlscriptdata_p.h>
16#include <private/qqmlvme_p.h>
17#include <private/qqmlvmemetaobject_p.h>
18#include <private/qv4functionobject_p.h>
19#include <private/qv4generatorobject_p.h>
20#include <private/qv4qmlcontext_p.h>
21#include <private/qv4resolvedtypereference_p.h>
22
23#include <QtQml/qqmlcomponent.h>
24#include <QtQml/qqmlproperty.h>
25
26#include <QtCore/qset.h>
27
29
30namespace QQmlPreview {
31
32// The indices at which object appears in oldUnit: its own (ddata) index plus any indices it
33// occupies through composite base levels (the VME meta-object chain).
34static QVarLengthArray<int, 4>
35objectIndices(QObject *object, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit)
36{
37 QVarLengthArray<int, 4> objectIndices;
38
39 QQmlData *ddata = QQmlData::get(object);
40 if (ddata->compilationUnit == oldUnit)
41 objectIndices.push_back(ddata->cuObjectIndex);
42 if (ddata->hasVMEMetaObject) {
43 for (QQmlVMEMetaObject *vme =
44 static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(object)->metaObject);
45 vme; vme = vme->parentVMEMetaObject()) {
46 if (vme->compilationUnit() == oldUnit)
47 objectIndices.push_back(vme->qmlObjectId());
48 }
49 }
50
51 return objectIndices;
52}
53
55nonCompositeBaseType(const QQmlPropertyCache::ConstPtr &propertyCache)
56{
57 for (QQmlPropertyCache::ConstPtr parent = propertyCache; parent; parent = parent->parent()) {
58 if (!parent->isComposite())
59 return parent;
60 }
61
62 return QQmlPropertyCache::ConstPtr();
63}
64
65static bool
66hasChangedNonCompositeBaseType(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
67 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
68 int objectIndex)
69{
70 const QV4::CompiledData::Object *oldObj = oldUnit->objectAt(objectIndex);
71 const auto *oldTypeRef = oldUnit->resolvedType(oldObj->inheritedTypeNameIndex);
72 if (!oldTypeRef)
73 return false; // Group property sub-objects have no inherited type.
74
75 const QV4::CompiledData::Object *newObj = newUnit->objectAt(objectIndex);
76 const auto *newTypeRef = newUnit->resolvedType(newObj->inheritedTypeNameIndex);
77 if (!newTypeRef)
78 return true; // Type disappeared — definitely changed.
79
80 return nonCompositeBaseType(oldTypeRef->typePropertyCache())
81 != nonCompositeBaseType(newTypeRef->typePropertyCache());
82}
83
84// reset() + repopulateBindings() cannot faithfully reproduce some components in place,
85// because either:
86// - Its non-composite (C++) base type changed: the reused QObject is still an instance of the old
87// class (see hasChangedNonCompositeBaseType).
88// - It carries deferred bindings. We cannot re-install those because their handling is in the
89// type's discretion.
90// Fore those return false here. Otherwise return true.
91static bool
92canRebuildComponentRootInPlace(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
93 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
94 int objectIndex)
95{
96 // An index out of range in the new CU is obsolete: rebuildObject() skips it and the remap loop
97 // retires it. There is nothing to recreate from an enclosing root.
98 if (objectIndex >= newUnit->objectCount())
99 return true;
100
101 if (newUnit->objectAt(objectIndex)->hasFlag(QV4::CompiledData::Object::HasDeferredBindings))
102 return false;
103
104 return !hasChangedNonCompositeBaseType(oldUnit, newUnit, objectIndex);
105}
106
107// Re-resolve the object's precomputed binding-target table against its relinked property cache.
108static void refreshBindingPropertyData(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &cu,
109 int objectIndex, const QQmlPropertyCache::ConstPtr &cache)
110{
111 QList<QV4::CompiledData::BindingPropertyData> &table =
112 cu->baseCompilationUnit()->bindingPropertyDataPerObject;
113 if (objectIndex >= table.size())
114 return;
115
116 QV4::CompiledData::BindingPropertyData &bindingData = table[objectIndex];
117
118 const QV4::CompiledData::Object *obj = cu->objectAt(objectIndex);
119 const QQmlPropertyResolver resolver(cache);
120
121 const QV4::CompiledData::Binding *binding = obj->bindingTable();
122 for (qsizetype i = 0, end = bindingData.size(); i < end; ++i, ++binding) {
123 if (!bindingData.at(i))
124 continue;
125
126 Q_ASSERT(i < obj->nBindings);
127 const QString name = BindingPatchContext::targetPropertyName(cu, objectIndex, binding);
128 if (name.isEmpty())
129 continue;
130
131 bindingData[i] =
132 (binding->hasFlag(QV4::CompiledData::Binding::IsSignalHandlerExpression)
133 || binding->hasFlag(QV4::CompiledData::Binding::IsSignalHandlerObject))
134 ? resolver.signal(name, nullptr, QQmlPropertyResolver::IgnoreRevision)
135 : resolver.property(name, nullptr, QQmlPropertyResolver::IgnoreRevision);
136 }
137}
138
139// Ensure the property cache at objectIndex in cu is derived from actualParent, the cache we just
140// used for the level below it in the VME chain.
142relinkCache(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &cu, int objectIndex,
143 const QQmlPropertyCache::ConstPtr &actualParent)
144{
145 QQmlPropertyCacheVector *caches = cu->propertyCachesPtr();
146 QQmlPropertyCache::ConstPtr cache = caches->at(objectIndex);
147
148 // The bottom-most composite level's own base is a non-composite (C++) type. That base never
149 // changes across a reload since a changed non-composite base is rejected by
150 // hasChangedNonCompositeBaseType.
151 if (!actualParent)
152 return cache;
153
154 // A type that needs no VME meta-object of its own reuses its base type's property cache.
155 if (!caches->needsVMEMetaObject(objectIndex)) {
156 if (cache != actualParent) {
157 caches->set(objectIndex, actualParent);
158 refreshBindingPropertyData(cu, objectIndex, actualParent);
159 }
160 return actualParent;
161 }
162
163 // A type with its own cache derived from the base: re-derive it from the relinked base so its
164 // inherited offsets match the (possibly changed) base layout.
165 if (cache->parent() != actualParent) {
166 cache = cache->rebased(actualParent);
167 caches->set(objectIndex, cache);
168 refreshBindingPropertyData(cu, objectIndex, cache);
169 }
170
171 return cache;
172}
173
174// Re-link every property cache in cu that (transitively) derives oldBaseCache so it derives
175// newBaseCache instead.
176static void relinkDerivedCaches(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &cu,
177 const QQmlPropertyCache::ConstPtr &oldBaseCache,
178 const QQmlPropertyCache::ConstPtr &newBaseCache)
179{
180 QQmlPropertyCacheVector *caches = cu->propertyCachesPtr();
181 Q_ASSERT(caches);
182
183 QHash<const QQmlPropertyCache *, QQmlPropertyCache::ConstPtr> replacements;
184 replacements.insert(oldBaseCache.data(), newBaseCache);
185
186 for (bool changed = true; changed;) {
187 changed = false;
188 for (int i = 0, end = caches->count(); i < end; ++i) {
189 const QQmlPropertyCache::ConstPtr cache = caches->at(i);
190 if (!cache)
191 continue;
192
193 const auto it = replacements.constFind(cache->parent().data());
194 if (it == replacements.constEnd())
195 continue;
196
197 const QQmlPropertyCache::ConstPtr rebased = cache->rebased(*it);
198 replacements.insert(cache.data(), rebased);
199 caches->set(i, rebased);
200 refreshBindingPropertyData(cu, i, rebased);
201 changed = true;
202 }
203 }
204}
205
213
214// Walk the type resolution chain starting from the object at cuIndex in unit,
215// collecting all composite (QML-defined) base type levels. Returns them ordered
216// deepest-first (e.g., grandparent before parent).
218collectCompositeLevels(const CompositeLevel &instanceLevel,
219 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
220 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
221{
222 std::vector<CompositeLevel> levels;
223
224 auto currentUnit = instanceLevel.newCu;
225 const QV4::CompiledData::Object *obj = currentUnit->objectAt(instanceLevel.objectIndex);
226 const QV4::ResolvedTypeReference *typeRef =
227 currentUnit->resolvedType(obj->inheritedTypeNameIndex);
228
229 while (typeRef && typeRef->type().isComposite()) {
230 QQmlRefPointer<QV4::ExecutableCompilationUnit> cu = typeRef->isSelfReference()
231 ? currentUnit
232 : currentUnit->engine->executableCompilationUnit(typeRef->compilationUnit());
233 Q_ASSERT(cu);
234
235 // Replace the old unit with the new one wherever it occurs
236 const auto oldCu = cu;
237 if (cu == oldUnit)
238 cu = newUnit;
239
240 int rootIndex;
241 QString icName;
242 if (typeRef->type().isInlineComponent()) {
243 icName = typeRef->type().elementName();
244 rootIndex = cu->inlineComponentId(icName);
245 } else {
246 rootIndex = 0;
247 }
248
249 levels.push_back({ oldCu, cu, rootIndex, icName, nullptr });
250
251 if (rootIndex < 0 || rootIndex >= cu->objectCount())
252 break;
253
254 // Walk deeper into the base type
255 const QV4::CompiledData::Object *rootObj = cu->objectAt(rootIndex);
256 typeRef = cu->resolvedType(rootObj->inheritedTypeNameIndex);
257 currentUnit = cu;
258 }
259
260 return levels;
261}
262
263// cuIndex is not necessarily the "outermost" index. There may be levels above oldUnit.
264// Those have to be preserved/rebuilt.
265static void rebuildObject(QObject *object, int cuIndex,
266 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
267 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
268{
269 // If the object's index doesn't exist in the new CU, it's obsolete.
270 if (cuIndex >= newUnit->objectCount())
271 return;
272
273 QQmlData *ddata = QQmlData::get(object);
274 Q_ASSERT(ddata);
275
276 QQmlRefPointer<QQmlContextData> outerContext =
277 QQmlRefPointer<QQmlContextData>(ddata->outerContext);
278
279 // If the object has no context, or is scheduled for deletion, it's half-dead already.
280 if (!ddata->context || !outerContext || !outerContext->isValid() || ddata->isQueuedForDeletion)
281 return;
282
283 CompositeLevel instanceLevel{ ddata->compilationUnit,
284 ddata->compilationUnit == oldUnit ? newUnit
285 : ddata->compilationUnit,
286 ddata->cuObjectIndex, QString(), outerContext };
287
288 // If the object doesn't exist anymore in the new CU it will be deleted via GC.
289 // Nothing to do here.
290 if (instanceLevel.objectIndex >= instanceLevel.newCu->objectCount())
291 return;
292
293 std::vector<CompositeLevel> levels = collectCompositeLevels(instanceLevel, oldUnit, newUnit);
294
295 // Build the set of compilation units that participate in this rebuild.
296 // Bindings from these CUs are "internal" and will be re-created by repopulateBindings.
297 std::vector<CompositeLevel> internalUnits;
298 internalUnits.push_back(instanceLevel);
299 for (const auto &level : levels)
300 internalUnits.push_back(level);
301
302 BindingPatchContext patchCtx(object, ddata->compilationUnit, ddata->cuObjectIndex);
303 QDuplicateTracker<QObject *> seenChildren;
304 patchCtx.stashExternalState(internalUnits, &seenChildren);
305 // Collect the CUs whose children will be recreated by repopulateBindings.
306 // This is oldUnit (being replaced) and the composite-level CUs.
307 // NOT newUnit (already-rebuilt objects pointing here must be preserved)
308 // and NOT instanceLevel.cu (its repopulateBindings only sets bindings, not children).
309 std::vector<QQmlRefPointer<QV4::ExecutableCompilationUnit>> unitsToUnparent;
310 unitsToUnparent.push_back(oldUnit);
311 for (const auto &level : levels)
312 unitsToUnparent.push_back(level.oldCu);
313 patchCtx.reset(unitsToUnparent, internalUnits);
314
315 QV4::ExecutionEngine *v4 = newUnit->engine;
316 Q_ASSERT(v4);
317 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(v4);
318 Q_ASSERT(enginePrivate);
319
320 if (outerContext->contextObject() == object) {
321 outerContext->setContextObject(nullptr);
322 outerContext = enginePrivate->createComponentRootContext(
323 instanceLevel.newCu, outerContext->parent(), instanceLevel.objectIndex);
324 outerContext->setContextObject(object);
325 instanceLevel.context = outerContext;
326 }
327
328 for (QQmlRefPointer<QQmlContextData> ctx(ddata->context); ctx; ctx = ctx->linkedContext()) {
329 if (ctx->contextObject() == object)
330 ctx->setContextObject(nullptr);
331 }
332
333 ddata->clear();
334
335 QObjectPrivate *objectPrivate = QObjectPrivate::get(object);
336 delete std::exchange(objectPrivate->metaObject, nullptr);
337
338 QQmlRefPointer<QQmlContextData> levelContext = outerContext;
339 for (qsizetype i = 0, end = levels.size(); i < end; ++i) {
340 CompositeLevel &level = levels[i];
341 levelContext = level.context = enginePrivate->createComponentRootContext(
342 level.newCu, levelContext, level.objectIndex);
343 levelContext->setContextObject(object);
344 }
345
346 if (outerContext->contextObject() == object) {
347 ddata->ownContext = outerContext;
348 ddata->context = outerContext.data();
349 if (!levels.empty())
350 ddata->context->setLinkedContext(levels.front().context);
351 } else if (!levels.empty()) {
352 ddata->ownContext = levels.back().context;
353 ddata->context = ddata->ownContext.data();
354 if (levels.size() > 1)
355 ddata->context->setLinkedContext(levels.front().context);
356 } else {
357 // Non-root child: doesn't own a context. Re-link to the parent context.
358 ddata->context = outerContext.data();
359 }
360
361 // Build the VME meta-object chain base-first, relinking each level's property cache to the
362 // actual (possibly freshly reloaded) parent cache we just used, so the whole chain's offsets
363 // stay consistent even when a composite base type's layout changed on reload.
364 QQmlPropertyCache::ConstPtr parentCache;
365 for (auto it = levels.crbegin(), end = levels.crend(); it != end; ++it) {
366 it->context->addOwnedObject(ddata);
367 QQmlPropertyCache::ConstPtr cache = relinkCache(it->newCu, it->objectIndex, parentCache);
368 if (it->newCu->propertyCachesPtr()->needsVMEMetaObject(it->objectIndex))
369 new QQmlVMEMetaObject(v4, object, cache, it->newCu, it->objectIndex);
370 parentCache = cache;
371 }
372
373 outerContext->addOwnedObject(ddata);
374 if (QQmlPropertyCacheVector *caches = instanceLevel.newCu->propertyCachesPtr();
375 caches->count() > instanceLevel.objectIndex) {
376 QQmlPropertyCache::ConstPtr cache =
377 relinkCache(instanceLevel.newCu, instanceLevel.objectIndex, parentCache);
378 if (caches->needsVMEMetaObject(instanceLevel.objectIndex))
379 new QQmlVMEMetaObject(v4, object, cache, instanceLevel.newCu, instanceLevel.objectIndex);
380 }
381
382 // Repopulate bindings at each composite level (deepest first).
383 // This sets up functions, evaluates bindings (creating child objects), etc.
384 // The object may get queued for deletion as result of some bindings.
385 // In that case we have to abort.
386 for (auto it = levels.crbegin(), end = levels.crend(); it != end && !ddata->isQueuedForDeletion;
387 ++it) {
388 QQmlObjectCreator creator(it->context, it->newCu, outerContext, it->icName, nullptr);
389 creator.repopulateBindings(it->objectIndex, object, it->context,
390 QQmlObjectCreator::InitFlag::IsContextObject
391 | QQmlObjectCreator::InitFlag::IsDocumentRoot);
392
393 QQmlInstantiationInterrupt interrupt;
394 creator.finalize(interrupt);
395 }
396
397 // The object may get queued for deletion as result of some bindings.
398 // In that case don't touch it any further.
399 if (!ddata->isQueuedForDeletion) {
400 // Repopulate bindings at the instance level in the parent CU.
401 QQmlObjectCreator creator(instanceLevel.context, instanceLevel.newCu, outerContext, QString(),
402 nullptr);
403 creator.repopulateBindings(instanceLevel.objectIndex, object, outerContext,
404 QQmlObjectCreator::InitFlag::None);
405 QQmlInstantiationInterrupt interrupt;
406 creator.finalize(interrupt);
407
408 // Restore externally set bindings and values that were stashed before reset.
409 // First refresh child object pointers — they may have been replaced during rebuild.
410 patchCtx.refreshObjects();
411 patchCtx.restoreExternalState();
412 }
413}
414
416static BindingKind bindingKind(const QV4::CompiledData::Binding *binding)
417{
418 switch (binding->type()) {
419 case QV4::CompiledData::Binding::Type_Script:
420 return BindingKind::Script;
421 case QV4::CompiledData::Binding::Type_Translation:
422 case QV4::CompiledData::Binding::Type_TranslationById:
424 case QV4::CompiledData::Binding::Type_Number:
425 case QV4::CompiledData::Binding::Type_Boolean:
426 case QV4::CompiledData::Binding::Type_String:
427 case QV4::CompiledData::Binding::Type_Null:
429 default:
431 }
432}
433
434// A trivial diff is one we can apply without rebuilding any VME meta-object and without
435// adding or removing objects: only existing bindings and binding-expression bodies change. Such a
436// diff is patched in place (see patchInPlace()). Everything else falls back to rebuilding roots.
437static bool changeIsTrivial(const QV4::CompiledData::Change &change,
438 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
439 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
440{
441 using ChangeType = QV4::CompiledData::ChangeType;
442 switch (change.type) {
443 // Source-location-only changes have no runtime effect.
444 case ChangeType::AliasLocationChanged:
445 case ChangeType::BindingLocationChanged:
446 case ChangeType::EnumLocationChanged:
447 case ChangeType::FunctionLocationChanged:
448 case ChangeType::ImportLocationChanged:
449 case ChangeType::InlineComponentLocationChanged:
450 case ChangeType::ObjectLocationChanged:
451 case ChangeType::PropertyLocationChanged:
452 case ChangeType::SignalLocationChanged:
453 // Internal table changes that are byproducts of recompiling expressions. They are picked up
454 // when we translate functions and remap compilation units; no structure changes.
455 case ChangeType::UnitMetadataChanged:
456 case ChangeType::ConstantAdded:
457 case ChangeType::ConstantChanged:
458 case ChangeType::ConstantRemoved:
459 case ChangeType::StringDataAdded:
460 case ChangeType::StringDataChanged:
461 case ChangeType::StringDataRemoved:
462 case ChangeType::LookupAdded:
463 case ChangeType::LookupChanged:
464 case ChangeType::LookupRemoved:
465 case ChangeType::RegExpAdded:
466 case ChangeType::RegExpChanged:
467 case ChangeType::RegExpRemoved:
468 case ChangeType::ClassAdded:
469 case ChangeType::ClassChanged:
470 case ChangeType::ClassRemoved:
471 case ChangeType::BlockAdded:
472 case ChangeType::BlockChanged:
473 case ChangeType::BlockRemoved:
474 case ChangeType::TemplateObjectAdded:
475 case ChangeType::TemplateObjectChanged:
476 case ChangeType::TemplateObjectRemoved:
477 case ChangeType::JSClassAdded:
478 case ChangeType::JSClassChanged:
479 case ChangeType::JSClassRemoved:
480 case ChangeType::TranslationDataAdded:
481 case ChangeType::TranslationDataChanged:
482 case ChangeType::TranslationDataRemoved:
483 return true;
484 case ChangeType::FunctionChanged:
485 // A recompiled function body at a stable index. Binding/handler expressions pick up the new
486 // body via refreshBindings(), VME methods via refreshVmeMethods(). Neither needs a rebuild,
487 // so a function change is trivial regardless of whether the function backs a binding or a
488 // method.
489 return true;
490 case ChangeType::BindingChanged: {
491 Q_ASSERT(change.objectIndex >= 0);
492 Q_ASSERT(change.objectIndex < oldUnit->objectCount());
493 Q_ASSERT(change.objectIndex < newUnit->objectCount());
494
495 const QV4::CompiledData::Object *oldObj = oldUnit->objectAt(change.objectIndex);
496 Q_ASSERT(change.index < oldObj->nBindings);
497 const QV4::CompiledData::Binding *oldBinding = oldObj->bindingTable() + change.index;
498
499 const QV4::CompiledData::Object *newObj = newUnit->objectAt(change.objectIndex);
500 Q_ASSERT(change.index < newObj->nBindings);
501 const QV4::CompiledData::Binding *newBinding = newObj->bindingTable() + change.index;
502
503 const BindingKind oldKind = bindingKind(oldBinding);
504 const BindingKind newKind = bindingKind(newBinding);
505 if (oldKind == BindingKind::Unpatchable || newKind == BindingKind::Unpatchable)
506 return false;
507
508 // We cannot swap a binding for one of a different kind, yet: turning a literal into a
509 // script or translation binding (or vice versa) would have to install or drop a live
510 // binding, and we cannot recompile a script binding into a translation binding either.
511 // TODO: Fix this.
512 if (oldKind != newKind)
513 return false;
514
515 // A BindingChanged at a stable index can also mean the binding was moved to a different
516 // target property (its propertyNameIndex changed). In-place patching cannot relocate a
517 // binding: it would have to remove it from the old property and install it on the new one.
518 // That is structural, so fall back to the rebuild path.
519
520 return BindingPatchContext::targetPropertyName(oldUnit, change.objectIndex, oldBinding)
521 == BindingPatchContext::targetPropertyName(newUnit, change.objectIndex, newBinding);
522 }
523 default:
524 // ObjectAdded/Removed/Changed, Binding Added/Removed, Property/Signal/Alias/Enum changes,
525 // Function Added/Removed, Import/InlineComponent/RequiredPropertyExtraData changes:
526 // structural, need a rebuild.
527 return false;
528 }
529}
530
531static bool isTrivialDiff(const QV4::CompiledData::CompilationUnitDiff &diff,
532 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
533 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
534{
535 return std::all_of(diff.changes.cbegin(), diff.changes.cend(),
536 [&](const QV4::CompiledData::Change &change) {
537 return changeIsTrivial(change, oldUnit, newUnit);
538 });
539}
540
541// Re-apply changed literal bindings. A literal value is written only if the property still holds
542// the value the old unit assigned; anything else is an external override we must leave untouched.
543// Changed script bindings carry no literal value and are handled by function translation instead.
544//
545// The binding may live on a value-type group sub-object (font.pixelSize) or an attached object
546// (Keys.enabled), which has no standalone QObject at its compilation-unit index. We use
547// BindingPatchContext to map every compilation-unit object index reachable from a live instance to
548// the owning QObject and the property-name prefix to address it.
549static void patchConstantBindings(const std::vector<QObject *> &objects,
550 const QV4::CompiledData::CompilationUnitDiff &diff,
551 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
552 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
553{
554 for (QObject *object : objects) {
555 // Skip half-dead objects, mirroring rebuildObject(): an invalidated context means the
556 // object is on its way out and must not be patched.
557 QQmlData *ddata = QQmlData::get(object);
558 if (!ddata->context || !ddata->outerContext || !ddata->outerContext->isValid()
559 || ddata->isQueuedForDeletion) {
560 continue;
561 }
562
563 QVarLengthArray<BindingPatchContext, 4> contexts;
564 for (int index : objectIndices(object, oldUnit))
565 contexts.append(BindingPatchContext(object, oldUnit, index));
566
567 for (const QV4::CompiledData::Change &change : diff.changes) {
568 if (change.type != QV4::CompiledData::ChangeType::BindingChanged)
569 continue;
570
571 for (BindingPatchContext &context : contexts) {
572 if (context.applyBindingChange(newUnit, change))
573 break;
574 }
575 }
576 }
577}
578
579// Point every live object that still references oldUnit at newUnit, in both its ddata and its
580// VME meta-object chain. Used after both in-place patching and root rebuilds.
581//
582// The ddata compilation unit and the VME chain are remapped independently: a composite-type
583// instance can carry oldUnit in its VME chain while its ddata->compilationUnit is a different
584// executable unit (the "topmost", possibly inaddressible, instantiation).
585static void remapObjectsToNewUnit(const std::vector<QObject *> &objects,
586 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
587 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
588{
589 for (QObject *object : objects) {
590 QQmlData *ddata = QQmlData::get(object);
591
592 // A QQmlComponent (e.g. an explicit or implicit `delegate:`) caches the compilation unit it
593 // instantiates from. Remap it so future create() calls produce instances from newUnit.
594 if (QQmlComponent *component = qobject_cast<QQmlComponent *>(object)) {
595 QQmlComponentPrivate *cp = QQmlComponentPrivate::get(component);
596 if (cp->compilationUnit() == oldUnit)
597 cp->setCompilationUnit(newUnit);
598 }
599
600 // Remap the ddata compilation unit if it points at oldUnit. An object whose index is out
601 // of range in the new CU is obsolete (it belonged to the old type definition); leave its
602 // ddata alone and let the VME nulling below retire it.
603 if (ddata->compilationUnit == oldUnit && ddata->cuObjectIndex < newUnit->objectCount())
604 ddata->compilationUnit = newUnit;
605
606 if (!ddata->hasVMEMetaObject)
607 continue;
608
609 // Remap (or retire) every VME meta-object in the chain that points at oldUnit.
610 auto *mainVme = static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(object)->metaObject);
611 for (QQmlVMEMetaObject *vmeMeta = mainVme; vmeMeta;
612 vmeMeta = vmeMeta->parentVMEMetaObject()) {
613 if (vmeMeta->compilationUnit() != oldUnit)
614 continue;
615 if (vmeMeta->qmlObjectId() < newUnit->objectCount()) {
616 vmeMeta->setCompilationUnit(newUnit);
617
618 // Give the live instance the reloaded type's identity. The instance must present
619 // newUnit's property cache.
620 const QQmlPropertyCache::ConstPtr newCache =
621 newUnit->propertyCachesPtr()->at(vmeMeta->qmlObjectId());
622 if (newCache) {
623 vmeMeta->setPropertyCache(newCache);
624 if (vmeMeta == mainVme)
625 ddata->propertyCache = newCache;
626 }
627 } else {
628 // Obsolete: null it so stale alias lookups (triggered by refreshBindings) safely
629 // return nullptr from findCompiledObject() instead of asserting.
630 vmeMeta->setCompilationUnit(nullptr);
631 }
632 }
633 }
634}
635
636// If the object is instantiated as a component root of the old unit (its document root or an
637// inline-component root) whose own non-composite base type differs in the new unit or which
638// carries deferred bindings, return false. Otherwise return true.
640 QObject *object,
641 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
642 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
643{
644 for (int index : objectIndices(object, oldUnit)) {
645 if (index >= oldUnit->objectCount())
646 continue;
647 const auto flags = oldUnit->objectAt(index)->flags();
648 if (index != 0 && !(flags & QV4::CompiledData::Object::IsInlineComponentRoot))
649 continue;
650 if (!canRebuildComponentRootInPlace(oldUnit, newUnit, index))
651 return false;
652 }
653 return true;
654}
655
656// A component root whose non-composite base type changed or that carries deferred bindings cannot
657// be rebuilt in place. Instead we travel the scope hierarchy upwards to the enclosing component
658// root that instantiates it and rebuild that: its reset() + repopulateBindings() recreates the
659// problematic object from scratch, as a fresh QObject. If that enclosing root is problematic, too,
660// we keep travelling up. Returns the object to rebuild, or nullptr if we reach the root scope
661// without finding a rebuildable enclosing root.
663 QObject *object,
664 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
665 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
666{
667 Q_ASSERT(object);
668
669 // Eventually we either reach the outermost context or a context without context object.
670 // Then we return nullptr if we haven't found anything better before.
671 while (true) {
672 QQmlData *ddata = QQmlData::get(object);
673 if (!ddata || !ddata->outerContext)
674 return nullptr;
675
676 QObject *outer = ddata->outerContext->contextObject();
677 if (!outer || outer == object)
678 return nullptr;
679
680 if (isRebuildableComponentRoot(outer, oldUnit, newUnit))
681 return outer;
682
683 object = outer;
684 }
685
686 Q_UNREACHABLE_RETURN(nullptr);
687}
688
689// TODO: This is dangerous. We are manipulating compilation units exposed to multiple
690// engines on potentially multiple threads.
692 QV4::ExecutionEngine *engine,
693 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &oldUnit,
694 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &newUnit)
695{
696 const auto units = engine->compilationUnits();
697 for (const auto &cu : units) {
698 for (auto *typeRef : std::as_const(cu->baseCompilationUnit()->resolvedTypes)) {
699 if (typeRef->isSelfReference())
700 continue;
701 if (typeRef->compilationUnit() != oldUnit)
702 continue;
703
704 typeRef->setCompilationUnit(newUnit);
705
706 QQmlPropertyCache::ConstPtr newCache;
707 const QQmlType type = typeRef->type();
708 if (type.isInlineComponent()) {
709 if (const int icId = newUnit->inlineComponentId(type.elementName()); icId >= 0)
710 newCache = newUnit->propertyCaches.at(icId);
711 } else {
712 newCache = newUnit->rootPropertyCache();
713 }
714
715 if (!newCache)
716 continue;
717
718 const QQmlPropertyCache::ConstPtr oldCache = typeRef->typePropertyCache();
719 typeRef->setTypePropertyCache(newCache);
720 relinkDerivedCaches(cu, oldCache, newCache);
721 }
722 }
723}
724
725// Re-create the VME method function objects from newUnit so that an object's methods follow the
726// unit remap, mirroring what refreshBindings() does for QQmlJavaScriptExpression-based bindings.
727static void refreshVmeMethods(const std::vector<QObject *> &objects,
728 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
729{
730 QV4::ExecutionEngine *v4 = newUnit->engine;
731 QV4::Scope scope(v4);
732 QV4::ScopedValue function(scope);
733
734 for (QObject *object : objects) {
735 QQmlData *ddata = QQmlData::get(object);
736 if (!ddata || !ddata->hasVMEMetaObject)
737 continue;
738
739 auto *mainVme = static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(object)->metaObject);
740 for (QQmlVMEMetaObject *vme = mainVme; vme; vme = vme->parentVMEMetaObject()) {
741 const auto cu = vme->compilationUnit();
742 if (cu != newUnit)
743 continue;
744
745 const QQmlRefPointer<QQmlContextData> context = vme->contextData();
746 if (!context)
747 continue;
748
749 const int objectIndex = vme->qmlObjectId();
750 if (objectIndex < 0 || objectIndex >= cu->objectCount())
751 continue;
752
753 const QV4::CompiledData::Object *obj = cu->objectAt(objectIndex);
754 const QQmlPropertyCache::ConstPtr cache = cu->propertyCachesPtr()->at(objectIndex);
755 if (!cache)
756 continue;
757
758 QV4::Scoped<QV4::QmlContext> qmlContext(
759 scope, QV4::QmlContext::create(v4->rootContext(), context, object));
760
761 const quint32_le *functionIdx = obj->functionOffsetTable();
762 for (quint32 i = 0; i < obj->nFunctions; ++i, ++functionIdx) {
763 QV4::Function *runtimeFunction = cu->runtimeFunctions[*functionIdx];
764 const QString name = runtimeFunction->name()->toQString();
765
766 const QQmlPropertyData *property = cache->property(name, object, context);
767 if (!property || !property->isVMEFunction())
768 continue;
769
770 // Mirror QQmlObjectCreator::setupFunctions(): generators need their own creator.
771 function = runtimeFunction->isGenerator()
772 ? QV4::GeneratorFunction::create(qmlContext, runtimeFunction)
773 : QV4::FunctionObject::createScriptFunction(qmlContext, runtimeFunction);
774 mainVme->setVmeMethod(property->coreIndex(), function);
775 }
776 }
777 }
778}
779
780PatchResult applyDiff(std::vector<QObject *> &objects,
781 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
782 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
783{
784 // Ensure the new CU's runtime data (strings, lookups, functions) is populated.
785 if (!newUnit->runtimeStrings)
786 newUnit->populate();
787
788 // Fast path: if only existing bindings changed -- nothing structural about any VME
789 // meta-object, and no objects added or removed -- patch the affected objects in place. We
790 // leave every object and its VME meta-object where it is, re-apply the changed literal
791 // bindings (patchConstantBindings), point the objects at newUnit (remapObjectsToNewUnit), and
792 // let the in-place refreshBindings() translate the still-live expressions' functions to
793 // newUnit. Because no functions are added or removed in a trivial diff, function indices are
794 // stable, which is what makes that same-index translation correct.
795 const QV4::CompiledData::CompilationUnitDiff diff =
796 QV4::CompiledData::diffCompilationUnits(oldUnit->unitData(), newUnit->unitData());
797 if (diff.success && isTrivialDiff(diff, oldUnit, newUnit)) {
798 patchConstantBindings(objects, diff, oldUnit, newUnit);
799 remapObjectsToNewUnit(objects, oldUnit, newUnit);
800 refreshVmeMethods(objects, newUnit);
802 }
803
804 // Otherwise we rebuild whole component roots: the document root (index 0) and any
805 // inline-component roots. We don't touch other objects directly. Rebuilding a root runs a
806 // full reset() + repopulateBindings() that cascades down the entire instantiation (and
807 // through its composite base levels), recreating every object below it. Since every
808 // non-root object is, by construction, a descendant of a component root, this is
809 // sufficient. A component root that cannot be rebuilt in place - its C++ base type changed, or
810 // it carries deferred bindings (see below) - is handled by rebuilding the enclosing component
811 // root instead, which may live in a different compilation unit and recreates the root as a
812 // fresh object (of the new C++ class, with its deferred bindings armed and executed).
813 //
814 // A consequence worth remembering: because every live object is recreated, every
815 // QQmlJavaScriptExpression on those objects is recreated too, freshly bound to newUnit. By
816 // the time refreshBindings() runs, the only expressions still referencing oldUnit are the
817 // dead, detached leftovers of the resets. That is why the rebuild path's refreshBindings()
818 // can simply disable (null) them instead of remapping them to newUnit.
819 // A rebuild target carries the compilation units to rebuild it with. Usually that's oldUnit ->
820 // newUnit. But when we travel up to an enclosing component root in a *different* compilation
821 // unit (because a root's base type changed), that unit is itself unchanged: we rebuild it
822 // against itself (a full re-instantiation of its children) after redirecting the resolved type
823 // references, so the problematic object is recreated with its new C++ base type.
824 std::vector<RebuildTarget> rebuild;
825 QSet<QObject *> rebuildSet;
826 bool redirectedTypes = false;
827
828 const auto addRebuild = [&](QObject *object, int index,
829 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldCu,
830 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newCu) {
831 if (rebuildSet.contains(object))
832 return;
833 rebuildSet.insert(object);
834 rebuild.push_back({ object, index, oldCu, newCu });
835 };
836
837 for (QObject *object : objects) {
838 const QVarLengthArray<int, 4> indices = objectIndices(object, oldUnit);
839 for (int index : indices) {
840 // Objects instantiated by an enclosing component (indices beyond the CU, explicit
841 // Component content) are recreated when that enclosing root is rebuilt, so we don't
842 // record them here.
843 if (index >= oldUnit->objectCount())
844 continue;
845 const auto flags = oldUnit->objectAt(index)->flags();
846 if (index != 0 && !(flags & QV4::CompiledData::Object::IsInlineComponentRoot))
847 continue;
848
849 // A component root whose own non-composite base type changed or which carries deferred
850 // bidnings cannot be rebuilt in place. Rebuild the enclosing component root instead, so
851 // that the object is recreated from scratch as an instance of the new C++ class with
852 // new deferred bindings. Travelling up the scope hierarchy this way only fails once we
853 // hit the root scope.
854 if (!canRebuildComponentRootInPlace(oldUnit, newUnit, index)) {
855 QObject *outer = outerRebuildTarget(object, oldUnit, newUnit);
856 if (!outer)
857 return PatchResult::Failed;
858
859 // The enclosing root may recreate the object from a different compilation unit
860 // (the object was instantiated as an external type). Point that unit at newUnit
861 // first, so the recreated object gets the new definition.
862 if (!redirectedTypes) {
863 redirectResolvedTypeReferences(newUnit->engine, oldUnit->baseCompilationUnit(),
864 newUnit->baseCompilationUnit());
865 redirectedTypes = true;
866 }
867
868 // Rebuild the enclosing root against its own compilation unit (or newUnit, if that
869 // root lives in oldUnit itself, e.g. an inline component). The unchanged outer unit
870 // re-instantiates its children, recreating the problematic object.
871 const QQmlRefPointer<QV4::ExecutableCompilationUnit> outerOld =
872 QQmlData::get(outer)->compilationUnit;
873 const QQmlRefPointer<QV4::ExecutableCompilationUnit> outerNew =
874 outerOld->baseCompilationUnit() == oldUnit->baseCompilationUnit()
875 ? newUnit
876 : outerOld;
877 addRebuild(outer, QQmlData::get(outer)->cuObjectIndex, outerOld, outerNew);
878 break;
879 }
880
881 addRebuild(object, indices.first(), oldUnit, newUnit);
882 break;
883 }
884 }
885
886 // Sort by ascending cuIndex so that parent objects are rebuilt before their
887 // children. A parent's reset() properly retires children (they still point to
888 // oldUnit) and repopulateBindings recreates them. This avoids leaking orphaned
889 // children and naturally handles cases where the context needs more ID slots.
890 std::sort(rebuild.begin(), rebuild.end(),
891 [](const RebuildTarget &a, const RebuildTarget &b) { return a.index < b.index; });
892
893 // Pre-compute which objects to skip: if an ancestor is also in the rebuild
894 // list, the child will be properly retired and recreated during the ancestor's
895 // rebuild. Rebuilding it individually would be wasted work on a stale pointer.
896 QSet<QObject *> skip;
897 for (const RebuildTarget &target : rebuild) {
898 const QObjectList &children = target.object->children();
899 for (QObject *child : children)
900 skip.insert(child);
901 }
902
903 for (const RebuildTarget &target : rebuild) {
904 if (skip.contains(target.object))
905 continue;
906
907 rebuildObject(target.object, target.index, target.oldCu, target.newCu);
908 }
909
910 remapObjectsToNewUnit(objects, oldUnit, newUnit);
912}
913
914// Translate a still-live expression's function from oldUnit to the function at the same index in
915// newUnit, or clear it if there is no newUnit. Expressions whose function does not point into
916// oldUnit are left untouched. Same-index translation is valid because we only do this with trivial
917// diffs that neither add nor remove functions.
918// Returns true if the resulting function is valid afterwards (and should be re-evaluated)
919static bool
920translateExpressionFunction(QQmlJavaScriptExpression *expr,
921 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
922 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
923{
924 QV4::Function *f = expr->function();
925 if (!f)
926 return false;
927
928 if (f->executableCompilationUnit() != oldUnit.data())
929 return true;
930
931 if (newUnit) {
932 const qsizetype index = oldUnit->runtimeFunctions.indexOf(f);
933 if (index >= 0 && index < newUnit->runtimeFunctions.size())
934 expr->setFunction(newUnit->runtimeFunctions[index]);
935 } else {
936 expr->setFunction(nullptr);
937 return false;
938 }
939
940 return true;
941}
942
944 const QQmlRefPointer<QQmlContextData> &context,
945 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
946 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
947{
948 for (auto child = context->childContexts(); child; child = child->nextChild())
949 translateAndRefreshExpressionsRecursive(child, oldUnit, newUnit);
950
951 for (auto *expr = context->expressions(); expr; expr = expr->nextExpression()) {
952 if (translateExpressionFunction(expr, oldUnit, newUnit))
953 expr->refresh();
954 }
955}
956
957void refreshBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
958 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
959{
960 translateAndRefreshExpressionsRecursive(
961 QQmlContextData::get(oldUnit->engine->qmlEngine()->rootContext()), oldUnit, newUnit);
962}
963
964} // namespace QQmlPreview
965
966QT_END_NAMESPACE
static std::vector< CompositeLevel > collectCompositeLevels(const CompositeLevel &instanceLevel, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static void refreshVmeMethods(const std::vector< QObject * > &objects, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
void refreshBindings(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static void refreshBindingPropertyData(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &cu, int objectIndex, const QQmlPropertyCache::ConstPtr &cache)
static bool canRebuildComponentRootInPlace(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit, int objectIndex)
static bool translateExpressionFunction(QQmlJavaScriptExpression *expr, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static bool isRebuildableComponentRoot(QObject *object, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
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 QQmlPropertyCache::ConstPtr relinkCache(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &cu, int objectIndex, const QQmlPropertyCache::ConstPtr &actualParent)
static void relinkDerivedCaches(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &cu, const QQmlPropertyCache::ConstPtr &oldBaseCache, const QQmlPropertyCache::ConstPtr &newBaseCache)
static BindingKind bindingKind(const QV4::CompiledData::Binding *binding)
void redirectResolvedTypeReferences(QV4::ExecutionEngine *engine, const QQmlRefPointer< QV4::CompiledData::CompilationUnit > &oldUnit, const QQmlRefPointer< QV4::CompiledData::CompilationUnit > &newUnit)
PatchResult applyDiff(std::vector< QObject * > &objects, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static bool isTrivialDiff(const QV4::CompiledData::CompilationUnitDiff &diff, 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)
static void translateAndRefreshExpressionsRecursive(const QQmlRefPointer< QQmlContextData > &context, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static bool changeIsTrivial(const QV4::CompiledData::Change &change, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static QObject * outerRebuildTarget(QObject *object, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static void patchConstantBindings(const std::vector< QObject * > &objects, const QV4::CompiledData::CompilationUnitDiff &diff, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
static void remapObjectsToNewUnit(const std::vector< QObject * > &objects, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &oldUnit, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit)
Combined button and popup list for selecting options.
QQmlRefPointer< QV4::ExecutableCompilationUnit > newCu
QQmlRefPointer< QV4::ExecutableCompilationUnit > oldCu