diff options
-rw-r--r-- | src/qml/qml/qqmlbinding.cpp | 119 | ||||
-rw-r--r-- | src/qml/qml/qqmlbinding_p.h | 5 | ||||
-rw-r--r-- | src/qml/qml/qqmljavascriptexpression.cpp | 147 | ||||
-rw-r--r-- | src/qml/qml/qqmljavascriptexpression_p.h | 4 |
4 files changed, 213 insertions, 62 deletions
diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index b21468951d..b9d783736c 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -247,13 +247,28 @@ protected: auto ep = QQmlEnginePrivate::get(scope.engine); ep->referenceScarceResources(); - bool isUndefined = false; - - QV4::ScopedValue result(scope, evaluate(&isUndefined)); - bool error = false; - if (!watcher.wasDeleted() && isAddedToObject() && !hasError()) - error = !write(result, isUndefined, flags); + auto canWrite = [&]() { return !watcher.wasDeleted() && isAddedToObject() && !hasError(); }; + const QV4::Function *v4Function = function(); + if (v4Function && v4Function->aotFunction && !hasBoundFunction()) { + const auto returnType = v4Function->aotFunction->returnType; + const auto size = returnType.sizeOf(); + if (Q_LIKELY(size > 0)) { + Q_ALLOCA_VAR(void, result, size); + returnType.construct(result); + const bool isUndefined = !evaluate(result, returnType); + if (canWrite()) + error = !write(result, returnType, isUndefined, flags); + returnType.destruct(result); + } else if (canWrite()) { + error = !write(QV4::Encode::undefined(), true, flags); + } + } else { + bool isUndefined = false; + QV4::ScopedValue result(scope, evaluate(&isUndefined)); + if (canWrite()) + error = !write(result, isUndefined, flags); + } if (!watcher.wasDeleted()) { @@ -273,6 +288,7 @@ protected: } virtual bool write(const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) = 0; + virtual bool write(void *result, QMetaType type, bool isUndefined, QQmlPropertyData::WriteFlags flags) = 0; }; template<int StaticPropType> @@ -280,6 +296,30 @@ class GenericBinding: public QQmlNonbindingBinding { protected: // Returns true if successful, false if an error description was set on expression + Q_ALWAYS_INLINE bool write(void *result, QMetaType type, bool isUndefined, + QQmlPropertyData::WriteFlags flags) override final + { + QQmlPropertyData *pd; + QQmlPropertyData vpd; + getPropertyData(&pd, &vpd); + Q_ASSERT(pd); + + if (Q_UNLIKELY(isUndefined || vpd.isValid())) { + return slowWrite(*pd, vpd, engine()->handle()->metaTypeToJS(type, result), + isUndefined, flags); + } + + if ((StaticPropType == QMetaType::UnknownType && pd->propType() == type) + || StaticPropType == type.id()) { + Q_ASSERT(targetObject()); + return pd->writeProperty(targetObject(), result, flags); + } + + // If the type didn't match, we need to do JavaScript conversion. This should be rare. + return write(engine()->handle()->metaTypeToJS(type, result), isUndefined, flags); + } + + // Returns true if successful, false if an error description was set on expression Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) override final { @@ -684,6 +724,48 @@ public: {} protected: + Q_ALWAYS_INLINE bool write(void *result, QMetaType type, bool isUndefined, + QQmlPropertyData::WriteFlags flags) override final + { + QQmlPropertyData *pd; + QQmlPropertyData vtpd; + getPropertyData(&pd, &vtpd); + if (Q_UNLIKELY(isUndefined || vtpd.isValid())) + return slowWrite(*pd, vtpd, result, type, isUndefined, flags); + + // Check if the result is a QObject: + QObject *resultObject = nullptr; + QQmlMetaObject resultMo; + const auto typeFlags = type.flags(); + if (!result || ((typeFlags & QMetaType::IsPointer) && !*static_cast<void **>(result))) { + // Special case: we can always write a nullptr. Don't bother checking anything else. + return pd->writeProperty(targetObject(), &resultObject, flags); + } else if (typeFlags & QMetaType::PointerToQObject) { + resultObject = *static_cast<QObject **>(result); + if (!resultObject) + return pd->writeProperty(targetObject(), &resultObject, flags); + if (QQmlData *ddata = QQmlData::get(resultObject, false)) + resultMo = ddata->propertyCache; + if (resultMo.isNull()) + resultMo = resultObject->metaObject(); + } else if (type == QMetaType::fromType<QVariant>()) { + QVariant value = *static_cast<QVariant *>(result); + QQmlEngine *qmlEngine = engine(); + Q_ASSERT(qmlEngine); + resultMo = QQmlPropertyPrivate::rawMetaObjectForType( + QQmlEnginePrivate::get(qmlEngine), value.userType()); + if (resultMo.isNull()) + return slowWrite(*pd, vtpd, result, type, isUndefined, flags); + resultObject = *static_cast<QObject *const *>(value.constData()); + } else { + return slowWrite(*pd, vtpd, result, type, isUndefined, flags); + } + + return compareAndSet(resultMo, resultObject, pd, flags, [&]() { + return slowWrite(*pd, vtpd, result, type, isUndefined, flags); + }); + } + Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) override final { @@ -711,8 +793,9 @@ protected: } else if (auto variant = result.as<QV4::VariantObject>()) { QVariant value = variant->d()->data(); QQmlEngine *qmlEngine = engine(); + Q_ASSERT(qmlEngine); resultMo = QQmlPropertyPrivate::rawMetaObjectForType( - qmlEngine ? QQmlEnginePrivate::get(qmlEngine) : nullptr, value.userType()); + QQmlEnginePrivate::get(qmlEngine), value.userType()); if (resultMo.isNull()) return slowWrite(*pd, vtpd, result, isUndefined, flags); resultObject = *static_cast<QObject *const *>(value.constData()); @@ -720,7 +803,25 @@ protected: return slowWrite(*pd, vtpd, result, isUndefined, flags); } - // Compare & set: + return compareAndSet(resultMo, resultObject, pd, flags, [&]() { + return slowWrite(*pd, vtpd, result, isUndefined, flags); + }); + } + +private: + using QQmlBinding::slowWrite; + bool slowWrite(const QQmlPropertyData &core, const QQmlPropertyData &valueTypeData, + void *result, QMetaType type, bool isUndefined, + QQmlPropertyData::WriteFlags flags) + { + return slowWrite(core, valueTypeData, engine()->handle()->metaTypeToJS(type, result), + isUndefined, flags); + } + + template<typename SlowWrite> + bool compareAndSet(const QQmlMetaObject &resultMo, QObject *resultObject, QQmlPropertyData *pd, + QQmlPropertyData::WriteFlags flags, const SlowWrite &slowWrite) const + { if (QQmlMetaObject::canConvert(resultMo, targetMetaObject)) { return pd->writeProperty(targetObject(), &resultObject, flags); } else if (!resultObject && QQmlMetaObject::canConvert(targetMetaObject, resultMo)) { @@ -729,7 +830,7 @@ protected: // the property type. return pd->writeProperty(targetObject(), &resultObject, flags); } else { - return slowWrite(*pd, vtpd, result, isUndefined, flags); + return slowWrite(); } } }; diff --git a/src/qml/qml/qqmlbinding_p.h b/src/qml/qml/qqmlbinding_p.h index bf73c24c9e..9542f3acca 100644 --- a/src/qml/qml/qqmlbinding_p.h +++ b/src/qml/qml/qqmlbinding_p.h @@ -116,6 +116,7 @@ public: void setBoundFunction(QV4::BoundFunction *boundFunction) { m_boundFunction.set(boundFunction->engine(), *boundFunction); } + bool hasBoundFunction() const { return m_boundFunction.valueRef(); } /** * This method returns a snapshot of the currently tracked dependencies of @@ -138,6 +139,10 @@ protected: const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags); QV4::ReturnedValue evaluate(bool *isUndefined); + bool evaluate(void *result, QMetaType type) + { + return QQmlJavaScriptExpression::evaluate(&result, &type, 0); + } private: inline bool updatingFlag() const; diff --git a/src/qml/qml/qqmljavascriptexpression.cpp b/src/qml/qml/qqmljavascriptexpression.cpp index 260ac79c25..c84523adfe 100644 --- a/src/qml/qml/qqmljavascriptexpression.cpp +++ b/src/qml/qml/qqmljavascriptexpression.cpp @@ -172,11 +172,6 @@ void QQmlJavaScriptExpression::setContext(const QQmlRefPointer<QQmlContextData> context->addExpression(this); } -QV4::Function *QQmlJavaScriptExpression::function() const -{ - return m_v4Function; -} - void QQmlJavaScriptExpression::refresh() { } @@ -190,6 +185,75 @@ QV4::ReturnedValue QQmlJavaScriptExpression::evaluate(bool *isUndefined) return evaluate(jsCall.callData(scope), isUndefined); } +class QQmlJavaScriptExpressionCapture +{ + Q_DISABLE_COPY_MOVE(QQmlJavaScriptExpressionCapture) +public: + QQmlJavaScriptExpressionCapture(QQmlJavaScriptExpression *expression, QQmlEngine *engine) + : watcher(expression) + , capture(engine, expression, &watcher) + , ep(QQmlEnginePrivate::get(engine)) + { + Q_ASSERT(expression->notifyOnValueChanged() || expression->activeGuards.isEmpty()); + + lastPropertyCapture = ep->propertyCapture; + ep->propertyCapture = expression->notifyOnValueChanged() ? &capture : nullptr; + + if (expression->notifyOnValueChanged()) + capture.guards.copyAndClearPrepend(expression->activeGuards); + } + + ~QQmlJavaScriptExpressionCapture() + { + if (capture.errorString) { + for (int ii = 0; ii < capture.errorString->count(); ++ii) + qWarning("%s", qPrintable(capture.errorString->at(ii))); + delete capture.errorString; + capture.errorString = nullptr; + } + + while (QQmlJavaScriptExpressionGuard *g = capture.guards.takeFirst()) + g->Delete(); + + if (!watcher.wasDeleted()) + capture.expression->setTranslationsCaptured(capture.translationCaptured); + + ep->propertyCapture = lastPropertyCapture; + } + + bool catchException(const QV4::Scope &scope) const + { + if (scope.hasException()) { + if (watcher.wasDeleted()) + scope.engine->catchException(); // ignore exception + else + capture.expression->delayedError()->catchJavaScriptException(scope.engine); + return true; + } + + if (!watcher.wasDeleted() && capture.expression->hasDelayedError()) + capture.expression->delayedError()->clearError(); + return false; + } + +private: + QQmlJavaScriptExpression::DeleteWatcher watcher; + QQmlPropertyCapture capture; + QQmlEnginePrivate *ep; + QQmlPropertyCapture *lastPropertyCapture; +}; + +static inline QV4::ReturnedValue thisObject(QObject *scopeObject, QV4::ExecutionEngine *v4) +{ + if (scopeObject) { + // The result of wrap() can only be null, undefined, or an object. + const QV4::ReturnedValue scope = QV4::QObjectWrapper::wrap(v4, scopeObject); + if (QV4::Value::fromReturnedValue(scope).isManaged()) + return scope; + } + return v4->globalObject->asReturnedValue(); +} + QV4::ReturnedValue QQmlJavaScriptExpression::evaluate(QV4::CallData *callData, bool *isUndefined) { Q_ASSERT(m_context && m_context->engine()); @@ -201,69 +265,48 @@ QV4::ReturnedValue QQmlJavaScriptExpression::evaluate(QV4::CallData *callData, b return QV4::Encode::undefined(); } - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(m_context->engine()); - // All code that follows must check with watcher before it accesses data members // incase we have been deleted. - DeleteWatcher watcher(this); + QQmlEngine *qmlEngine = m_context->engine(); + QQmlJavaScriptExpressionCapture capture(this, qmlEngine); - Q_ASSERT(notifyOnValueChanged() || activeGuards.isEmpty()); - QQmlPropertyCapture capture(m_context->engine(), this, &watcher); - - QQmlPropertyCapture *lastPropertyCapture = ep->propertyCapture; - ep->propertyCapture = notifyOnValueChanged() ? &capture : nullptr; - - - if (notifyOnValueChanged()) - capture.guards.copyAndClearPrepend(activeGuards); - - QV4::ExecutionEngine *v4 = m_context->engine()->handle(); - callData->thisObject = v4->globalObject; - if (scopeObject()) { - QV4::ReturnedValue scope = QV4::QObjectWrapper::wrap(v4, scopeObject()); - if (QV4::Value::fromReturnedValue(scope).isObject()) - callData->thisObject = scope; - } + QV4::Scope scope(qmlEngine->handle()); + callData->thisObject = thisObject(scopeObject(), scope.engine); Q_ASSERT(m_qmlScope.valueRef()); - QV4::ReturnedValue res = v4Function->call( + QV4::ScopedValue result(scope, v4Function->call( &(callData->thisObject.asValue<QV4::Value>()), callData->argValues<QV4::Value>(), callData->argc(), - static_cast<QV4::ExecutionContext *>(m_qmlScope.valueRef())); - QV4::Scope scope(v4); - QV4::ScopedValue result(scope, res); + static_cast<QV4::ExecutionContext *>(m_qmlScope.valueRef()))); - if (scope.hasException()) { - if (watcher.wasDeleted()) - scope.engine->catchException(); // ignore exception - else - delayedError()->catchJavaScriptException(scope.engine); + if (capture.catchException(scope)) { if (isUndefined) *isUndefined = true; - } else { - if (isUndefined) - *isUndefined = result->isUndefined(); - - if (!watcher.wasDeleted() && hasDelayedError()) - delayedError()->clearError(); + } else if (isUndefined) { + *isUndefined = result->isUndefined(); } - if (capture.errorString) { - for (int ii = 0; ii < capture.errorString->count(); ++ii) - qWarning("%s", qPrintable(capture.errorString->at(ii))); - delete capture.errorString; - capture.errorString = nullptr; - } + return result->asReturnedValue(); +} - while (QQmlJavaScriptExpressionGuard *g = capture.guards.takeFirst()) - g->Delete(); +bool QQmlJavaScriptExpression::evaluate(void **a, QMetaType *types, int argc) +{ + Q_ASSERT(m_context && m_context->engine()); - if (!watcher.wasDeleted()) - setTranslationsCaptured(capture.translationCaptured); + // All code that follows must check with watcher before it accesses data members + // incase we have been deleted. + QQmlEngine *qmlEngine = m_context->engine(); + QQmlJavaScriptExpressionCapture capture(this, qmlEngine); - ep->propertyCapture = lastPropertyCapture; + QV4::Scope scope(qmlEngine->handle()); + QV4::ScopedValue self(scope, thisObject(scopeObject(), scope.engine)); - return result->asReturnedValue(); + Q_ASSERT(m_qmlScope.valueRef()); + Q_ASSERT(function()); + function()->call(self, a, types, argc, + static_cast<QV4::ExecutionContext *>(m_qmlScope.valueRef())); + + return !capture.catchException(scope); } void QQmlPropertyCapture::captureProperty(QQmlNotifier *n) diff --git a/src/qml/qml/qqmljavascriptexpression_p.h b/src/qml/qml/qqmljavascriptexpression_p.h index 499d4d430f..6e4c0ac381 100644 --- a/src/qml/qml/qqmljavascriptexpression_p.h +++ b/src/qml/qml/qqmljavascriptexpression_p.h @@ -108,6 +108,7 @@ public: QV4::ReturnedValue evaluate(bool *isUndefined); QV4::ReturnedValue evaluate(QV4::CallData *callData, bool *isUndefined); + bool evaluate(void **a, QMetaType *types, int argc); inline bool notifyOnValueChanged() const; @@ -135,7 +136,7 @@ public: *listHead = this; } - QV4::Function *function() const; + QV4::Function *function() const { return m_v4Function; } virtual void refresh(); @@ -207,6 +208,7 @@ private: friend class QQmlPropertyCapture; friend void QQmlJavaScriptExpressionGuard_callback(QQmlNotifierEndpoint *, void **); friend class QQmlTranslationBinding; + friend class QQmlJavaScriptExpressionCapture; // Not refcounted as the context will clear the expressions when destructed. QQmlContextData *m_context; |