1// Copyright (C) 2020 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 QQMLPROPERTYBINDING_P_H
5#define QQMLPROPERTYBINDING_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 purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <private/qqmljavascriptexpression_p.h>
19#include <private/qqmlpropertydata_p.h>
20#include <private/qv4alloca_p.h>
21#include <private/qqmltranslation_p.h>
22
23#include <QtCore/qproperty.h>
24
25#include <memory>
26
27QT_BEGIN_NAMESPACE
28
29namespace QV4 {
30 struct BoundFunction;
31}
32
33class QQmlPropertyBinding;
34class QQmlScriptString;
35
36class Q_QML_PRIVATE_EXPORT QQmlPropertyBindingJS : public QQmlJavaScriptExpression
37{
38 bool mustCaptureBindableProperty() const final {return false;}
39
40 friend class QQmlPropertyBinding;
41 void expressionChanged() override;
42 QQmlPropertyBinding *asBinding()
43 {
44 return const_cast<QQmlPropertyBinding *>(static_cast<const QQmlPropertyBindingJS *>(this)->asBinding());
45 }
46
47 inline QQmlPropertyBinding const *asBinding() const;
48};
49
50class Q_QML_PRIVATE_EXPORT QQmlPropertyBindingJSForBoundFunction : public QQmlPropertyBindingJS
51{
52public:
53 QV4::ReturnedValue evaluate(bool *isUndefined);
54 QV4::PersistentValue m_boundFunction;
55};
56
57class Q_QML_PRIVATE_EXPORT QQmlPropertyBinding : public QPropertyBindingPrivate
58
59{
60 friend class QQmlPropertyBindingJS;
61
62 static constexpr std::size_t jsExpressionOffsetLength() {
63 struct composite { QQmlPropertyBinding b; QQmlPropertyBindingJS js; };
64 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF
65 return sizeof (QQmlPropertyBinding) - offsetof(composite, js);
66 QT_WARNING_POP
67 }
68
69public:
70
71 QQmlPropertyBindingJS *jsExpression()
72 {
73 return const_cast<QQmlPropertyBindingJS *>(static_cast<const QQmlPropertyBinding *>(this)->jsExpression());
74 }
75
76 QQmlPropertyBindingJS const *jsExpression() const
77 {
78 return std::launder(p: reinterpret_cast<QQmlPropertyBindingJS const *>(
79 reinterpret_cast<std::byte const*>(this)
80 + QPropertyBindingPrivate::getSizeEnsuringAlignment()
81 + jsExpressionOffsetLength()));
82 }
83
84 static QUntypedPropertyBinding create(const QQmlPropertyData *pd, QV4::Function *function,
85 QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
86 QV4::ExecutionContext *scope, QObject *target,
87 QQmlPropertyIndex targetIndex);
88 static QUntypedPropertyBinding create(QMetaType propertyType, QV4::Function *function,
89 QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
90 QV4::ExecutionContext *scope, QObject *target,
91 QQmlPropertyIndex targetIndex);
92 static QUntypedPropertyBinding createFromCodeString(const QQmlPropertyData *property,
93 const QString &str, QObject *obj,
94 const QQmlRefPointer<QQmlContextData> &ctxt,
95 const QString &url, quint16 lineNumber,
96 QObject *target, QQmlPropertyIndex targetIndex);
97 static QUntypedPropertyBinding createFromScriptString(const QQmlPropertyData *property,
98 const QQmlScriptString& script, QObject *obj,
99 QQmlContext *ctxt, QObject *target,
100 QQmlPropertyIndex targetIndex);
101
102 static QUntypedPropertyBinding createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function,
103 QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
104 QV4::ExecutionContext *scope, QObject *target,
105 QQmlPropertyIndex targetIndex);
106
107 static bool isUndefined(const QUntypedPropertyBinding &binding)
108 {
109 return isUndefined(binding: QPropertyBindingPrivate::get(binding));
110 }
111
112 static bool isUndefined(const QPropertyBindingPrivate *binding)
113 {
114 if (!(binding && binding->hasCustomVTable()))
115 return false;
116 return static_cast<const QQmlPropertyBinding *>(binding)->isUndefined();
117 }
118
119 template<QMetaType::Type type>
120 static bool doEvaluate(QMetaType metaType, QUntypedPropertyData *dataPtr, void *f) {
121 auto address = static_cast<std::byte*>(f);
122 address -= QPropertyBindingPrivate::getSizeEnsuringAlignment(); // f now points to QPropertyBindingPrivate suboject
123 // and that has the same address as QQmlPropertyBinding
124 return reinterpret_cast<QQmlPropertyBinding *>(address)->evaluate<type>(metaType, dataPtr);
125 }
126
127 bool hasDependencies()
128 {
129 return (dependencyObserverCount > 0) || !jsExpression()->activeGuards.isEmpty();
130 }
131
132private:
133 template <QMetaType::Type type>
134 bool evaluate(QMetaType metaType, void *dataPtr);
135
136 Q_NEVER_INLINE void handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr);
137
138 QString createBindingLoopErrorDescription();
139
140 struct TargetData {
141 enum BoundFunction : bool {
142 WithoutBoundFunction = false,
143 HasBoundFunction = true,
144 };
145 TargetData(QObject *target, QQmlPropertyIndex index, BoundFunction state)
146 : target(target), targetIndex(index), hasBoundFunction(state)
147 {}
148 QObject *target;
149 QQmlPropertyIndex targetIndex;
150 bool hasBoundFunction;
151 bool isUndefined = false;
152 };
153 QQmlPropertyBinding(QMetaType metaType, QObject *target, QQmlPropertyIndex targetIndex, TargetData::BoundFunction hasBoundFunction);
154
155 QObject *target()
156 {
157 return std::launder(p: reinterpret_cast<TargetData *>(&declarativeExtraData))->target;
158 }
159
160 QQmlPropertyIndex targetIndex()
161 {
162 return std::launder(p: reinterpret_cast<TargetData *>(&declarativeExtraData))->targetIndex;
163 }
164
165 bool hasBoundFunction()
166 {
167 return std::launder(p: reinterpret_cast<TargetData *>(&declarativeExtraData))->hasBoundFunction;
168 }
169
170 bool isUndefined() const
171 {
172 return std::launder(p: reinterpret_cast<TargetData const *>(&declarativeExtraData))->isUndefined;
173 }
174
175 void setIsUndefined(bool isUndefined)
176 {
177 std::launder(p: reinterpret_cast<TargetData *>(&declarativeExtraData))->isUndefined = isUndefined;
178 }
179
180 static void bindingErrorCallback(QPropertyBindingPrivate *);
181};
182
183template <auto I>
184struct Print {};
185
186namespace QtPrivate {
187template<QMetaType::Type type>
188inline constexpr BindingFunctionVTable bindingFunctionVTableForQQmlPropertyBinding = {
189 &QQmlPropertyBinding::doEvaluate<type>,
190 [](void *qpropertyBinding){
191 QQmlPropertyBinding *binding = reinterpret_cast<QQmlPropertyBinding *>(qpropertyBinding);
192 binding->jsExpression()->~QQmlPropertyBindingJS();
193 binding->~QQmlPropertyBinding();
194 auto address = static_cast<std::byte*>(qpropertyBinding);
195 delete[] address;
196 },
197 [](void *, void *){},
198 0
199};
200}
201
202inline const QtPrivate::BindingFunctionVTable *bindingFunctionVTableForQQmlPropertyBinding(QMetaType type)
203{
204#define FOR_TYPE(TYPE) \
205 case TYPE: return &QtPrivate::bindingFunctionVTableForQQmlPropertyBinding<TYPE>
206 switch (type.id()) {
207 FOR_TYPE(QMetaType::Int);
208 FOR_TYPE(QMetaType::QString);
209 FOR_TYPE(QMetaType::Double);
210 FOR_TYPE(QMetaType::Float);
211 FOR_TYPE(QMetaType::Bool);
212 default:
213 if (type.flags() & QMetaType::PointerToQObject)
214 return &QtPrivate::bindingFunctionVTableForQQmlPropertyBinding<QMetaType::QObjectStar>;
215 return &QtPrivate::bindingFunctionVTableForQQmlPropertyBinding<QMetaType::UnknownType>;
216 }
217#undef FOR_TYPE
218}
219
220class QQmlTranslationPropertyBinding
221{
222public:
223 static QUntypedPropertyBinding Q_QML_PRIVATE_EXPORT create(const QQmlPropertyData *pd,
224 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
225 const QV4::CompiledData::Binding *binding);
226 static QUntypedPropertyBinding Q_QML_PRIVATE_EXPORT
227 create(const QMetaType &pd,
228 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
229 const QQmlTranslation &translationData);
230};
231
232inline const QQmlPropertyBinding *QQmlPropertyBindingJS::asBinding() const
233{
234 return std::launder(p: reinterpret_cast<QQmlPropertyBinding const *>(
235 reinterpret_cast<std::byte const*>(this)
236 - QPropertyBindingPrivate::getSizeEnsuringAlignment()
237 - QQmlPropertyBinding::jsExpressionOffsetLength()));
238}
239
240static_assert(sizeof(QQmlPropertyBinding) == sizeof(QPropertyBindingPrivate)); // else the whole offset computatation will break
241template<typename T>
242bool compareAndAssign(void *dataPtr, const void *result)
243{
244 if (*static_cast<const T *>(result) == *static_cast<const T *>(dataPtr))
245 return false;
246 *static_cast<T *>(dataPtr) = *static_cast<const T *>(result);
247 return true;
248}
249
250template <QMetaType::Type type>
251bool QQmlPropertyBinding::evaluate(QMetaType metaType, void *dataPtr)
252{
253 const auto ctxt = jsExpression()->context();
254 QQmlEngine *engine = ctxt ? ctxt->engine() : nullptr;
255 if (!engine) {
256 QPropertyBindingError error(QPropertyBindingError::EvaluationError);
257 if (auto currentBinding = QPropertyBindingPrivate::currentlyEvaluatingBinding())
258 currentBinding->setError(std::move(error));
259 return false;
260 }
261 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: engine);
262 ep->referenceScarceResources();
263
264 const auto handleErrorAndUndefined = [&](bool evaluatedToUndefined) {
265 ep->dereferenceScarceResources();
266 if (jsExpression()->hasError()) {
267 QPropertyBindingError error(QPropertyBindingError::UnknownError,
268 jsExpression()->delayedError()->error().description());
269 QPropertyBindingPrivate::currentlyEvaluatingBinding()->setError(std::move(error));
270 bindingErrorCallback(this);
271 return false;
272 }
273
274 if (evaluatedToUndefined) {
275 handleUndefinedAssignment(ep, dataPtr);
276 // if property has been changed due to reset, reset is responsible for
277 // notifying observers
278 return false;
279 } else if (isUndefined()) {
280 setIsUndefined(false);
281 }
282
283 return true;
284 };
285
286 if (!hasBoundFunction()) {
287 Q_ASSERT(metaType.sizeOf() > 0);
288
289 // No need to construct here. evaluate() expects uninitialized memory.
290 const auto size = [&]() -> qsizetype {
291 switch (type) {
292 case QMetaType::QObjectStar: return sizeof(QObject *);
293 case QMetaType::Bool: return sizeof(bool);
294 case QMetaType::Int: return (sizeof(int));
295 case QMetaType::Double: return (sizeof(double));
296 case QMetaType::Float: return (sizeof(float));
297 case QMetaType::QString: return (sizeof(QString));
298 default: return metaType.sizeOf();
299 }
300 }();
301 Q_ALLOCA_VAR(void, result, size);
302
303 const bool evaluatedToUndefined = !jsExpression()->evaluate(a: &result, types: &metaType, argc: 0);
304 if (!handleErrorAndUndefined(evaluatedToUndefined))
305 return false;
306
307 switch (type) {
308 case QMetaType::QObjectStar:
309 return compareAndAssign<QObject *>(dataPtr, result);
310 case QMetaType::Bool:
311 return compareAndAssign<bool>(dataPtr, result);
312 case QMetaType::Int:
313 return compareAndAssign<int>(dataPtr, result);
314 case QMetaType::Double:
315 return compareAndAssign<double>(dataPtr, result);
316 case QMetaType::Float:
317 return compareAndAssign<float>(dataPtr, result);
318 case QMetaType::QString: {
319 const bool hasChanged = compareAndAssign<QString>(dataPtr, result);
320 static_cast<QString *>(result)->~QString();
321 return hasChanged;
322 }
323 default:
324 break;
325 }
326
327 const bool hasChanged = !metaType.equals(lhs: result, rhs: dataPtr);
328 if (hasChanged) {
329 metaType.destruct(data: dataPtr);
330 metaType.construct(where: dataPtr, copy: result);
331 }
332 metaType.destruct(data: result);
333 return hasChanged;
334 }
335
336 bool evaluatedToUndefined = false;
337 QV4::Scope scope(engine->handle());
338 QV4::ScopedValue result(scope, static_cast<QQmlPropertyBindingJSForBoundFunction *>(
339 jsExpression())->evaluate(isUndefined: &evaluatedToUndefined));
340
341 if (!handleErrorAndUndefined(evaluatedToUndefined))
342 return false;
343
344 switch (type) {
345 case QMetaType::Bool: {
346 bool b;
347 if (result->isBoolean())
348 b = result->booleanValue();
349 else
350 b = result->toBoolean();
351 if (b == *static_cast<bool *>(dataPtr))
352 return false;
353 *static_cast<bool *>(dataPtr) = b;
354 return true;
355 }
356 case QMetaType::Int: {
357 int i;
358 if (result->isInteger())
359 i = result->integerValue();
360 else if (result->isNumber()) {
361 i = QV4::StaticValue::toInteger(d: result->doubleValue());
362 } else {
363 break;
364 }
365 if (i == *static_cast<int *>(dataPtr))
366 return false;
367 *static_cast<int *>(dataPtr) = i;
368 return true;
369 }
370 case QMetaType::Double:
371 if (result->isNumber()) {
372 double d = result->asDouble();
373 if (d == *static_cast<double *>(dataPtr))
374 return false;
375 *static_cast<double *>(dataPtr) = d;
376 return true;
377 }
378 break;
379 case QMetaType::Float:
380 if (result->isNumber()) {
381 float d = float(result->asDouble());
382 if (d == *static_cast<float *>(dataPtr))
383 return false;
384 *static_cast<float *>(dataPtr) = d;
385 return true;
386 }
387 break;
388 case QMetaType::QString:
389 if (result->isString()) {
390 QString s = result->toQStringNoThrow();
391 if (s == *static_cast<QString *>(dataPtr))
392 return false;
393 *static_cast<QString *>(dataPtr) = s;
394 return true;
395 }
396 break;
397 default:
398 break;
399 }
400
401 QVariant resultVariant(QV4::ExecutionEngine::toVariant(value: result, typeHint: metaType));
402 resultVariant.convert(type: metaType);
403 const bool hasChanged = !metaType.equals(lhs: resultVariant.constData(), rhs: dataPtr);
404 metaType.destruct(data: dataPtr);
405 metaType.construct(where: dataPtr, copy: resultVariant.constData());
406 return hasChanged;
407}
408
409QT_END_NAMESPACE
410
411#endif // QQMLPROPERTYBINDING_P_H
412

source code of qtdeclarative/src/qml/qml/qqmlpropertybinding_p.h