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// 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_GroupProperty:
259 childContext(unit, binding, seenChildren);
260 continue;
261 default:
262 break;
263 }
264
265 if (binding->isSignalHandler())
266 continue;
267
268 if (binding->hasFlag(QV4::CompiledData::Binding::IsCustomParserBinding))
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 (group 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 group 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 (group 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 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 (binding->hasFlag(QV4::CompiledData::Binding::IsDeferredBinding)) {
602 // Not a property of m_object: a deferred group binding is a generalized grouped
603 // property whose first chain part is an id ("someId.x"), targeting an external object.
604 //
605 // This is a deferred property, so it's not actually guaranteed to do what we think
606 // it does. However, if we actually find the expected object and if we can patch its
607 // property, that is it's current value is the one we'd expect from the old binding, we
608 // assume we're guessing right.
609 groupObject = idTarget(m_object, name);
610 }
611
612 if (groupObject) {
613 if (seenChildren->hasSeen(groupObject))
614 return nullptr;
615 child = std::make_unique<BindingPatchContext>(groupObject, unit,
616 binding->value.objectIndex);
617 } else {
618 child = std::make_unique<BindingPatchContext>(m_object, unit, binding->value.objectIndex,
619 name);
620 }
621 return child.get();
622}
623
625BindingPatchContext::childContext(const QString &name, QObject *object,
626 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit,
627 int objectIndex, QDuplicateTracker<QObject *> *seenChildren)
628{
629 const size_t size = m_children.size();
630 std::unique_ptr<BindingPatchContext> &child = m_children[name];
631 if (size == m_children.size()) {
632 Q_ASSERT(!child || child->m_object == object);
633 return child.get();
634 }
635
636 if (!seenChildren || seenChildren->hasSeen(object))
637 return nullptr;
638
639 child = std::make_unique<BindingPatchContext>(object, unit, objectIndex);
640 return child.get();
641}
642
644BindingPatchContext::attachedContext(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit,
645 const QV4::CompiledData::Binding *binding,
646 QDuplicateTracker<QObject *> *seenChildren)
647{
648 const QString name = unit->stringAt(binding->propertyNameIndex);
649
650 const size_t size = m_children.size();
651 std::unique_ptr<BindingPatchContext> &child = m_children[name];
652 if (size == m_children.size())
653 return child.get();
654
655 if (!seenChildren)
656 return nullptr;
657
658 QV4::ResolvedTypeReference *typeRef = unit->resolvedType(binding->propertyNameIndex);
659 Q_ASSERT(typeRef);
660 QQmlAttachedPropertiesFunc func =
661 typeRef->type().attachedPropertiesFunction(unit->engine->typeLoader());
662 Q_ASSERT(func);
663
664 if (QObject *attached = QQmlData::get(m_object)->attachedProperties()->value(func)) {
665 if (seenChildren->hasSeen(attached))
666 return nullptr;
667 child = std::make_unique<BindingPatchContext>(attached, unit, binding->value.objectIndex);
668 }
669 return child.get();
670}
671
672bool BindingPatchContext::applyBindingChange(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
673 const QV4::CompiledData::Change &change)
674{
675 Q_ASSERT(change.type == QV4::CompiledData::ChangeType::BindingChanged);
676
677 if (!m_object || objectIndex < 0 || objectIndex >= unit->objectCount())
678 return false;
679
680 if (objectIndex == change.objectIndex) {
681 patchBinding(newUnit, change);
682 return true;
683 }
684
685 QDuplicateTracker<QObject *> seenChildren;
686 const QV4::CompiledData::Object *obj = unit->objectAt(objectIndex);
687 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) {
688 BindingPatchContext *child = nullptr;
689 switch (binding->type()) {
690 case QV4::CompiledData::Binding::Type_GroupProperty:
691 child = childContext(unit, binding, &seenChildren);
692 break;
693 case QV4::CompiledData::Binding::Type_AttachedProperty:
694 child = attachedContext(unit, binding, &seenChildren);
695 break;
696 default:
697 continue;
698 }
699 if (child && child->applyBindingChange(newUnit, change))
700 return true;
701 }
702
703 return false;
704}
705
707 const std::vector<QQmlRefPointer<QV4::ExecutableCompilationUnit>> &unitsToUnparent,
708 const std::vector<CompositeLevel> &internalUnits)
709{
710 QQmlData *ddata = QQmlData::get(m_object);
711
712 const auto levelFor = [&internalUnits](
713 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldCu, int oldIndex) {
714 for (const CompositeLevel &level : internalUnits) {
715 if (level.oldCu == oldCu && level.objectIndex == oldIndex)
716 return level;
717 }
718 return CompositeLevel();
719 };
720
721 const QHash<QQmlAttachedPropertiesFunc, QObject *> *attachedProperties =
722 ddata->hasExtendedData() ? ddata->attachedProperties() : nullptr;
723 QObjectList children = m_object->children();
724
725 // Remove the children we shouldn't retire from the list. That is the ones that haven't been
726 // created by the relevant compilation units.
727 const auto newEnd = std::remove_if(children.begin(), children.end(), [&](QObject *child) {
728 const QQmlData *childDdata = QQmlData::get(child);
729 if (!childDdata)
730 return true;
731
732 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &cu = childDdata->compilationUnit;
733 if (!cu)
734 return true;
735
736 if (std::find(unitsToUnparent.begin(), unitsToUnparent.end(), cu) == unitsToUnparent.end())
737 return true;
738
739 if (!attachedProperties)
740 return false;
741
742 for (QObject *attached : *attachedProperties) {
743 if (attached == child)
744 return true;
745 }
746
747 return false;
748 });
749 children.erase(newEnd, children.end());
750
751 // Remove childrens' bindings before resetBindings() runs.
752 // resetBindings() clears list/default properties via list.clear(), which calls
753 // setParentItem(nullptr) and emits parentChanged. Any QProperty binding on a
754 // child that reads 'parent' would then fire with parent == null. Clearing those
755 // bindings first prevents a flood of related warnings.
756 for (QObject *child : children)
757 clearBindingsRecursive(child);
758
759 const CompositeLevel level = levelFor(unit, objectIndex);
760 resetBindings(unit, objectIndex, level.newCu, level.objectIndex);
761
762 for (QQmlVMEMetaObject *vmeMeta = ddata->hasVMEMetaObject
763 ? static_cast<QQmlVMEMetaObject *>(QObjectPrivate::get(m_object)->metaObject)
764 : nullptr;
765 vmeMeta; vmeMeta = vmeMeta->parentVMEMetaObject()) {
766 const CompositeLevel level = levelFor(vmeMeta->compilationUnit(), vmeMeta->qmlObjectId());
767 resetBindings(vmeMeta->compilationUnit(), vmeMeta->qmlObjectId(), level.newCu,
768 level.objectIndex);
769 }
770
771 // Remove remaining composite signal handlers (all internal ones).
772 // External handlers were already detached by stashExternalState() and are invisible here.
773 // The object creator will recreate the internal handlers when it rebuilds the object.
774 while (QQmlBoundSignal *signalHandler = ddata->signalHandlers)
775 delete signalHandler;
776
777 // Objects from the old CU or composite-level CUs will be recreated by
778 // repopulateBindings. Unparent them so they don't interfere with the new objects.
779 // Attached property objects are reused across rebuilds.
780 for (QObject *child : children)
781 retireObject(child);
782}
783
784// Fully retire an old object that is being replaced by repopulateBindings.
785// Recursively removes bindings from all descendants (unlinking expressions
786// from context lists), then removes the subtree from the tree and schedules it
787// for deletion. compilationUnit is intentionally left intact. The GC needs it.
788void BindingPatchContext::retireObject(QObject *object)
789{
790 // Remove from parent (in QtQuick "visual parent" or parentItem) via the meta property system.
791 // We must not assume any particular property to be the "parent" property here. That's what
792 // we have the ParentProperty classInfo for.
793 const QMetaObject *mo = object->metaObject();
794 if (const int classInfoIndex = mo->indexOfClassInfo("ParentProperty"); classInfoIndex >= 0) {
795 const QMetaClassInfo classInfo = mo->classInfo(classInfoIndex);
796 if (const int propertyIndex = mo->indexOfProperty(classInfo.value()); propertyIndex >= 0) {
797 const QMetaProperty property = mo->property(propertyIndex);
798 if ((!property.isResettable() || !property.reset(object)) && property.isWritable())
799 property.write(object, QVariant(property.metaType()));
800 }
801 }
802
803 // Unparent from QObject hierarchy so it no longer appears in
804 // parent->children(), then schedule deletion. The destructor will
805 // cascade-delete all QObject children (the recursive descendants).
806 QQml_setParent_noEvent(object, nullptr);
807 object->deleteLater();
808}
809
810void BindingPatchContext::clearBindingsRecursive(QObject *object)
811{
812 QQueue<QObject *> queue;
813 queue.enqueue(object);
814
815 while (!queue.isEmpty()) {
816 QObject *next = queue.dequeue();
817 queue.append(next->children());
818
819 QQmlData *ddata = QQmlData::get(next);
820 if (!ddata)
821 continue;
822
823 while (ddata->bindings)
824 QQmlPropertyPrivate::removeBinding(ddata->bindings);
825
826 // QProperty (BINDABLE) bindings live in the property's QPropertyBindingStorage,
827 // not in ddata->bindings. Remove them so they don't re-evaluate when
828 // setParentItem(nullptr) emits parentChanged during retireObject().
829 const QMetaObject *mo = next->metaObject();
830 for (int i = 0, n = mo->propertyCount(); i < n; ++i) {
831 if (!ddata->hasBindingBit(i))
832 continue;
833 const QMetaProperty prop = mo->property(i);
834 if (!prop.isBindable())
835 continue;
836 QUntypedBindable bindable = prop.bindable(next);
837 if (bindable.hasBinding())
838 bindable.takeBinding();
839 }
840
841 // Don't delete signal handlers right away since we might still have
842 // them in other objects "external" state. Disable them so that they
843 // don't fire anymore until this object is actually deleted.
844 for (QQmlBoundSignal *sig = ddata->signalHandlers; sig; sig = sig->m_nextSignal)
845 sig->setEnabled(false);
846 }
847}
848
849// Map the names of the properties that the new compilation unit binds on the given object
850// to their bindings. repopulateBindings() will re-assign these, so resetting them before
851// is redundant.
853 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, int cuIndex)
854{
855 ReboundBindings bindings;
856 if (!unit || cuIndex < 0 || cuIndex >= unit->objectCount())
857 return bindings;
858
859 const QV4::CompiledData::Object *obj = unit->objectAt(cuIndex);
860
861 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding)
862 bindings.insert(BindingPatchContext::targetPropertyName(unit, cuIndex, binding), binding);
863 return bindings;
864}
865
866// The new sub-object index for a group/attached property that the new CU rebinds with the
867// same kind of binding, or -1 if the new CU doesn't rebind it.
868static int reboundSubObjectIndex(const ReboundBindings &rebound,
869 const QString &name, QV4::CompiledData::Binding::Type type)
870{
871 const QV4::CompiledData::Binding *newBinding = rebound.value(name);
872 return (newBinding && newBinding->type() == type) ? newBinding->value.objectIndex : -1;
873}
874
875void BindingPatchContext::resetBinding(
876 const QV4::CompiledData::Binding *binding, const QString &name,
877 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit,
878 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
879 const ReboundBindings &rebound)
880{
881 if (binding->hasFlag(QV4::CompiledData::Binding::IsCustomParserBinding))
882 return;
883
884 QQmlProperty prop(m_object, prefix + name);
885 QQmlPropertyIndex propIdx = QQmlPropertyPrivate::propertyIndex(prop);
886 if (propIdx.coreIndex() < 0) {
887 // A generalized grouped property whose first chain part is an id (e.g. "someId.x")
888 // does not name a property of m_object; it targets an external object resolved by id.
889 // Reset the sub-bindings on that target.
890 Q_ASSERT(binding->isGroupProperty());
891 if (BindingPatchContext *child = childContext(oldUnit, binding, nullptr)) {
892 child->resetBindings(
893 oldUnit, binding->value.objectIndex, newUnit,
894 reboundSubObjectIndex(rebound, name,
895 QV4::CompiledData::Binding::Type_GroupProperty));
896 }
897 return;
898 }
899
900 const QMetaType type = prop.propertyMetaType();
901
902 const QMetaType::TypeFlags flags = type.flags();
903 if (flags.testFlag(QMetaType::IsQmlList)) {
904 // Lists need to always be cleared because they're generally additive.
905 // TODO: Handle ListPropertyAssignBehavior
906 QQmlListReference list = prop.read().value<QQmlListReference>();
907 if (list.clear())
908 return;
909 } else if (flags.testFlag(QMetaType::PointerToQObject) && binding->isGroupProperty()) {
910 if (BindingPatchContext *child = childContext(oldUnit, binding, nullptr)) {
911 child->resetBindings(
912 oldUnit, binding->value.objectIndex, newUnit,
913 reboundSubObjectIndex(rebound, name,
914 QV4::CompiledData::Binding::Type_GroupProperty));
915 }
916 return;
917 }
918
919 // Don't reset individual bindings that are re-bound anyway.
920 if (rebound.contains(name))
921 return;
922
923 if ((!prop.isResettable() || !prop.reset()) && prop.isWritable())
924 prop.write(QVariant(type));
925}
926
927void BindingPatchContext::resetBindings(
928 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &oldUnit, int cuIndex,
929 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit, int newCuIndex)
930{
931 const QV4::CompiledData::Object *obj = oldUnit->objectAt(cuIndex);
932
933 const ReboundBindings rebound = reboundBindings(newUnit, newCuIndex);
934
935 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) {
936 const QString name = targetPropertyName(oldUnit, cuIndex, binding);
937 if (name.isEmpty())
938 continue;
939
940 if (binding->isAttachedProperty()) {
941 // Recurse into existing attached objects to reset their bindings.
942 // The object creator will reuse them via qmlAttachedPropertiesObject().
943 if (!QQmlData::get(m_object)->hasExtendedData())
944 continue;
945
946 if (BindingPatchContext *attached = attachedContext(oldUnit, binding, nullptr)) {
947 attached->resetBindings(
948 oldUnit, binding->value.objectIndex, newUnit,
949 reboundSubObjectIndex(rebound, name,
950 QV4::CompiledData::Binding::Type_AttachedProperty));
951 }
952
953 continue;
954 }
955
956 // Signal handlers are disconnected centrally.
957 if (!binding->isSignalHandler())
958 resetBinding(binding, name, oldUnit, newUnit, rebound);
959 }
960}
961
962// The name of the property a binding assigns, resolving the default property for unnamed bindings.
963QString
964BindingPatchContext::targetPropertyName(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit,
965 int objectIndex, const QV4::CompiledData::Binding *binding)
966{
967 if (binding->propertyNameIndex != 0)
968 return unit->stringAt(binding->propertyNameIndex);
969 const QQmlPropertyCache::ConstPtr cache = unit->propertyCachesPtr()->at(objectIndex);
970 return cache ? cache->defaultPropertyName() : QString();
971}
972
973void BindingPatchContext::patchBinding(
974 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &newUnit,
975 const QV4::CompiledData::Change &change)
976{
977 const QV4::CompiledData::Binding *newBinding =
978 newUnit->objectAt(change.objectIndex)->bindingTable() + change.index;
979 const QVariant newValue = literalBindingValue(newUnit, newBinding);
980 if (!newValue.isValid())
981 return; // Script binding: handled by function translation.
982
983 const QQmlProperty qProp(m_object,
984 prefix + targetPropertyName(newUnit, change.objectIndex, newBinding));
985 if (!qProp.isValid())
986 return;
987
988 QV4::ExecutionEngine *v4 = unit->engine;
989 const QMetaType metaType = qProp.propertyMetaType();
990
991 const QV4::CompiledData::Binding *oldBinding =
992 unit->objectAt(change.objectIndex)->bindingTable() + change.index;
993 const QVariant expectedOld =
994 coerceToPropertyType(v4, literalBindingValue(unit, oldBinding), metaType);
995 if (qProp.read() == expectedOld)
996 qProp.write(coerceToPropertyType(v4, newValue, metaType));
997}
998
999} // namespace QQmlPreview
1000
1001QT_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 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.