Qt 6.x
The Qt SDK
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
19Q_LOGGING_CATEGORY(lcQQPropertyBinding, "qt.qml.propertybinding");
20
24{
25 Q_ASSERT(pd);
26 return create(pd->propType(), function, obj, ctxt, scope, target, targetIndex);
27}
28
30 QObject *obj,
33 QQmlPropertyIndex targetIndex)
34{
36 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
37 auto binding = new (buffer) QQmlPropertyBinding(propertyType, target, targetIndex,
38 TargetData::WithoutBoundFunction);
39 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
40 Q_ASSERT(binding->jsExpression() == js);
41 Q_ASSERT(js->asBinding() == binding);
42 Q_UNUSED(js);
43 binding->jsExpression()->setNotifyOnValueChanged(true);
44 binding->jsExpression()->setContext(ctxt);
45 binding->jsExpression()->setScopeObject(obj);
46 binding->jsExpression()->setupFunction(scope, function);
48}
49
51{
53 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
54 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::WithoutBoundFunction);
55 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
56 Q_ASSERT(binding->jsExpression() == js);
57 Q_ASSERT(js->asBinding() == binding);
58 Q_UNUSED(js);
59 binding->jsExpression()->setNotifyOnValueChanged(true);
60 binding->jsExpression()->setContext(ctxt);
61 binding->jsExpression()->createQmlBinding(ctxt, obj, str, url, lineNumber);
63}
64
66{
67 const QQmlScriptStringPrivate *scriptPrivate = script.d.data();
68 // without a valid context, we cannot create anything
69 if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) {
70 return {};
71 }
72
73 auto scopeObject = obj ? obj : scriptPrivate->scope;
74
75 QV4::Function *runtimeFunction = nullptr;
79 if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit()) {
80 url = ctxtdata->urlString();
81 if (scriptPrivate->bindingId != QQmlBinding::Invalid)
82 runtimeFunction = ctxtdata->typeCompilationUnit()->runtimeFunctions.at(scriptPrivate->bindingId);
83 }
84 // Do we actually have a function in the script string? If not, this becomes createCodeFromString
85 if (!runtimeFunction)
86 return createFromCodeString(property, scriptPrivate->script, obj, ctxtdata, url, scriptPrivate->lineNumber, target, targetIndex);
87
89 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
90 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(property->propType()), target, targetIndex, TargetData::WithoutBoundFunction);
91 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
92 Q_ASSERT(binding->jsExpression() == js);
93 Q_ASSERT(js->asBinding() == binding);
94 js->setContext(QQmlContextData::get(ctxt ? ctxt : scriptPrivate->context));
95
96 QV4::ExecutionEngine *v4 = engine->v4engine();
97 QV4::Scope scope(v4);
99 js->setupFunction(qmlContext, runtimeFunction);
101}
102
104{
106 + sizeof(QQmlPropertyBindingJSForBoundFunction)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
107 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::HasBoundFunction);
109 Q_ASSERT(binding->jsExpression() == js);
110 Q_ASSERT(js->asBinding() == binding);
111 Q_UNUSED(js);
112 binding->jsExpression()->setNotifyOnValueChanged(true);
113 binding->jsExpression()->setContext(ctxt);
114 binding->jsExpression()->setScopeObject(obj);
115 binding->jsExpression()->setupFunction(scope, function->function());
116 js->m_boundFunction.set(function->engine(), *function);
118}
119
133{
134 auto binding = asBinding();
135 if (!binding->propertyDataPtr)
136 return;
137 const auto currentTag = m_error.tag();
138 if (currentTag == InEvaluationLoop) {
139 QQmlError err;
141 err.setUrl(QUrl{location.sourceFile});
142 err.setLine(location.line);
143 err.setColumn(location.column);
144 const auto ctxt = context();
145 QQmlEngine *engine = ctxt ? ctxt->engine() : nullptr;
146 if (engine)
147 err.setDescription(asBinding()->createBindingLoopErrorDescription());
148 else
149 err.setDescription(QString::fromLatin1("Binding loop detected"));
150 err.setObject(asBinding()->target());
151 qmlWarning(this->scopeObject(), err);
152 return;
153 }
155 PendingBindingObserverList bindingObservers;
156 binding->evaluateRecursive(bindingObservers);
157 binding->notifyNonRecursive(bindingObservers);
159}
160
161QQmlPropertyBinding::QQmlPropertyBinding(QMetaType mt, QObject *target, QQmlPropertyIndex targetIndex, TargetData::BoundFunction hasBoundFunction)
165{
166 static_assert (std::is_trivially_destructible_v<TargetData>);
167 static_assert (sizeof(TargetData) + sizeof(DeclarativeErrorCallback) <= sizeof(QPropertyBindingSourceLocation));
168 static_assert (alignof(TargetData) <= alignof(QPropertyBindingSourceLocation));
169 const auto state = hasBoundFunction ? TargetData::HasBoundFunction : TargetData::WithoutBoundFunction;
170 new (&declarativeExtraData) TargetData {target, targetIndex, state};
171 errorCallBack = bindingErrorCallback;
172}
173
175{
176 // XXX Qt 7: We need a clean way to access the binding data
177 /* This function makes the (dangerous) assumption that if we could not get the binding data
178 from the binding storage, we must have been handed a QProperty.
179 This does hold for anything a user could write, as there the only ways of providing a bindable property
180 are to use the Q_X_BINDABLE macros, or to directly expose a QProperty.
181 As long as we can ensure that any "fancier" property we implement is not resettable, we should be fine.
182 We procede to calculate the address of the binding data pointer from the address of the data pointer
183 */
184 Q_ASSERT(dataPtr);
185 std::byte *qpropertyPointer = reinterpret_cast<std::byte *>(dataPtr);
186 qpropertyPointer += type.sizeOf();
187 constexpr auto alignment = alignof(QtPrivate::QPropertyBindingData *);
188 auto aligned = (quintptr(qpropertyPointer) + alignment - 1) & ~(alignment - 1); // ensure pointer alignment
189 return reinterpret_cast<QtPrivate::QPropertyBindingData *>(aligned);
190}
191
192void QQmlPropertyBinding::handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr)
193{
194 const QQmlPropertyData *propertyData = nullptr;
195 QQmlPropertyData valueTypeData;
196 QQmlData *data = QQmlData::get(target(), false);
197 Q_ASSERT(data);
198 if (Q_UNLIKELY(!data->propertyCache))
199 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
200
201 propertyData = data->propertyCache->property(targetIndex().coreIndex());
202 Q_ASSERT(propertyData);
203 Q_ASSERT(!targetIndex().hasValueTypeIndex());
204 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
205 // helper function for writing back value into dataPtr
206 // this is necessary for QObjectCompatProperty, which doesn't give us the "real" dataPtr
207 // if we don't write the correct value, we would otherwise set the default constructed value
208 auto writeBackCurrentValue = [&](QVariant &&currentValue) {
209 if (currentValue.metaType() != valueMetaType())
210 currentValue.convert(valueMetaType());
211 auto metaType = valueMetaType();
212 metaType.destruct(dataPtr);
213 metaType.construct(dataPtr, currentValue.constData());
214 };
215 if (prop.isResettable()) {
216 // Normally a reset would remove any existing binding; but now we need to keep the binding alive
217 // to handle the case where this binding becomes defined again
218 // We therefore detach the binding, call reset, and reattach again
219 const auto storage = qGetBindingStorage(target());
220 auto bindingData = storage->bindingData(propertyDataPtr);
221 if (!bindingData)
222 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
223 QPropertyBindingDataPointer bindingDataPointer{bindingData};
224 auto firstObserver = takeObservers();
225 bindingData->d_ref() = 0;
226 if (firstObserver) {
227 bindingDataPointer.setObservers(firstObserver.ptr);
228 }
229 Q_ASSERT(!bindingData->hasBinding());
230 setIsUndefined(true);
231 //suspend binding evaluation state for reset and subsequent read
233 prop.reset(); // May re-allocate the bindingData
236 writeBackCurrentValue(std::move(currentValue));
237
238 // Re-fetch binding data
239 bindingData = storage->bindingData(propertyDataPtr);
240 if (!bindingData)
241 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
242 bindingDataPointer = QPropertyBindingDataPointer {bindingData};
243
244 // reattach the binding (without causing a new notification)
246 qCWarning(lcQQPropertyBinding) << "Resetting " << prop.name() << "due to the binding becoming undefined caused a new binding to be installed\n"
247 << "The old binding binding will be abandoned";
248 deref();
249 return;
250 }
251 // reset might have changed observers (?), so refresh firstObserver
252 firstObserver = bindingDataPointer.firstObserver();
253 bindingData->d_ref() = reinterpret_cast<quintptr>(this) | QtPrivate::QPropertyBindingData::BindingBit;
254 if (firstObserver) {
255 bindingDataPointer.setObservers(firstObserver.ptr);
256 prependObserver(firstObserver);
257 }
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 QStringLiteral(R"(QML %1: Binding loop detected for property "%2")").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);
308}
309
310template<typename TranslateWithUnit>
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,
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,
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:292
qsizetype size() const noexcept
Definition qlist.h:386
pointer data()
Definition qlist.h:414
\inmodule QtCore
Definition qmetatype.h:320
\inmodule QtCore
Definition qobject.h:90
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
bool isValid() const
Returns whether the context is valid.
QQmlEngine * engine() const
Return the context's QQmlEngine, or \nullptr if the context has no QQmlEngine or the QQmlEngine was d...
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_PRIVATE_EXPORT create(const QQmlPropertyData *pd, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &compilationUnit, const QV4::CompiledData::Binding *binding)
QString translate() const
T * data()
Returns a pointer to the shared data object.
Definition qshareddata.h:47
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5710
void setTag(Tag tag)
Tag tag() const noexcept
\inmodule QtCore
Definition qurl.h:94
\inmodule QtCore
Definition qvariant.h:64
bool convert(QMetaType type)
Casts the variant to the requested type, targetType.
const void * constData() const
Definition qvariant.h:446
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 DBusBusType DBusError return DBusConnection DBusHandleMessageFunction function
DBusConnection const char DBusError * error
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
const QBindingStorage * qGetBindingStorage(const QObject *o)
Definition qobject.h:429
GLint location
GLboolean GLboolean GLboolean b
GLenum GLuint buffer
GLenum type
GLenum target
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLhandleARB obj
[2]
QQmlEngine * qmlEngine(const QObject *obj)
Definition qqml.cpp:76
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:71
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:43
size_t quintptr
Definition qtypes.h:72
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
CallData * callData(const Scope &scope, const FunctionObject *f=nullptr) const
Definition qv4jscall_p.h:89
static Heap::QmlContext * create(QV4::ExecutionContext *parent, QQmlRefPointer< QQmlContextData > context, QObject *scopeObject)