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