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 | |
27 | QT_BEGIN_NAMESPACE |
28 | |
29 | namespace QV4 { |
30 | struct BoundFunction; |
31 | } |
32 | |
33 | class QQmlPropertyBinding; |
34 | class QQmlScriptString; |
35 | |
36 | class 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 | |
50 | class Q_QML_PRIVATE_EXPORT QQmlPropertyBindingJSForBoundFunction : public QQmlPropertyBindingJS |
51 | { |
52 | public: |
53 | QV4::ReturnedValue evaluate(bool *isUndefined); |
54 | QV4::PersistentValue m_boundFunction; |
55 | }; |
56 | |
57 | class 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 | |
69 | public: |
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 | |
132 | private: |
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 | |
183 | template <auto I> |
184 | struct Print {}; |
185 | |
186 | namespace QtPrivate { |
187 | template<QMetaType::Type type> |
188 | inline 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 | |
202 | inline 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 | |
220 | class QQmlTranslationPropertyBinding |
221 | { |
222 | public: |
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 | |
232 | inline 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 | |
240 | static_assert(sizeof(QQmlPropertyBinding) == sizeof(QPropertyBindingPrivate)); // else the whole offset computatation will break |
241 | template<typename T> |
242 | bool 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 | |
250 | template <QMetaType::Type type> |
251 | bool 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 | |
409 | QT_END_NAMESPACE |
410 | |
411 | #endif // QQMLPROPERTYBINDING_P_H |
412 | |