/**************************************************************************** ** ** 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$ ** ****************************************************************************/ #ifndef QQMLPROPERTYBINDING_P_H #define QQMLPROPERTYBINDING_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include #include #include #include #include QT_BEGIN_NAMESPACE namespace QV4 { struct BoundFunction; } class QQmlPropertyBinding; class QQmlScriptString; class Q_QML_PRIVATE_EXPORT QQmlPropertyBindingJS : public QQmlJavaScriptExpression { bool mustCaptureBindableProperty() const final {return false;} friend class QQmlPropertyBinding; void expressionChanged() override; QQmlPropertyBinding *asBinding() { return const_cast(static_cast(this)->asBinding()); } inline QQmlPropertyBinding const *asBinding() const; }; class Q_QML_PRIVATE_EXPORT QQmlPropertyBindingJSForBoundFunction : public QQmlPropertyBindingJS { public: QV4::ReturnedValue evaluate(bool *isUndefined); QV4::PersistentValue m_boundFunction; }; class Q_QML_PRIVATE_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(static_cast(this)->jsExpression()); } QQmlPropertyBindingJS const *jsExpression() const { return std::launder(reinterpret_cast( reinterpret_cast(this) + QPropertyBindingPrivate::getSizeEnsuringAlignment() + jsExpressionOffsetLength())); } static QUntypedPropertyBinding create(const QQmlPropertyData *pd, QV4::Function *function, QObject *obj, const QQmlRefPointer &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex); static QUntypedPropertyBinding create(QMetaType propertyType, QV4::Function *function, QObject *obj, const QQmlRefPointer &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex); static QUntypedPropertyBinding createFromCodeString(const QQmlPropertyData *property, const QString &str, QObject *obj, const QQmlRefPointer &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 &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(binding)->isUndefined(); } template static bool doEvaluate(QMetaType metaType, QUntypedPropertyData *dataPtr, void *f) { auto address = static_cast(f); address -= QPropertyBindingPrivate::getSizeEnsuringAlignment(); // f now points to QPropertyBindingPrivate suboject // and that has the same address as QQmlPropertyBinding return reinterpret_cast(address)->evaluate(metaType, dataPtr); } bool hasDependencies() { return (dependencyObserverCount > 0) || !jsExpression()->activeGuards.isEmpty(); } private: template bool evaluate(QMetaType metaType, void *dataPtr); Q_NEVER_INLINE void handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr); QString createBindingLoopErrorDescription(QJSEnginePrivate *ep); 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(&declarativeExtraData))->target; } QQmlPropertyIndex targetIndex() { return std::launder(reinterpret_cast(&declarativeExtraData))->targetIndex; } bool hasBoundFunction() { return std::launder(reinterpret_cast(&declarativeExtraData))->hasBoundFunction; } bool isUndefined() const { return std::launder(reinterpret_cast(&declarativeExtraData))->isUndefined; } void setIsUndefined(bool isUndefined) { std::launder(reinterpret_cast(&declarativeExtraData))->isUndefined = isUndefined; } static void bindingErrorCallback(QPropertyBindingPrivate *); }; template struct Print {}; namespace QtPrivate { template inline constexpr BindingFunctionVTable bindingFunctionVTableForQQmlPropertyBinding = { &QQmlPropertyBinding::doEvaluate, [](void *qpropertyBinding){ QQmlPropertyBinding *binding = reinterpret_cast(qpropertyBinding); binding->jsExpression()->~QQmlPropertyBindingJS(); binding->~QQmlPropertyBinding(); auto address = static_cast(qpropertyBinding); delete[] address; }, [](void *, void *){}, 0 }; } inline const QtPrivate::BindingFunctionVTable *bindingFunctionVTableForQQmlPropertyBinding(QMetaType type) { #define FOR_TYPE(TYPE) \ case TYPE: return &QtPrivate::bindingFunctionVTableForQQmlPropertyBinding 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; return &QtPrivate::bindingFunctionVTableForQQmlPropertyBinding; } #undef FOR_TYPE } class QQmlTranslationPropertyBinding { public: static QUntypedPropertyBinding Q_QML_PRIVATE_EXPORT create(const QQmlPropertyData *pd, const QQmlRefPointer &compilationUnit, const QV4::CompiledData::Binding *binding); }; inline const QQmlPropertyBinding *QQmlPropertyBindingJS::asBinding() const { return std::launder(reinterpret_cast( reinterpret_cast(this) - QPropertyBindingPrivate::getSizeEnsuringAlignment() - QQmlPropertyBinding::jsExpressionOffsetLength())); } static_assert(sizeof(QQmlPropertyBinding) == sizeof(QPropertyBindingPrivate)); // else the whole offset computatation will break template bool compareAndAssign(void *dataPtr, const void *result) { if (*static_cast(result) == *static_cast(dataPtr)) return false; *static_cast(dataPtr) = *static_cast(result); return true; } template 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); // No need to construct here. evaluate() expects uninitialized memory. const auto size = [&]() -> qsizetype { switch (type) { case QMetaType::QObjectStar: return sizeof(QObject *); case QMetaType::Bool: return sizeof(bool); case QMetaType::Int: return (sizeof(int)); case QMetaType::Double: return (sizeof(double)); case QMetaType::Float: return (sizeof(float)); case QMetaType::QString: return (sizeof(QString)); default: return metaType.sizeOf(); } }(); Q_ALLOCA_VAR(void, result, size); const bool evaluatedToUndefined = !jsExpression()->evaluate(&result, &metaType, 0); if (!handleErrorAndUndefined(evaluatedToUndefined)) return false; switch (type) { case QMetaType::QObjectStar: return compareAndAssign(dataPtr, result); case QMetaType::Bool: return compareAndAssign(dataPtr, result); case QMetaType::Int: return compareAndAssign(dataPtr, result); case QMetaType::Double: return compareAndAssign(dataPtr, result); case QMetaType::Float: return compareAndAssign(dataPtr, result); case QMetaType::QString: { const bool hasChanged = compareAndAssign(dataPtr, result); static_cast(result)->~QString(); return hasChanged; } default: break; } const bool hasChanged = !metaType.equals(result, dataPtr); if (hasChanged) { metaType.destruct(dataPtr); metaType.construct(dataPtr, result); } metaType.destruct(result); return hasChanged; } bool evaluatedToUndefined = false; QV4::Scope scope(engine->handle()); QV4::ScopedValue result(scope, static_cast( 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(dataPtr)) return false; *static_cast(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(dataPtr)) return false; *static_cast(dataPtr) = i; return true; } case QMetaType::Double: if (result->isNumber()) { double d = result->asDouble(); if (d == *static_cast(dataPtr)) return false; *static_cast(dataPtr) = d; return true; } break; case QMetaType::Float: if (result->isNumber()) { float d = float(result->asDouble()); if (d == *static_cast(dataPtr)) return false; *static_cast(dataPtr) = d; return true; } break; case QMetaType::QString: if (result->isString()) { QString s = result->toQStringNoThrow(); if (s == *static_cast(dataPtr)) return false; *static_cast(dataPtr) = s; return true; } break; default: break; } QVariant resultVariant(scope.engine->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