/**************************************************************************** ** ** Copyright (C) 2016 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$ ** ****************************************************************************/ #include "qqmlbinding_p.h" #include "qqml.h" #include "qqmlcontext.h" #include "qqmlinfo.h" #include "qqmldata_p.h" #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt) { QQmlBinding *b = newBinding(QQmlEnginePrivate::get(ctxt), property); if (ctxt && !ctxt->isValid()) return b; const QQmlScriptStringPrivate *scriptPrivate = script.d.data(); if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) return b; QString url; QV4::Function *runtimeFunction = 0; QQmlContextData *ctxtdata = QQmlContextData::get(scriptPrivate->context); QQmlEnginePrivate *engine = QQmlEnginePrivate::get(scriptPrivate->context->engine()); if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit) { url = ctxtdata->urlString(); if (scriptPrivate->bindingId != QQmlBinding::Invalid) runtimeFunction = ctxtdata->typeCompilationUnit->runtimeFunctions.at(scriptPrivate->bindingId); } b->setNotifyOnValueChanged(true); b->QQmlJavaScriptExpression::setContext(QQmlContextData::get(ctxt ? ctxt : scriptPrivate->context)); b->setScopeObject(obj ? obj : scriptPrivate->scope); QV4::ExecutionEngine *v4 = QQmlEnginePrivate::get(b->context()->engine)->v4engine(); if (runtimeFunction) { QV4::Scope scope(v4); QV4::Scoped qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxtdata, b->scopeObject())); b->setupFunction(qmlContext, runtimeFunction); } else { QString code = scriptPrivate->script; b->createQmlBinding(b->context(), b->scopeObject(), code, url, scriptPrivate->lineNumber); } return b; } QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QString &str, QObject *obj, QQmlContextData *ctxt, const QString &url, quint16 lineNumber) { QQmlBinding *b = newBinding(QQmlEnginePrivate::get(ctxt), property); b->setNotifyOnValueChanged(true); b->QQmlJavaScriptExpression::setContext(ctxt); b->setScopeObject(obj); b->createQmlBinding(ctxt, obj, str, url, lineNumber); return b; } QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, QV4::Function *function, QObject *obj, QQmlContextData *ctxt, QV4::ExecutionContext *scope) { QQmlBinding *b = newBinding(QQmlEnginePrivate::get(ctxt), property); b->setNotifyOnValueChanged(true); b->QQmlJavaScriptExpression::setContext(ctxt); b->setScopeObject(obj); Q_ASSERT(scope); b->setupFunction(scope, function); return b; } QQmlBinding::~QQmlBinding() { } void QQmlBinding::setNotifyOnValueChanged(bool v) { QQmlJavaScriptExpression::setNotifyOnValueChanged(v); } void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) { if (!enabledFlag() || !context() || !context()->isValid()) return; // Check that the target has not been deleted if (QQmlData::wasDeleted(targetObject())) return; // Check for a binding update loop if (Q_UNLIKELY(updatingFlag())) { QQmlPropertyData *d = nullptr; QQmlPropertyData vtd; getPropertyData(&d, &vtd); Q_ASSERT(d); QQmlProperty p = QQmlPropertyPrivate::restore(targetObject(), *d, &vtd, 0); QQmlAbstractBinding::printBindingLoopError(p); return; } setUpdatingFlag(true); DeleteWatcher watcher(this); QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context()->engine); QV4::Scope scope(ep->v4engine()); if (canUseAccessor()) flags.setFlag(QQmlPropertyData::BypassInterceptor); QQmlBindingProfiler prof(ep->profiler, function()); doUpdate(watcher, flags, scope); if (!watcher.wasDeleted()) setUpdatingFlag(false); } // QQmlBindingBinding is for target properties which are of type "binding" (instead of, say, int or // double). The reason for being is that GenericBinding::fastWrite needs a compile-time constant // expression for the switch for the compiler to generate the optimal code, but // qMetaTypeId() needs to be used for the ID. So QQmlBinding::newBinding uses that // to instantiate this class. class QQmlBindingBinding: public QQmlBinding { protected: void doUpdate(const DeleteWatcher &, QQmlPropertyData::WriteFlags flags, QV4::Scope &) Q_DECL_OVERRIDE Q_DECL_FINAL { Q_ASSERT(!m_targetIndex.hasValueTypeIndex()); QQmlPropertyData *pd = nullptr; getPropertyData(&pd, nullptr); QQmlBinding *thisPtr = this; pd->writeProperty(*m_target, &thisPtr, flags); } }; // For any target that's not a binding, we have a common doUpdate. However, depending on the type // of the target property, there is a specialized write method. class QQmlNonbindingBinding: public QQmlBinding { protected: void doUpdate(const DeleteWatcher &watcher, QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) Q_DECL_OVERRIDE Q_DECL_FINAL { auto ep = QQmlEnginePrivate::get(scope.engine); ep->referenceScarceResources(); bool isUndefined = false; QV4::ScopedCallData callData(scope); QQmlJavaScriptExpression::evaluate(callData, &isUndefined, scope); bool error = false; if (!watcher.wasDeleted() && isAddedToObject() && !hasError()) error = !write(scope.result, isUndefined, flags); if (!watcher.wasDeleted()) { if (error) { delayedError()->setErrorLocation(sourceLocation()); delayedError()->setErrorObject(m_target.data()); } if (hasError()) { if (!delayedError()->addError(ep)) ep->warning(this->error(context()->engine)); } else { clearError(); } cancelPermanentGuards(); } ep->dereferenceScarceResources(); } virtual bool write(const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) = 0; }; template class GenericBinding: public QQmlNonbindingBinding { protected: // 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) Q_DECL_OVERRIDE Q_DECL_FINAL { Q_ASSERT(targetObject()); QQmlPropertyData *pd; QQmlPropertyData vpd; getPropertyData(&pd, &vpd); Q_ASSERT(pd); int propertyType = StaticPropType; // If the binding is specialized to a type, the if and switch below will be constant-folded. if (propertyType == QMetaType::UnknownType) propertyType = pd->propType(); if (Q_LIKELY(!isUndefined && !vpd.isValid())) { switch (propertyType) { case QMetaType::Bool: if (result.isBoolean()) return doStore(result.booleanValue(), pd, flags); else return doStore(result.toBoolean(), pd, flags); case QMetaType::Int: if (result.isInteger()) return doStore(result.integerValue(), pd, flags); else if (result.isNumber()) return doStore(result.doubleValue(), pd, flags); break; case QMetaType::Double: if (result.isNumber()) return doStore(result.asDouble(), pd, flags); break; case QMetaType::Float: if (result.isNumber()) return doStore(result.asDouble(), pd, flags); break; case QMetaType::QString: if (result.isString()) return doStore(result.toQStringNoThrow(), pd, flags); break; default: if (const QV4::QQmlValueTypeWrapper *vtw = result.as()) { if (vtw->d()->valueType->typeId == pd->propType()) { return vtw->write(m_target.data(), pd->coreIndex()); } } break; } } return slowWrite(*pd, vpd, result, isUndefined, flags); } template Q_ALWAYS_INLINE bool doStore(T value, const QQmlPropertyData *pd, QQmlPropertyData::WriteFlags flags) const { void *o = &value; return pd->writeProperty(targetObject(), o, flags); } }; Q_NEVER_INLINE bool QQmlBinding::slowWrite(const QQmlPropertyData &core, const QQmlPropertyData &valueTypeData, const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) { QQmlEngine *engine = context()->engine; QV8Engine *v8engine = QQmlEnginePrivate::getV8Engine(engine); int type = valueTypeData.isValid() ? valueTypeData.propType() : core.propType(); QQmlJavaScriptExpression::DeleteWatcher watcher(this); QVariant value; bool isVarProperty = core.isVarProperty(); if (isUndefined) { } else if (core.isQList()) { value = QV8Engine::getV4(v8engine)->toVariant(result, qMetaTypeId >()); } else if (result.isNull() && core.isQObject()) { value = QVariant::fromValue((QObject *)0); } else if (core.propType() == qMetaTypeId >()) { value = QQmlPropertyPrivate::resolvedUrlSequence(QV8Engine::getV4(v8engine)->toVariant(result, qMetaTypeId >()), context()); } else if (!isVarProperty && type != qMetaTypeId()) { value = QV8Engine::getV4(v8engine)->toVariant(result, type); } if (hasError()) { return false; } else if (isVarProperty) { const QV4::FunctionObject *f = result.as(); if (f && f->isBinding()) { // we explicitly disallow this case to avoid confusion. Users can still store one // in an array in a var property if they need to, but the common case is user error. delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration.")); return false; } QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(m_target.data()); Q_ASSERT(vmemo); vmemo->setVMEProperty(core.coreIndex(), result); } else if (isUndefined && core.isResettable()) { void *args[] = { 0 }; QMetaObject::metacall(m_target.data(), QMetaObject::ResetProperty, core.coreIndex(), args); } else if (isUndefined && type == qMetaTypeId()) { QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, QVariant(), context(), flags); } else if (type == qMetaTypeId()) { const QV4::FunctionObject *f = result.as(); if (f && f->isBinding()) { delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration.")); return false; } QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, QVariant::fromValue( QJSValue(QV8Engine::getV4(v8engine), result.asReturnedValue())), context(), flags); } else if (isUndefined) { const QLatin1String typeName(QMetaType::typeName(type) ? QMetaType::typeName(type) : "[unknown property type]"); delayedError()->setErrorDescription(QLatin1String("Unable to assign [undefined] to ") + typeName); return false; } else if (const QV4::FunctionObject *f = result.as()) { if (f->isBinding()) delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration.")); else delayedError()->setErrorDescription(QLatin1String("Unable to assign a function to a property of any type other than var.")); return false; } else if (!QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, value, context(), flags)) { if (watcher.wasDeleted()) return true; const char *valueType = 0; const char *propertyType = 0; const int userType = value.userType(); if (userType == QMetaType::QObjectStar) { if (QObject *o = *(QObject *const *)value.constData()) { valueType = o->metaObject()->className(); QQmlMetaObject propertyMetaObject = QQmlPropertyPrivate::rawMetaObjectForType(QQmlEnginePrivate::get(engine), type); if (!propertyMetaObject.isNull()) propertyType = propertyMetaObject.className(); } } else if (userType != QVariant::Invalid) { if (userType == QMetaType::Nullptr || userType == QMetaType::VoidStar) valueType = "null"; else valueType = QMetaType::typeName(userType); } if (!valueType) valueType = "undefined"; if (!propertyType) propertyType = QMetaType::typeName(type); if (!propertyType) propertyType = "[unknown property type]"; delayedError()->setErrorDescription(QLatin1String("Unable to assign ") + QLatin1String(valueType) + QLatin1String(" to ") + QLatin1String(propertyType)); return false; } return true; } QVariant QQmlBinding::evaluate() { QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context()->engine); ep->referenceScarceResources(); bool isUndefined = false; QV4::Scope scope(ep->v4engine()); QV4::ScopedCallData callData(scope); QQmlJavaScriptExpression::evaluate(callData, &isUndefined, scope); ep->dereferenceScarceResources(); return scope.engine->toVariant(scope.result, qMetaTypeId >()); } QString QQmlBinding::expressionIdentifier() const { auto f = function(); QString url = f->sourceFile(); quint16 lineNumber = f->compiledFunction->location.line; quint16 columnNumber = f->compiledFunction->location.column; return url + QString::asprintf(":%u:%u", uint(lineNumber), uint(columnNumber)); } void QQmlBinding::expressionChanged() { update(); } void QQmlBinding::refresh() { update(); } void QQmlBinding::setEnabled(bool e, QQmlPropertyData::WriteFlags flags) { setEnabledFlag(e); setNotifyOnValueChanged(e); m_nextBinding.setFlag2(); // Always use accessors, only not when: if (auto interceptorMetaObject = QQmlInterceptorMetaObject::get(targetObject())) { if (!m_targetIndex.isValid() || interceptorMetaObject->intercepts(m_targetIndex)) m_nextBinding.clearFlag2(); } if (e) update(flags); } QString QQmlBinding::expression() const { return QStringLiteral("function() { [code] }"); } void QQmlBinding::setTarget(const QQmlProperty &prop) { auto pd = QQmlPropertyPrivate::get(prop); setTarget(prop.object(), pd->core, &pd->valueTypeData); } void QQmlBinding::setTarget(QObject *object, const QQmlPropertyData &core, const QQmlPropertyData *valueType) { m_target = object; if (!object) { m_targetIndex = QQmlPropertyIndex(); return; } int coreIndex = core.coreIndex(); int valueTypeIndex = valueType ? valueType->coreIndex() : -1; for (bool isAlias = core.isAlias(); isAlias; ) { QQmlVMEMetaObject *vme = QQmlVMEMetaObject::getForProperty(object, coreIndex); int aValueTypeIndex; if (!vme->aliasTarget(coreIndex, &object, &coreIndex, &aValueTypeIndex)) { m_target = 0; m_targetIndex = QQmlPropertyIndex(); return; } if (valueTypeIndex == -1) valueTypeIndex = aValueTypeIndex; QQmlData *data = QQmlData::get(object, false); if (!data || !data->propertyCache) { m_target = 0; m_targetIndex = QQmlPropertyIndex(); return; } QQmlPropertyData *propertyData = data->propertyCache->property(coreIndex); Q_ASSERT(propertyData); m_target = object; isAlias = propertyData->isAlias(); coreIndex = propertyData->coreIndex(); } m_targetIndex = QQmlPropertyIndex(coreIndex, valueTypeIndex); QQmlData *data = QQmlData::get(*m_target, true); if (!data->propertyCache) { data->propertyCache = QQmlEnginePrivate::get(context()->engine)->cache(m_target->metaObject()); data->propertyCache->addref(); } } void QQmlBinding::getPropertyData(QQmlPropertyData **propertyData, QQmlPropertyData *valueTypeData) const { Q_ASSERT(propertyData); QQmlData *data = QQmlData::get(*m_target, false); Q_ASSERT(data); if (Q_UNLIKELY(!data->propertyCache)) { data->propertyCache = QQmlEnginePrivate::get(context()->engine)->cache(m_target->metaObject()); data->propertyCache->addref(); } *propertyData = data->propertyCache->property(m_targetIndex.coreIndex()); Q_ASSERT(*propertyData); if (Q_UNLIKELY(m_targetIndex.hasValueTypeIndex() && valueTypeData)) { const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType((*propertyData)->propType()); Q_ASSERT(valueTypeMetaObject); QMetaProperty vtProp = valueTypeMetaObject->property(m_targetIndex.valueTypeIndex()); valueTypeData->setFlags(QQmlPropertyData::flagsForProperty(vtProp)); valueTypeData->setPropType(vtProp.userType()); valueTypeData->setCoreIndex(m_targetIndex.valueTypeIndex()); } } class QObjectPointerBinding: public QQmlNonbindingBinding { QQmlMetaObject targetMetaObject; public: QObjectPointerBinding(QQmlEnginePrivate *engine, int propertyType) : targetMetaObject(QQmlPropertyPrivate::rawMetaObjectForType(engine, propertyType)) {} protected: Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) Q_DECL_OVERRIDE Q_DECL_FINAL { QQmlPropertyData *pd; QQmlPropertyData vtpd; getPropertyData(&pd, &vtpd); if (Q_UNLIKELY(isUndefined || vtpd.isValid())) return slowWrite(*pd, vtpd, result, isUndefined, flags); // Check if the result is a QObject: QObject *resultObject = nullptr; QQmlMetaObject resultMo; if (result.isNull()) { // Special case: we can always write a nullptr. Don't bother checking anything else. return pd->writeProperty(targetObject(), &resultObject, flags); } else if (auto wrapper = result.as()) { resultObject = wrapper->object(); 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 (auto variant = result.as()) { QVariant value = variant->d()->data(); QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context()); resultMo = QQmlPropertyPrivate::rawMetaObjectForType(ep, value.userType()); if (resultMo.isNull()) return slowWrite(*pd, vtpd, result, isUndefined, flags); resultObject = *static_cast(value.constData()); } else { return slowWrite(*pd, vtpd, result, isUndefined, flags); } // Compare & set: if (QQmlMetaObject::canConvert(resultMo, targetMetaObject)) { return pd->writeProperty(targetObject(), &resultObject, flags); } else if (!resultObject && QQmlMetaObject::canConvert(targetMetaObject, resultMo)) { // In the case of a null QObject, we assign the null if there is // any change that the null variant type could be up or down cast to // the property type. return pd->writeProperty(targetObject(), &resultObject, flags); } else { return slowWrite(*pd, vtpd, result, isUndefined, flags); } } }; QQmlBinding *QQmlBinding::newBinding(QQmlEnginePrivate *engine, const QQmlPropertyData *property) { if (property && property->isQObject()) return new QObjectPointerBinding(engine, property->propType()); const int type = (property && property->isFullyResolved()) ? property->propType() : QMetaType::UnknownType; if (type == qMetaTypeId()) { return new QQmlBindingBinding; } switch (type) { case QMetaType::Bool: return new GenericBinding; case QMetaType::Int: return new GenericBinding; case QMetaType::Double: return new GenericBinding; case QMetaType::Float: return new GenericBinding; case QMetaType::QString: return new GenericBinding; default: return new GenericBinding; } } QT_END_NAMESPACE