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 HasBoundFunction::No);
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 makeUntyped(binding);
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, HasBoundFunction::No);
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 makeUntyped(binding);
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, HasBoundFunction::No);
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 makeUntyped(binding);
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, HasBoundFunction::Yes);
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 makeUntyped(binding);
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,
168 QQmlPropertyIndex targetIndex,
169 HasBoundFunction hasBoundFunction)
170 : QQmlPropertyBindingBase(target, targetIndex, mt,
171 bindingFunctionVTableForQQmlPropertyBinding(mt),
172 BindingKind::JavaScript, hasBoundFunction)
173{
174 errorCallBack = bindingErrorCallback;
175}
176
177static QtPrivate::QPropertyBindingData *bindingDataFromPropertyData(QUntypedPropertyData *dataPtr, QMetaType type)
178{
179 // XXX Qt 7: We need a clean way to access the binding data
180 /* This function makes the (dangerous) assumption that if we could not get the binding data
181 from the binding storage, we must have been handed a QProperty.
182 This does hold for anything a user could write, as there the only ways of providing a bindable property
183 are to use the Q_X_BINDABLE macros, or to directly expose a QProperty.
184 As long as we can ensure that any "fancier" property we implement is not resettable, we should be fine.
185 We procede to calculate the address of the binding data pointer from the address of the data pointer
186 */
187 Q_ASSERT(dataPtr);
188 std::byte *qpropertyPointer = reinterpret_cast<std::byte *>(dataPtr);
189 qpropertyPointer += type.sizeOf();
190 constexpr auto alignment = alignof(QtPrivate::QPropertyBindingData *);
191 auto aligned = (quintptr(qpropertyPointer) + alignment - 1) & ~(alignment - 1); // ensure pointer alignment
192 return reinterpret_cast<QtPrivate::QPropertyBindingData *>(aligned);
193}
194
195void QQmlPropertyBinding::handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr)
196{
197 const QQmlPropertyData *propertyData = nullptr;
198 QQmlPropertyData valueTypeData;
199 QQmlData *data = QQmlData::get(target(), false);
200 Q_ASSERT(data);
201 if (Q_UNLIKELY(!data->propertyCache))
202 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
203
204 propertyData = data->propertyCache->property(targetIndex().coreIndex());
205 Q_ASSERT(propertyData);
206 Q_ASSERT(!targetIndex().hasValueTypeIndex());
207 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
208 // helper function for writing back value into dataPtr
209 // this is necessary for QObjectCompatProperty, which doesn't give us the "real" dataPtr
210 // if we don't write the correct value, we would otherwise set the default constructed value
211 auto writeBackCurrentValue = [&](QVariant &&currentValue) {
212 if (currentValue.metaType() != valueMetaType())
213 currentValue.convert(valueMetaType());
214 auto metaType = valueMetaType();
215 metaType.destruct(dataPtr);
216 metaType.construct(dataPtr, currentValue.constData());
217 };
218 if (prop.isResettable()) {
219 // Normally a reset would remove any existing binding; but now we need to keep the binding alive
220 // to handle the case where this binding becomes defined again
221 // We therefore detach the binding, call reset, and reattach again
222 const auto storage = qGetBindingStorage(target());
223 auto bindingData = storage->bindingData(propertyDataPtr);
224 if (!bindingData)
225 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
226 QPropertyBindingDataPointer bindingDataPointer{bindingData};
227 auto firstObserver = takeObservers();
228 bindingData->d_ref() = 0;
229 if (firstObserver) {
230 bindingDataPointer.setObservers(firstObserver.ptr);
231 }
232 Q_ASSERT(!bindingData->hasBinding());
233 setIsUndefined(true);
234 //suspend binding evaluation state for reset and subsequent read
235 auto state = QtPrivate::suspendCurrentBindingStatus();
236 prop.reset(); // May re-allocate the bindingData
237 QVariant currentValue = QVariant(prop.propertyMetaType(), propertyDataPtr);
238 QtPrivate::restoreBindingStatus(state);
239 writeBackCurrentValue(std::move(currentValue));
240
241 // Re-fetch binding data
242 bindingData = storage->bindingData(propertyDataPtr);
243 if (!bindingData)
244 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
245 bindingDataPointer = QPropertyBindingDataPointer {bindingData};
246
247 // reattach the binding (without causing a new notification)
248 if (Q_UNLIKELY(bindingData->d() & QtPrivate::QPropertyBindingData::BindingBit)) {
249 qCWarning(lcQQPropertyBinding) << "Resetting " << prop.name() << "due to the binding becoming undefined caused a new binding to be installed\n"
250 << "The old binding binding will be abandoned";
251 deref();
252 return;
253 }
254 // reset might have changed observers (?), so refresh firstObserver
255 firstObserver = bindingDataPointer.firstObserver();
256 bindingData->d_ref() = reinterpret_cast<quintptr>(this) | QtPrivate::QPropertyBindingData::BindingBit;
257 if (firstObserver)
258 prependObserver(firstObserver);
259 } else {
260 QQmlError qmlError;
261 auto location = jsExpression()->sourceLocation();
262 qmlError.setColumn(location.column);
263 qmlError.setLine(location.line);
264 qmlError.setUrl(QUrl {location.sourceFile});
265 const QString description = QStringLiteral(R"(QML %1: Unable to assign [undefined] to "%2")").arg(QQmlMetaType::prettyTypeName(target()) , prop.name());
266 qmlError.setDescription(description);
267 qmlError.setObject(target());
268 ep->warning(qmlError);
269 }
270}
271
272QString QQmlPropertyBinding::createBindingLoopErrorDescription()
273{
274 const QQmlPropertyData *propertyData = nullptr;
275 QQmlPropertyData valueTypeData;
276 QQmlData *data = QQmlData::get(target(), false);
277 Q_ASSERT(data);
278 if (Q_UNLIKELY(!data->propertyCache))
279 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
280
281 propertyData = data->propertyCache->property(targetIndex().coreIndex());
282 Q_ASSERT(propertyData);
283 Q_ASSERT(!targetIndex().hasValueTypeIndex());
284 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
285 return R"(QML %1: Binding loop detected for property "%2")"_L1.arg(QQmlMetaType::prettyTypeName(target()) , prop.name());
286}
287
288void QQmlPropertyBinding::bindingErrorCallback(QPropertyBindingPrivate *that)
289{
290 auto This = static_cast<QQmlPropertyBinding *>(that);
291 auto target = This->target();
292 auto engine = qmlEngine(target);
293 if (!engine)
294 return;
295
296 auto error = This->bindingError();
297 QQmlError qmlError;
298 auto location = This->jsExpression()->sourceLocation();
299 qmlError.setColumn(location.column);
300 qmlError.setLine(location.line);
301 qmlError.setUrl(QUrl {location.sourceFile});
302 auto description = error.description();
303 if (error.type() == QPropertyBindingError::BindingLoop) {
304 description = This->createBindingLoopErrorDescription();
305 }
306 qmlError.setDescription(description);
307 qmlError.setObject(target);
308 QQmlEnginePrivate::get(engine)->warning(qmlError);
309}
310
311template<typename TranslateWithUnit>
313 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
314 TranslateWithUnit &&translateWithUnit)
315{
316 return [compilationUnit, translateWithUnit](QMetaType metaType, void *dataPtr) -> bool {
317 // Create a dependency to the translationLanguage
318 QQmlEnginePrivate::get(compilationUnit->engine)->translationLanguage.value();
319
320 QVariant resultVariant(translateWithUnit(compilationUnit));
321 if (metaType != QMetaType::fromType<QString>())
322 resultVariant.convert(metaType);
323
324 const bool hasChanged = !metaType.equals(resultVariant.constData(), dataPtr);
325 metaType.destruct(dataPtr);
326 metaType.construct(dataPtr, resultVariant.constData());
327 return hasChanged;
328 };
329}
330
331QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(
332 const QQmlPropertyData *pd,
333 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
334 const QV4::CompiledData::Binding *binding)
335{
336 auto translationBinding = qQmlTranslationPropertyBindingCreateBinding(
337 compilationUnit,
338 [binding](const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) {
339 return compilationUnit->bindingValueAsString(binding);
340 });
341
342 return QUntypedPropertyBinding(QMetaType(pd->propType()), translationBinding,
343 QPropertyBindingSourceLocation());
344}
345
346QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(
347 const QMetaType &propertyType,
348 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
349 const QQmlTranslation &translationData)
350{
351 auto translationBinding = qQmlTranslationPropertyBindingCreateBinding(
352 compilationUnit,
353 [translationData](
354 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) {
355 Q_UNUSED(compilationUnit);
356 return translationData.translate();
357 });
358
359 return QUntypedPropertyBinding(propertyType, translationBinding,
360 QPropertyBindingSourceLocation());
361}
362
363QV4::ReturnedValue QQmlPropertyBindingJSForBoundFunction::evaluate(bool *isUndefined)
364{
365 QV4::ExecutionEngine *v4 = engine()->handle();
366 int argc = 0;
367 const QV4::Value *argv = nullptr;
368 const QV4::Value *thisObject = nullptr;
369 QV4::BoundFunction *b = nullptr;
370 if ((b = m_boundFunction.as<QV4::BoundFunction>())) {
371 QV4::Heap::MemberData *args = b->boundArgs();
372 if (args) {
373 argc = args->values.size;
374 argv = args->values.data();
375 }
376 thisObject = &b->d()->boundThis;
377 }
378 QV4::Scope scope(v4);
379 QV4::JSCallData jsCall(thisObject, argv, argc);
380
381 return QQmlJavaScriptExpression::evaluate(jsCall.callData(scope), isUndefined);
382}
383
384QT_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)