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
qqmlpreviewbindingpatchcontext.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/qqmlnotifier_p.h>
9#include <private/qqmlobjectcreator_p.h>
10#include <private/qqmlproperty_p.h>
11#include <private/qqmlproperty_p.h>
12#include <private/qqmlpropertybinding_p.h>
13#include <private/qqmlpropertytopropertybinding_p.h>
14#include <private/qqmltypeloader_p.h>
15#include <private/qqmlvaluetypeproxybinding_p.h>
16#include <private/qqmlvme_p.h>
17#include <private/qv4functionobject_p.h>
18#include <private/qv4generatorobject_p.h>
19#include <private/qv4qmlcontext_p.h>
20#include <private/qv4resolvedtypereference_p.h>
21
22#include <QtCore/qqueue.h>
23#include <QtCore/qset.h>
24
26
27namespace QQmlPreview {
28
29
30static bool functionBelongsToObject(const QV4::Function *f,
31 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &cu,
32 int objectIndex)
33{
34 if (f->executableCompilationUnit() != cu)
35 return false;
36
37 const QV4::CompiledData::Object *obj = cu->objectAt(objectIndex);
38 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) {
39 switch (binding->type()) {
40 case QV4::CompiledData::Binding::Type_GroupProperty:
41 case QV4::CompiledData::Binding::Type_AttachedProperty:
42 case QV4::CompiledData::Binding::Type_Object:
43 if (functionBelongsToObject(f, cu, binding->value.objectIndex))
44 return true;
45 break;
46 case QV4::CompiledData::Binding::Type_Script:
47 if (cu->runtimeFunctions[binding->value.compiledScriptIndex] == f)
48 return true;
49 default:
50 break;
51 }
52 }
53 return false;
54}
55
56// Classifies a single binding's JavaScript function: does it belong to one of the compilation
57// units participating in the rebuild (internal, will be recreated), or to some other component
58// (external, must be preserved)? A null function is treated as external.
59static bool isExternalFunction(const QV4::Function *f,
60 const std::vector<CompositeLevel> &internalUnits, QObject *target)
61{
62 if (!f)
63 return true;
64
65 for (const auto &internalUnit : internalUnits) {
66 if (functionBelongsToObject(f, internalUnit.oldCu, internalUnit.objectIndex)
67 || functionBelongsToObject(f, internalUnit.newCu, internalUnit.objectIndex)) {
68 return false;
69 }
70 }
71
72 const QQmlData *ddata = QQmlData::get(target);
73 if (!ddata)
74 return true;
75
76 // If the binding lives in an outer context that's still part of the rebuild, it is not
77 // actually external since it will be re-recreated.
78
79 for (QQmlRefPointer<QQmlContextData> context = ddata->outerContext; context;
80 context = context->parent()) {
81 const QQmlRefPointer<QV4::ExecutableCompilationUnit> cu = context->typeCompilationUnit();
82 if (!cu)
83 continue;
84
85 if (f->executableCompilationUnit() == cu)
86 return false;
87
88 if (std::any_of(internalUnits.begin(), internalUnits.end(),
89 [&](const CompositeLevel &level) {
90 return level.oldCu == cu || level.newCu == cu;
91 })) {
92 break;
93 }
94 }
95
96 return true;
97}
98
99// Determines whether a binding on a property is "external", i.e. not from any of the
100// compilation units that participate in the rebuild of this object.
101// External bindings come from other compilation units (e.g. a parent component setting a
102// property binding on a child instance) and must be preserved across rebuilds.
103static bool isExternalBinding(const QQmlAnyBinding &binding,
104 const std::vector<CompositeLevel> &internalUnits, QObject *target)
105{
106 if (!binding)
107 return false;
108
109 if (const QQmlAbstractBinding *abstractBinding = binding.asAbstractBinding()) {
110 switch (abstractBinding->kind()) {
111 case QQmlAbstractBinding::QmlBinding:
112 return isExternalFunction(static_cast<const QQmlBinding *>(abstractBinding)->function(),
113 internalUnits, target);
114 case QQmlAbstractBinding::ValueTypeProxy: {
115 // A value-type group binding has no function of its own: it is a proxy holding one
116 // QQmlBinding per bound sub-property. Classify it by its sub-bindings.
117 const auto *proxy = static_cast<const QQmlValueTypeProxyBinding *>(abstractBinding);
118 for (QQmlAbstractBinding *sub = proxy->subBindings(); sub; sub = sub->nextBinding()) {
119 if (sub->kind() != QQmlAbstractBinding::QmlBinding)
120 continue;
121 if (!isExternalFunction(static_cast<const QQmlBinding *>(sub)->function(),
122 internalUnits, target)) {
123 return false;
124 }
125 }
126 return true;
127 }
128 case QQmlAbstractBinding::PropertyToPropertyBinding:
129 return true;
130 }
131 return true;
132 }
133
134 if (const QPropertyBindingPrivate *priv =
135 QPropertyBindingPrivate::get(binding.asUntypedPropertyBinding());
136 priv && priv->isQmlBinding()) {
137 // QPropertyBindingPrivate-based binding. Check if it's a QQmlPropertyBinding
138 // with a JS expression we can trace back to a CU.
139 const auto base = static_cast<const QQmlPropertyBindingBase *>(priv);
140 if (base->bindingKind() == QQmlPropertyBindingBase::BindingKind::JavaScript) {
141 if (const QQmlPropertyBindingJS *jsExpr =
142 static_cast<const QQmlPropertyBinding *>(base)->jsExpression()) {
143 return isExternalFunction(jsExpr->function(), internalUnits, target);
144 }
145 }
146 }
147
148 return true;
149}
150
151static QObject *propertyToPropertySource(const QQmlAnyBinding &binding)
152{
153 if (const QQmlAbstractBinding *abstractBinding = binding.asAbstractBinding()) {
154 if (abstractBinding->kind() == QQmlAbstractBinding::PropertyToPropertyBinding) {
155 return static_cast<const QQmlPropertyToUnbindablePropertyBinding *>(abstractBinding)
156 ->source();
157 }
158 } else if (const QPropertyBindingPrivate *priv =
159 QPropertyBindingPrivate::get(binding.asUntypedPropertyBinding());
160 priv && priv->isQmlBinding()) {
161 const auto base = static_cast<const QQmlPropertyBindingBase *>(priv);
162 if (base->bindingKind() == QQmlPropertyBindingBase::BindingKind::PropertyToProperty)
163 return static_cast<const QQmlPropertyToBindablePropertyBinding *>(priv)->source();
164 }
165 return nullptr;
166}
167
168static QVariant literalBindingValue(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit,
169 const QV4::CompiledData::Binding *binding)
170{
171 switch (binding->type()) {
172 case QV4::CompiledData::Binding::Type_Number: {
173 const double d = unit->bindingValueAsNumber(binding);
174 return QV4::Value::isInt32(d) ? QVariant(int(d)) : QVariant(d);
175 }
176 case QV4::CompiledData::Binding::Type_Boolean:
177 return QVariant(bool(binding->value.b));
178 case QV4::CompiledData::Binding::Type_Translation:
179 case QV4::CompiledData::Binding::Type_TranslationById:
180 case QV4::CompiledData::Binding::Type_String:
181 return unit->bindingValueAsString(binding);
182 case QV4::CompiledData::Binding::Type_Null:
183 return QVariant::fromValue(nullptr);
184 default:
185 // Script bindings, object bindings, etc. carry no constant value.
186 return QVariant();
187 }
188}
189
190static QVariant coerceToPropertyType(QV4::ExecutionEngine *v4, const QVariant &value,
191 QMetaType propertyType)
192{
193 if (propertyType == QMetaType::fromType<QVariant>() || value.metaType() == propertyType)
194 return value;
195
196 QV4::Scope scope(v4);
197 QV4::ScopedValue v(scope, v4->metaTypeToJS(value.metaType(), value.constData()));
198 QVariant result(propertyType);
199 v4->metaTypeFromJS(v, propertyType, result.data());
200 return result;
201}
202
203void BindingPatchContext::recordBindingValues(
204 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, int cuIndex,
205 QHash<QString, QVariant> *constantValues, QDuplicateTracker<QObject *> *seenChildren)
206{
207 Q_ASSERT(constantValues);
208
209 if (!unit || cuIndex >= unit->objectCount())
210 return;
211
212 const QV4::CompiledData::Object *obj = unit->objectAt(cuIndex);
213 const QQmlPropertyCache::ConstPtr cache = unit->propertyCachesPtr()->at(cuIndex);
214
215 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) {
216
217 const QString name = targetPropertyName(unit, cuIndex, binding);
218 if (name.isEmpty())
219 continue;
220
221 switch (binding->type()) {
222 case QV4::CompiledData::Binding::Type_AttachedProperty:
223 attachedContext(unit, binding, seenChildren);
224 continue;
225 case QV4::CompiledData::Binding::Type_GroupProperty:
226 childContext(unit, binding, seenChildren);
227 continue;
228 default:
229 break;
230 }
231
232 if (binding->isSignalHandler())
233 continue;
234
235 if (binding->hasFlag(QV4::CompiledData::Binding::IsCustomParserBinding))
236 continue;
237
238 const qsizetype size = constantValues->size();
239 QVariant &value = (*constantValues)[name];
240 if (constantValues->size() == size)
241 continue;
242
243 // Extract constant value from the binding for comparison. Script and object bindings
244 // yield an invalid QVariant: they shouldn't be touched since we've just installed the
245 // new bindings (unless there is yet another, external binding).
246 value = literalBindingValue(unit, binding);
247 }
248
249 for (int propertyIndex = 0, end = obj->propertyCount(); propertyIndex != end; ++propertyIndex) {
250 const qsizetype size = constantValues->size();
251 QVariant &value =
252 (*constantValues)[unit->stringAt(obj->propertyTable()[propertyIndex].nameIndex())];
253 if (constantValues->size() != size)
254 value = QVariant(cache->property(cache->propertyOffset() + propertyIndex)->propType());
255 }
256}
257
258void BindingPatchContext::stashExternalState(const std::vector<CompositeLevel> &internalUnits,
259 QDuplicateTracker<QObject *> *seenChildren)
260{
261 // Determine which properties are assigned by the CU and their constant values
262 QHash<QString, QVariant> constantValues;
263 recordBindingValues(unit, objectIndex, &constantValues, seenChildren);
264
265 if (prefix.isEmpty()) {
266 const QQmlData *ddata = QQmlData::get(m_object);
267 if (ddata->compilationUnit) {
268 recordBindingValues(ddata->compilationUnit, ddata->cuObjectIndex, &constantValues,
269 seenChildren);
270 }
271
272 if (ddata->hasVMEMetaObject) {
273 for (QQmlVMEMetaObject *vmeMeta = static_cast<QQmlVMEMetaObject *>(
274 QObjectPrivate::get(m_object)->metaObject);
275 vmeMeta; vmeMeta = vmeMeta->parentVMEMetaObject()) {
276 if (auto cu = vmeMeta->compilationUnit())
277 recordBindingValues(cu, vmeMeta->qmlObjectId(), &constantValues, seenChildren);
278 }
279 }
280 }
281
282 // Iterate all properties. For those in the CU's binding table, check if the current state
283 // differs from what the CU set (indicating an external override to preserve). For the rest,
284 // check for external bindings installed by other components.
285 // Additionally, for QObject* properties pointing to QML-created children, register them
286 // as child contexts so their external signal handlers are stashed recursively at the end.
287 const QMetaObject *mo = m_object->metaObject();
288 for (int i = 0, count = mo->propertyCount(); i < count; ++i) {
289 const QMetaProperty metaProp = mo->property(i);
290 const QString propName = QString::fromUtf8(metaProp.name());
291
292 const QQmlProperty qProp(m_object, propName);
293 if (!qProp.isValid())
294 continue;
295
296 // Discover QML-created child objects accessible via QObject* properties.
297 // Objects without a CU (like lazily-created grouped property objects) survive
298 // rebuilds unchanged and don't need stashing.
299 if (qProp.propertyMetaType().flags().testFlag(QMetaType::PointerToQObject)) {
300 if (QObject *child = qProp.read().value<QObject *>()) {
301 if (QQmlData *childDdata = QQmlData::get(child)) {
302 if (const auto &childCU = childDdata->compilationUnit; childCU
303 && std::find_if(internalUnits.begin(), internalUnits.end(),
304 [&](const CompositeLevel &level) {
305 return level.newCu == childCU || level.oldCu == childCU;
306 })
307 != internalUnits.end()) {
308 childContext(propName, child, childCU, childDdata->cuObjectIndex,
309 seenChildren);
310 }
311 }
312 }
313 }
314
315 const auto it = constantValues.constFind(propName);
316 if (it == constantValues.cend()) {
317 // Property not in CU's binding table — check for external bindings.
318 const QQmlAnyBinding binding = QQmlAnyBinding::ofProperty(qProp);
319 if (isExternalBinding(binding, internalUnits, m_object)) {
320 QQmlAnyBinding taken = QQmlAnyBinding::takeFrom(qProp);
321 m_storedBindings.push_back(
322 { propName, std::move(taken), propertyToPropertySource(binding) });
323 }
324 continue;
325 }
326
327 // Property is in the CU's binding table.
328 const QQmlAnyBinding binding = QQmlAnyBinding::ofProperty(qProp);
329 if (isExternalBinding(binding, internalUnits, m_object)) {
330 QQmlAnyBinding taken = QQmlAnyBinding::takeFrom(qProp);
331 m_storedBindings.push_back(
332 { propName, std::move(taken), propertyToPropertySource(binding) });
333 continue;
334 }
335
336 // Internal binding is still valid. Apparently it doesn't get overridden by an external
337 // constant or binding. Nothing to store.
338 if (binding)
339 continue;
340
341 if (!it->isValid()) {
342 // This is potentially an internal binding overridden by an external constant. But
343 // it can also be an enum assignment optimized away to omit the binding itself. We
344 // can't discern those. So we don't store them for now.
345 // TODO: We can probably do better here.
346 continue;
347 }
348
349 // Two constant values. Figure out if they're the same. If not, store.
350
351 const QVariant expected = coerceToPropertyType(unit->engine, *it, qProp.propertyMetaType());
352 if (const QVariant current = qProp.read(); current != expected)
353 m_storedValues.push_back({ propName, current });
354 }
355
356 const auto stashBoundSignal = [&](QQmlBoundSignal *boundSignal) {
357 const QByteArray signature =
358 QMetaObjectPrivate::signal(m_object->metaObject(), boundSignal->signalIndex())
359 .methodSignature();
360 QQmlNotifierEndpoint *next = boundSignal->nextEndpoint();
361 boundSignal->disconnect();
362 m_storedSignalHandlers.push_back(
363 { QString::fromUtf8(signature), std::unique_ptr<QQmlBoundSignal>(boundSignal) });
364 return next;
365 };
366
367 // Stash external signal handlers connected to this object's signals.
368 // A handler is "internal" only if its function will be recreated during repopulation
369 // (i.e., it's a signal handler binding at one of the specific object indices being rebuilt).
370 // Only QQmlBoundSignal endpoints are stashed — other notifier endpoints (e.g. alias
371 // tracking) are embedded in VME data arrays and cannot be safely owned or relocated.
372 if (QQmlNotifyList *list = QQmlData::get(m_object)->notifyList.loadRelaxed()) {
373 // Ensure all endpoints are moved from the pending 'todo' list into the
374 // laid-out 'notifies' array. Endpoints remain in 'todo' until a signal
375 // with a high enough index is actually delivered, so without this call
376 // we'd miss handlers for signals that were never fired (e.g. clicked()).
377 if (list->todo)
378 list->layout();
379 for (quint16 i = 0, end = list->notifiesSize; i < end; ++i) {
380 for (QQmlNotifierEndpoint *ep = list->notifies[i]; ep;) {
381 if (ep->callbackType() != QQmlNotifierEndpoint::QQmlBoundSignal) {
382 ep = ep->nextEndpoint();
383 continue;
384 }
385
386 QQmlBoundSignal *boundSignal = static_cast<QQmlBoundSignal *>(ep);
387 QQmlBoundSignalExpression *expr = boundSignal->expression();
388 if (!expr) {
389 ep = stashBoundSignal(boundSignal);
390 continue;
391 }
392
393 const QV4::Function *f = expr->function();
394 if (!f) {
395 ep = stashBoundSignal(boundSignal);
396 continue;
397 }
398
399 bool isInternal = false;
400 for (const CompositeLevel &internalUnit : internalUnits) {
401 if (functionBelongsToObject(f, internalUnit.oldCu, internalUnit.objectIndex)
402 || functionBelongsToObject(f, internalUnit.newCu,
403 internalUnit.objectIndex)) {
404 isInternal = true;
405 break;
406 }
407 }
408
409 ep = isInternal ? ep->nextEndpoint() : stashBoundSignal(boundSignal);
410 }
411 }
412 }
413
414 // Recurse into child contexts (group properties)
415 for (auto &[name, child] : m_children) {
416 if (child)
417 child->stashExternalState(internalUnits, seenChildren);
418 }
419}
420
422{
423 if (!m_object)
424 return;
425
426 // After a rebuild, child objects (accessed via grouped properties) may have
427 // been replaced. Re-fetch QObject pointers from the parent's properties so
428 // that restoreExternalState() reconnects to the new objects.
429 for (auto &[name, child] : m_children) {
430 if (!child)
431 continue;
432
433 // Children with a non-empty prefix share m_object with their parent
434 // (value-type group properties like "font."). Update them to match.
435 if (!child->prefix.isEmpty()) {
436 child->m_object = m_object;
437 child->refreshObjects();
438 continue;
439 }
440
441 if (QObject *newObj = m_object->property(name.toUtf8()).value<QObject *>())
442 child->m_object = newObj;
443
444 child->refreshObjects();
445 }
446}
447
449{
450 if (!m_object)
451 return;
452
453 // Restore external bindings (look up by name since indices may have shifted)
454 for (auto &stored : m_storedBindings) {
455 if (!stored.binding)
456 continue;
457
458 // If this was a property-to-property binding, verify the source object survived the
459 // rebuild. Delegate model items (the source for required-property bindings) are
460 // destroyed when the delegate is re-instantiated and new bindings are created
461 // automatically. Restoring a stale binding would dereference freed memory.
462 if (stored.sourceGuard.isNull()) {
463 bool isPTP = false;
464 if (auto *abstractBinding = stored.binding.asAbstractBinding()) {
465 isPTP = abstractBinding->kind()
466 == QQmlAbstractBinding::PropertyToPropertyBinding;
467 } else if (const QPropertyBindingPrivate *priv = QPropertyBindingPrivate::get(
468 stored.binding.asUntypedPropertyBinding())) {
469 const auto base = static_cast<const QQmlPropertyBindingBase *>(priv);
470 isPTP = base->bindingKind()
471 == QQmlPropertyBindingBase::BindingKind::PropertyToProperty;
472 }
473 if (isPTP)
474 continue;
475 }
476
477 QQmlProperty qProp(m_object, stored.propertyName);
478 if (!qProp.isValid())
479 continue;
480
481 // After a rebuild, child objects may have been replaced (refreshObjects).
482 // The stashed binding's targetObject still references the old object.
483 // Update it to the new object before installing, otherwise installOn()
484 // asserts that targetObject() == target.object().
485 if (auto *abstractBinding = stored.binding.asAbstractBinding()) {
486 if (abstractBinding->targetObject() != qProp.object())
487 abstractBinding->setTarget(qProp);
488 }
489
490 stored.binding.installOn(qProp);
491 }
492 m_storedBindings.clear();
493
494 // Restore externally set values (only if no new binding was installed)
495 for (auto &stored : m_storedValues) {
496 const QMetaObject *mo = m_object->metaObject();
497 const int idx = mo->indexOfProperty(stored.propertyName.toUtf8().constData());
498 if (idx < 0)
499 continue;
500
501 // Don't overwrite if a binding was just installed by repopulateBindings
502 QQmlProperty qProp(m_object, stored.propertyName);
503 if (!qProp.isValid())
504 continue;
505 QQmlAnyBinding currentBinding = QQmlAnyBinding::ofProperty(qProp);
506 if (currentBinding)
507 continue;
508
509 mo->property(idx).write(m_object, stored.value);
510 }
511 m_storedValues.clear();
512
513 // Restore external signal handlers that were detached during stash.
514 // Reconnect them to this object's signals.
515 if (!m_storedSignalHandlers.empty()) {
516 QQmlEngine *engine = unit->engine->qmlEngine();
517 for (auto &stored : m_storedSignalHandlers) {
518 const QMetaObject *metaObject = m_object->metaObject();
519 const int signalIndex = QMetaObjectPrivate::signalIndex(
520 metaObject->method(metaObject->indexOfSignal(stored.signature.toUtf8())));
521 if (signalIndex >= 0)
522 QQmlData::connectEndpoint(stored.handler.release(), m_object, signalIndex, engine);
523 }
524 }
525 m_storedSignalHandlers.clear();
526
527 // Recurse into child contexts (group properties)
528 for (auto &[name, child] : m_children) {
529 if (child)
530 child->restoreExternalState();
531 }
532}
533
534// The object referenced by the id "name" in the context the object belongs to, or nullptr if
535// there is no such id. Used to resolve the first chain part of a generalized grouped property
536// ("someId.x": here name == "someId").
537static QObject *idTarget(QObject *object, const QString &name)
538{
539 const QQmlData *ddata = QQmlData::get(object);
540 Q_ASSERT(ddata);
541 QQmlContextData *context = ddata->ownContext ? ddata->ownContext.data() : ddata->context;
542 Q_ASSERT(context);
543 return context->asQQmlContext()->objectForName(name);
544}
545
547BindingPatchContext::childContext(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit,
548 const QV4::CompiledData::Binding *binding,
549 QDuplicateTracker<QObject *> *seenChildren)
550{
551 const QString name = unit->stringAt(binding->propertyNameIndex);
552
553 const size_t size = m_children.size();
554 std::unique_ptr<BindingPatchContext> &child = m_children[name];
555 if (size == m_children.size())
556 return child.get();
557
558 if (!seenChildren)
559 return nullptr;
560
561 const QByteArray nameUtf8 = name.toUtf8();
562 QObject *groupObject = nullptr;
563 if (m_object->metaObject()->indexOfProperty(nameUtf8.constData()) >= 0) {
564 // "name" is a property of m_object, so it always wins over an id of the same name. A
565 // QObject-valued property recurses into that object; a value-type property
566 // ("font.pixelSize") uses a prefix on m_object (target stays null).
567 groupObject = m_object->property(nameUtf8).value<QObject *>();
568 } else if (binding->hasFlag(QV4::CompiledData::Binding::IsDeferredBinding)) {
569 // Not a property of m_object: a deferred group binding is a generalized grouped
570 // property whose first chain part is an id ("someId.x"), targeting an external object.
571 //
572 // This is a deferred property, so it's not actually guaranteed to do what we think
573 // it does. However, if we actually find the expected object and if we can patch its
574 // property, that is it's current value is the one we'd expect from the old binding, we
575 // assume we're guessing right.
576 groupObject = idTarget(m_object, name);
577 }
578
579 if (groupObject) {
580 if (seenChildren->hasSeen(groupObject))
581 return nullptr;
582 child = std::make_unique<BindingPatchContext>(groupObject, unit,
583 binding->value.objectIndex);
584 } else {
585 child = std::make_unique<BindingPatchContext>(m_object, unit, binding->value.objectIndex,
586 name);
587 }
588 return child.get();
589}
590
592BindingPatchContext::childContext(const QString &name, QObject *object,
593 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit,
594 int objectIndex, QDuplicateTracker<QObject *> *seenChildren)
595{
596 const size_t size = m_children.size();
597 std::unique_ptr<BindingPatchContext> &child = m_children[name];
598 if (size == m_children.size()) {
599 Q_ASSERT(!child || child->m_object == object);
600 return child.get();
601 }
602
603 if (!seenChildren || seenChildren->hasSeen(object))
604 return nullptr;
605
606 child = std::make_unique<BindingPatchContext>(object, unit, objectIndex);
607 return child.get();
608}
609
611BindingPatchContext::attachedContext(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit,
612 const QV4::CompiledData::Binding *binding,
613 QDuplicateTracker<QObject *> *seenChildren)
614{
615 const QString name = unit->stringAt(binding->propertyNameIndex);
616
617 const size_t size = m_children.size();
618 std::unique_ptr<BindingPatchContext> &child = m_children[name];
619 if (size == m_children.size())
620 return child.get();
621
622 if (!seenChildren)
623 return nullptr;
624
625 QV4::ResolvedTypeReference *typeRef = unit->resolvedType(binding->propertyNameIndex);
626 Q_ASSERT(typeRef);
627 QQmlAttachedPropertiesFunc func =
628 typeRef->type().attachedPropertiesFunction(unit->engine->typeLoader());
629 Q_ASSERT(func);
630
631 if (QObject *attached = QQmlData::get(m_object)->attachedProperties()->value(func)) {
632 if (seenChildren->hasSeen(attached))
633 return nullptr;
634 child = std::make_unique<BindingPatchContext>(attached, unit, binding->value.objectIndex);
635 }
636 return child.get();
637}
638
639bool BindingPatchContext::applyBindingChange(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
640 const QV4::CompiledData::Change &change)
641{
642 Q_ASSERT(change.type == QV4::CompiledData::ChangeType::BindingChanged);
643
644 if (!m_object || objectIndex < 0 || objectIndex >= unit->objectCount())
645 return false;
646
647 if (objectIndex == change.objectIndex) {
648 patchBinding(newUnit, change);
649 return true;
650 }
651
652 QDuplicateTracker<QObject *> seenChildren;
653 const QV4::CompiledData::Object *obj = unit->objectAt(objectIndex);
654 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) {
655 BindingPatchContext *child = nullptr;
656 switch (binding->type()) {
657 case QV4::CompiledData::Binding::Type_GroupProperty:
658 child = childContext(unit, binding, &seenChildren);
659 break;
660 case QV4::CompiledData::Binding::Type_AttachedProperty:
661 child = attachedContext(unit, binding, &seenChildren);
662 break;
663 default:
664 continue;
665 }
666 if (child && child->applyBindingChange(newUnit, change))
667 return true;
668 }
669
670 return false;
671}
672
674 const std::vector<QQmlRefPointer<QV4::ExecutableCompilationUnit>> &unitsToUnparent,
675 const std::vector<CompositeLevel> &internalUnits)
676{
677 QQmlData *ddata = QQmlData::get(m_object);
678
679 const auto levelFor = [&internalUnits](
680 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldCu, int oldIndex) {
681 for (const CompositeLevel &level : internalUnits) {
682 if (level.oldCu == oldCu && level.objectIndex == oldIndex)
683 return level;
684 }
685 return CompositeLevel();
686 };
687
688 const QHash<QQmlAttachedPropertiesFunc, QObject *> *attachedProperties =
689 ddata->hasExtendedData() ? ddata->attachedProperties() : nullptr;
690 QObjectList children = m_object->children();
691
692 // Remove the children we shouldn't retire from the list. That is the ones that haven't been
693 // created by the relevant compilation units.
694 const auto newEnd = std::remove_if(children.begin(), children.end(), [&](QObject *child) {
695 const QQmlData *childDdata = QQmlData::get(child);
696 if (!childDdata)
697 return true;
698
699 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &cu = childDdata->compilationUnit;
700 if (!cu)
701 return true;
702
703 if (std::find(unitsToUnparent.begin(), unitsToUnparent.end(), cu) == unitsToUnparent.end())
704 return true;
705
706 if (!attachedProperties)
707 return false;
708
709 for (QObject *attached : *attachedProperties) {
710 if (attached == child)
711 return true;
712 }
713
714 return false;
715 });
716 children.erase(newEnd, children.end());
717
718 // Remove childrens' bindings before resetBindings() runs.
719 // resetBindings() clears list/default properties via list.clear(), which calls
720 // setParentItem(nullptr) and emits parentChanged. Any QProperty binding on a
721 // child that reads 'parent' would then fire with parent == null. Clearing those
722 // bindings first prevents a flood of related warnings.
723 for (QObject *child : children)
724 clearBindingsRecursive(child);
725
726 const CompositeLevel level = levelFor(unit, objectIndex);
727 resetBindings(unit, objectIndex, level.newCu, level.objectIndex);
728
729 for (QQmlVMEMetaObject *vmeMeta = ddata->hasVMEMetaObject
730 ? static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(m_object)->metaObject)
731 : nullptr;
732 vmeMeta; vmeMeta = vmeMeta->parentVMEMetaObject()) {
733 const CompositeLevel level = levelFor(vmeMeta->compilationUnit(), vmeMeta->qmlObjectId());
734 resetBindings(vmeMeta->compilationUnit(), vmeMeta->qmlObjectId(), level.newCu,
735 level.objectIndex);
736 }
737
738 // Remove remaining composite signal handlers (all internal ones).
739 // External handlers were already detached by stashExternalState() and are invisible here.
740 // The object creator will recreate the internal handlers when it rebuilds the object.
741 while (QQmlBoundSignal *signalHandler = ddata->signalHandlers)
742 delete signalHandler;
743
744 // Objects from the old CU or composite-level CUs will be recreated by
745 // repopulateBindings. Unparent them so they don't interfere with the new objects.
746 // Attached property objects are reused across rebuilds.
747 for (QObject *child : children)
748 retireObject(child);
749}
750
751// Fully retire an old object that is being replaced by repopulateBindings.
752// Recursively removes bindings from all descendants (unlinking expressions
753// from context lists), then removes the subtree from the tree and schedules it
754// for deletion. compilationUnit is intentionally left intact. The GC needs it.
755void BindingPatchContext::retireObject(QObject *object)
756{
757 // Remove from parent (in QtQuick "visual parent" or parentItem) via the meta property system.
758 // We must not assume any particular property to be the "parent" property here. That's what
759 // we have the ParentProperty classInfo for.
760 const QMetaObject *mo = object->metaObject();
761 if (const int classInfoIndex = mo->indexOfClassInfo("ParentProperty"); classInfoIndex >= 0) {
762 const QMetaClassInfo classInfo = mo->classInfo(classInfoIndex);
763 if (const int propertyIndex = mo->indexOfProperty(classInfo.value()); propertyIndex >= 0) {
764 const QMetaProperty property = mo->property(propertyIndex);
765 if ((!property.isResettable() || !property.reset(object)) && property.isWritable())
766 property.write(object, QVariant(property.metaType()));
767 }
768 }
769
770 // Unparent from QObject hierarchy so it no longer appears in
771 // parent->children(), then schedule deletion. The destructor will
772 // cascade-delete all QObject children (the recursive descendants).
773 QQml_setParent_noEvent(object, nullptr);
774 object->deleteLater();
775}
776
777void BindingPatchContext::clearBindingsRecursive(QObject *object)
778{
779 QQueue<QObject *> queue;
780 queue.enqueue(object);
781
782 while (!queue.isEmpty()) {
783 QObject *next = queue.dequeue();
784 queue.append(next->children());
785
786 QQmlData *ddata = QQmlData::get(next);
787 if (!ddata)
788 continue;
789
790 while (ddata->bindings)
791 QQmlPropertyPrivate::removeBinding(ddata->bindings);
792
793 // QProperty (BINDABLE) bindings live in the property's QPropertyBindingStorage,
794 // not in ddata->bindings. Remove them so they don't re-evaluate when
795 // setParentItem(nullptr) emits parentChanged during retireObject().
796 const QMetaObject *mo = next->metaObject();
797 for (int i = 0, n = mo->propertyCount(); i < n; ++i) {
798 if (!ddata->hasBindingBit(i))
799 continue;
800 const QMetaProperty prop = mo->property(i);
801 if (!prop.isBindable())
802 continue;
803 QUntypedBindable bindable = prop.bindable(next);
804 if (bindable.hasBinding())
805 bindable.takeBinding();
806 }
807
808 // Don't delete signal handlers right away since we might still have
809 // them in other objects "external" state. Disable them so that they
810 // don't fire anymore until this object is actually deleted.
811 for (QQmlBoundSignal *sig = ddata->signalHandlers; sig; sig = sig->m_nextSignal)
812 sig->setEnabled(false);
813 }
814}
815
816// Map the names of the properties that the new compilation unit binds on the given object
817// to their bindings. repopulateBindings() will re-assign these, so resetting them before
818// is redundant.
820 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, int cuIndex)
821{
822 ReboundBindings bindings;
823 if (!unit || cuIndex < 0 || cuIndex >= unit->objectCount())
824 return bindings;
825
826 const QV4::CompiledData::Object *obj = unit->objectAt(cuIndex);
827
828 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding)
829 bindings.insert(BindingPatchContext::targetPropertyName(unit, cuIndex, binding), binding);
830 return bindings;
831}
832
833// The new sub-object index for a group/attached property that the new CU rebinds with the
834// same kind of binding, or -1 if the new CU doesn't rebind it.
835static int reboundSubObjectIndex(const ReboundBindings &rebound,
836 const QString &name, QV4::CompiledData::Binding::Type type)
837{
838 const QV4::CompiledData::Binding *newBinding = rebound.value(name);
839 return (newBinding && newBinding->type() == type) ? newBinding->value.objectIndex : -1;
840}
841
842void BindingPatchContext::resetBinding(
843 const QV4::CompiledData::Binding *binding, const QString &name,
844 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
845 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
846 const ReboundBindings &rebound)
847{
848 if (binding->hasFlag(QV4::CompiledData::Binding::IsCustomParserBinding))
849 return;
850
851 QQmlProperty prop(m_object, prefix + name);
852 QQmlPropertyIndex propIdx = QQmlPropertyPrivate::propertyIndex(prop);
853 if (propIdx.coreIndex() < 0) {
854 // A generalized grouped property whose first chain part is an id (e.g. "someId.x")
855 // does not name a property of m_object; it targets an external object resolved by id.
856 // Reset the sub-bindings on that target.
857 Q_ASSERT(binding->isGroupProperty());
858 if (BindingPatchContext *child = childContext(oldUnit, binding, nullptr)) {
859 child->resetBindings(
860 oldUnit, binding->value.objectIndex, newUnit,
861 reboundSubObjectIndex(rebound, name,
862 QV4::CompiledData::Binding::Type_GroupProperty));
863 }
864 return;
865 }
866
867 const QMetaType type = prop.propertyMetaType();
868
869 const QMetaType::TypeFlags flags = type.flags();
870 if (flags.testFlag(QMetaType::IsQmlList)) {
871 // Lists need to always be cleared because they're generally additive.
872 // TODO: Handle ListPropertyAssignBehavior
873 QQmlListReference list = prop.read().value<QQmlListReference>();
874 if (list.clear())
875 return;
876 } else if (flags.testFlag(QMetaType::PointerToQObject) && binding->isGroupProperty()) {
877 if (BindingPatchContext *child = childContext(oldUnit, binding, nullptr)) {
878 child->resetBindings(
879 oldUnit, binding->value.objectIndex, newUnit,
880 reboundSubObjectIndex(rebound, name,
881 QV4::CompiledData::Binding::Type_GroupProperty));
882 }
883 return;
884 }
885
886 // Don't reset individual bindings that are re-bound anyway.
887 if (rebound.contains(name))
888 return;
889
890 if ((!prop.isResettable() || !prop.reset()) && prop.isWritable())
891 prop.write(QVariant(type));
892}
893
894void BindingPatchContext::resetBindings(
895 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit, int cuIndex,
896 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit, int newCuIndex)
897{
898 const QV4::CompiledData::Object *obj = oldUnit->objectAt(cuIndex);
899
900 const ReboundBindings rebound = reboundBindings(newUnit, newCuIndex);
901
902 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) {
903 const QString name = targetPropertyName(oldUnit, cuIndex, binding);
904 if (name.isEmpty())
905 continue;
906
907 if (binding->isAttachedProperty()) {
908 // Recurse into existing attached objects to reset their bindings.
909 // The object creator will reuse them via qmlAttachedPropertiesObject().
910 if (!QQmlData::get(m_object)->hasExtendedData())
911 continue;
912
913 if (BindingPatchContext *attached = attachedContext(oldUnit, binding, nullptr)) {
914 attached->resetBindings(
915 oldUnit, binding->value.objectIndex, newUnit,
916 reboundSubObjectIndex(rebound, name,
917 QV4::CompiledData::Binding::Type_AttachedProperty));
918 }
919
920 continue;
921 }
922
923 // Signal handlers are disconnected centrally.
924 if (!binding->isSignalHandler())
925 resetBinding(binding, name, oldUnit, newUnit, rebound);
926 }
927}
928
929// The name of the property a binding assigns, resolving the default property for unnamed bindings.
930QString
931BindingPatchContext::targetPropertyName(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit,
932 int objectIndex, const QV4::CompiledData::Binding *binding)
933{
934 if (binding->propertyNameIndex != 0)
935 return unit->stringAt(binding->propertyNameIndex);
936 const QQmlPropertyCache::ConstPtr cache = unit->propertyCachesPtr()->at(objectIndex);
937 return cache ? cache->defaultPropertyName() : QString();
938}
939
940void BindingPatchContext::patchBinding(
941 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
942 const QV4::CompiledData::Change &change)
943{
944 const QV4::CompiledData::Binding *newBinding =
945 newUnit->objectAt(change.objectIndex)->bindingTable() + change.index;
946 const QVariant newValue = literalBindingValue(newUnit, newBinding);
947 if (!newValue.isValid())
948 return; // Script binding: handled by function translation.
949
950 const QQmlProperty qProp(m_object,
951 prefix + targetPropertyName(newUnit, change.objectIndex, newBinding));
952 if (!qProp.isValid())
953 return;
954
955 QV4::ExecutionEngine *v4 = unit->engine;
956 const QMetaType metaType = qProp.propertyMetaType();
957
958 const QV4::CompiledData::Binding *oldBinding =
959 unit->objectAt(change.objectIndex)->bindingTable() + change.index;
960 const QVariant expectedOld =
961 coerceToPropertyType(v4, literalBindingValue(unit, oldBinding), metaType);
962 if (qProp.read() == expectedOld)
963 qProp.write(coerceToPropertyType(v4, newValue, metaType));
964}
965
966} // namespace QQmlPreview
967
968QT_END_NAMESPACE
bool applyBindingChange(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &newUnit, const QV4::CompiledData::Change &change)
void reset(const std::vector< QQmlRefPointer< QV4::ExecutableCompilationUnit > > &unitsToUnparent, const std::vector< CompositeLevel > &internalUnits)
BindingPatchContext * attachedContext(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &unit, const QV4::CompiledData::Binding *binding, QDuplicateTracker< QObject * > *seenChildren)
BindingPatchContext * childContext(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &unit, const QV4::CompiledData::Binding *binding, QDuplicateTracker< QObject * > *seenChildren)
BindingPatchContext * childContext(const QString &name, QObject *object, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &unit, int objectIndex, QDuplicateTracker< QObject * > *seenChildren)
void stashExternalState(const std::vector< CompositeLevel > &internalUnits, QDuplicateTracker< QObject * > *seenChildren)
static QVariant literalBindingValue(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &unit, const QV4::CompiledData::Binding *binding)
static bool isExternalFunction(const QV4::Function *f, const std::vector< CompositeLevel > &internalUnits, QObject *target)
static QVariant coerceToPropertyType(QV4::ExecutionEngine *v4, const QVariant &value, QMetaType propertyType)
static ReboundBindings reboundBindings(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &unit, int cuIndex)
static bool functionBelongsToObject(const QV4::Function *f, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &cu, int objectIndex)
static QObject * propertyToPropertySource(const QQmlAnyBinding &binding)
static int reboundSubObjectIndex(const ReboundBindings &rebound, const QString &name, QV4::CompiledData::Binding::Type type)
static QObject * idTarget(QObject *object, const QString &name)
static bool isExternalBinding(const QQmlAnyBinding &binding, const std::vector< CompositeLevel > &internalUnits, QObject *target)
Combined button and popup list for selecting options.