Revision control

++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qqmlpreviewobjectpatch_p.h"
++
++ #include <private/qqmlcomponent_p.h>
++ #include <private/qqmlcontextdata_p.h>
++ #include <private/qqmldata_p.h>
++ #include <private/qqmljavascriptexpression_p.h>
++ #include <private/qqmlobjectcreator_p.h>
++ #include <private/qqmlpreviewbindingpatchcontext_p.h>
++ #include <private/qqmlpreviewdiff_p.h>
++ #include <private/qqmlpropertyresolver_p.h>
++ #include <private/qqmlscriptdata_p.h>
++ #include <private/qqmlsignalnames_p.h>
++ #include <private/qqmlvme_p.h>
++ #include <private/qqmlvmemetaobject_p.h>
++ #include <private/qv4functionobject_p.h>
++ #include <private/qv4generatorobject_p.h>
++ #include <private/qv4qmlcontext_p.h>
++ #include <private/qv4resolvedtypereference_p.h>
++
++ #include <QtQml/qqmlcomponent.h>
++ #include <QtQml/qqmlproperty.h>
++
++ #include <QtCore/qset.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QQmlPreview {
++
++ // The indices at which object appears in oldUnit: its own (ddata) index plus any indices it
++ // occupies through composite base levels (the VME meta-object chain).
++ static QVarLengthArray<int, 4>
++ objectIndices(QObject *object, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit)
++ {
++ QVarLengthArray<int, 4> objectIndices;
++
++ QQmlData *ddata = QQmlData::get(object);
++ if (!ddata)
++ return objectIndices;
++
++ if (ddata->compilationUnit == oldUnit)
++ objectIndices.push_back(ddata->cuObjectIndex);
++ if (ddata->hasVMEMetaObject) {
++ for (QQmlVMEMetaObject *vme =
++ static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(object)->metaObject);
++ vme; vme = vme->parentVMEMetaObject()) {
++ if (vme->compilationUnit() == oldUnit)
++ objectIndices.push_back(vme->qmlObjectId());
++ }
++ }
++
++ return objectIndices;
++ }
++
++ static QQmlPropertyCache::ConstPtr
++ nonCompositeBaseType(const QQmlPropertyCache::ConstPtr &propertyCache)
++ {
++ for (QQmlPropertyCache::ConstPtr parent = propertyCache; parent; parent = parent->parent()) {
++ if (!parent->isComposite())
++ return parent;
++ }
++
++ return QQmlPropertyCache::ConstPtr();
++ }
++
++ static bool
++ hasChangedNonCompositeBaseType(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
++ int objectIndex)
++ {
++ const QV4::CompiledData::Object *oldObj = oldUnit->objectAt(objectIndex);
++ const auto *oldTypeRef = oldUnit->resolvedType(oldObj->inheritedTypeNameIndex);
++ if (!oldTypeRef)
++ return false; // Grouped property sub-objects have no inherited type.
++
++ const QV4::CompiledData::Object *newObj = newUnit->objectAt(objectIndex);
++ const auto *newTypeRef = newUnit->resolvedType(newObj->inheritedTypeNameIndex);
++ if (!newTypeRef)
++ return true; // Type disappeared — definitely changed.
++
++ return nonCompositeBaseType(oldTypeRef->typePropertyCache())
++ != nonCompositeBaseType(newTypeRef->typePropertyCache());
++ }
++
++ // Whether the object at objectIndex is the root of a component: The document, an inline component,
++ // or an explicit or implicit Component.
++ static bool isComponentRoot(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit,
++ int objectIndex)
++ {
++ if (objectIndex == 0)
++ return true;
++
++ if (objectIndex >= unit->objectCount())
++ return false;
++
++ if (unit->objectAt(objectIndex)->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot))
++ return true;
++
++ if (unit->baseCompilationUnit()->implicitComponentForObject(objectIndex) >= 0)
++ return true;
++
++ for (int i = 0, end = unit->objectCount(); i < end; ++i) {
++ const QV4::CompiledData::Object *object = unit->objectAt(i);
++ if (object->hasFlag(QV4::CompiledData::Object::IsComponent)
++ && int(object->bindingTable()->value.objectIndex) == objectIndex) {
++ return true;
++ }
++ }
++ return false;
++ }
++
++ // reset() + repopulateBindings() cannot faithfully reproduce some components in place,
++ // because either:
++ // - Its index doesn't denote a component root anymore in the new unit, because objects were
++ // added or removed in front of it.
++ // - Its non-composite (C++) base type changed: the reused QObject is still an instance of the old
++ // class (see hasChangedNonCompositeBaseType).
++ // - It carries deferred bindings. We cannot re-install those because their handling is in the
++ // type's discretion.
++ // Fore those return false here. Otherwise return true.
++ static bool
++ canRebuildComponentRootInPlace(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
++ int objectIndex)
++ {
++ // An index out of range in the new CU is obsolete: rebuildObject() skips it and applyDiff()
++ // retires it afterwards. There is nothing to recreate from an enclosing root.
++ if (objectIndex >= newUnit->objectCount())
++ return true;
++
++ if (!isComponentRoot(newUnit, objectIndex))
++ return false;
++
++ if (newUnit->objectHasDeferredBindings(objectIndex))
++ return false;
++
++ return !hasChangedNonCompositeBaseType(oldUnit, newUnit, objectIndex);
++ }
++
++ // Re-resolve the object's precomputed binding-target table against its relinked property cache.
++ static void refreshBindingPropertyData(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &cu,
++ int objectIndex, const QQmlPropertyCache::ConstPtr &cache)
++ {
++ QList<QV4::CompiledData::BindingPropertyData> &table =
++ cu->baseCompilationUnit()->bindingPropertyDataPerObject;
++ if (objectIndex >= table.size())
++ return;
++
++ QV4::CompiledData::BindingPropertyData &bindingData = table[objectIndex];
++
++ const QV4::CompiledData::Object *obj = cu->objectAt(objectIndex);
++ const QQmlPropertyResolver resolver(cache);
++
++ const QV4::CompiledData::Binding *binding = obj->bindingTable();
++ for (qsizetype i = 0, end = bindingData.size(); i < end; ++i, ++binding) {
++ if (!bindingData.at(i))
++ continue;
++
++ Q_ASSERT(i < obj->nBindings);
++ const QString name = BindingPatchContext::targetPropertyName(cu, objectIndex, binding);
++ if (name.isEmpty())
++ continue;
++
++ if (cu->bindingIsSignalHandlerExpression(objectIndex, i)
++ || cu->bindingIsSignalHandlerObject(objectIndex, i)) {
++ bindingData[i] = resolver.signal(
++ *QQmlSignalNames::anyHandlerNameToSignalName(name), nullptr,
++ QQmlPropertyResolver::IgnoreRevision);
++ } else if (cu->bindingIsPropertyObserver(objectIndex, i)) {
++ bindingData[i] = resolver.property(
++ *QQmlSignalNames::changedHandlerNameToPropertyName(name), nullptr,
++ QQmlPropertyResolver::IgnoreRevision);
++ } else {
++ bindingData[i] = resolver.property(
++ name, nullptr, QQmlPropertyResolver::IgnoreRevision);
++ }
++ }
++ }
++
++ // Ensure the property cache at objectIndex in cu is derived from actualParent, the cache we just
++ // used for the level below it in the VME chain.
++ static QQmlPropertyCache::ConstPtr
++ relinkCache(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &cu, int objectIndex,
++ const QQmlPropertyCache::ConstPtr &actualParent)
++ {
++ QQmlPropertyCacheVector *caches = cu->propertyCachesPtr();
++ QQmlPropertyCache::ConstPtr cache = caches->at(objectIndex);
++
++ // The bottom-most composite level's own base is a non-composite (C++) type. That base never
++ // changes across a reload since a changed non-composite base is rejected by
++ // hasChangedNonCompositeBaseType.
++ if (!actualParent)
++ return cache;
++
++ // A type that needs no VME meta-object of its own reuses its base type's property cache.
++ if (!caches->needsVMEMetaObject(objectIndex)) {
++ if (cache != actualParent) {
++ caches->set(objectIndex, actualParent);
++ refreshBindingPropertyData(cu, objectIndex, actualParent);
++ }
++ return actualParent;
++ }
++
++ // A type with its own cache derived from the base: re-derive it from the relinked base so its
++ // inherited offsets match the (possibly changed) base layout.
++ if (cache->parent() != actualParent) {
++ cache = cache->rebased(actualParent);
++ caches->set(objectIndex, cache);
++ refreshBindingPropertyData(cu, objectIndex, cache);
++ }
++
++ return cache;
++ }
++
++ // Re-link every property cache in cu that (transitively) derives oldBaseCache so it derives
++ // newBaseCache instead.
++ static void relinkDerivedCaches(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &cu,
++ const QQmlPropertyCache::ConstPtr &oldBaseCache,
++ const QQmlPropertyCache::ConstPtr &newBaseCache)
++ {
++ QQmlPropertyCacheVector *caches = cu->propertyCachesPtr();
++ Q_ASSERT(caches);
++
++ QHash<const QQmlPropertyCache *, QQmlPropertyCache::ConstPtr> replacements;
++ replacements.insert(oldBaseCache.data(), newBaseCache);
++
++ for (bool changed = true; changed;) {
++ changed = false;
++ for (int i = 0, end = caches->count(); i < end; ++i) {
++ const QQmlPropertyCache::ConstPtr cache = caches->at(i);
++ if (!cache)
++ continue;
++
++ const auto it = replacements.constFind(cache->parent().data());
++ if (it == replacements.constEnd())
++ continue;
++
++ const QQmlPropertyCache::ConstPtr rebased = cache->rebased(*it);
++ replacements.insert(cache.data(), rebased);
++ caches->set(i, rebased);
++ refreshBindingPropertyData(cu, i, rebased);
++ changed = true;
++ }
++ }
++ }
++
++ struct RebuildTarget
++ {
++ QObject *object = nullptr;
++ int index = -1;
++ QQmlRefPointer<QV4::ExecutableCompilationUnit> oldCu;
++ QQmlRefPointer<QV4::ExecutableCompilationUnit> newCu;
++ };
++
++ // Walk the type resolution chain starting from the object at cuIndex in unit,
++ // collecting all composite (QML-defined) base type levels. Returns them ordered
++ // deepest-first (e.g., grandparent before parent).
++ static std::vector<CompositeLevel>
++ collectCompositeLevels(const CompositeLevel &instanceLevel,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ std::vector<CompositeLevel> levels;
++
++ auto currentUnit = instanceLevel.newCu;
++ const QV4::CompiledData::Object *obj = currentUnit->objectAt(instanceLevel.objectIndex);
++ const QV4::ResolvedTypeReference *typeRef =
++ currentUnit->resolvedType(obj->inheritedTypeNameIndex);
++
++ while (typeRef && typeRef->type().isComposite()) {
++ QQmlRefPointer<QV4::ExecutableCompilationUnit> cu = typeRef->isSelfReference()
++ ? currentUnit
++ : currentUnit->engine->executableCompilationUnit(typeRef->compilationUnit());
++ Q_ASSERT(cu);
++
++ // Replace the old unit with the new one wherever it occurs
++ const auto oldCu = cu;
++ if (cu == oldUnit)
++ cu = newUnit;
++
++ int rootIndex;
++ QString icName;
++ if (typeRef->type().isInlineComponent()) {
++ icName = typeRef->type().elementName();
++ rootIndex = cu->inlineComponentId(icName);
++ } else {
++ rootIndex = 0;
++ }
++
++ levels.push_back({ oldCu, cu, rootIndex, icName, nullptr });
++
++ if (rootIndex < 0 || rootIndex >= cu->objectCount())
++ break;
++
++ // Walk deeper into the base type
++ const QV4::CompiledData::Object *rootObj = cu->objectAt(rootIndex);
++ typeRef = cu->resolvedType(rootObj->inheritedTypeNameIndex);
++ currentUnit = cu;
++ }
++
++ return levels;
++ }
++
++ // Rebuilding a component root replaces the contexts it is the context object of. Instances of
++ // inner components created in such a context still have it as parent context. When rebuilding
++ // those, we have to parent their new contexts to the replacements, or they keep resolving ids
++ // against the old context, whose objects have been retired.
++ struct ReplacedContext
++ {
++ QQmlRefPointer<QQmlContextData> oldContext;
++ QQmlRefPointer<QQmlContextData> newContext;
++ };
++ using ReplacedContexts = QHash<const QQmlContextData *, ReplacedContext>;
++
++ static QQmlRefPointer<QQmlContextData>
++ currentContext(const QQmlRefPointer<QQmlContextData> &context,
++ const ReplacedContexts &replacedContexts)
++ {
++ const auto it = replacedContexts.constFind(context.data());
++ return it == replacedContexts.constEnd() ? context : it->newContext;
++ }
++
++ // The contexts of the composite levels of object, in the order of collectCompositeLevels().
++ static QVarLengthArray<QQmlRefPointer<QQmlContextData>, 4>
++ levelContexts(QObject *object, const QQmlRefPointer<QQmlContextData> &outerContext)
++ {
++ QVarLengthArray<QQmlRefPointer<QQmlContextData>, 4> contexts;
++ for (QQmlRefPointer<QQmlContextData> ctx(QQmlData::get(object)->context); ctx;
++ ctx = ctx->linkedContext()) {
++ if (ctx != outerContext && ctx->contextObject() == object)
++ contexts.append(ctx);
++ }
++
++ // The linked contexts are ordered from the deepest base type to the most derived one.
++ std::reverse(contexts.begin(), contexts.end());
++
++ return contexts;
++ }
++
++ // cuIndex is not necessarily the "outermost" index. There may be levels above oldUnit.
++ // Those have to be preserved/rebuilt.
++ static void rebuildObject(QObject *object, int cuIndex,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
++ ReplacedContexts *replacedContexts)
++ {
++ // If the object's index doesn't exist in the new CU, it's obsolete.
++ if (cuIndex >= newUnit->objectCount())
++ return;
++
++ QQmlData *ddata = QQmlData::get(object);
++ Q_ASSERT(ddata);
++
++ QQmlRefPointer<QQmlContextData> outerContext =
++ QQmlRefPointer<QQmlContextData>(ddata->outerContext);
++
++ // If the object has no context, or is scheduled for deletion, it's half-dead already.
++ if (!ddata->context || !outerContext || !outerContext->isValid() || ddata->isQueuedForDeletion)
++ return;
++
++ CompositeLevel instanceLevel{ ddata->compilationUnit,
++ ddata->compilationUnit == oldUnit ? newUnit
++ : ddata->compilationUnit,
++ ddata->cuObjectIndex, QString(), outerContext };
++
++ // If the object doesn't exist anymore in the new CU it will be deleted via GC.
++ // Nothing to do here.
++ if (instanceLevel.objectIndex >= instanceLevel.newCu->objectCount())
++ return;
++
++ std::vector<CompositeLevel> levels = collectCompositeLevels(instanceLevel, oldUnit, newUnit);
++
++ // Build the set of compilation units that participate in this rebuild.
++ // Bindings from these CUs are "internal" and will be re-created by repopulateBindings.
++ std::vector<CompositeLevel> internalUnits;
++ internalUnits.push_back(instanceLevel);
++ for (const auto &level : levels)
++ internalUnits.push_back(level);
++
++ BindingPatchContext patchCtx(object, ddata->compilationUnit, ddata->cuObjectIndex);
++ QDuplicateTracker<QObject *> seenChildren;
++ patchCtx.stashExternalState(internalUnits, &seenChildren);
++ // Collect the CUs whose children will be recreated by repopulateBindings.
++ // This is oldUnit (being replaced) and the composite-level CUs.
++ // NOT newUnit (already-rebuilt objects pointing here must be preserved)
++ // and NOT instanceLevel.cu (its repopulateBindings only sets bindings, not children).
++ std::vector<QQmlRefPointer<QV4::ExecutableCompilationUnit>> unitsToUnparent;
++ unitsToUnparent.push_back(oldUnit);
++ for (const auto &level : levels)
++ unitsToUnparent.push_back(level.oldCu);
++ patchCtx.reset(unitsToUnparent, internalUnits);
++
++ QV4::ExecutionEngine *v4 = newUnit->engine;
++ Q_ASSERT(v4);
++ QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(v4);
++ Q_ASSERT(enginePrivate);
++
++ const QVarLengthArray<QQmlRefPointer<QQmlContextData>, 4> oldLevelContexts =
++ levelContexts(object, outerContext);
++
++ if (outerContext->contextObject() == object) {
++ const QQmlRefPointer<QQmlContextData> oldOuterContext = outerContext;
++ oldOuterContext->setContextObject(nullptr);
++ outerContext = enginePrivate->createComponentRootContext(
++ instanceLevel.newCu, currentContext(oldOuterContext->parent(), *replacedContexts),
++ instanceLevel.objectIndex);
++ outerContext->setContextObject(object);
++ instanceLevel.context = outerContext;
++ replacedContexts->insert(oldOuterContext.data(), { oldOuterContext, outerContext });
++ }
++
++ for (QQmlRefPointer<QQmlContextData> ctx(ddata->context); ctx; ctx = ctx->linkedContext()) {
++ if (ctx->contextObject() == object)
++ ctx->setContextObject(nullptr);
++ }
++
++ ddata->clear();
++
++ QObjectPrivate *objectPrivate = QObjectPrivate::get(object);
++ delete std::exchange(objectPrivate->metaObject, nullptr);
++
++ QQmlRefPointer<QQmlContextData> levelContext = outerContext;
++ qsizetype replacedLevels = 0;
++ for (qsizetype i = 0, end = levels.size(); i < end; ++i) {
++ CompositeLevel &level = levels[i];
++ levelContext = level.context = enginePrivate->createComponentRootContext(
++ level.newCu, levelContext, level.objectIndex);
++ levelContext->setContextObject(object);
++
++ // The base type chain may have changed. Only match the old contexts up to the first
++ // level that differs.
++ if (i >= oldLevelContexts.size() || i != replacedLevels)
++ continue;
++ const QQmlRefPointer<QQmlContextData> &oldLevelContext = oldLevelContexts[i];
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> oldLevelCu =
++ oldLevelContext->typeCompilationUnit();
++ if ((oldLevelCu == oldUnit ? newUnit : oldLevelCu) != level.newCu)
++ continue;
++ replacedContexts->insert(oldLevelContext.data(), { oldLevelContext, levelContext });
++ ++replacedLevels;
++ }
++
++ if (outerContext->contextObject() == object) {
++ ddata->ownContext = outerContext;
++ ddata->context = outerContext.data();
++ if (!levels.empty())
++ ddata->context->setLinkedContext(levels.front().context);
++ } else if (!levels.empty()) {
++ ddata->ownContext = levels.back().context;
++ ddata->context = ddata->ownContext.data();
++ if (levels.size() > 1)
++ ddata->context->setLinkedContext(levels.front().context);
++ } else {
++ // Non-root child: doesn't own a context. Re-link to the parent context.
++ ddata->context = outerContext.data();
++ }
++
++ // Build the VME meta-object chain base-first, relinking each level's property cache to the
++ // actual (possibly freshly reloaded) parent cache we just used, so the whole chain's offsets
++ // stay consistent even when a composite base type's layout changed on reload.
++ QQmlPropertyCache::ConstPtr parentCache;
++ for (auto it = levels.crbegin(), end = levels.crend(); it != end; ++it) {
++ it->context->addOwnedObject(ddata);
++ QQmlPropertyCache::ConstPtr cache = relinkCache(it->newCu, it->objectIndex, parentCache);
++ if (it->newCu->propertyCachesPtr()->needsVMEMetaObject(it->objectIndex))
++ new QQmlVMEMetaObject(v4, object, cache, it->newCu, it->objectIndex);
++ parentCache = cache;
++ }
++
++ outerContext->addOwnedObject(ddata);
++ if (QQmlPropertyCacheVector *caches = instanceLevel.newCu->propertyCachesPtr();
++ caches->count() > instanceLevel.objectIndex) {
++ QQmlPropertyCache::ConstPtr cache =
++ relinkCache(instanceLevel.newCu, instanceLevel.objectIndex, parentCache);
++ if (caches->needsVMEMetaObject(instanceLevel.objectIndex))
++ new QQmlVMEMetaObject(v4, object, cache, instanceLevel.newCu, instanceLevel.objectIndex);
++ }
++
++ // Repopulate bindings at each composite level (deepest first).
++ // This sets up functions, evaluates bindings (creating child objects), etc.
++ // The object may get queued for deletion as result of some bindings.
++ // In that case we have to abort.
++ for (auto it = levels.crbegin(), end = levels.crend(); it != end && !ddata->isQueuedForDeletion;
++ ++it) {
++ QQmlObjectCreator creator(it->context, it->newCu, outerContext, it->icName, nullptr);
++ creator.repopulateBindings(it->objectIndex, object, it->context,
++ QQmlObjectCreator::InitFlag::IsContextObject
++ | QQmlObjectCreator::InitFlag::IsDocumentRoot);
++
++ QQmlInstantiationInterrupt interrupt;
++ creator.finalize(interrupt);
++ }
++
++ // The object may get queued for deletion as result of some bindings.
++ // In that case don't touch it any further.
++ if (!ddata->isQueuedForDeletion) {
++ // Repopulate bindings at the instance level in the parent CU.
++ QQmlObjectCreator creator(instanceLevel.context, instanceLevel.newCu, outerContext, QString(),
++ nullptr);
++ creator.repopulateBindings(instanceLevel.objectIndex, object, outerContext,
++ QQmlObjectCreator::InitFlag::None);
++ QQmlInstantiationInterrupt interrupt;
++ creator.finalize(interrupt);
++
++ // Restore externally set bindings and values that were stashed before reset.
++ // First refresh child object pointers — they may have been replaced during rebuild.
++ patchCtx.refreshObjects();
++ patchCtx.restoreExternalState();
++ }
++ }
++
++ enum class BindingKind { Literal, Script, Translation, Unpatchable };
++ static BindingKind bindingKind(const QV4::CompiledData::Binding *binding)
++ {
++ switch (binding->type()) {
++ case QV4::CompiledData::Binding::Type_Script:
++ return BindingKind::Script;
++ case QV4::CompiledData::Binding::Type_Translation:
++ case QV4::CompiledData::Binding::Type_TranslationById:
++ return BindingKind::Translation;
++ case QV4::CompiledData::Binding::Type_Number:
++ case QV4::CompiledData::Binding::Type_Boolean:
++ case QV4::CompiledData::Binding::Type_String:
++ case QV4::CompiledData::Binding::Type_Null:
++ return BindingKind::Literal;
++ default:
++ return BindingKind::Unpatchable;
++ }
++ }
++
++ // A trivial diff is one we can apply without rebuilding any VME meta-object and without
++ // adding or removing objects: only existing bindings and binding-expression bodies change. Such a
++ // diff is patched in place (see patchInPlace()). Everything else falls back to rebuilding roots.
++ static bool changeIsTrivial(const QV4::CompiledData::Change &change,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ using ChangeType = QV4::CompiledData::ChangeType;
++ switch (change.type) {
++ // Source-location-only changes have no runtime effect.
++ case ChangeType::AliasLocationChanged:
++ case ChangeType::BindingLocationChanged:
++ case ChangeType::EnumLocationChanged:
++ case ChangeType::FunctionLocationChanged:
++ case ChangeType::ImportLocationChanged:
++ case ChangeType::InlineComponentLocationChanged:
++ case ChangeType::ObjectLocationChanged:
++ case ChangeType::PropertyLocationChanged:
++ case ChangeType::SignalLocationChanged:
++ // Internal table changes that are byproducts of recompiling expressions. They are picked up
++ // when we translate functions and remap compilation units; no structure changes.
++ case ChangeType::UnitMetadataChanged:
++ case ChangeType::ConstantAdded:
++ case ChangeType::ConstantChanged:
++ case ChangeType::ConstantRemoved:
++ case ChangeType::StringDataAdded:
++ case ChangeType::StringDataChanged:
++ case ChangeType::StringDataRemoved:
++ case ChangeType::LookupAdded:
++ case ChangeType::LookupChanged:
++ case ChangeType::LookupRemoved:
++ case ChangeType::RegExpAdded:
++ case ChangeType::RegExpChanged:
++ case ChangeType::RegExpRemoved:
++ case ChangeType::ClassAdded:
++ case ChangeType::ClassChanged:
++ case ChangeType::ClassRemoved:
++ case ChangeType::BlockAdded:
++ case ChangeType::BlockChanged:
++ case ChangeType::BlockRemoved:
++ case ChangeType::TemplateObjectAdded:
++ case ChangeType::TemplateObjectChanged:
++ case ChangeType::TemplateObjectRemoved:
++ case ChangeType::JSClassAdded:
++ case ChangeType::JSClassChanged:
++ case ChangeType::JSClassRemoved:
++ case ChangeType::TranslationDataAdded:
++ case ChangeType::TranslationDataChanged:
++ case ChangeType::TranslationDataRemoved:
++ return true;
++ case ChangeType::FunctionChanged:
++ // A recompiled function body at a stable index. Binding/handler expressions pick up the new
++ // body via refreshBindings(), VME methods via refreshVmeMethods(). Neither needs a rebuild,
++ // so a function change is trivial regardless of whether the function backs a binding or a
++ // method.
++ return true;
++ case ChangeType::BindingChanged: {
++ Q_ASSERT(change.objectIndex >= 0);
++ Q_ASSERT(change.objectIndex < oldUnit->objectCount());
++ Q_ASSERT(change.objectIndex < newUnit->objectCount());
++
++ const QV4::CompiledData::Object *oldObj = oldUnit->objectAt(change.objectIndex);
++ Q_ASSERT(change.index < oldObj->nBindings);
++ const QV4::CompiledData::Binding *oldBinding = oldObj->bindingTable() + change.index;
++
++ const QV4::CompiledData::Object *newObj = newUnit->objectAt(change.objectIndex);
++ Q_ASSERT(change.index < newObj->nBindings);
++ const QV4::CompiledData::Binding *newBinding = newObj->bindingTable() + change.index;
++
++ const BindingKind oldKind = bindingKind(oldBinding);
++ const BindingKind newKind = bindingKind(newBinding);
++ if (oldKind == BindingKind::Unpatchable || newKind == BindingKind::Unpatchable)
++ return false;
++
++ // We cannot swap a binding for one of a different kind, yet: turning a literal into a
++ // script or translation binding (or vice versa) would have to install or drop a live
++ // binding, and we cannot recompile a script binding into a translation binding either.
++ // TODO: Fix this.
++ if (oldKind != newKind)
++ return false;
++
++ // A BindingChanged at a stable index can also mean the binding was moved to a different
++ // target property (its propertyNameIndex changed). In-place patching cannot relocate a
++ // binding: it would have to remove it from the old property and install it on the new one.
++ // That is structural, so fall back to the rebuild path.
++
++ return BindingPatchContext::targetPropertyName(oldUnit, change.objectIndex, oldBinding)
++ == BindingPatchContext::targetPropertyName(newUnit, change.objectIndex, newBinding);
++ }
++ default:
++ // ObjectAdded/Removed/Changed, Binding Added/Removed, Property/Signal/Alias/Enum changes,
++ // Function Added/Removed, Import/InlineComponent/RequiredPropertyExtraData changes:
++ // structural, need a rebuild.
++ return false;
++ }
++ }
++
++ static bool isTrivialDiff(const QV4::CompiledData::CompilationUnitDiff &diff,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ return std::all_of(diff.changes.cbegin(), diff.changes.cend(),
++ [&](const QV4::CompiledData::Change &change) {
++ return changeIsTrivial(change, oldUnit, newUnit);
++ });
++ }
++
++ // Re-apply changed literal bindings. A literal value is written only if the property still holds
++ // the value the old unit assigned; anything else is an external override we must leave untouched.
++ // Changed script bindings carry no literal value and are handled by function translation instead.
++ //
++ // The binding may live on a value-type group sub-object (font.pixelSize) or an attached object
++ // (Keys.enabled), which has no standalone QObject at its compilation-unit index. We use
++ // BindingPatchContext to map every compilation-unit object index reachable from a live instance to
++ // the owning QObject and the property-name prefix to address it.
++ static void patchConstantBindings(const std::vector<QObject *> &objects,
++ const QV4::CompiledData::CompilationUnitDiff &diff,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ for (QObject *object : objects) {
++ // Skip half-dead objects, mirroring rebuildObject(): an invalidated context means the
++ // object is on its way out and must not be patched.
++ QQmlData *ddata = QQmlData::get(object);
++ if (!ddata->context || !ddata->outerContext || !ddata->outerContext->isValid()
++ || ddata->isQueuedForDeletion) {
++ continue;
++ }
++
++ QVarLengthArray<BindingPatchContext, 4> contexts;
++ for (int index : objectIndices(object, oldUnit))
++ contexts.append(BindingPatchContext(object, oldUnit, index));
++
++ for (const QV4::CompiledData::Change &change : diff.changes) {
++ if (change.type != QV4::CompiledData::ChangeType::BindingChanged)
++ continue;
++
++ for (BindingPatchContext &context : contexts) {
++ if (context.applyBindingChange(newUnit, change))
++ break;
++ }
++ }
++ }
++ }
++
++ // Point every live object that still references oldUnit at newUnit, in both its ddata and its
++ // VME meta-object chain. Used after in-place patching, which keeps all object indices stable.
++ //
++ // The ddata compilation unit and the VME chain are remapped independently: a composite-type
++ // instance can carry oldUnit in its VME chain while its ddata->compilationUnit is a different
++ // executable unit (the "topmost", possibly inaddressible, instantiation).
++ static void remapObjectsToNewUnit(const std::vector<QObject *> &objects,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ for (QObject *object : objects) {
++ QQmlData *ddata = QQmlData::get(object);
++
++ // A QQmlComponent (e.g. an explicit or implicit `delegate:`) caches the compilation unit it
++ // instantiates from. Remap it so future create() calls produce instances from newUnit.
++ if (QQmlComponent *component = qobject_cast<QQmlComponent *>(object)) {
++ QQmlComponentPrivate *cp = QQmlComponentPrivate::get(component);
++ if (cp->compilationUnit() == oldUnit)
++ cp->setCompilationUnit(newUnit);
++ }
++
++ // Remap the ddata compilation unit if it points at oldUnit. An object whose index is out
++ // of range in the new CU is obsolete (it belonged to the old type definition); leave its
++ // ddata alone and let the VME nulling below retire it.
++ if (ddata->compilationUnit == oldUnit && ddata->cuObjectIndex < newUnit->objectCount())
++ ddata->compilationUnit = newUnit;
++
++ if (!ddata->hasVMEMetaObject)
++ continue;
++
++ // Remap (or retire) every VME meta-object in the chain that points at oldUnit.
++ auto *mainVme = static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(object)->metaObject);
++ for (QQmlVMEMetaObject *vmeMeta = mainVme; vmeMeta;
++ vmeMeta = vmeMeta->parentVMEMetaObject()) {
++ if (vmeMeta->compilationUnit() != oldUnit)
++ continue;
++ if (vmeMeta->qmlObjectId() < newUnit->objectCount()) {
++ vmeMeta->setCompilationUnit(newUnit);
++
++ // Give the live instance the reloaded type's identity. The instance must present
++ // newUnit's property cache.
++ const QQmlPropertyCache::ConstPtr newCache =
++ newUnit->propertyCachesPtr()->at(vmeMeta->qmlObjectId());
++ if (newCache) {
++ vmeMeta->setPropertyCache(newCache);
++ if (vmeMeta == mainVme)
++ ddata->propertyCache = newCache;
++ }
++ } else {
++ // Obsolete: null it so stale alias lookups (triggered by refreshBindings) safely
++ // return nullptr from findCompiledObject() instead of asserting.
++ vmeMeta->setCompilationUnit(nullptr);
++ }
++ }
++ }
++ }
++
++ // Returns true if the object is instantiated as a component root of the old unit that we can
++ // rebuild in place (see canRebuildComponentRootInPlace()), or as a component root of a different,
++ // unchanged unit. The latter is rebuilt against its own unit. Otherwise, for example if the object
++ // is not a component root at all or not even created from QML, return false.
++ static bool isRebuildableComponentRoot(
++ QObject *object,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ bool foundComponentRoot = false;
++ for (int index : objectIndices(object, oldUnit)) {
++ if (index >= oldUnit->objectCount() || !isComponentRoot(oldUnit, index))
++ continue;
++ if (!canRebuildComponentRootInPlace(oldUnit, newUnit, index))
++ return false;
++ foundComponentRoot = true;
++ }
++
++ if (foundComponentRoot)
++ return true;
++
++ const QQmlData *ddata = QQmlData::get(object);
++ return ddata && ddata->compilationUnit && ddata->compilationUnit != oldUnit
++ && isComponentRoot(ddata->compilationUnit, ddata->cuObjectIndex);
++ }
++
++ // A component root whose non-composite base type changed or that carries deferred bindings cannot
++ // be rebuilt in place. Instead we travel the scope hierarchy upwards to the enclosing component
++ // root that instantiates it and rebuild that: its reset() + repopulateBindings() recreates the
++ // problematic object from scratch, as a fresh QObject. If that enclosing root is problematic, too,
++ // we keep travelling up. Returns the object to rebuild, or nullptr if we reach the root scope
++ // without finding a rebuildable enclosing root.
++ static QObject *outerRebuildTarget(
++ QObject *object,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ Q_ASSERT(object);
++
++ const QQmlData *ddata = QQmlData::get(object);
++ if (!ddata)
++ return nullptr;
++
++ // Walk up the context hierarchy. The root of a component instance is the context object of
++ // its own context. Contexts may also have no context object or one that is not a component
++ // root at all (e.g. a delegate model's item). If we reach the root context, we give up.
++ for (QQmlRefPointer<QQmlContextData> context(ddata->outerContext); context;
++ context = context->parent()) {
++ QObject *outer = context->contextObject();
++ if (outer && outer != object && isRebuildableComponentRoot(outer, oldUnit, newUnit))
++ return outer;
++ }
++
++ return nullptr;
++ }
++
++ // TODO: This is dangerous. We are manipulating compilation units exposed to multiple
++ // engines on potentially multiple threads.
++ void redirectResolvedTypeReferences(
++ QV4::ExecutionEngine *engine,
++ const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &newUnit)
++ {
++ const auto units = engine->compilationUnits();
++ for (const auto &cu : units) {
++ for (auto *typeRef : std::as_const(cu->baseCompilationUnit()->resolvedTypes)) {
++ if (typeRef->isSelfReference())
++ continue;
++ if (typeRef->compilationUnit() != oldUnit)
++ continue;
++
++ typeRef->setCompilationUnit(newUnit);
++
++ QQmlPropertyCache::ConstPtr newCache;
++ const QQmlType type = typeRef->type();
++ if (type.isInlineComponent()) {
++ if (const int icId = newUnit->inlineComponentId(type.elementName()); icId >= 0)
++ newCache = newUnit->propertyCaches.at(icId);
++ } else {
++ newCache = newUnit->rootPropertyCache();
++ }
++
++ if (!newCache)
++ continue;
++
++ const QQmlPropertyCache::ConstPtr oldCache = typeRef->typePropertyCache();
++ typeRef->setTypePropertyCache(newCache);
++ relinkDerivedCaches(cu, oldCache, newCache);
++ }
++ }
++ }
++
++ // Re-create the VME method function objects from newUnit so that an object's methods follow the
++ // unit remap, mirroring what refreshBindings() does for QQmlJavaScriptExpression-based bindings.
++ static void refreshVmeMethods(const std::vector<QObject *> &objects,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ QV4::ExecutionEngine *v4 = newUnit->engine;
++ QV4::Scope scope(v4);
++ QV4::ScopedValue function(scope);
++
++ for (QObject *object : objects) {
++ QQmlData *ddata = QQmlData::get(object);
++ if (!ddata || !ddata->hasVMEMetaObject)
++ continue;
++
++ auto *mainVme = static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(object)->metaObject);
++ for (QQmlVMEMetaObject *vme = mainVme; vme; vme = vme->parentVMEMetaObject()) {
++ const auto cu = vme->compilationUnit();
++ if (cu != newUnit)
++ continue;
++
++ const QQmlRefPointer<QQmlContextData> context = vme->contextData();
++ if (!context)
++ continue;
++
++ const int objectIndex = vme->qmlObjectId();
++ if (objectIndex < 0 || objectIndex >= cu->objectCount())
++ continue;
++
++ const QV4::CompiledData::Object *obj = cu->objectAt(objectIndex);
++ const QQmlPropertyCache::ConstPtr cache = cu->propertyCachesPtr()->at(objectIndex);
++ if (!cache)
++ continue;
++
++ QV4::Scoped<QV4::QmlContext> qmlContext(
++ scope, QV4::QmlContext::create(v4->rootContext(), context, object));
++
++ const quint32_le *functionIdx = obj->functionOffsetTable();
++ for (quint32 i = 0; i < obj->nFunctions; ++i, ++functionIdx) {
++ QV4::Function *runtimeFunction = cu->runtimeFunctions[*functionIdx];
++ const QString name = runtimeFunction->name()->toQString();
++
++ const QQmlPropertyData *property = cache->property(name, object, context);
++ if (!property || !property->isVMEFunction())
++ continue;
++
++ // Mirror QQmlObjectCreator::setupFunctions(): generators need their own creator.
++ function = runtimeFunction->isGenerator()
++ ? QV4::GeneratorFunction::create(qmlContext, runtimeFunction)
++ : QV4::FunctionObject::createScriptFunction(qmlContext, runtimeFunction);
++ mainVme->setVmeMethod(property->coreIndex(), function);
++ }
++ }
++ }
++ }
++
++ PatchResult applyDiff(std::vector<QObject *> &objects,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ // Ensure the new CU's runtime data (strings, lookups, functions) is populated.
++ if (!newUnit->runtimeStrings)
++ newUnit->populate();
++
++ // Fast path: if only existing bindings changed -- nothing structural about any VME
++ // meta-object, and no objects added or removed -- patch the affected objects in place. We
++ // leave every object and its VME meta-object where it is, re-apply the changed literal
++ // bindings (patchConstantBindings), point the objects at newUnit (remapObjectsToNewUnit), and
++ // let the in-place refreshBindings() translate the still-live expressions' functions to
++ // newUnit. Because no functions are added or removed in a trivial diff, function indices are
++ // stable, which is what makes that same-index translation correct.
++ const QV4::CompiledData::CompilationUnitDiff diff =
++ QV4::CompiledData::diffCompilationUnits(oldUnit->unitData(), newUnit->unitData());
++ if (diff.success && isTrivialDiff(diff, oldUnit, newUnit)) {
++ patchConstantBindings(objects, diff, oldUnit, newUnit);
++ remapObjectsToNewUnit(objects, oldUnit, newUnit);
++ refreshVmeMethods(objects, newUnit);
++ return PatchResult::PatchedInPlace;
++ }
++
++ // Otherwise we rebuild whole component roots: the document root (index 0) and any
++ // inline-component roots. We don't touch other objects directly. Rebuilding a root runs a
++ // full reset() + repopulateBindings() that cascades down the entire instantiation (and
++ // through its composite base levels), recreating every object below it. Since every
++ // non-root object is, by construction, a descendant of a component root, this is
++ // sufficient. A component root that cannot be rebuilt in place - its C++ base type changed, or
++ // it carries deferred bindings (see below) - is handled by rebuilding the enclosing component
++ // root instead, which may live in a different compilation unit and recreates the root as a
++ // fresh object (of the new C++ class, with its deferred bindings armed and executed).
++ //
++ // A consequence worth remembering: because every live object is recreated, every
++ // QQmlJavaScriptExpression on those objects is recreated too, freshly bound to newUnit. By
++ // the time refreshBindings() runs, the only expressions still referencing oldUnit are the
++ // dead, detached leftovers of the resets. That is why the rebuild path's refreshBindings()
++ // can simply disable (null) them instead of remapping them to newUnit.
++ // A rebuild target carries the compilation units to rebuild it with. Usually that's oldUnit ->
++ // newUnit. But when we travel up to an enclosing component root in a *different* compilation
++ // unit (because a root's base type changed), that unit is itself unchanged: we rebuild it
++ // against itself (a full re-instantiation of its children) after redirecting the resolved type
++ // references, so the problematic object is recreated with its new C++ base type.
++ std::vector<RebuildTarget> rebuild;
++ QSet<QObject *> rebuildSet;
++ bool redirectedTypes = false;
++
++ const auto addRebuild = [&](QObject *object, int index,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldCu,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newCu) {
++ if (rebuildSet.contains(object))
++ return;
++ rebuildSet.insert(object);
++ rebuild.push_back({ object, index, oldCu, newCu });
++ };
++
++ for (QObject *object : objects) {
++ const QVarLengthArray<int, 4> indices = objectIndices(object, oldUnit);
++ for (int index : indices) {
++ // Objects instantiated by an enclosing component (indices beyond the CU) are recreated
++ // when that enclosing root is rebuilt, so we don't record them here. Neither do we
++ // record any other objects but component roots. Instances of inner components are not
++ // recreated by rebuilding the enclosing component. Therefore, we record their roots.
++ if (index >= oldUnit->objectCount() || !isComponentRoot(oldUnit, index))
++ continue;
++
++ // A component root whose own non-composite base type changed, which carries deferred
++ // bidnings, or which isn't a component root anymore cannot be rebuilt in place. Rebuild
++ // the enclosing component root instead, so that the object is recreated from scratch
++ // as an instance of the new C++ class with new deferred bindings. A surviving instance
++ // of an inner component stays on oldUnit then. Travelling up the scope hierarchy this
++ // way only fails once we hit the root scope.
++ if (!canRebuildComponentRootInPlace(oldUnit, newUnit, index)) {
++ QObject *outer = outerRebuildTarget(object, oldUnit, newUnit);
++ if (!outer)
++ return PatchResult::Failed;
++
++ // The enclosing root may recreate the object from a different compilation unit
++ // (the object was instantiated as an external type). Point that unit at newUnit
++ // first, so the recreated object gets the new definition.
++ if (!redirectedTypes) {
++ redirectResolvedTypeReferences(newUnit->engine, oldUnit->baseCompilationUnit(),
++ newUnit->baseCompilationUnit());
++ redirectedTypes = true;
++ }
++
++ // Rebuild the enclosing root against its own compilation unit (or newUnit, if that
++ // root lives in oldUnit itself, e.g. an inline component). The unchanged outer unit
++ // re-instantiates its children, recreating the problematic object.
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> outerOld =
++ QQmlData::get(outer)->compilationUnit;
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> outerNew =
++ outerOld->baseCompilationUnit() == oldUnit->baseCompilationUnit()
++ ? newUnit
++ : outerOld;
++ addRebuild(outer, QQmlData::get(outer)->cuObjectIndex, outerOld, outerNew);
++ break;
++ }
++
++ addRebuild(object, indices.first(), oldUnit, newUnit);
++ break;
++ }
++ }
++
++ // Sort by ascending cuIndex so that parent objects are rebuilt before their
++ // children. A parent's reset() properly retires children (they still point to
++ // oldUnit) and repopulateBindings recreates them. This avoids leaking orphaned
++ // children and naturally handles cases where the context needs more ID slots.
++ std::sort(rebuild.begin(), rebuild.end(),
++ [](const RebuildTarget &a, const RebuildTarget &b) { return a.index < b.index; });
++
++ // Pre-compute which objects to skip: if an ancestor is also in the rebuild
++ // list, the child will be properly retired and recreated during the ancestor's
++ // rebuild. Rebuilding it individually would be wasted work on a stale pointer.
++ QSet<QObject *> skip;
++ for (const RebuildTarget &target : rebuild) {
++ const QObjectList &children = target.object->children();
++ for (QObject *child : children)
++ skip.insert(child);
++ }
++
++ ReplacedContexts replacedContexts;
++ for (const RebuildTarget &target : rebuild) {
++ if (skip.contains(target.object))
++ continue;
++
++ rebuildObject(target.object, target.index, target.oldCu, target.newCu, &replacedContexts);
++ }
++
++ // Everything lexically inside the rebuilt components has been recreated or rebuilt now. Whatever
++ // was instantiated by oldUnit and survived without being rebuilt is obsolete. We cannot remap it
++ // to newUnit by index since indices may have shifted. Retire it.
++ for (QObject *object : objects) {
++ const QQmlData *ddata = QQmlData::get(object);
++ if (ddata->compilationUnit != oldUnit || ddata->isQueuedForDeletion)
++ continue;
++ BindingPatchContext::clearBindingsRecursive(object);
++ BindingPatchContext::retireObject(object);
++ }
++
++ return PatchResult::Rebuilt;
++ }
++
++ // Translate a still-live expression's function from oldUnit to the function at the same index in
++ // newUnit, or clear it if there is no newUnit. Expressions whose function does not point into
++ // oldUnit are left untouched. Same-index translation is valid because we only do this with trivial
++ // diffs that neither add nor remove functions.
++ // Returns true if the resulting function is valid afterwards (and should be re-evaluated)
++ static bool
++ translateExpressionFunction(QQmlJavaScriptExpression *expr,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ QV4::Function *f = expr->function();
++ if (!f)
++ return false;
++
++ if (f->executableCompilationUnit() != oldUnit.data())
++ return true;
++
++ if (newUnit) {
++ const qsizetype index = oldUnit->runtimeFunctions.indexOf(f);
++ if (index >= 0 && index < newUnit->runtimeFunctions.size())
++ expr->setFunction(newUnit->runtimeFunctions[index]);
++ } else {
++ expr->setFunction(nullptr);
++ return false;
++ }
++
++ return true;
++ }
++
++ static void translateAndRefreshExpressionsRecursive(
++ const QQmlRefPointer<QQmlContextData> &context,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ for (auto child = context->childContexts(); child; child = child->nextChild())
++ translateAndRefreshExpressionsRecursive(child, oldUnit, newUnit);
++
++ for (auto *expr = context->expressions(); expr; expr = expr->nextExpression()) {
++ if (translateExpressionFunction(expr, oldUnit, newUnit))
++ expr->refresh();
++ }
++ }
++
++ void refreshBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit)
++ {
++ translateAndRefreshExpressionsRecursive(
++ QQmlContextData::get(oldUnit->engine->qmlEngine()->rootContext()), oldUnit, newUnit);
++ }
++
++ } // namespace QQmlPreview
++
++ QT_END_NAMESPACE