Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
5
6#include <private/qqmlbinding_p.h>
7#include <private/qqmlglobal_p.h>
8#include <private/qqmlscriptstring_p.h>
9#include <private/qv4functionobject_p.h>
10#include <private/qv4jscall_p.h>
11#include <private/qv4qmlcontext_p.h>
12
13#include <QtQml/qqmlinfo.h>
14
15#include <QtCore/qloggingcategory.h>
16
18
19using namespace Qt::Literals::StringLiterals;
20
21Q_LOGGING_CATEGORY(lcQQPropertyBinding, "qt.qml.propertybinding");
22
24 QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
26{
27 Q_ASSERT(pd);
28 return create(pd->propType(), function, obj, ctxt, scope, target, targetIndex);
29}
30
32 QObject *obj,
33 const QQmlRefPointer<QQmlContextData> &ctxt,
35 QQmlPropertyIndex targetIndex)
36{
38 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
39 auto binding = new (buffer) QQmlPropertyBinding(propertyType, target, targetIndex,
40 TargetData::WithoutBoundFunction);
41 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
42 Q_ASSERT(binding->jsExpression() == js);
43 Q_ASSERT(js->asBinding() == binding);
44 Q_UNUSED(js);
45 binding->jsExpression()->setNotifyOnValueChanged(true);
46 binding->jsExpression()->setContext(ctxt);
47 binding->jsExpression()->setScopeObject(obj);
48 binding->jsExpression()->setupFunction(scope, function);
50}
51
52QUntypedPropertyBinding QQmlPropertyBinding::createFromCodeString(const QQmlPropertyData *pd, const QString& str, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber, QObject *target, QQmlPropertyIndex targetIndex)
53{
55 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
56 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::WithoutBoundFunction);
57 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
58 Q_ASSERT(binding->jsExpression() == js);
59 Q_ASSERT(js->asBinding() == binding);
60 Q_UNUSED(js);
61 binding->jsExpression()->setNotifyOnValueChanged(true);
62 binding->jsExpression()->setContext(ctxt);
63 binding->jsExpression()->createQmlBinding(ctxt, obj, str, url, lineNumber);
65}
66
68{
69 const QQmlScriptStringPrivate *scriptPrivate = script.d.data();
70 // without a valid context, we cannot create anything
71 if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) {
72 return {};
73 }
74
75 auto scopeObject = obj ? obj : scriptPrivate->scope;
76
77 QV4::Function *runtimeFunction = nullptr;
79 QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(scriptPrivate->context);
80 QQmlEnginePrivate *engine = QQmlEnginePrivate::get(scriptPrivate->context->engine());
81 if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit()) {
82 url = ctxtdata->urlString();
83 if (scriptPrivate->bindingId != QQmlBinding::Invalid)
84 runtimeFunction = ctxtdata->typeCompilationUnit()->runtimeFunctions.at(scriptPrivate->bindingId);
85 }
86 // Do we actually have a function in the script string? If not, this becomes createCodeFromString
87 if (!runtimeFunction)
88 return createFromCodeString(property, scriptPrivate->script, obj, ctxtdata, url, scriptPrivate->lineNumber, target, targetIndex);
89
91 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
92 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(property->propType()), target, targetIndex, TargetData::WithoutBoundFunction);
93 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
94 Q_ASSERT(binding->jsExpression() == js);
95 Q_ASSERT(js->asBinding() == binding);
96 js->setContext(QQmlContextData::get(ctxt ? ctxt : scriptPrivate->context));
97
98 QV4::ExecutionEngine *v4 = engine->v4engine();
99 QV4::Scope scope(v4);
101 js->setupFunction(qmlContext, runtimeFunction);
103}
104
106{
108 + sizeof(QQmlPropertyBindingJSForBoundFunction)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
109 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::HasBoundFunction);
111 Q_ASSERT(binding->jsExpression() == js);
112 Q_ASSERT(js->asBinding() == binding);
113 Q_UNUSED(js);
114 binding->jsExpression()->setNotifyOnValueChanged(true);
115 binding->jsExpression()->setContext(ctxt);
116 binding->jsExpression()->setScopeObject(obj);
117 binding->jsExpression()->setupFunction(scope, function->function());
118 js->m_boundFunction.set(function->engine(), *function);
120}
121
135{
136 auto binding = asBinding();
137 if (!binding->propertyDataPtr)
138 return;
139 const auto currentTag = m_error.tag();
140 if (currentTag == InEvaluationLoop) {
141 QQmlError err;
143 err.setUrl(QUrl{location.sourceFile});
144 err.setLine(location.line);
145 err.setColumn(location.column);
146 const auto ctxt = context();
147 QQmlEngine *engine = ctxt ? ctxt->engine() : nullptr;
148 if (engine)
149 err.setDescription(asBinding()->createBindingLoopErrorDescription());
150 else
151 err.setDescription(QString::fromLatin1("Binding loop detected"));
152 err.setObject(asBinding()->target());
153 qmlWarning(this->scopeObject(), err);
154 return;
155 }
157 PendingBindingObserverList bindingObservers;
158 binding->evaluateRecursive(bindingObservers);
159 binding->notifyNonRecursive(bindingObservers);
161}
162
163QQmlPropertyBinding::QQmlPropertyBinding(QMetaType mt, QObject *target, QQmlPropertyIndex targetIndex, TargetData::BoundFunction hasBoundFunction)
167{
168 static_assert (std::is_trivially_destructible_v<TargetData>);
169 static_assert (sizeof(TargetData) + sizeof(DeclarativeErrorCallback) <= sizeof(QPropertyBindingSourceLocation));
170 static_assert (alignof(TargetData) <= alignof(QPropertyBindingSourceLocation));
171 const auto state = hasBoundFunction ? TargetData::HasBoundFunction : TargetData::WithoutBoundFunction;
172 new (&declarativeExtraData) TargetData {target, targetIndex, state};
173 errorCallBack = bindingErrorCallback;
174}
175
177{
178 // XXX Qt 7: We need a clean way to access the binding data
179 /* This function makes the (dangerous) assumption that if we could not get the binding data
180 from the binding storage, we must have been handed a QProperty.
181 This does hold for anything a user could write, as there the only ways of providing a bindable property
182 are to use the Q_X_BINDABLE macros, or to directly expose a QProperty.
183 As long as we can ensure that any "fancier" property we implement is not resettable, we should be fine.
184 We procede to calculate the address of the binding data pointer from the address of the data pointer
185 */
186 Q_ASSERT(dataPtr);
187 std::byte *qpropertyPointer = reinterpret_cast<std::byte *>(dataPtr);
188 qpropertyPointer += type.sizeOf();
189 constexpr auto alignment = alignof(QtPrivate::QPropertyBindingData *);
190 auto aligned = (quintptr(qpropertyPointer) + alignment - 1) & ~(alignment - 1); // ensure pointer alignment
191 return reinterpret_cast<QtPrivate::QPropertyBindingData *>(aligned);
192}
193
194void QQmlPropertyBinding::handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr)
195{
196 const QQmlPropertyData *propertyData = nullptr;
197 QQmlPropertyData valueTypeData;
198 QQmlData *data = QQmlData::get(target(), false);
199 Q_ASSERT(data);
200 if (Q_UNLIKELY(!data->propertyCache))
201 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
202
203 propertyData = data->propertyCache->property(targetIndex().coreIndex());
204 Q_ASSERT(propertyData);
205 Q_ASSERT(!targetIndex().hasValueTypeIndex());
206 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
207 // helper function for writing back value into dataPtr
208 // this is necessary for QObjectCompatProperty, which doesn't give us the "real" dataPtr
209 // if we don't write the correct value, we would otherwise set the default constructed value
210 auto writeBackCurrentValue = [&](QVariant &&currentValue) {
211 if (currentValue.metaType() != valueMetaType())
212 currentValue.convert(valueMetaType());
213 auto metaType = valueMetaType();
214 metaType.destruct(dataPtr);
215 metaType.construct(dataPtr, currentValue.constData());
216 };
217 if (prop.isResettable()) {
218 // Normally a reset would remove any existing binding; but now we need to keep the binding alive
219 // to handle the case where this binding becomes defined again
220 // We therefore detach the binding, call reset, and reattach again
221 const auto storage = qGetBindingStorage(target());
222 auto bindingData = storage->bindingData(propertyDataPtr);
223 if (!bindingData)
224 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
225 QPropertyBindingDataPointer bindingDataPointer{bindingData};
226 auto firstObserver = takeObservers();
227 bindingData->d_ref() = 0;
228 if (firstObserver) {
229 bindingDataPointer.setObservers(firstObserver.ptr);
230 }
231 Q_ASSERT(!bindingData->hasBinding());
232 setIsUndefined(true);
233 //suspend binding evaluation state for reset and subsequent read
235 prop.reset(); // May re-allocate the bindingData
238 writeBackCurrentValue(std::move(currentValue));
239
240 // Re-fetch binding data
241 bindingData = storage->bindingData(propertyDataPtr);
242 if (!bindingData)
243 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
244 bindingDataPointer = QPropertyBindingDataPointer {bindingData};
245
246 // reattach the binding (without causing a new notification)
248 qCWarning(lcQQPropertyBinding) << "Resetting " << prop.name() << "due to the binding becoming undefined caused a new binding to be installed\n"
249 << "The old binding binding will be abandoned";
250 deref();
251 return;
252 }
253 // reset might have changed observers (?), so refresh firstObserver
254 firstObserver = bindingDataPointer.firstObserver();
255 bindingData->d_ref() = reinterpret_cast<quintptr>(this) | QtPrivate::QPropertyBindingData::BindingBit;
256 if (firstObserver)
257 prependObserver(firstObserver);
258 } else {
259 QQmlError qmlError;
261 qmlError.setColumn(location.column);
262 qmlError.setLine(location.line);
263 qmlError.setUrl(QUrl {location.sourceFile});
264 const QString description = QStringLiteral(R"(QML %1: Unable to assign [undefined] to "%2")").arg(QQmlMetaType::prettyTypeName(target()) , prop.name());
265 qmlError.setDescription(description);
266 qmlError.setObject(target());
267 ep->warning(qmlError);
268 }
269}
270
271QString QQmlPropertyBinding::createBindingLoopErrorDescription()
272{
273 const QQmlPropertyData *propertyData = nullptr;
274 QQmlPropertyData valueTypeData;
275 QQmlData *data = QQmlData::get(target(), false);
276 Q_ASSERT(data);
277 if (Q_UNLIKELY(!data->propertyCache))
278 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
279
280 propertyData = data->propertyCache->property(targetIndex().coreIndex());
281 Q_ASSERT(propertyData);
282 Q_ASSERT(!targetIndex().hasValueTypeIndex());
283 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
284 return R"(QML %1: Binding loop detected for property "%2")"_L1.arg(QQmlMetaType::prettyTypeName(target()) , prop.name());
285}
286
287void QQmlPropertyBinding::bindingErrorCallback(QPropertyBindingPrivate *that)
288{
289 auto This = static_cast<QQmlPropertyBinding *>(that);
290 auto target = This->target();
291 auto engine = qmlEngine(target);
292 if (!engine)
293 return;
294
295 auto error = This->bindingError();
296 QQmlError qmlError;
297 auto location = This->jsExpression()->sourceLocation();
298 qmlError.setColumn(location.column);
299 qmlError.setLine(location.line);
300 qmlError.setUrl(QUrl {location.sourceFile});
301 auto description = error.description();
303 description = This->createBindingLoopErrorDescription();
304 }
305 qmlError.setDescription(description);
306 qmlError.setObject(target);
307 QQmlEnginePrivate::get(engine)->warning(qmlError);
308}
309
310template<typename TranslateWithUnit>
312 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
313 TranslateWithUnit &&translateWithUnit)
314{
315 return [compilationUnit, translateWithUnit](QMetaType metaType, void *dataPtr) -> bool {
316 // Create a dependency to the translationLanguage
317 QQmlEnginePrivate::get(compilationUnit->engine)->translationLanguage.value();
318
319 QVariant resultVariant(translateWithUnit(compilationUnit));
320 if (metaType != QMetaType::fromType<QString>())
321 resultVariant.convert(metaType);
322
323 const bool hasChanged = !metaType.equals(resultVariant.constData(), dataPtr);
324 metaType.destruct(dataPtr);
325 metaType.construct(dataPtr, resultVariant.constData());
326 return hasChanged;
327 };
328}
329
331 const QQmlPropertyData *pd,
332 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
333 const QV4::CompiledData::Binding *binding)
334{
335 auto translationBinding = qQmlTranslationPropertyBindingCreateBinding(
336 compilationUnit,
337 [binding](const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) {
338 return compilationUnit->bindingValueAsString(binding);
339 });
340
341 return QUntypedPropertyBinding(QMetaType(pd->propType()), translationBinding,
343}
344
346 const QMetaType &propertyType,
347 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
348 const QQmlTranslation &translationData)
349{
350 auto translationBinding = qQmlTranslationPropertyBindingCreateBinding(
351 compilationUnit,
352 [translationData](
353 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) {
354 Q_UNUSED(compilationUnit);
355 return translationData.translate();
356 });
357
358 return QUntypedPropertyBinding(propertyType, translationBinding,
360}
361
363{
365 int argc = 0;
366 const QV4::Value *argv = nullptr;
367 const QV4::Value *thisObject = nullptr;
368 QV4::BoundFunction *b = nullptr;
370 QV4::Heap::MemberData *args = b->boundArgs();
371 if (args) {
372 argc = args->values.size;
373 argv = args->values.data();
374 }
375 thisObject = &b->d()->boundThis;
376 }
377 QV4::Scope scope(v4);
378 QV4::JSCallData jsCall(thisObject, argv, argc);
379
380 return QQmlJavaScriptExpression::evaluate(jsCall.callData(scope), isUndefined);
381}
382
QV4::ExecutionEngine * handle() const
Definition qjsengine.h:298
qsizetype size() const noexcept
Definition qlist.h:397
pointer data()
Definition qlist.h:431
\inmodule QtCore
Definition qmetatype.h:341
\inmodule QtCore
Definition qobject.h:103
friend class QPropertyBindingPrivatePtr
QMetaType valueMetaType() const
QPropertyObserverPointer takeObservers()
static constexpr size_t getSizeEnsuringAlignment()
void prependObserver(QPropertyObserverPointer observer)
QUntypedPropertyData * propertyDataPtr
static QQmlRefPointer< QQmlContextData > get(QQmlContext *context)
The QQmlContext class defines a context within a QML engine.
Definition qqmlcontext.h:25
static QQmlData * get(QObjectPrivate *priv, bool create)
Definition qqmldata_p.h:199
void warning(const QQmlError &)
static QQmlEnginePrivate * get(QQmlEngine *e)
The QQmlEngine class provides an environment for instantiating QML components.
Definition qqmlengine.h:57
The QQmlError class encapsulates a QML error.
Definition qqmlerror.h:18
void setObject(QObject *)
Sets the nearest object where this error occurred.
void setColumn(int)
Sets the error column number.
void setLine(int)
Sets the error line number.
void setDescription(const QString &)
Sets the error description.
void setUrl(const QUrl &)
Sets the url for the file that caused this error.
QV4::ReturnedValue evaluate(bool *isUndefined)
QQmlRefPointer< QQmlContextData > context() const
virtual QQmlSourceLocation sourceLocation() const
QTaggedPointer< QQmlDelayedError, Tag > m_error
static QString prettyTypeName(const QObject *object)
Returns the pretty QML type name (e.g.
static QQmlPropertyCache::ConstPtr propertyCache(QObject *object, QTypeRevision version=QTypeRevision())
Returns a QQmlPropertyCache for obj if one is available.
QV4::ReturnedValue evaluate(bool *isUndefined)
static QUntypedPropertyBinding createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function, QObject *obj, const QQmlRefPointer< QQmlContextData > &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex)
static QUntypedPropertyBinding createFromCodeString(const QQmlPropertyData *property, const QString &str, QObject *obj, const QQmlRefPointer< QQmlContextData > &ctxt, const QString &url, quint16 lineNumber, QObject *target, QQmlPropertyIndex targetIndex)
QQmlPropertyBindingJS * jsExpression()
static QUntypedPropertyBinding create(const QQmlPropertyData *pd, QV4::Function *function, QObject *obj, const QQmlRefPointer< QQmlContextData > &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex)
static QUntypedPropertyBinding createFromScriptString(const QQmlPropertyData *property, const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt, QObject *target, QQmlPropertyIndex targetIndex)
QMetaType propType() const
static QQmlProperty restore(QObject *, const QQmlPropertyData &, const QQmlPropertyData *, const QQmlRefPointer< QQmlContextData > &)
The QQmlProperty class abstracts accessing properties on objects created from QML.
QMetaType propertyMetaType() const
Returns the metatype of the property.
bool isResettable() const
Returns true if the property is resettable, otherwise false.
bool reset() const
Resets the property and returns true if the property is resettable.
The QQmlScriptString class encapsulates a script and its context.
static QUntypedPropertyBinding Q_QML_EXPORT create(const QQmlPropertyData *pd, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &compilationUnit, const QV4::CompiledData::Binding *binding)
T * data()
Returns a pointer to the shared data object.
Definition qshareddata.h:47
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
void setTag(Tag tag)
Tag tag() const noexcept
\inmodule QtCore
Definition qurl.h:94
\inmodule QtCore
Definition qvariant.h:65
static constexpr quintptr BindingBit
QString str
[2]
uint alignment
else opt state
[0]
Combined button and popup list for selecting options.
quint64 ReturnedValue
BindingEvaluationState * suspendCurrentBindingStatus()
void restoreBindingStatus(BindingEvaluationState *status)
#define Q_UNLIKELY(x)
DBusConnection const char DBusError * error
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
const QBindingStorage * qGetBindingStorage(const QObject *o)
Definition qobject.h:469
GLint location
GLboolean GLboolean GLboolean b
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLenum type
GLenum target
GLhandleARB obj
[2]
QQmlEngine * qmlEngine(const QObject *obj)
Definition qqml.cpp:80
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:75
Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me)
static QtPrivate::QPropertyBindingData * bindingDataFromPropertyData(QUntypedPropertyData *dataPtr, QMetaType type)
auto qQmlTranslationPropertyBindingCreateBinding(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &compilationUnit, TranslateWithUnit &&translateWithUnit)
const QtPrivate::BindingFunctionVTable * bindingFunctionVTableForQQmlPropertyBinding(QMetaType type)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
Int aligned(Int v, Int byteAlign)
#define QStringLiteral(str)
#define Q_UNUSED(x)
unsigned short quint16
Definition qtypes.h:48
size_t quintptr
Definition qtypes.h:167
const char property[13]
Definition qwizard.cpp:101
QStorageInfo storage
[1]
QUrl url("example.com")
[constructor-url-reference]
obj metaObject() -> className()
view create()
QJSValueList args
QJSEngine engine
[0]
ExecutionContext * rootContext() const
static Heap::QmlContext * create(QV4::ExecutionContext *parent, QQmlRefPointer< QQmlContextData > context, QObject *scopeObject)