diff options
Diffstat (limited to 'src/qml/qml/qqmlpropertybinding_p.h')
-rw-r--r-- | src/qml/qml/qqmlpropertybinding_p.h | 367 |
1 files changed, 308 insertions, 59 deletions
diff --git a/src/qml/qml/qqmlpropertybinding_p.h b/src/qml/qml/qqmlpropertybinding_p.h index 7c7fac866a..840239285e 100644 --- a/src/qml/qml/qqmlpropertybinding_p.h +++ b/src/qml/qml/qqmlpropertybinding_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QQMLPROPERTYBINDING_P_H #define QQMLPROPERTYBINDING_P_H @@ -51,11 +15,12 @@ // We mean it. // -#include <QtCore/qproperty.h> -#include <QtCore/private/qproperty_p.h> +#include <private/qqmljavascriptexpression_p.h> +#include <private/qqmlpropertydata_p.h> +#include <private/qv4alloca_p.h> +#include <private/qqmltranslation_p.h> -#include "qqmlpropertydata_p.h" -#include "qqmljavascriptexpression_p.h" +#include <QtCore/qproperty.h> #include <memory> @@ -66,76 +31,151 @@ namespace QV4 { } class QQmlPropertyBinding; +class QQmlScriptString; -class Q_QML_PRIVATE_EXPORT QQmlPropertyBindingJS : public QQmlJavaScriptExpression +class Q_QML_EXPORT QQmlPropertyBindingJS : public QQmlJavaScriptExpression { + bool mustCaptureBindableProperty() const final {return false;} + friend class QQmlPropertyBinding; void expressionChanged() override; QQmlPropertyBinding *asBinding() { return const_cast<QQmlPropertyBinding *>(static_cast<const QQmlPropertyBindingJS *>(this)->asBinding()); } - QQmlPropertyBinding const *asBinding() const;; + + inline QQmlPropertyBinding const *asBinding() const; }; -class Q_QML_PRIVATE_EXPORT QQmlPropertyBindingJSForBoundFunction : public QQmlPropertyBindingJS +class Q_QML_EXPORT QQmlPropertyBindingJSForBoundFunction : public QQmlPropertyBindingJS { public: QV4::ReturnedValue evaluate(bool *isUndefined); QV4::PersistentValue m_boundFunction; }; -class Q_QML_PRIVATE_EXPORT QQmlPropertyBinding : public QPropertyBindingPrivate +class Q_QML_EXPORT QQmlPropertyBinding : public QPropertyBindingPrivate { friend class QQmlPropertyBindingJS; + + static constexpr std::size_t jsExpressionOffsetLength() { + struct composite { QQmlPropertyBinding b; QQmlPropertyBindingJS js; }; + QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF + return sizeof (QQmlPropertyBinding) - offsetof(composite, js); + QT_WARNING_POP + } + public: QQmlPropertyBindingJS *jsExpression() { return const_cast<QQmlPropertyBindingJS *>(static_cast<const QQmlPropertyBinding *>(this)->jsExpression()); } - QQmlPropertyBindingJS const *jsExpression() const; + + QQmlPropertyBindingJS const *jsExpression() const + { + return std::launder(reinterpret_cast<QQmlPropertyBindingJS const *>( + reinterpret_cast<std::byte const*>(this) + + QPropertyBindingPrivate::getSizeEnsuringAlignment() + + jsExpressionOffsetLength())); + } static QUntypedPropertyBinding create(const QQmlPropertyData *pd, QV4::Function *function, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex); + static QUntypedPropertyBinding create(QMetaType propertyType, QV4::Function *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); + static QUntypedPropertyBinding createFromScriptString(const QQmlPropertyData *property, + const QQmlScriptString& script, QObject *obj, + QQmlContext *ctxt, QObject *target, + QQmlPropertyIndex targetIndex); static QUntypedPropertyBinding createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex); + static bool isUndefined(const QUntypedPropertyBinding &binding) + { + return isUndefined(QPropertyBindingPrivate::get(binding)); + } + + static bool isUndefined(const QPropertyBindingPrivate *binding) + { + if (!(binding && binding->hasCustomVTable())) + return false; + return static_cast<const QQmlPropertyBinding *>(binding)->isUndefined(); + } + template<QMetaType::Type type> static bool doEvaluate(QMetaType metaType, QUntypedPropertyData *dataPtr, void *f) { auto address = static_cast<std::byte*>(f); - address -= sizeof (QPropertyBindingPrivate); // f now points to QPropertyBindingPrivate suboject + address -= QPropertyBindingPrivate::getSizeEnsuringAlignment(); // f now points to QPropertyBindingPrivate suboject // and that has the same address as QQmlPropertyBinding - return reinterpret_cast<QQmlPropertyBinding *>(address)->evaluate(metaType, dataPtr); + return reinterpret_cast<QQmlPropertyBinding *>(address)->evaluate<type>(metaType, dataPtr); } -private: - QQmlPropertyBinding(QMetaType metaType, QObject *target, QQmlPropertyIndex targetIndex, bool hasBoundFunction); + bool hasDependencies() + { + return (dependencyObserverCount > 0) || !jsExpression()->activeGuards.isEmpty(); + } +private: + template <QMetaType::Type type> bool evaluate(QMetaType metaType, void *dataPtr); - QString createBindingLoopErrorDescription(QJSEnginePrivate *ep); + Q_NEVER_INLINE void handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr); + + QString createBindingLoopErrorDescription(); struct TargetData { + enum BoundFunction : bool { + WithoutBoundFunction = false, + HasBoundFunction = true, + }; + TargetData(QObject *target, QQmlPropertyIndex index, BoundFunction state) + : target(target), targetIndex(index), hasBoundFunction(state) + {} QObject *target; QQmlPropertyIndex targetIndex; bool hasBoundFunction; + bool isUndefined = false; }; + QQmlPropertyBinding(QMetaType metaType, QObject *target, QQmlPropertyIndex targetIndex, TargetData::BoundFunction hasBoundFunction); + + QObject *target() + { + return std::launder(reinterpret_cast<TargetData *>(&declarativeExtraData))->target; + } + + QQmlPropertyIndex targetIndex() + { + return std::launder(reinterpret_cast<TargetData *>(&declarativeExtraData))->targetIndex; + } + + bool hasBoundFunction() + { + return std::launder(reinterpret_cast<TargetData *>(&declarativeExtraData))->hasBoundFunction; + } + + bool isUndefined() const + { + return std::launder(reinterpret_cast<TargetData const *>(&declarativeExtraData))->isUndefined; + } - QObject *target(); - QQmlPropertyIndex targetIndex(); - bool hasBoundFunction(); + void setIsUndefined(bool isUndefined) + { + std::launder(reinterpret_cast<TargetData *>(&declarativeExtraData))->isUndefined = isUndefined; + } static void bindingErrorCallback(QPropertyBindingPrivate *); }; @@ -144,9 +184,9 @@ template <auto I> struct Print {}; namespace QtPrivate { -template<> -inline constexpr BindingFunctionVTable bindingFunctionVTable<QQmlPropertyBinding> = { - &QQmlPropertyBinding::doEvaluate, +template<QMetaType::Type type> +inline constexpr BindingFunctionVTable bindingFunctionVTableForQQmlPropertyBinding = { + &QQmlPropertyBinding::doEvaluate<type>, [](void *qpropertyBinding){ QQmlPropertyBinding *binding = reinterpret_cast<QQmlPropertyBinding *>(qpropertyBinding); binding->jsExpression()->~QQmlPropertyBindingJS(); @@ -159,14 +199,223 @@ inline constexpr BindingFunctionVTable bindingFunctionVTable<QQmlPropertyBinding }; } +inline const QtPrivate::BindingFunctionVTable *bindingFunctionVTableForQQmlPropertyBinding(QMetaType type) +{ +#define FOR_TYPE(TYPE) \ + case TYPE: return &QtPrivate::bindingFunctionVTableForQQmlPropertyBinding<TYPE> + switch (type.id()) { + FOR_TYPE(QMetaType::Int); + FOR_TYPE(QMetaType::QString); + FOR_TYPE(QMetaType::Double); + FOR_TYPE(QMetaType::Float); + FOR_TYPE(QMetaType::Bool); + default: + if (type.flags() & QMetaType::PointerToQObject) + return &QtPrivate::bindingFunctionVTableForQQmlPropertyBinding<QMetaType::QObjectStar>; + return &QtPrivate::bindingFunctionVTableForQQmlPropertyBinding<QMetaType::UnknownType>; + } +#undef FOR_TYPE +} + class QQmlTranslationPropertyBinding { public: - static QUntypedPropertyBinding Q_QML_PRIVATE_EXPORT create(const QQmlPropertyData *pd, + static QUntypedPropertyBinding Q_QML_EXPORT create(const QQmlPropertyData *pd, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding); + static QUntypedPropertyBinding Q_QML_EXPORT + create(const QMetaType &pd, + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, + const QQmlTranslation &translationData); }; +inline const QQmlPropertyBinding *QQmlPropertyBindingJS::asBinding() const +{ + return std::launder(reinterpret_cast<QQmlPropertyBinding const *>( + reinterpret_cast<std::byte const*>(this) + - QPropertyBindingPrivate::getSizeEnsuringAlignment() + - QQmlPropertyBinding::jsExpressionOffsetLength())); +} + +static_assert(sizeof(QQmlPropertyBinding) == sizeof(QPropertyBindingPrivate)); // else the whole offset computatation will break +template<typename T> +bool compareAndAssign(void *dataPtr, const void *result) +{ + if (*static_cast<const T *>(result) == *static_cast<const T *>(dataPtr)) + return false; + *static_cast<T *>(dataPtr) = *static_cast<const T *>(result); + return true; +} + +template <QMetaType::Type type> +bool QQmlPropertyBinding::evaluate(QMetaType metaType, void *dataPtr) +{ + const auto ctxt = jsExpression()->context(); + QQmlEngine *engine = ctxt ? ctxt->engine() : nullptr; + if (!engine) { + QPropertyBindingError error(QPropertyBindingError::EvaluationError); + if (auto currentBinding = QPropertyBindingPrivate::currentlyEvaluatingBinding()) + currentBinding->setError(std::move(error)); + return false; + } + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); + ep->referenceScarceResources(); + + const auto handleErrorAndUndefined = [&](bool evaluatedToUndefined) { + ep->dereferenceScarceResources(); + if (jsExpression()->hasError()) { + QPropertyBindingError error(QPropertyBindingError::UnknownError, + jsExpression()->delayedError()->error().description()); + QPropertyBindingPrivate::currentlyEvaluatingBinding()->setError(std::move(error)); + bindingErrorCallback(this); + return false; + } + + if (evaluatedToUndefined) { + handleUndefinedAssignment(ep, dataPtr); + // if property has been changed due to reset, reset is responsible for + // notifying observers + return false; + } else if (isUndefined()) { + setIsUndefined(false); + } + + return true; + }; + + if (!hasBoundFunction()) { + Q_ASSERT(metaType.sizeOf() > 0); + + using Tuple = std::tuple<qsizetype, bool, bool>; + const auto [size, needsConstruction, needsDestruction] = [&]() -> Tuple { + switch (type) { + case QMetaType::QObjectStar: return Tuple(sizeof(QObject *), false, false); + case QMetaType::Bool: return Tuple(sizeof(bool), false, false); + case QMetaType::Int: return Tuple(sizeof(int), false, false); + case QMetaType::Double: return Tuple(sizeof(double), false, false); + case QMetaType::Float: return Tuple(sizeof(float), false, false); + case QMetaType::QString: return Tuple(sizeof(QString), true, true); + default: { + const auto flags = metaType.flags(); + return Tuple( + metaType.sizeOf(), + flags & QMetaType::NeedsConstruction, + flags & QMetaType::NeedsDestruction); + } + } + }(); + Q_ALLOCA_VAR(void, result, size); + if (needsConstruction) + metaType.construct(result); + + const bool evaluatedToUndefined = !jsExpression()->evaluate(&result, &metaType, 0); + if (!handleErrorAndUndefined(evaluatedToUndefined)) + return false; + + switch (type) { + case QMetaType::QObjectStar: + return compareAndAssign<QObject *>(dataPtr, result); + case QMetaType::Bool: + return compareAndAssign<bool>(dataPtr, result); + case QMetaType::Int: + return compareAndAssign<int>(dataPtr, result); + case QMetaType::Double: + return compareAndAssign<double>(dataPtr, result); + case QMetaType::Float: + return compareAndAssign<float>(dataPtr, result); + case QMetaType::QString: { + const bool hasChanged = compareAndAssign<QString>(dataPtr, result); + static_cast<QString *>(result)->~QString(); + return hasChanged; + } + default: + break; + } + + const bool hasChanged = !metaType.equals(result, dataPtr); + if (hasChanged) { + if (needsDestruction) + metaType.destruct(dataPtr); + metaType.construct(dataPtr, result); + } + if (needsDestruction) + metaType.destruct(result); + return hasChanged; + } + + bool evaluatedToUndefined = false; + QV4::Scope scope(engine->handle()); + QV4::ScopedValue result(scope, static_cast<QQmlPropertyBindingJSForBoundFunction *>( + jsExpression())->evaluate(&evaluatedToUndefined)); + + if (!handleErrorAndUndefined(evaluatedToUndefined)) + return false; + + switch (type) { + case QMetaType::Bool: { + bool b; + if (result->isBoolean()) + b = result->booleanValue(); + else + b = result->toBoolean(); + if (b == *static_cast<bool *>(dataPtr)) + return false; + *static_cast<bool *>(dataPtr) = b; + return true; + } + case QMetaType::Int: { + int i; + if (result->isInteger()) + i = result->integerValue(); + else if (result->isNumber()) { + i = QV4::StaticValue::toInteger(result->doubleValue()); + } else { + break; + } + if (i == *static_cast<int *>(dataPtr)) + return false; + *static_cast<int *>(dataPtr) = i; + return true; + } + case QMetaType::Double: + if (result->isNumber()) { + double d = result->asDouble(); + if (d == *static_cast<double *>(dataPtr)) + return false; + *static_cast<double *>(dataPtr) = d; + return true; + } + break; + case QMetaType::Float: + if (result->isNumber()) { + float d = float(result->asDouble()); + if (d == *static_cast<float *>(dataPtr)) + return false; + *static_cast<float *>(dataPtr) = d; + return true; + } + break; + case QMetaType::QString: + if (result->isString()) { + QString s = result->toQStringNoThrow(); + if (s == *static_cast<QString *>(dataPtr)) + return false; + *static_cast<QString *>(dataPtr) = s; + return true; + } + break; + default: + break; + } + + QVariant resultVariant(QV4::ExecutionEngine::toVariant(result, metaType)); + resultVariant.convert(metaType); + const bool hasChanged = !metaType.equals(resultVariant.constData(), dataPtr); + metaType.destruct(dataPtr); + metaType.construct(dataPtr, resultVariant.constData()); + return hasChanged; +} + QT_END_NAMESPACE #endif // QQMLPROPERTYBINDING_P_H |