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