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.cpp
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
6
7#include <private/qqmlbinding_p.h>
8#include <private/qqmlglobal_p.h>
9#include <private/qqmlscriptstring_p.h>
10#include <private/qv4functionobject_p.h>
11#include <private/qv4jscall_p.h>
12#include <private/qv4qmlcontext_p.h>
13
14#include <QtQml/qqmlinfo.h>
15
16#include <QtCore/qloggingcategory.h>
17
19
20using namespace Qt::Literals::StringLiterals;
21
22Q_STATIC_LOGGING_CATEGORY(lcQQPropertyBinding, "qt.qml.propertybinding");
23
24QUntypedPropertyBinding QQmlPropertyBinding::create(const QQmlPropertyData *pd, QV4::Function *function,
25 QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
26 QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex)
27{
28 Q_ASSERT(pd);
29 return create(pd->propType(), function, obj, ctxt, scope, target, targetIndex);
30}
31
32QUntypedPropertyBinding QQmlPropertyBinding::create(QMetaType propertyType, QV4::Function *function,
33 QObject *obj,
34 const QQmlRefPointer<QQmlContextData> &ctxt,
35 QV4::ExecutionContext *scope, QObject *target,
36 QQmlPropertyIndex targetIndex)
37{
38 auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment()
39 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
40 auto binding = new (buffer) QQmlPropertyBinding(propertyType, target, targetIndex,
41 TargetData::WithoutBoundFunction);
42 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
43 Q_ASSERT(binding->jsExpression() == js);
44 Q_ASSERT(js->asBinding() == binding);
45 Q_UNUSED(js);
46 binding->jsExpression()->setNotifyOnValueChanged(true);
47 binding->jsExpression()->setContext(ctxt);
48 binding->jsExpression()->setScopeObject(obj);
49 binding->jsExpression()->setupFunction(scope, function);
50 return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
51}
52
53QUntypedPropertyBinding QQmlPropertyBinding::createFromCodeString(const QQmlPropertyData *pd, const QString& str, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber, QObject *target, QQmlPropertyIndex targetIndex)
54{
55 auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment()
56 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
57 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::WithoutBoundFunction);
58 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
59 Q_ASSERT(binding->jsExpression() == js);
60 Q_ASSERT(js->asBinding() == binding);
61 Q_UNUSED(js);
62 binding->jsExpression()->setNotifyOnValueChanged(true);
63 binding->jsExpression()->setContext(ctxt);
64 binding->jsExpression()->createQmlBinding(ctxt, obj, str, url, lineNumber);
65 return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
66}
67
68QUntypedPropertyBinding QQmlPropertyBinding::createFromScriptString(const QQmlPropertyData *property, const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt, QObject *target, QQmlPropertyIndex targetIndex)
69{
70 const QQmlScriptStringPrivate *scriptPrivate = script.d.data();
71 // without a valid context, we cannot create anything
72 if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) {
73 return {};
74 }
75
76 auto scopeObject = obj ? obj : scriptPrivate->scope;
77
78 QV4::Function *runtimeFunction = nullptr;
79 QString url;
80 QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(scriptPrivate->context);
81 QQmlEnginePrivate *engine = QQmlEnginePrivate::get(scriptPrivate->context->engine());
82 if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit()) {
83 url = ctxtdata->urlString();
84 if (scriptPrivate->bindingId != QQmlBinding::Invalid)
85 runtimeFunction = ctxtdata->typeCompilationUnit()->runtimeFunctions.at(scriptPrivate->bindingId);
86 }
87 // Do we actually have a function in the script string? If not, this becomes createCodeFromString
88 if (!runtimeFunction)
89 return createFromCodeString(property, scriptPrivate->script, obj, ctxtdata, url, scriptPrivate->lineNumber, target, targetIndex);
90
91 auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment()
92 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
93 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(property->propType()), target, targetIndex, TargetData::WithoutBoundFunction);
94 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
95 Q_ASSERT(binding->jsExpression() == js);
96 Q_ASSERT(js->asBinding() == binding);
97 js->setContext(QQmlContextData::get(ctxt ? ctxt : scriptPrivate->context));
98
99 QV4::ExecutionEngine *v4 = engine->v4Engine.get();
100 QV4::Scope scope(v4);
101 QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxtdata, scopeObject));
102 js->setupFunction(qmlContext, runtimeFunction);
103 return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
104}
105
106QUntypedPropertyBinding QQmlPropertyBinding::createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex)
107{
108 auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment()
109 + sizeof(QQmlPropertyBindingJSForBoundFunction)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
110 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::HasBoundFunction);
111 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJSForBoundFunction();
112 Q_ASSERT(binding->jsExpression() == js);
113 Q_ASSERT(js->asBinding() == binding);
114 Q_UNUSED(js);
115 binding->jsExpression()->setNotifyOnValueChanged(true);
116 binding->jsExpression()->setContext(ctxt);
117 binding->jsExpression()->setScopeObject(obj);
118 binding->jsExpression()->setupFunction(scope, function->function());
119 js->m_boundFunction.set(function->engine(), *function);
120 return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
121}
122
123/*!
124 \fn bool QQmlPropertyBindingJS::hasDependencies()
125 \internal
126
127 Returns true if this binding has dependencies.
128 Dependencies can be either QProperty dependencies or dependencies of
129 the JS expression (aka activeGuards). Translations end up as a QProperty
130 dependency, so they do not need any special handling
131 Note that a QQmlPropertyBinding never stores qpropertyChangeTriggers.
132 */
133
134
135void QQmlPropertyBindingJS::expressionChanged()
136{
137 if (!hasValidContext())
138 return;
139
140 auto binding = asBinding();
141 if (!binding->propertyDataPtr)
142 return;
143 const auto currentTag = m_error.tag();
144 if (currentTag == InEvaluationLoop) {
145 QQmlError err;
146 auto location = QQmlJavaScriptExpression::sourceLocation();
147 err.setUrl(QUrl{location.sourceFile});
148 err.setLine(location.line);
149 err.setColumn(location.column);
150 const auto ctxt = context();
151 QQmlEngine *engine = ctxt ? ctxt->engine() : nullptr;
152 if (engine)
153 err.setDescription(asBinding()->createBindingLoopErrorDescription());
154 else
155 err.setDescription(QString::fromLatin1("Binding loop detected"));
156 err.setObject(asBinding()->target());
157 qmlWarning(this->scopeObject(), err);
158 return;
159 }
160 m_error.setTag(InEvaluationLoop);
161 PendingBindingObserverList bindingObservers;
162 binding->evaluateRecursive(bindingObservers);
163 binding->notifyNonRecursive(bindingObservers);
164 m_error.setTag(NoTag);
165}
166
167QQmlPropertyBinding::QQmlPropertyBinding(QMetaType mt, QObject *target, QQmlPropertyIndex targetIndex, TargetData::BoundFunction hasBoundFunction)
168 : QPropertyBindingPrivate(mt,
169 bindingFunctionVTableForQQmlPropertyBinding(mt),
170 QPropertyBindingSourceLocation(), true)
171{
172 static_assert (std::is_trivially_destructible_v<TargetData>);
173 static_assert (sizeof(TargetData) + sizeof(DeclarativeErrorCallback) <= sizeof(QPropertyBindingSourceLocation));
174 static_assert (alignof(TargetData) <= alignof(QPropertyBindingSourceLocation));
175 const auto state = hasBoundFunction ? TargetData::HasBoundFunction : TargetData::WithoutBoundFunction;
176 new (&declarativeExtraData) TargetData {target, targetIndex, state};
177 errorCallBack = bindingErrorCallback;
178}
179
180static QtPrivate::QPropertyBindingData *bindingDataFromPropertyData(QUntypedPropertyData *dataPtr, QMetaType type)
181{
182 // XXX Qt 7: We need a clean way to access the binding data
183 /* This function makes the (dangerous) assumption that if we could not get the binding data
184 from the binding storage, we must have been handed a QProperty.
185 This does hold for anything a user could write, as there the only ways of providing a bindable property
186 are to use the Q_X_BINDABLE macros, or to directly expose a QProperty.
187 As long as we can ensure that any "fancier" property we implement is not resettable, we should be fine.
188 We procede to calculate the address of the binding data pointer from the address of the data pointer
189 */
190 Q_ASSERT(dataPtr);
191 std::byte *qpropertyPointer = reinterpret_cast<std::byte *>(dataPtr);
192 qpropertyPointer += type.sizeOf();
193 constexpr auto alignment = alignof(QtPrivate::QPropertyBindingData *);
194 auto aligned = (quintptr(qpropertyPointer) + alignment - 1) & ~(alignment - 1); // ensure pointer alignment
195 return reinterpret_cast<QtPrivate::QPropertyBindingData *>(aligned);
196}
197
198void QQmlPropertyBinding::handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr)
199{
200 const QQmlPropertyData *propertyData = nullptr;
201 QQmlPropertyData valueTypeData;
202 QQmlData *data = QQmlData::get(target(), false);
203 Q_ASSERT(data);
204 if (Q_UNLIKELY(!data->propertyCache))
205 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
206
207 propertyData = data->propertyCache->property(targetIndex().coreIndex());
208 Q_ASSERT(propertyData);
209 Q_ASSERT(!targetIndex().hasValueTypeIndex());
210 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
211 // helper function for writing back value into dataPtr
212 // this is necessary for QObjectCompatProperty, which doesn't give us the "real" dataPtr
213 // if we don't write the correct value, we would otherwise set the default constructed value
214 auto writeBackCurrentValue = [&](QVariant &&currentValue) {
215 if (currentValue.metaType() != valueMetaType())
216 currentValue.convert(valueMetaType());
217 auto metaType = valueMetaType();
218 metaType.destruct(dataPtr);
219 metaType.construct(dataPtr, currentValue.constData());
220 };
221 if (prop.isResettable()) {
222 // Normally a reset would remove any existing binding; but now we need to keep the binding alive
223 // to handle the case where this binding becomes defined again
224 // We therefore detach the binding, call reset, and reattach again
225 const auto storage = qGetBindingStorage(target());
226 auto bindingData = storage->bindingData(propertyDataPtr);
227 if (!bindingData)
228 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
229 QPropertyBindingDataPointer bindingDataPointer{bindingData};
230 auto firstObserver = takeObservers();
231 bindingData->d_ref() = 0;
232 if (firstObserver) {
233 bindingDataPointer.setObservers(firstObserver.ptr);
234 }
235 Q_ASSERT(!bindingData->hasBinding());
236 setIsUndefined(true);
237 //suspend binding evaluation state for reset and subsequent read
238 auto state = QtPrivate::suspendCurrentBindingStatus();
239 prop.reset(); // May re-allocate the bindingData
240 QVariant currentValue = QVariant(prop.propertyMetaType(), propertyDataPtr);
241 QtPrivate::restoreBindingStatus(state);
242 writeBackCurrentValue(std::move(currentValue));
243
244 // Re-fetch binding data
245 bindingData = storage->bindingData(propertyDataPtr);
246 if (!bindingData)
247 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
248 bindingDataPointer = QPropertyBindingDataPointer {bindingData};
249
250 // reattach the binding (without causing a new notification)
251 if (Q_UNLIKELY(bindingData->d() & QtPrivate::QPropertyBindingData::BindingBit)) {
252 qCWarning(lcQQPropertyBinding) << "Resetting " << prop.name() << "due to the binding becoming undefined caused a new binding to be installed\n"
253 << "The old binding binding will be abandoned";
254 deref();
255 return;
256 }
257 // reset might have changed observers (?), so refresh firstObserver
258 firstObserver = bindingDataPointer.firstObserver();
259 bindingData->d_ref() = reinterpret_cast<quintptr>(this) | QtPrivate::QPropertyBindingData::BindingBit;
260 if (firstObserver)
261 prependObserver(firstObserver);
262 } else {
263 QQmlError qmlError;
264 auto location = jsExpression()->sourceLocation();
265 qmlError.setColumn(location.column);
266 qmlError.setLine(location.line);
267 qmlError.setUrl(QUrl {location.sourceFile});
268 const QString description = QStringLiteral(R"(QML %1: Unable to assign [undefined] to "%2")").arg(QQmlMetaType::prettyTypeName(target()) , prop.name());
269 qmlError.setDescription(description);
270 qmlError.setObject(target());
271 ep->warning(qmlError);
272 }
273}
274
275QString QQmlPropertyBinding::createBindingLoopErrorDescription()
276{
277 const QQmlPropertyData *propertyData = nullptr;
278 QQmlPropertyData valueTypeData;
279 QQmlData *data = QQmlData::get(target(), false);
280 Q_ASSERT(data);
281 if (Q_UNLIKELY(!data->propertyCache))
282 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
283
284 propertyData = data->propertyCache->property(targetIndex().coreIndex());
285 Q_ASSERT(propertyData);
286 Q_ASSERT(!targetIndex().hasValueTypeIndex());
287 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
288 return R"(QML %1: Binding loop detected for property "%2")"_L1.arg(QQmlMetaType::prettyTypeName(target()) , prop.name());
289}
290
291void QQmlPropertyBinding::bindingErrorCallback(QPropertyBindingPrivate *that)
292{
293 auto This = static_cast<QQmlPropertyBinding *>(that);
294 auto target = This->target();
295 auto engine = qmlEngine(target);
296 if (!engine)
297 return;
298
299 auto error = This->bindingError();
300 QQmlError qmlError;
301 auto location = This->jsExpression()->sourceLocation();
302 qmlError.setColumn(location.column);
303 qmlError.setLine(location.line);
304 qmlError.setUrl(QUrl {location.sourceFile});
305 auto description = error.description();
306 if (error.type() == QPropertyBindingError::BindingLoop) {
307 description = This->createBindingLoopErrorDescription();
308 }
309 qmlError.setDescription(description);
310 qmlError.setObject(target);
311 QQmlEnginePrivate::get(engine)->warning(qmlError);
312}
313
314template<typename TranslateWithUnit>
316 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
317 TranslateWithUnit &&translateWithUnit)
318{
319 return [compilationUnit, translateWithUnit](QMetaType metaType, void *dataPtr) -> bool {
320 // Create a dependency to the translationLanguage
321 QQmlEnginePrivate::get(compilationUnit->engine)->translationLanguage.value();
322
323 QVariant resultVariant(translateWithUnit(compilationUnit));
324 if (metaType != QMetaType::fromType<QString>())
325 resultVariant.convert(metaType);
326
327 const bool hasChanged = !metaType.equals(resultVariant.constData(), dataPtr);
328 metaType.destruct(dataPtr);
329 metaType.construct(dataPtr, resultVariant.constData());
330 return hasChanged;
331 };
332}
333
334QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(
335 const QQmlPropertyData *pd,
336 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
337 const QV4::CompiledData::Binding *binding)
338{
339 auto translationBinding = qQmlTranslationPropertyBindingCreateBinding(
340 compilationUnit,
341 [binding](const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) {
342 return compilationUnit->bindingValueAsString(binding);
343 });
344
345 return QUntypedPropertyBinding(QMetaType(pd->propType()), translationBinding,
346 QPropertyBindingSourceLocation());
347}
348
349QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(
350 const QMetaType &propertyType,
351 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
352 const QQmlTranslation &translationData)
353{
354 auto translationBinding = qQmlTranslationPropertyBindingCreateBinding(
355 compilationUnit,
356 [translationData](
357 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) {
358 Q_UNUSED(compilationUnit);
359 return translationData.translate();
360 });
361
362 return QUntypedPropertyBinding(propertyType, translationBinding,
363 QPropertyBindingSourceLocation());
364}
365
366QV4::ReturnedValue QQmlPropertyBindingJSForBoundFunction::evaluate(bool *isUndefined)
367{
368 QV4::ExecutionEngine *v4 = engine()->handle();
369 int argc = 0;
370 const QV4::Value *argv = nullptr;
371 const QV4::Value *thisObject = nullptr;
372 QV4::BoundFunction *b = nullptr;
373 if ((b = m_boundFunction.as<QV4::BoundFunction>())) {
374 QV4::Heap::MemberData *args = b->boundArgs();
375 if (args) {
376 argc = args->values.size;
377 argv = args->values.data();
378 }
379 thisObject = &b->d()->boundThis;
380 }
381 QV4::Scope scope(v4);
382 QV4::JSCallData jsCall(thisObject, argv, argc);
383
384 return QQmlJavaScriptExpression::evaluate(jsCall.callData(scope), isUndefined);
385}
386
387QT_END_NAMESPACE
Combined button and popup list for selecting options.
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)