| 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 | |
| 4 | #include "qqmlpropertybinding_p.h" |
| 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 | |
| 17 | QT_BEGIN_NAMESPACE |
| 18 | |
| 19 | using namespace Qt::Literals::StringLiterals; |
| 20 | |
| 21 | Q_LOGGING_CATEGORY(lcQQPropertyBinding, "qt.qml.propertybinding" ); |
| 22 | |
| 23 | QUntypedPropertyBinding QQmlPropertyBinding::create(const QQmlPropertyData *pd, QV4::Function *function, |
| 24 | QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, |
| 25 | QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex) |
| 26 | { |
| 27 | Q_ASSERT(pd); |
| 28 | return create(propertyType: pd->propType(), function, obj, ctxt, scope, target, targetIndex); |
| 29 | } |
| 30 | |
| 31 | QUntypedPropertyBinding QQmlPropertyBinding::create(QMetaType propertyType, QV4::Function *function, |
| 32 | QObject *obj, |
| 33 | const QQmlRefPointer<QQmlContextData> &ctxt, |
| 34 | QV4::ExecutionContext *scope, QObject *target, |
| 35 | QQmlPropertyIndex targetIndex) |
| 36 | { |
| 37 | auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment() |
| 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(qmlContext: scope, f: function); |
| 49 | return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data())); |
| 50 | } |
| 51 | |
| 52 | QUntypedPropertyBinding QQmlPropertyBinding::createFromCodeString(const QQmlPropertyData *pd, const QString& str, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber, QObject *target, QQmlPropertyIndex targetIndex) |
| 53 | { |
| 54 | auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment() |
| 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, scope: obj, code: str, filename: url, line: lineNumber); |
| 64 | return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data())); |
| 65 | } |
| 66 | |
| 67 | QUntypedPropertyBinding QQmlPropertyBinding::createFromScriptString(const QQmlPropertyData *property, const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt, QObject *target, QQmlPropertyIndex targetIndex) |
| 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; |
| 78 | QString url; |
| 79 | QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(context: scriptPrivate->context); |
| 80 | QQmlEnginePrivate *engine = QQmlEnginePrivate::get(e: 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(i: 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(pd: property, str: scriptPrivate->script, obj, ctxt: ctxtdata, url, lineNumber: scriptPrivate->lineNumber, target, targetIndex); |
| 89 | |
| 90 | auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment() |
| 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(context: ctxt ? ctxt : scriptPrivate->context)); |
| 97 | |
| 98 | QV4::ExecutionEngine *v4 = engine->v4engine(); |
| 99 | QV4::Scope scope(v4); |
| 100 | QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(parent: v4->rootContext(), context: ctxtdata, scopeObject)); |
| 101 | js->setupFunction(qmlContext, f: runtimeFunction); |
| 102 | return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data())); |
| 103 | } |
| 104 | |
| 105 | QUntypedPropertyBinding QQmlPropertyBinding::createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex) |
| 106 | { |
| 107 | auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment() |
| 108 | + sizeof(QQmlPropertyBindingJSForBoundFunction)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[] |
| 109 | auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::HasBoundFunction); |
| 110 | auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJSForBoundFunction(); |
| 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(qmlContext: scope, f: function->function()); |
| 118 | js->m_boundFunction.set(engine: function->engine(), value: *function); |
| 119 | return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data())); |
| 120 | } |
| 121 | |
| 122 | /*! |
| 123 | \fn bool QQmlPropertyBindingJS::hasDependencies() |
| 124 | \internal |
| 125 | |
| 126 | Returns true if this binding has dependencies. |
| 127 | Dependencies can be either QProperty dependencies or dependencies of |
| 128 | the JS expression (aka activeGuards). Translations end up as a QProperty |
| 129 | dependency, so they do not need any special handling |
| 130 | Note that a QQmlPropertyBinding never stores qpropertyChangeTriggers. |
| 131 | */ |
| 132 | |
| 133 | |
| 134 | void QQmlPropertyBindingJS::expressionChanged() |
| 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; |
| 142 | auto location = QQmlJavaScriptExpression::sourceLocation(); |
| 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(ba: "Binding loop detected" )); |
| 152 | err.setObject(asBinding()->target()); |
| 153 | qmlWarning(me: this->scopeObject(), error: err); |
| 154 | return; |
| 155 | } |
| 156 | m_error.setTag(InEvaluationLoop); |
| 157 | PendingBindingObserverList bindingObservers; |
| 158 | binding->evaluateRecursive(bindingObservers); |
| 159 | binding->notifyNonRecursive(bindingObservers); |
| 160 | m_error.setTag(NoTag); |
| 161 | } |
| 162 | |
| 163 | QQmlPropertyBinding::QQmlPropertyBinding(QMetaType mt, QObject *target, QQmlPropertyIndex targetIndex, TargetData::BoundFunction hasBoundFunction) |
| 164 | : QPropertyBindingPrivate(mt, |
| 165 | bindingFunctionVTableForQQmlPropertyBinding(type: mt), |
| 166 | QPropertyBindingSourceLocation(), true) |
| 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 | |
| 176 | static QtPrivate::QPropertyBindingData *bindingDataFromPropertyData(QUntypedPropertyData *dataPtr, QMetaType type) |
| 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 | |
| 194 | void QQmlPropertyBinding::handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr) |
| 195 | { |
| 196 | const QQmlPropertyData *propertyData = nullptr; |
| 197 | QQmlPropertyData valueTypeData; |
| 198 | QQmlData *data = QQmlData::get(object: target(), create: false); |
| 199 | Q_ASSERT(data); |
| 200 | if (Q_UNLIKELY(!data->propertyCache)) |
| 201 | data->propertyCache = QQmlMetaType::propertyCache(metaObject: target()->metaObject()); |
| 202 | |
| 203 | propertyData = data->propertyCache->property(index: 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 &¤tValue) { |
| 211 | if (currentValue.metaType() != valueMetaType()) |
| 212 | currentValue.convert(type: valueMetaType()); |
| 213 | auto metaType = valueMetaType(); |
| 214 | metaType.destruct(data: dataPtr); |
| 215 | metaType.construct(where: dataPtr, copy: 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(o: target()); |
| 222 | auto bindingData = storage->bindingData(data: propertyDataPtr); |
| 223 | if (!bindingData) |
| 224 | bindingData = bindingDataFromPropertyData(dataPtr: propertyDataPtr, type: propertyData->propType()); |
| 225 | QPropertyBindingDataPointer bindingDataPointer{.ptr: 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 |
| 234 | auto state = QtPrivate::suspendCurrentBindingStatus(); |
| 235 | prop.reset(); // May re-allocate the bindingData |
| 236 | QVariant currentValue = QVariant(prop.propertyMetaType(), propertyDataPtr); |
| 237 | QtPrivate::restoreBindingStatus(status: state); |
| 238 | writeBackCurrentValue(std::move(currentValue)); |
| 239 | |
| 240 | // Re-fetch binding data |
| 241 | bindingData = storage->bindingData(data: propertyDataPtr); |
| 242 | if (!bindingData) |
| 243 | bindingData = bindingDataFromPropertyData(dataPtr: propertyDataPtr, type: propertyData->propType()); |
| 244 | bindingDataPointer = QPropertyBindingDataPointer {.ptr: bindingData}; |
| 245 | |
| 246 | // reattach the binding (without causing a new notification) |
| 247 | if (Q_UNLIKELY(bindingData->d() & QtPrivate::QPropertyBindingData::BindingBit)) { |
| 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(observer: firstObserver); |
| 258 | } else { |
| 259 | QQmlError qmlError; |
| 260 | auto location = jsExpression()->sourceLocation(); |
| 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(args: QQmlMetaType::prettyTypeName(object: target()) , args: prop.name()); |
| 265 | qmlError.setDescription(description); |
| 266 | qmlError.setObject(target()); |
| 267 | ep->warning(qmlError); |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | QString QQmlPropertyBinding::createBindingLoopErrorDescription() |
| 272 | { |
| 273 | const QQmlPropertyData *propertyData = nullptr; |
| 274 | QQmlPropertyData valueTypeData; |
| 275 | QQmlData *data = QQmlData::get(object: target(), create: false); |
| 276 | Q_ASSERT(data); |
| 277 | if (Q_UNLIKELY(!data->propertyCache)) |
| 278 | data->propertyCache = QQmlMetaType::propertyCache(metaObject: target()->metaObject()); |
| 279 | |
| 280 | propertyData = data->propertyCache->property(index: 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(args: QQmlMetaType::prettyTypeName(object: target()) , args: prop.name()); |
| 285 | } |
| 286 | |
| 287 | void 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(); |
| 302 | if (error.type() == QPropertyBindingError::BindingLoop) { |
| 303 | description = This->createBindingLoopErrorDescription(); |
| 304 | } |
| 305 | qmlError.setDescription(description); |
| 306 | qmlError.setObject(target); |
| 307 | QQmlEnginePrivate::get(e: engine)->warning(qmlError); |
| 308 | } |
| 309 | |
| 310 | template<typename TranslateWithUnit> |
| 311 | auto qQmlTranslationPropertyBindingCreateBinding( |
| 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(e: compilationUnit->engine)->translationLanguage.value(); |
| 318 | |
| 319 | QVariant resultVariant(translateWithUnit(compilationUnit)); |
| 320 | if (metaType != QMetaType::fromType<QString>()) |
| 321 | resultVariant.convert(type: metaType); |
| 322 | |
| 323 | const bool hasChanged = !metaType.equals(lhs: resultVariant.constData(), rhs: dataPtr); |
| 324 | metaType.destruct(data: dataPtr); |
| 325 | metaType.construct(where: dataPtr, copy: resultVariant.constData()); |
| 326 | return hasChanged; |
| 327 | }; |
| 328 | } |
| 329 | |
| 330 | QUntypedPropertyBinding QQmlTranslationPropertyBinding::create( |
| 331 | const QQmlPropertyData *pd, |
| 332 | const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, |
| 333 | const QV4::CompiledData::Binding *binding) |
| 334 | { |
| 335 | auto translationBinding = qQmlTranslationPropertyBindingCreateBinding( |
| 336 | compilationUnit, |
| 337 | translateWithUnit: [binding](const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) { |
| 338 | return compilationUnit->bindingValueAsString(binding); |
| 339 | }); |
| 340 | |
| 341 | return QUntypedPropertyBinding(QMetaType(pd->propType()), translationBinding, |
| 342 | QPropertyBindingSourceLocation()); |
| 343 | } |
| 344 | |
| 345 | QUntypedPropertyBinding QQmlTranslationPropertyBinding::create( |
| 346 | const QMetaType &propertyType, |
| 347 | const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, |
| 348 | const QQmlTranslation &translationData) |
| 349 | { |
| 350 | auto translationBinding = qQmlTranslationPropertyBindingCreateBinding( |
| 351 | compilationUnit, |
| 352 | translateWithUnit: [translationData]( |
| 353 | const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) { |
| 354 | Q_UNUSED(compilationUnit); |
| 355 | return translationData.translate(); |
| 356 | }); |
| 357 | |
| 358 | return QUntypedPropertyBinding(propertyType, translationBinding, |
| 359 | QPropertyBindingSourceLocation()); |
| 360 | } |
| 361 | |
| 362 | QV4::ReturnedValue QQmlPropertyBindingJSForBoundFunction::evaluate(bool *isUndefined) |
| 363 | { |
| 364 | QV4::ExecutionEngine *v4 = engine()->handle(); |
| 365 | int argc = 0; |
| 366 | const QV4::Value *argv = nullptr; |
| 367 | const QV4::Value *thisObject = nullptr; |
| 368 | QV4::BoundFunction *b = nullptr; |
| 369 | if ((b = m_boundFunction.as<QV4::BoundFunction>())) { |
| 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(callData: jsCall.callData(scope), isUndefined); |
| 381 | } |
| 382 | |
| 383 | QT_END_NAMESPACE |
| 384 | |