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 Q_ALWAYS_INLINE void 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 Q_ALWAYS_INLINE bool 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.
666 // It can also reallocate binding data pointer.
667 // So, we need to re-fetch here.
668 bd = storage->bindingData(this, false);
669 QPropertyBindingDataPointer dd{bd};
670 if (QPropertyObserverPointer obs = dd.firstObserver())
671 obs.notify(propertyDataPtr: this);
672 for (auto&& bindingPtr: bindingObservers) {
673 auto *binding = static_cast<QPropertyBindingPrivate *>(bindingPtr.get());
674 binding->notifyNonRecursive();
675 }
676 }
677 }
678 }
679 }
680 }
681 if constexpr (!std::is_null_pointer_v<decltype(Signal)>) {
682 if constexpr (SignalTakesValue::value)
683 (owner()->*Signal)(getPropertyValue(d: this));
684 else
685 (owner()->*Signal)();
686 }
687 }
688
689 QPropertyBinding<T> binding() const
690 {
691 auto *bd = qGetBindingStorage(owner())->bindingData(this);
692 return static_cast<QPropertyBinding<T> &&>(QUntypedPropertyBinding(bd ? bd->binding() : nullptr));
693 }
694
695 QPropertyBinding<T> takeBinding()
696 {
697 return setBinding(QPropertyBinding<T>());
698 }
699
700 template<typename Functor>
701 QPropertyChangeHandler<Functor> onValueChanged(Functor f)
702 {
703 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
704 return QPropertyChangeHandler<Functor>(*this, f);
705 }
706
707 template<typename Functor>
708 QPropertyChangeHandler<Functor> subscribe(Functor f)
709 {
710 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
711 f();
712 return onValueChanged(f);
713 }
714
715 template<typename Functor>
716 QPropertyNotifier addNotifier(Functor f)
717 {
718 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
719 return QPropertyNotifier(*this, f);
720 }
721
722 QtPrivate::QPropertyBindingData &bindingData() const
723 {
724 auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));
725 return *storage->bindingData(const_cast<QObjectCompatProperty *>(this), true);
726 }
727};
728
729namespace QtPrivate {
730template<typename Class, typename Ty, auto Offset, auto Setter, auto Signal, auto Getter>
731class QBindableInterfaceForProperty<
732 QObjectCompatProperty<Class, Ty, Offset, Setter, Signal, Getter>, std::void_t<Class>>
733{
734 using Property = QObjectCompatProperty<Class, Ty, Offset, Setter, Signal, Getter>;
735 using T = typename Property::value_type;
736public:
737 static constexpr QBindableInterface iface = {
738 [](const QUntypedPropertyData *d, void *value) -> void
739 { *static_cast<T*>(value) = Property::getPropertyValue(d); },
740 [](QUntypedPropertyData *d, const void *value) -> void
741 {
742 (static_cast<Property *>(d)->owner()->*Setter)(*static_cast<const T*>(value));
743 },
744 [](const QUntypedPropertyData *d) -> QUntypedPropertyBinding
745 { return static_cast<const Property *>(d)->binding(); },
746 [](QUntypedPropertyData *d, const QUntypedPropertyBinding &binding) -> QUntypedPropertyBinding
747 { return static_cast<Property *>(d)->setBinding(static_cast<const QPropertyBinding<T> &>(binding)); },
748 [](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding
749 { return Qt::makePropertyBinding([d]() -> T { return Property::getPropertyValue(d); }, location); },
750 [](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void
751 { observer->setSource(static_cast<const Property *>(d)->bindingData()); },
752 []() { return QMetaType::fromType<T>(); }
753 };
754};
755}
756
757#define QT_OBJECT_COMPAT_PROPERTY_4(Class, Type, name, setter) \
758 static constexpr size_t _qt_property_##name##_offset() { \
759 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
760 return offsetof(Class, name); \
761 QT_WARNING_POP \
762 } \
763 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter> name;
764
765#define QT_OBJECT_COMPAT_PROPERTY_5(Class, Type, name, setter, signal) \
766 static constexpr size_t _qt_property_##name##_offset() { \
767 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
768 return offsetof(Class, name); \
769 QT_WARNING_POP \
770 } \
771 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, signal> name;
772
773#define Q_OBJECT_COMPAT_PROPERTY(...) \
774 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
775 QT_OVERLOADED_MACRO(QT_OBJECT_COMPAT_PROPERTY, __VA_ARGS__) \
776 QT_WARNING_POP
777
778#define QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS_5(Class, Type, name, setter, value) \
779 static constexpr size_t _qt_property_##name##_offset() { \
780 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
781 return offsetof(Class, name); \
782 QT_WARNING_POP \
783 } \
784 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter> name = \
785 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter>( \
786 value);
787
788#define QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS_6(Class, Type, name, setter, signal, value) \
789 static constexpr size_t _qt_property_##name##_offset() { \
790 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
791 return offsetof(Class, name); \
792 QT_WARNING_POP \
793 } \
794 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, signal> name = \
795 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, \
796 signal>(value);
797
798#define QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS_7(Class, Type, name, setter, signal, getter, value) \
799 static constexpr size_t _qt_property_##name##_offset() { \
800 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
801 return offsetof(Class, name); \
802 QT_WARNING_POP \
803 } \
804 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, signal, getter>\
805 name = QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, \
806 signal, getter>(value);
807
808#define Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(...) \
809 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
810 QT_OVERLOADED_MACRO(QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS, __VA_ARGS__) \
811 QT_WARNING_POP
812
813
814namespace QtPrivate {
815Q_CORE_EXPORT BindingEvaluationState *suspendCurrentBindingStatus();
816Q_CORE_EXPORT void restoreBindingStatus(BindingEvaluationState *status);
817}
818
819struct QUntypedBindablePrivate
820{
821 static QtPrivate::QBindableInterface const *getInterface(const QUntypedBindable &bindable)
822 {
823 return bindable.iface;
824 }
825
826 static QUntypedPropertyData *getPropertyData(const QUntypedBindable &bindable)
827 {
828 return bindable.data;
829 }
830};
831
832inline bool QPropertyBindingPrivate::evaluateRecursive_inline(PendingBindingObserverList &bindingObservers, QBindingStatus *status)
833{
834 if (updating) {
835 m_error = QPropertyBindingError(QPropertyBindingError::BindingLoop);
836 if (isQQmlPropertyBinding)
837 errorCallBack(this);
838 return false;
839 }
840
841 /*
842 * Evaluating the binding might lead to the binding being broken. This can
843 * cause ref to reach zero at the end of the function. However, the
844 * updateGuard's destructor will then still trigger, trying to set the
845 * updating bool to its old value
846 * To prevent this, we create a QPropertyBindingPrivatePtr which ensures
847 * that the object is still alive when updateGuard's dtor runs.
848 */
849 QPropertyBindingPrivatePtr keepAlive {this};
850
851 QScopedValueRollback<bool> updateGuard(updating, true);
852
853 QtPrivate::BindingEvaluationState evaluationFrame(this, status);
854
855 auto bindingFunctor = reinterpret_cast<std::byte *>(this) +
856 QPropertyBindingPrivate::getSizeEnsuringAlignment();
857 bool changed = false;
858 if (hasBindingWrapper) {
859 changed = staticBindingWrapper(metaType, propertyDataPtr,
860 {.vtable: vtable, .functor: bindingFunctor});
861 } else {
862 changed = vtable->call(metaType, propertyDataPtr, bindingFunctor);
863 }
864 // If there was a change, we must set pendingNotify.
865 // If there was not, we must not clear it, as that only should happen in notifyRecursive
866 pendingNotify = pendingNotify || changed;
867 if (!changed || !firstObserver)
868 return changed;
869
870 firstObserver.noSelfDependencies(binding: this);
871 firstObserver.evaluateBindings(bindingObservers, status);
872 return true;
873}
874
875/*!
876 \internal
877
878 Walks through the list of property observers, and calls any ChangeHandler
879 found there.
880 It doesn't do anything with bindings, which are only handled in
881 QPropertyBindingPrivate::evaluateRecursive.
882 */
883inline void QPropertyObserverPointer::notify(QUntypedPropertyData *propertyDataPtr)
884{
885 auto observer = const_cast<QPropertyObserver*>(ptr);
886 /*
887 * The basic idea of the loop is as follows: We iterate over all observers in the linked list,
888 * and execute the functionality corresponding to their tag.
889 * However, complication arise due to the fact that the triggered operations might modify the list,
890 * which includes deletion and move of the current and next nodes.
891 * Therefore, we take a few safety precautions:
892 * 1. Before executing any action which might modify the list, we insert a placeholder node after the current node.
893 * As that one is stack allocated and owned by us, we can rest assured that it is
894 * still there after the action has executed, and placeHolder->next points to the actual next node in the list.
895 * Note that taking next at the beginning of the loop does not work, as the executed action might either move
896 * or delete that node.
897 * 2. After the triggered action has finished, we can use the next pointer in the placeholder node as a safe way to
898 * retrieve the next node.
899 * 3. Some care needs to be taken to avoid infinite recursion with change handlers, so we add an extra test there, that
900 * checks whether we're already have the same change handler in our call stack. This can be done by checking whether
901 * the node after the current one is a placeholder node.
902 */
903 while (observer) {
904 QPropertyObserver *next = observer->next.data();
905 switch (QPropertyObserver::ObserverTag(observer->next.tag())) {
906 case QPropertyObserver::ObserverNotifiesChangeHandler:
907 {
908 auto handlerToCall = observer->changeHandler;
909 // prevent recursion
910 if (next && next->next.tag() == QPropertyObserver::ObserverIsPlaceholder) {
911 observer = next->next.data();
912 continue;
913 }
914 // handlerToCall might modify the list
915 QPropertyObserverNodeProtector protector(observer);
916 handlerToCall(observer, propertyDataPtr);
917 next = protector.next();
918 break;
919 }
920 case QPropertyObserver::ObserverNotifiesBinding:
921 break;
922 case QPropertyObserver::ObserverIsPlaceholder:
923 // recursion is already properly handled somewhere else
924 break;
925#if QT_DEPRECATED_SINCE(6, 6)
926 QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
927 case QPropertyObserver::ObserverIsAlias:
928 break;
929 QT_WARNING_POP
930#endif
931 default: Q_UNREACHABLE();
932 }
933 observer = next;
934 }
935}
936
937inline QPropertyObserverNodeProtector::~QPropertyObserverNodeProtector()
938{
939 QPropertyObserverPointer d{.ptr: static_cast<QPropertyObserver *>(&m_placeHolder)};
940 d.unlink_fast();
941}
942
943QBindingObserverPtr::QBindingObserverPtr(QPropertyObserver *observer) noexcept : d(observer)
944{
945 Q_ASSERT(d);
946 QPropertyObserverPointer{.ptr: d}.binding()->addRef();
947}
948
949QBindingObserverPtr::~QBindingObserverPtr()
950{
951 if (!d)
952 return;
953
954 QPropertyBindingPrivate *bindingPrivate = binding();
955 if (!bindingPrivate->deref())
956 QPropertyBindingPrivate::destroyAndFreeMemory(priv: bindingPrivate);
957}
958
959QPropertyBindingPrivate *QBindingObserverPtr::binding() const noexcept { return QPropertyObserverPointer{.ptr: d}.binding(); }
960
961QPropertyObserver *QBindingObserverPtr::operator->() { return d; }
962
963namespace QtPrivate {
964class QPropertyAdaptorSlotObject : public QUntypedPropertyData, public QSlotObjectBase
965{
966 QPropertyBindingData bindingData_;
967 QObject *obj;
968 QMetaProperty metaProperty_;
969
970#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
971 static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret);
972#else
973 static void impl(QSlotObjectBase *this_, QObject *r, void **a, int which, bool *ret);
974#endif
975
976 QPropertyAdaptorSlotObject(QObject *o, const QMetaProperty& p);
977
978public:
979 static QPropertyAdaptorSlotObject *cast(QSlotObjectBase *ptr, int propertyIndex)
980 {
981 if (ptr->isImpl(f: &QPropertyAdaptorSlotObject::impl)) {
982 auto p = static_cast<QPropertyAdaptorSlotObject *>(ptr);
983 if (p->metaProperty_.propertyIndex() == propertyIndex)
984 return p;
985 }
986 return nullptr;
987 }
988
989 inline const QPropertyBindingData &bindingData() const { return bindingData_; }
990 inline QPropertyBindingData &bindingData() { return bindingData_; }
991 inline QObject *object() const { return obj; }
992 inline const QMetaProperty &metaProperty() const { return metaProperty_; }
993
994 friend class QT_PREPEND_NAMESPACE(QUntypedBindable);
995};
996}
997
998QT_END_NAMESPACE
999
1000#endif // QPROPERTY_P_H
1001

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