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