1// Copyright (C) 2022 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
4#ifndef QPROPERTY_P_H
5#define QPROPERTY_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists for the convenience
12// of a number of Qt sources files. This header file may change from
13// version to version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <private/qglobal_p.h>
19#include <qproperty.h>
20
21#include <qmetaobject.h>
22#include <qscopedvaluerollback.h>
23#include <qvariant.h>
24#include <vector>
25#include <QtCore/QVarLengthArray>
26
27#include <memory>
28
29QT_BEGIN_NAMESPACE
30
31namespace QtPrivate {
32 Q_CORE_EXPORT bool isAnyBindingEvaluating();
33 struct QBindingStatusAccessToken {};
34}
35
36
37/*!
38 \internal
39 Similar to \c QPropertyBindingPrivatePtr, but stores a
40 \c QPropertyObserver * linking to the QPropertyBindingPrivate*
41 instead of the QPropertyBindingPrivate* itself
42 */
43struct QBindingObserverPtr
44{
45private:
46 QPropertyObserver *d = nullptr;
47public:
48 QBindingObserverPtr() = default;
49 Q_DISABLE_COPY(QBindingObserverPtr)
50 void swap(QBindingObserverPtr &other) noexcept
51 { qt_ptr_swap(lhs&: d, rhs&: other.d); }
52 QBindingObserverPtr(QBindingObserverPtr &&other) noexcept : d(std::exchange(obj&: other.d, new_val: nullptr)) {}
53 QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QBindingObserverPtr)
54
55
56 inline QBindingObserverPtr(QPropertyObserver *observer) noexcept;
57 inline ~QBindingObserverPtr();
58 inline QPropertyBindingPrivate *binding() const noexcept;
59 inline QPropertyObserver *operator ->();
60};
61
62using PendingBindingObserverList = QVarLengthArray<QPropertyBindingPrivatePtr>;
63
64// Keep all classes related to QProperty in one compilation unit. Performance of this code is crucial and
65// we need to allow the compiler to inline where it makes sense.
66
67// This is a helper "namespace"
68struct QPropertyBindingDataPointer
69{
70 const QtPrivate::QPropertyBindingData *ptr = nullptr;
71
72 QPropertyBindingPrivate *binding() const
73 {
74 return ptr->binding();
75 }
76
77 void setObservers(QPropertyObserver *observer)
78 {
79 auto &d = ptr->d_ref();
80 observer->prev = reinterpret_cast<QPropertyObserver**>(&d);
81 d = reinterpret_cast<quintptr>(observer);
82 }
83 static void fixupAfterMove(QtPrivate::QPropertyBindingData *ptr);
84 void Q_ALWAYS_INLINE addObserver(QPropertyObserver *observer);
85 inline void setFirstObserver(QPropertyObserver *observer);
86 inline QPropertyObserverPointer firstObserver() const;
87 static QPropertyProxyBindingData *proxyData(QtPrivate::QPropertyBindingData *ptr);
88
89 inline int observerCount() const;
90
91 template <typename T>
92 static QPropertyBindingDataPointer get(QProperty<T> &property)
93 {
94 return QPropertyBindingDataPointer{&property.bindingData()};
95 }
96};
97
98struct QPropertyObserverNodeProtector
99{
100 Q_DISABLE_COPY_MOVE(QPropertyObserverNodeProtector)
101
102 QPropertyObserverBase m_placeHolder;
103 Q_NODISCARD_CTOR
104 QPropertyObserverNodeProtector(QPropertyObserver *observer)
105 {
106 // insert m_placeholder after observer into the linked list
107 QPropertyObserver *next = observer->next.data();
108 m_placeHolder.next = next;
109 observer->next = static_cast<QPropertyObserver *>(&m_placeHolder);
110 if (next)
111 next->prev = &m_placeHolder.next;
112 m_placeHolder.prev = &observer->next;
113 m_placeHolder.next.setTag(QPropertyObserver::ObserverIsPlaceholder);
114 }
115
116 QPropertyObserver *next() const { return m_placeHolder.next.data(); }
117
118 ~QPropertyObserverNodeProtector();
119};
120
121// This is a helper "namespace"
122struct QPropertyObserverPointer
123{
124 QPropertyObserver *ptr = nullptr;
125
126 void unlink()
127 {
128 unlink_common();
129#if QT_DEPRECATED_SINCE(6, 6)
130 QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
131 if (ptr->next.tag() == QPropertyObserver::ObserverIsAlias)
132 ptr->aliasData = nullptr;
133 QT_WARNING_POP
134#endif
135 }
136
137 void unlink_fast()
138 {
139#if QT_DEPRECATED_SINCE(6, 6)
140 QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
141 Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsAlias);
142 QT_WARNING_POP
143#endif
144 unlink_common();
145 }
146
147 void setBindingToNotify(QPropertyBindingPrivate *binding)
148 {
149 Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder);
150 ptr->binding = binding;
151 ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding);
152 }
153
154 void setBindingToNotify_unsafe(QPropertyBindingPrivate *binding);
155 void setChangeHandler(QPropertyObserver::ChangeHandler changeHandler);
156
157 enum class Notify {Everything, OnlyChangeHandlers};
158
159 void notify(QUntypedPropertyData *propertyDataPtr);
160#ifndef QT_NO_DEBUG
161 void noSelfDependencies(QPropertyBindingPrivate *binding);
162#else
163 void noSelfDependencies(QPropertyBindingPrivate *) {}
164#endif
165 void evaluateBindings(PendingBindingObserverList &bindingObservers, QBindingStatus *status);
166 void observeProperty(QPropertyBindingDataPointer property);
167
168 explicit operator bool() const { return ptr != nullptr; }
169
170 QPropertyObserverPointer nextObserver() const { return {.ptr: ptr->next.data()}; }
171
172 QPropertyBindingPrivate *binding() const
173 {
174 Q_ASSERT(ptr->next.tag() == QPropertyObserver::ObserverNotifiesBinding);
175 return ptr->binding;
176 }
177
178private:
179 void unlink_common()
180 {
181 if (ptr->next)
182 ptr->next->prev = ptr->prev;
183 if (ptr->prev)
184 ptr->prev.setPointer(ptr->next.data());
185 ptr->next = nullptr;
186 ptr->prev.clear();
187 }
188};
189
190class QPropertyBindingErrorPrivate : public QSharedData
191{
192public:
193 QPropertyBindingError::Type type = QPropertyBindingError::NoError;
194 QString description;
195};
196
197namespace QtPrivate {
198
199struct BindingEvaluationState
200{
201 BindingEvaluationState(QPropertyBindingPrivate *binding, QBindingStatus *status);
202 ~BindingEvaluationState()
203 {
204 *currentState = previousState;
205 }
206
207 QPropertyBindingPrivate *binding;
208 BindingEvaluationState *previousState = nullptr;
209 BindingEvaluationState **currentState = nullptr;
210 QVarLengthArray<const QPropertyBindingData *, 8> alreadyCaptureProperties;
211};
212
213/*!
214 * \internal
215 * CompatPropertySafePoint needs to be constructed before the setter of
216 * a QObjectCompatProperty runs. It prevents spurious binding dependencies
217 * caused by reads of properties inside the compat property setter.
218 * Moreover, it ensures that we don't destroy bindings when using operator=
219 */
220struct CompatPropertySafePoint
221{
222 Q_CORE_EXPORT CompatPropertySafePoint(QBindingStatus *status, QUntypedPropertyData *property);
223 ~CompatPropertySafePoint()
224 {
225 *currentState = previousState;
226 *currentlyEvaluatingBindingList = bindingState;
227 }
228 QUntypedPropertyData *property;
229 CompatPropertySafePoint *previousState = nullptr;
230 CompatPropertySafePoint **currentState = nullptr;
231 QtPrivate::BindingEvaluationState **currentlyEvaluatingBindingList = nullptr;
232 QtPrivate::BindingEvaluationState *bindingState = nullptr;
233};
234
235/*!
236 * \internal
237 * While the regular QProperty notification for a compat property runs we
238 * don't want to have any currentCompatProperty set. This would be a _different_
239 * one than the one we are current evaluating. Therefore it's misleading and
240 * prevents the registering of actual dependencies.
241 */
242struct CurrentCompatPropertyThief
243{
244 Q_DISABLE_COPY_MOVE(CurrentCompatPropertyThief)
245 QScopedValueRollback<CompatPropertySafePoint *> m_guard;
246public:
247 Q_NODISCARD_CTOR
248 CurrentCompatPropertyThief(QBindingStatus *status)
249 : m_guard(status->currentCompatProperty, nullptr)
250 {
251 }
252};
253
254}
255
256class Q_CORE_EXPORT QPropertyBindingPrivate : public QtPrivate::RefCounted
257{
258private:
259 friend struct QPropertyBindingDataPointer;
260 friend class QPropertyBindingPrivatePtr;
261
262 using ObserverArray = std::array<QPropertyObserver, 4>;
263
264private:
265
266 // used to detect binding loops for lazy evaluated properties
267 bool updating = false;
268 bool hasStaticObserver = false;
269 bool pendingNotify = false;
270 bool hasBindingWrapper:1;
271 // used to detect binding loops for eagerly evaluated properties
272 bool isQQmlPropertyBinding:1;
273 /* a sticky binding does not get removed in removeBinding
274 this is used to support QQmlPropertyData::DontRemoveBinding
275 in qtdeclarative
276 */
277 bool m_sticky:1;
278
279 const QtPrivate::BindingFunctionVTable *vtable;
280
281 union {
282 QtPrivate::QPropertyObserverCallback staticObserverCallback = nullptr;
283 QtPrivate::QPropertyBindingWrapper staticBindingWrapper;
284 };
285 ObserverArray inlineDependencyObservers; // for things we are observing
286
287 QPropertyObserverPointer firstObserver; // list of observers observing us
288 std::unique_ptr<std::vector<QPropertyObserver>> heapObservers; // for things we are observing
289
290protected:
291 QUntypedPropertyData *propertyDataPtr = nullptr;
292
293 /* For bindings set up from C++, location stores where the binding was created in the C++ source
294 For QQmlPropertyBinding that information does not make sense, and the location in the QML file
295 is stored somewhere else. To make efficient use of the space, we instead provide a scratch space
296 for QQmlPropertyBinding (which stores further binding information there).
297 Anything stored in the union must be trivially destructible.
298 (checked in qproperty.cpp)
299 */
300 using DeclarativeErrorCallback = void(*)(QPropertyBindingPrivate *);
301 union {
302 QPropertyBindingSourceLocation location;
303 struct {
304 std::byte declarativeExtraData[sizeof(QPropertyBindingSourceLocation) - sizeof(DeclarativeErrorCallback)];
305 DeclarativeErrorCallback errorCallBack;
306 };
307 };
308private:
309 QPropertyBindingError m_error;
310
311 QMetaType metaType;
312
313public:
314 static constexpr size_t getSizeEnsuringAlignment() {
315 constexpr auto align = alignof (std::max_align_t) - 1;
316 constexpr size_t sizeEnsuringAlignment = (sizeof(QPropertyBindingPrivate) + align) & ~align;
317 static_assert (sizeEnsuringAlignment % alignof (std::max_align_t) == 0,
318 "Required for placement new'ing the function behind it.");
319 return sizeEnsuringAlignment;
320 }
321
322
323 // public because the auto-tests access it, too.
324 size_t dependencyObserverCount = 0;
325
326 bool isUpdating() {return updating;}
327 void setSticky(bool keep = true) {m_sticky = keep;}
328 bool isSticky() {return m_sticky;}
329 void scheduleNotify() {pendingNotify = true;}
330
331 QPropertyBindingPrivate(QMetaType metaType, const QtPrivate::BindingFunctionVTable *vtable,
332 const QPropertyBindingSourceLocation &location, bool isQQmlPropertyBinding=false)
333 : hasBindingWrapper(false)
334 , isQQmlPropertyBinding(isQQmlPropertyBinding)
335 , m_sticky(false)
336 , vtable(vtable)
337 , location(location)
338 , metaType(metaType)
339 {}
340 ~QPropertyBindingPrivate();
341
342
343 void setProperty(QUntypedPropertyData *propertyPtr) { propertyDataPtr = propertyPtr; }
344 void setStaticObserver(QtPrivate::QPropertyObserverCallback callback, QtPrivate::QPropertyBindingWrapper bindingWrapper)
345 {
346 Q_ASSERT(!(callback && bindingWrapper));
347 if (callback) {
348 hasStaticObserver = true;
349 hasBindingWrapper = false;
350 staticObserverCallback = callback;
351 } else if (bindingWrapper) {
352 hasStaticObserver = false;
353 hasBindingWrapper = true;
354 staticBindingWrapper = bindingWrapper;
355 } else {
356 hasStaticObserver = false;
357 hasBindingWrapper = false;
358 staticObserverCallback = nullptr;
359 }
360 }
361 void prependObserver(QPropertyObserverPointer observer)
362 {
363 observer.ptr->prev = const_cast<QPropertyObserver **>(&firstObserver.ptr);
364 firstObserver = observer;
365 }
366
367 QPropertyObserverPointer takeObservers()
368 {
369 auto observers = firstObserver;
370 firstObserver.ptr = nullptr;
371 return observers;
372 }
373
374 void clearDependencyObservers();
375
376 Q_ALWAYS_INLINE QPropertyObserverPointer allocateDependencyObserver() {
377 if (dependencyObserverCount < inlineDependencyObservers.size()) {
378 ++dependencyObserverCount;
379 return {.ptr: &inlineDependencyObservers[dependencyObserverCount - 1]};
380 }
381 return allocateDependencyObserver_slow();
382 }
383
384 QPropertyObserverPointer allocateDependencyObserver_slow();
385
386 QPropertyBindingSourceLocation sourceLocation() const
387 {
388 if (!hasCustomVTable())
389 return location;
390 QPropertyBindingSourceLocation result;
391 constexpr auto msg = "Custom location";
392 result.fileName = msg;
393 return result;
394 }
395 QPropertyBindingError bindingError() const { return m_error; }
396 QMetaType valueMetaType() const { return metaType; }
397
398 void unlinkAndDeref();
399
400 bool evaluateRecursive(PendingBindingObserverList &bindingObservers, QBindingStatus *status = nullptr);
401
402 bool Q_ALWAYS_INLINE evaluateRecursive_inline(PendingBindingObserverList &bindingObservers, QBindingStatus *status);
403
404 void notifyNonRecursive(const PendingBindingObserverList &bindingObservers);
405 enum NotificationState : bool { Delayed, Sent };
406 NotificationState notifyNonRecursive();
407
408 static QPropertyBindingPrivate *get(const QUntypedPropertyBinding &binding)
409 { return static_cast<QPropertyBindingPrivate *>(binding.d.data()); }
410
411 void setError(QPropertyBindingError &&e)
412 { m_error = std::move(e); }
413
414 void detachFromProperty()
415 {
416 hasStaticObserver = false;
417 hasBindingWrapper = false;
418 propertyDataPtr = nullptr;
419 clearDependencyObservers();
420 }
421
422 static QPropertyBindingPrivate *currentlyEvaluatingBinding();
423
424 bool hasCustomVTable() const
425 {
426 return vtable->size == 0;
427 }
428
429 static void destroyAndFreeMemory(QPropertyBindingPrivate *priv) {
430 if (priv->hasCustomVTable()) {
431 // special hack for QQmlPropertyBinding which has a
432 // different memory layout than normal QPropertyBindings
433 priv->vtable->destroy(priv);
434 } else{
435 priv->~QPropertyBindingPrivate();
436 delete[] reinterpret_cast<std::byte *>(priv);
437 }
438 }
439};
440
441inline void QPropertyBindingDataPointer::setFirstObserver(QPropertyObserver *observer)
442{
443 if (auto *b = binding()) {
444 b->firstObserver.ptr = observer;
445 return;
446 }
447 auto &d = ptr->d_ref();
448 d = reinterpret_cast<quintptr>(observer);
449}
450
451inline void QPropertyBindingDataPointer::fixupAfterMove(QtPrivate::QPropertyBindingData *ptr)
452{
453 auto &d = ptr->d_ref();
454 if (ptr->isNotificationDelayed()) {
455 QPropertyProxyBindingData *proxy = ptr->proxyData();
456 Q_ASSERT(proxy);
457 proxy->originalBindingData = ptr;
458 }
459 // If QPropertyBindingData has been moved, and it has an observer
460 // we have to adjust the firstObserver's prev pointer to point to
461 // the moved to QPropertyBindingData's d_ptr
462 if (d & QtPrivate::QPropertyBindingData::BindingBit)
463 return; // nothing to do if the observer is stored in the binding
464 if (auto observer = reinterpret_cast<QPropertyObserver *>(d))
465 observer->prev = reinterpret_cast<QPropertyObserver **>(&d);
466}
467
468inline QPropertyObserverPointer QPropertyBindingDataPointer::firstObserver() const
469{
470 if (auto *b = binding())
471 return b->firstObserver;
472 return { .ptr: reinterpret_cast<QPropertyObserver *>(ptr->d()) };
473}
474
475/*!
476 \internal
477 Returns the proxy data of \a ptr, or \c nullptr if \a ptr has no delayed notification
478 */
479inline QPropertyProxyBindingData *QPropertyBindingDataPointer::proxyData(QtPrivate::QPropertyBindingData *ptr)
480{
481 if (!ptr->isNotificationDelayed())
482 return nullptr;
483 return ptr->proxyData();
484}
485
486inline int QPropertyBindingDataPointer::observerCount() const
487{
488 int count = 0;
489 for (auto observer = firstObserver(); observer; observer = observer.nextObserver())
490 ++count;
491 return count;
492}
493
494namespace QtPrivate {
495 Q_CORE_EXPORT bool isPropertyInBindingWrapper(const QUntypedPropertyData *property);
496 void Q_CORE_EXPORT initBindingStatusThreadId();
497}
498
499template<typename Class, typename T, auto Offset, auto Setter, auto Signal = nullptr,
500 auto Getter = nullptr>
501class QObjectCompatProperty : public QPropertyData<T>
502{
503 template<typename Property, typename>
504 friend class QtPrivate::QBindableInterfaceForProperty;
505
506 using ThisType = QObjectCompatProperty<Class, T, Offset, Setter, Signal, Getter>;
507 using SignalTakesValue = std::is_invocable<decltype(Signal), Class, T>;
508 Class *owner()
509 {
510 char *that = reinterpret_cast<char *>(this);
511 return reinterpret_cast<Class *>(that - QtPrivate::detail::getOffset(Offset));
512 }
513 const Class *owner() const
514 {
515 char *that = const_cast<char *>(reinterpret_cast<const char *>(this));
516 return reinterpret_cast<Class *>(that - QtPrivate::detail::getOffset(Offset));
517 }
518
519 static bool bindingWrapper(QMetaType type, QUntypedPropertyData *dataPtr, QtPrivate::QPropertyBindingFunction binding)
520 {
521 auto *thisData = static_cast<ThisType *>(dataPtr);
522 QBindingStorage *storage = qGetBindingStorage(thisData->owner());
523 QPropertyData<T> copy;
524 {
525 QtPrivate::CurrentCompatPropertyThief thief(storage->bindingStatus);
526 binding.vtable->call(type, &copy, binding.functor);
527 if constexpr (QTypeTraits::has_operator_equal_v<T>)
528 if (copy.valueBypassingBindings() == thisData->valueBypassingBindings())
529 return false;
530 }
531 // ensure value and setValue know we're currently evaluating our binding
532 QtPrivate::CompatPropertySafePoint guardThis(storage->bindingStatus, thisData);
533 (thisData->owner()->*Setter)(copy.valueBypassingBindings());
534 return true;
535 }
536 bool inBindingWrapper(const QBindingStorage *storage) const
537 {
538 return storage->bindingStatus && storage->bindingStatus->currentCompatProperty
539 && QtPrivate::isPropertyInBindingWrapper(property: this);
540 }
541
542 inline static T getPropertyValue(const QUntypedPropertyData *d) {
543 auto prop = static_cast<const ThisType *>(d);
544 if constexpr (std::is_null_pointer_v<decltype(Getter)>)
545 return prop->value();
546 else
547 return (prop->owner()->*Getter)();
548 }
549
550public:
551 using value_type = typename QPropertyData<T>::value_type;
552 using parameter_type = typename QPropertyData<T>::parameter_type;
553 using arrow_operator_result = typename QPropertyData<T>::arrow_operator_result;
554
555 QObjectCompatProperty() = default;
556 explicit QObjectCompatProperty(const T &initialValue) : QPropertyData<T>(initialValue) {}
557 explicit QObjectCompatProperty(T &&initialValue) : QPropertyData<T>(std::move(initialValue)) {}
558
559 parameter_type value() const
560 {
561 const QBindingStorage *storage = qGetBindingStorage(owner());
562 // make sure we don't register this binding as a dependency to itself
563 if (storage->bindingStatus && storage->bindingStatus->currentlyEvaluatingBinding && !inBindingWrapper(storage))
564 storage->registerDependency_helper(data: this);
565 return this->val;
566 }
567
568 arrow_operator_result operator->() const
569 {
570 if constexpr (QTypeTraits::is_dereferenceable_v<T>) {
571 return value();
572 } else if constexpr (std::is_pointer_v<T>) {
573 value();
574 return this->val;
575 } else {
576 return;
577 }
578 }
579
580 parameter_type operator*() const
581 {
582 return value();
583 }
584
585 operator parameter_type() const
586 {
587 return value();
588 }
589
590 void setValue(parameter_type t)
591 {
592 QBindingStorage *storage = qGetBindingStorage(owner());
593 if (auto *bd = storage->bindingData(this)) {
594 // make sure we don't remove the binding if called from the bindingWrapper
595 if (bd->hasBinding() && !inBindingWrapper(storage))
596 bd->removeBinding_helper();
597 }
598 this->val = t;
599 }
600
601 QObjectCompatProperty &operator=(parameter_type newValue)
602 {
603 setValue(newValue);
604 return *this;
605 }
606
607 QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding)
608 {
609 QtPrivate::QPropertyBindingData *bd = qGetBindingStorage(owner())->bindingData(this, true);
610 QUntypedPropertyBinding oldBinding(bd->setBinding(newBinding, propertyDataPtr: this, staticObserverCallback: nullptr, bindingWrapper));
611 // notification is already handled in QPropertyBindingData::setBinding
612 return static_cast<QPropertyBinding<T> &>(oldBinding);
613 }
614
615 bool setBinding(const QUntypedPropertyBinding &newBinding)
616 {
617 if (!newBinding.isNull() && newBinding.valueMetaType() != QMetaType::fromType<T>())
618 return false;
619 setBinding(static_cast<const QPropertyBinding<T> &>(newBinding));
620 return true;
621 }
622
623#ifndef Q_QDOC
624 template <typename Functor>
625 QPropertyBinding<T> setBinding(Functor &&f,
626 const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION,
627 std::enable_if_t<std::is_invocable_v<Functor>> * = nullptr)
628 {
629 return setBinding(Qt::makePropertyBinding(std::forward<Functor>(f), location));
630 }
631#else
632 template <typename Functor>
633 QPropertyBinding<T> setBinding(Functor f);
634#endif
635
636 bool hasBinding() const {
637 auto *bd = qGetBindingStorage(owner())->bindingData(this);
638 return bd && bd->binding() != nullptr;
639 }
640
641 void removeBindingUnlessInWrapper()
642 {
643 QBindingStorage *storage = qGetBindingStorage(owner());
644 if (auto *bd = storage->bindingData(this)) {
645 // make sure we don't remove the binding if called from the bindingWrapper
646 if (bd->hasBinding() && !inBindingWrapper(storage))
647 bd->removeBinding_helper();
648 }
649 }
650
651 void notify()
652 {
653 QBindingStorage *storage = qGetBindingStorage(owner());
654 if (auto bd = storage->bindingData(this, false)) {
655 // This partly duplicates QPropertyBindingData::notifyObservers because we want to
656 // check for inBindingWrapper() after checking for isNotificationDelayed() and
657 // firstObserver. This is because inBindingWrapper() is the most expensive check.
658 if (!bd->isNotificationDelayed()) {
659 QPropertyBindingDataPointer d{bd};
660 if (QPropertyObserverPointer observer = d.firstObserver()) {
661 if (!inBindingWrapper(storage)) {
662 PendingBindingObserverList bindingObservers;
663 if (bd->notifyObserver_helper(this, storage, observer, bindingObservers)
664 == QtPrivate::QPropertyBindingData::Evaluated) {
665 // evaluateBindings() can trash the observers. We need to re-fetch here.
666 if (QPropertyObserverPointer observer = d.firstObserver())
667 observer.notify(propertyDataPtr: this);
668 for (auto&& bindingPtr: bindingObservers) {
669 auto *binding = static_cast<QPropertyBindingPrivate *>(bindingPtr.get());
670 binding->notifyNonRecursive();
671 }
672 }
673 }
674 }
675 }
676 }
677 if constexpr (!std::is_null_pointer_v<decltype(Signal)>) {
678 if constexpr (SignalTakesValue::value)
679 (owner()->*Signal)(getPropertyValue(d: this));
680 else
681 (owner()->*Signal)();
682 }
683 }
684
685 QPropertyBinding<T> binding() const
686 {
687 auto *bd = qGetBindingStorage(owner())->bindingData(this);
688 return static_cast<QPropertyBinding<T> &&>(QUntypedPropertyBinding(bd ? bd->binding() : nullptr));
689 }
690
691 QPropertyBinding<T> takeBinding()
692 {
693 return setBinding(QPropertyBinding<T>());
694 }
695
696 template<typename Functor>
697 QPropertyChangeHandler<Functor> onValueChanged(Functor f)
698 {
699 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
700 return QPropertyChangeHandler<Functor>(*this, f);
701 }
702
703 template<typename Functor>
704 QPropertyChangeHandler<Functor> subscribe(Functor f)
705 {
706 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
707 f();
708 return onValueChanged(f);
709 }
710
711 template<typename Functor>
712 QPropertyNotifier addNotifier(Functor f)
713 {
714 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
715 return QPropertyNotifier(*this, f);
716 }
717
718 QtPrivate::QPropertyBindingData &bindingData() const
719 {
720 auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));
721 return *storage->bindingData(const_cast<QObjectCompatProperty *>(this), true);
722 }
723};
724
725namespace QtPrivate {
726template<typename Class, typename Ty, auto Offset, auto Setter, auto Signal, auto Getter>
727class QBindableInterfaceForProperty<
728 QObjectCompatProperty<Class, Ty, Offset, Setter, Signal, Getter>, std::void_t<Class>>
729{
730 using Property = QObjectCompatProperty<Class, Ty, Offset, Setter, Signal, Getter>;
731 using T = typename Property::value_type;
732public:
733 static constexpr QBindableInterface iface = {
734 [](const QUntypedPropertyData *d, void *value) -> void
735 { *static_cast<T*>(value) = Property::getPropertyValue(d); },
736 [](QUntypedPropertyData *d, const void *value) -> void
737 {
738 (static_cast<Property *>(d)->owner()->*Setter)(*static_cast<const T*>(value));
739 },
740 [](const QUntypedPropertyData *d) -> QUntypedPropertyBinding
741 { return static_cast<const Property *>(d)->binding(); },
742 [](QUntypedPropertyData *d, const QUntypedPropertyBinding &binding) -> QUntypedPropertyBinding
743 { return static_cast<Property *>(d)->setBinding(static_cast<const QPropertyBinding<T> &>(binding)); },
744 [](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding
745 { return Qt::makePropertyBinding([d]() -> T { return Property::getPropertyValue(d); }, location); },
746 [](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void
747 { observer->setSource(static_cast<const Property *>(d)->bindingData()); },
748 []() { return QMetaType::fromType<T>(); }
749 };
750};
751}
752
753#define QT_OBJECT_COMPAT_PROPERTY_4(Class, Type, name, setter) \
754 static constexpr size_t _qt_property_##name##_offset() { \
755 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
756 return offsetof(Class, name); \
757 QT_WARNING_POP \
758 } \
759 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter> name;
760
761#define QT_OBJECT_COMPAT_PROPERTY_5(Class, Type, name, setter, signal) \
762 static constexpr size_t _qt_property_##name##_offset() { \
763 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
764 return offsetof(Class, name); \
765 QT_WARNING_POP \
766 } \
767 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, signal> name;
768
769#define Q_OBJECT_COMPAT_PROPERTY(...) \
770 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
771 QT_OVERLOADED_MACRO(QT_OBJECT_COMPAT_PROPERTY, __VA_ARGS__) \
772 QT_WARNING_POP
773
774#define QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS_5(Class, Type, name, setter, value) \
775 static constexpr size_t _qt_property_##name##_offset() { \
776 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
777 return offsetof(Class, name); \
778 QT_WARNING_POP \
779 } \
780 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter> name = \
781 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter>( \
782 value);
783
784#define QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS_6(Class, Type, name, setter, signal, value) \
785 static constexpr size_t _qt_property_##name##_offset() { \
786 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
787 return offsetof(Class, name); \
788 QT_WARNING_POP \
789 } \
790 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, signal> name = \
791 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, \
792 signal>(value);
793
794#define QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS_7(Class, Type, name, setter, signal, getter, value) \
795 static constexpr size_t _qt_property_##name##_offset() { \
796 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
797 return offsetof(Class, name); \
798 QT_WARNING_POP \
799 } \
800 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, signal, getter>\
801 name = QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, \
802 signal, getter>(value);
803
804#define Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(...) \
805 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
806 QT_OVERLOADED_MACRO(QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS, __VA_ARGS__) \
807 QT_WARNING_POP
808
809
810namespace QtPrivate {
811Q_CORE_EXPORT BindingEvaluationState *suspendCurrentBindingStatus();
812Q_CORE_EXPORT void restoreBindingStatus(BindingEvaluationState *status);
813}
814
815struct QUntypedBindablePrivate
816{
817 static QtPrivate::QBindableInterface const *getInterface(const QUntypedBindable &bindable)
818 {
819 return bindable.iface;
820 }
821
822 static QUntypedPropertyData *getPropertyData(const QUntypedBindable &bindable)
823 {
824 return bindable.data;
825 }
826};
827
828inline bool QPropertyBindingPrivate::evaluateRecursive_inline(PendingBindingObserverList &bindingObservers, QBindingStatus *status)
829{
830 if (updating) {
831 m_error = QPropertyBindingError(QPropertyBindingError::BindingLoop);
832 if (isQQmlPropertyBinding)
833 errorCallBack(this);
834 return false;
835 }
836
837 /*
838 * Evaluating the binding might lead to the binding being broken. This can
839 * cause ref to reach zero at the end of the function. However, the
840 * updateGuard's destructor will then still trigger, trying to set the
841 * updating bool to its old value
842 * To prevent this, we create a QPropertyBindingPrivatePtr which ensures
843 * that the object is still alive when updateGuard's dtor runs.
844 */
845 QPropertyBindingPrivatePtr keepAlive {this};
846
847 QScopedValueRollback<bool> updateGuard(updating, true);
848
849 QtPrivate::BindingEvaluationState evaluationFrame(this, status);
850
851 auto bindingFunctor = reinterpret_cast<std::byte *>(this) +
852 QPropertyBindingPrivate::getSizeEnsuringAlignment();
853 bool changed = false;
854 if (hasBindingWrapper) {
855 changed = staticBindingWrapper(metaType, propertyDataPtr,
856 {.vtable: vtable, .functor: bindingFunctor});
857 } else {
858 changed = vtable->call(metaType, propertyDataPtr, bindingFunctor);
859 }
860 // If there was a change, we must set pendingNotify.
861 // If there was not, we must not clear it, as that only should happen in notifyRecursive
862 pendingNotify = pendingNotify || changed;
863 if (!changed || !firstObserver)
864 return changed;
865
866 firstObserver.noSelfDependencies(binding: this);
867 firstObserver.evaluateBindings(bindingObservers, status);
868 return true;
869}
870
871/*!
872 \internal
873
874 Walks through the list of property observers, and calls any ChangeHandler
875 found there.
876 It doesn't do anything with bindings, which are only handled in
877 QPropertyBindingPrivate::evaluateRecursive.
878 */
879inline void QPropertyObserverPointer::notify(QUntypedPropertyData *propertyDataPtr)
880{
881 auto observer = const_cast<QPropertyObserver*>(ptr);
882 /*
883 * The basic idea of the loop is as follows: We iterate over all observers in the linked list,
884 * and execute the functionality corresponding to their tag.
885 * However, complication arise due to the fact that the triggered operations might modify the list,
886 * which includes deletion and move of the current and next nodes.
887 * Therefore, we take a few safety precautions:
888 * 1. Before executing any action which might modify the list, we insert a placeholder node after the current node.
889 * As that one is stack allocated and owned by us, we can rest assured that it is
890 * still there after the action has executed, and placeHolder->next points to the actual next node in the list.
891 * Note that taking next at the beginning of the loop does not work, as the executed action might either move
892 * or delete that node.
893 * 2. After the triggered action has finished, we can use the next pointer in the placeholder node as a safe way to
894 * retrieve the next node.
895 * 3. Some care needs to be taken to avoid infinite recursion with change handlers, so we add an extra test there, that
896 * checks whether we're already have the same change handler in our call stack. This can be done by checking whether
897 * the node after the current one is a placeholder node.
898 */
899 while (observer) {
900 QPropertyObserver *next = observer->next.data();
901 switch (QPropertyObserver::ObserverTag(observer->next.tag())) {
902 case QPropertyObserver::ObserverNotifiesChangeHandler:
903 {
904 auto handlerToCall = observer->changeHandler;
905 // prevent recursion
906 if (next && next->next.tag() == QPropertyObserver::ObserverIsPlaceholder) {
907 observer = next->next.data();
908 continue;
909 }
910 // handlerToCall might modify the list
911 QPropertyObserverNodeProtector protector(observer);
912 handlerToCall(observer, propertyDataPtr);
913 next = protector.next();
914 break;
915 }
916 case QPropertyObserver::ObserverNotifiesBinding:
917 break;
918 case QPropertyObserver::ObserverIsPlaceholder:
919 // recursion is already properly handled somewhere else
920 break;
921#if QT_DEPRECATED_SINCE(6, 6)
922 QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
923 case QPropertyObserver::ObserverIsAlias:
924 break;
925 QT_WARNING_POP
926#endif
927 default: Q_UNREACHABLE();
928 }
929 observer = next;
930 }
931}
932
933inline QPropertyObserverNodeProtector::~QPropertyObserverNodeProtector()
934{
935 QPropertyObserverPointer d{.ptr: static_cast<QPropertyObserver *>(&m_placeHolder)};
936 d.unlink_fast();
937}
938
939QBindingObserverPtr::QBindingObserverPtr(QPropertyObserver *observer) noexcept : d(observer)
940{
941 Q_ASSERT(d);
942 QPropertyObserverPointer{.ptr: d}.binding()->addRef();
943}
944
945QBindingObserverPtr::~QBindingObserverPtr()
946{
947 if (!d)
948 return;
949
950 QPropertyBindingPrivate *bindingPrivate = binding();
951 if (!bindingPrivate->deref())
952 QPropertyBindingPrivate::destroyAndFreeMemory(priv: bindingPrivate);
953}
954
955QPropertyBindingPrivate *QBindingObserverPtr::binding() const noexcept { return QPropertyObserverPointer{.ptr: d}.binding(); }
956
957QPropertyObserver *QBindingObserverPtr::operator->() { return d; }
958
959namespace QtPrivate {
960class QPropertyAdaptorSlotObject : public QUntypedPropertyData, public QSlotObjectBase
961{
962 QPropertyBindingData bindingData_;
963 QObject *obj;
964 QMetaProperty metaProperty_;
965
966#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
967 static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret);
968#else
969 static void impl(QSlotObjectBase *this_, QObject *r, void **a, int which, bool *ret);
970#endif
971
972 QPropertyAdaptorSlotObject(QObject *o, const QMetaProperty& p);
973
974public:
975 static QPropertyAdaptorSlotObject *cast(QSlotObjectBase *ptr, int propertyIndex)
976 {
977 if (ptr->isImpl(f: &QPropertyAdaptorSlotObject::impl)) {
978 auto p = static_cast<QPropertyAdaptorSlotObject *>(ptr);
979 if (p->metaProperty_.propertyIndex() == propertyIndex)
980 return p;
981 }
982 return nullptr;
983 }
984
985 inline const QPropertyBindingData &bindingData() const { return bindingData_; }
986 inline QPropertyBindingData &bindingData() { return bindingData_; }
987 inline QObject *object() const { return obj; }
988 inline const QMetaProperty &metaProperty() const { return metaProperty_; }
989
990 friend class QT_PREPEND_NAMESPACE(QUntypedBindable);
991};
992}
993
994QT_END_NAMESPACE
995
996#endif // QPROPERTY_P_H
997

source code of qtbase/src/corelib/kernel/qproperty_p.h