/**************************************************************************** ** ** 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 "qqmlvaluetypewrapper_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcBindingRemoval) DEFINE_OBJECT_VTABLE(QV4::QQmlValueTypeWrapper); namespace QV4 { namespace Heap { struct QQmlValueTypeReference : QQmlValueTypeWrapper { void init() { QQmlValueTypeWrapper::init(); object.init(); } void destroy() { object.destroy(); QQmlValueTypeWrapper::destroy(); } QV4QPointer object; int property; }; } struct QQmlValueTypeReference : public QQmlValueTypeWrapper { V4_OBJECT2(QQmlValueTypeReference, QQmlValueTypeWrapper) V4_NEEDS_DESTROY bool readReferenceValue() const; }; } DEFINE_OBJECT_VTABLE(QV4::QQmlValueTypeReference); using namespace QV4; void Heap::QQmlValueTypeWrapper::destroy() { if (m_gadgetPtr) { m_valueType->metaType.destruct(m_gadgetPtr); ::operator delete(m_gadgetPtr); } Object::destroy(); } void Heap::QQmlValueTypeWrapper::setValue(const QVariant &value) const { Q_ASSERT(valueType()->metaType.id() == value.userType()); if (auto *gadget = gadgetPtr()) valueType()->metaType.destruct(gadget); if (!gadgetPtr()) setGadgetPtr(::operator new(valueType()->metaType.sizeOf())); valueType()->metaType.construct(gadgetPtr(), value.constData()); } QVariant Heap::QQmlValueTypeWrapper::toVariant() const { Q_ASSERT(gadgetPtr()); return QVariant(valueType()->metaType, gadgetPtr()); } bool QQmlValueTypeReference::readReferenceValue() const { if (!d()->object) return false; // A reference resource may be either a "true" reference (eg, to a QVector3D property) // or a "variant" reference (eg, to a QVariant property which happens to contain a value-type). QMetaProperty writebackProperty = d()->object->metaObject()->property(d()->property); if (writebackProperty.userType() == QMetaType::QVariant) { // variant-containing-value-type reference QVariant variantReferenceValue; void *a[] = { &variantReferenceValue, nullptr }; QMetaObject::metacall(d()->object, QMetaObject::ReadProperty, d()->property, a); const QMetaType variantReferenceType = variantReferenceValue.metaType(); if (variantReferenceType != type()) { // This is a stale VariantReference. That is, the variant has been // overwritten with a different type in the meantime. // We need to modify this reference to the updated value type, if // possible, or return false if it is not a value type. if (QQmlMetaType::isValueType(variantReferenceType)) { const QMetaObject *mo = QQmlMetaType::metaObjectForMetaType(variantReferenceType); if (d()->gadgetPtr()) { d()->valueType()->metaType.destruct(d()->gadgetPtr()); ::operator delete(d()->gadgetPtr()); } d()->setGadgetPtr(nullptr); d()->setMetaObject(mo); d()->setValueType(QQmlMetaType::valueType(variantReferenceType)); if (!mo) return false; } else { return false; } } d()->setValue(variantReferenceValue); } else { if (!d()->gadgetPtr()) { d()->setGadgetPtr(::operator new(d()->valueType()->metaType.sizeOf())); d()->valueType()->metaType.construct(d()->gadgetPtr(), nullptr); } // value-type reference void *args[] = { d()->gadgetPtr(), nullptr }; QMetaObject::metacall(d()->object, QMetaObject::ReadProperty, d()->property, args); } return true; } void QQmlValueTypeWrapper::initProto(ExecutionEngine *v4) { if (v4->valueTypeWrapperPrototype()->d_unchecked()) return; Scope scope(v4); ScopedObject o(scope, v4->newObject()); o->defineDefaultProperty(v4->id_toString(), method_toString, 1); v4->jsObjects[QV4::ExecutionEngine::ValueTypeProto] = o->d(); } ReturnedValue QQmlValueTypeWrapper::create(ExecutionEngine *engine, QObject *object, int property, const QMetaObject *metaObject, QMetaType type) { Scope scope(engine); initProto(engine); Scoped r(scope, engine->memoryManager->allocate()); r->d()->object = object; r->d()->property = property; r->d()->setMetaObject(metaObject); auto valueType = QQmlMetaType::valueType(type); if (!valueType) { return engine->throwTypeError(QLatin1String("Type %1 is not a value type") .arg(QString::fromUtf8(type.name()))); } r->d()->setValueType(valueType); r->d()->setGadgetPtr(nullptr); return r->asReturnedValue(); } ReturnedValue QQmlValueTypeWrapper::create(ExecutionEngine *engine, const QVariant &value, const QMetaObject *metaObject, QMetaType type) { Scope scope(engine); initProto(engine); Scoped r(scope, engine->memoryManager->allocate()); r->d()->setMetaObject(metaObject); auto valueType = QQmlMetaType::valueType(type); if (!valueType) { return engine->throwTypeError(QLatin1String("Type %1 is not a value type") .arg(QString::fromUtf8(type.name()))); } r->d()->setValueType(valueType); r->d()->setGadgetPtr(nullptr); r->d()->setValue(value); return r->asReturnedValue(); } QVariant QQmlValueTypeWrapper::toVariant() const { if (const QQmlValueTypeReference *ref = as()) if (!ref->readReferenceValue()) return QVariant(); return d()->toVariant(); } bool QQmlValueTypeWrapper::toGadget(void *data) const { if (const QQmlValueTypeReference *ref = as()) if (!ref->readReferenceValue()) return false; const int typeId = d()->valueType()->metaType.id(); QMetaType(typeId).destruct(data); QMetaType(typeId).construct(data, d()->gadgetPtr()); return true; } bool QQmlValueTypeWrapper::virtualIsEqualTo(Managed *m, Managed *other) { Q_ASSERT(m && m->as() && other); QV4::QQmlValueTypeWrapper *lv = static_cast(m); if (QV4::VariantObject *rv = other->as()) return lv->isEqual(rv->d()->data()); if (QV4::QQmlValueTypeWrapper *v = other->as()) return lv->isEqual(v->toVariant()); return false; } static ReturnedValue getGadgetProperty(ExecutionEngine *engine, Heap::QQmlValueTypeWrapper *valueTypeWrapper, QMetaType metaType, quint16 coreIndex, bool isFunction, bool isEnum) { if (isFunction) { // calling a Q_INVOKABLE function of a value type return QV4::QObjectMethod::create(engine->rootContext(), valueTypeWrapper, coreIndex); } #define VALUE_TYPE_LOAD(metatype, cpptype, constructor) \ if (metaTypeId == metatype) { \ cpptype v; \ void *args[] = { &v, nullptr }; \ metaObject->d.static_metacall(reinterpret_cast(valueTypeWrapper->gadgetPtr()), \ QMetaObject::ReadProperty, index, args); \ return QV4::Encode(constructor(v)); \ } const QMetaObject *metaObject = valueTypeWrapper->metaObject(); int index = coreIndex; QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(QMetaObject::ReadProperty, &metaObject, &index); // These four types are the most common used by the value type wrappers int metaTypeId = metaType.id(); VALUE_TYPE_LOAD(QMetaType::Double, double, double); VALUE_TYPE_LOAD(QMetaType::Float, float, float); VALUE_TYPE_LOAD(QMetaType::Int || isEnum, int, int); VALUE_TYPE_LOAD(QMetaType::QString, QString, engine->newString); VALUE_TYPE_LOAD(QMetaType::Bool, bool, bool); QVariant v; void *args[] = { nullptr, nullptr }; if (metaType == QMetaType::fromType()) { args[0] = &v; } else { v = QVariant(metaType, static_cast(nullptr)); args[0] = v.data(); } metaObject->d.static_metacall(reinterpret_cast(valueTypeWrapper->gadgetPtr()), QMetaObject::ReadProperty, index, args); return engine->fromVariant(v); #undef VALUE_TYPE_LOAD } PropertyAttributes QQmlValueTypeWrapper::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) { if (id.isString()) { const QQmlValueTypeWrapper *r = static_cast(m); QQmlPropertyData result = r->dataForPropertyKey(id); if (p && result.isValid()) p->value = getGadgetProperty(r->engine(), r->d(), result.propType(), result.coreIndex(), result.isFunction(), result.isEnum()); return result.isValid() ? Attr_Data : Attr_Invalid; } return QV4::Object::virtualGetOwnProperty(m, id, p); } struct QQmlValueTypeWrapperOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator { int propertyIndex = 0; ~QQmlValueTypeWrapperOwnPropertyKeyIterator() override = default; PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; }; PropertyKey QQmlValueTypeWrapperOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) { const QQmlValueTypeWrapper *that = static_cast(o); if (const QQmlValueTypeReference *ref = that->as()) { if (!ref->readReferenceValue()) return PropertyKey::invalid(); } const QMetaObject *mo = that->d()->metaObject(); // We don't return methods, ie. they are not visible when iterating const int propertyCount = mo->propertyCount(); if (propertyIndex < propertyCount) { Scope scope(that->engine()); QMetaProperty p = mo->property(propertyIndex); // TODO: Implement and use QBasicMetaProperty ScopedString propName(scope, that->engine()->newString(QString::fromUtf8(p.name()))); ++propertyIndex; if (attrs) *attrs = QV4::Attr_Data; if (pd) { QQmlPropertyData data; data.load(p); pd->value = getGadgetProperty(that->engine(), that->d(), data.propType(), data.coreIndex(), data.isFunction(), data.isEnum()); } return propName->toPropertyKey(); } return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); } OwnPropertyKeyIterator *QQmlValueTypeWrapper::virtualOwnPropertyKeys(const Object *m, Value *target) { *target = *m; return new QQmlValueTypeWrapperOwnPropertyKeyIterator; } bool QQmlValueTypeWrapper::isEqual(const QVariant& value) const { if (const QQmlValueTypeReference *ref = as()) if (!ref->readReferenceValue()) return false; int id1 = value.metaType().id(); QVariant v = d()->toVariant(); int id2 = v.metaType().id(); if (id1 != id2) { // conversions for weak comparison switch (id1) { case QMetaType::QPoint: if (id2 == QMetaType::QPointF) return value.value() == v.value(); break; case QMetaType::QPointF: if (id2 == QMetaType::QPoint) return value.value() == v.value(); break; case QMetaType::QRect: if (id2 == QMetaType::QRectF) return value.value() == v.value(); break; case QMetaType::QRectF: if (id2 == QMetaType::QRect) return value.value() == v.value(); break; case QMetaType::QLine: if (id2 == QMetaType::QLineF) return value.value() == v.value(); break; case QMetaType::QLineF: if (id2 == QMetaType::QLine) return value.value() == v.value(); break; case QMetaType::QSize: if (id2 == QMetaType::QSizeF) return value.value() == v.value(); break; case QMetaType::QSizeF: if (id2 == QMetaType::QSize) return value.value() == v.value(); break; default: break; } } return (value == v); } int QQmlValueTypeWrapper::typeId() const { return d()->valueType()->metaType.id(); } QMetaType QQmlValueTypeWrapper::type() const { return d()->valueType()->metaType; } bool QQmlValueTypeWrapper::write(QObject *target, int propertyIndex) const { bool destructGadgetOnExit = false; Q_ALLOCA_DECLARE(void, gadget); if (const QQmlValueTypeReference *ref = as()) { if (!d()->gadgetPtr()) { Q_ALLOCA_ASSIGN(void, gadget, d()->valueType()->metaType.sizeOf()); d()->setGadgetPtr(gadget); d()->valueType()->metaType.construct(d()->gadgetPtr(), nullptr); destructGadgetOnExit = true; } if (!ref->readReferenceValue()) return false; } int flags = 0; int status = -1; void *a[] = { d()->gadgetPtr(), nullptr, &status, &flags }; QMetaObject::metacall(target, QMetaObject::WriteProperty, propertyIndex, a); if (destructGadgetOnExit) { d()->valueType()->metaType.destruct(d()->gadgetPtr()); d()->setGadgetPtr(nullptr); } return true; } QQmlPropertyData QQmlValueTypeWrapper::dataForPropertyKey(PropertyKey id) const { if (!id.isStringOrSymbol()) return QQmlPropertyData {}; QByteArray name = id.asStringOrSymbol()->toQString().toUtf8(); const QMetaObject *mo = d()->metaObject(); QQmlPropertyData result; QMetaMethod metaMethod = QMetaObjectPrivate::firstMethod(mo, name); if (metaMethod.isValid()) { result.load(metaMethod); } else { int propertyIndex = d()->metaObject()->indexOfProperty(name.constData()); if (propertyIndex >= 0) result.load(mo->property(propertyIndex)); } return result; } ReturnedValue QQmlValueTypeWrapper::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) { const Object *o = thisObject->as(); if (!o) return b->engine()->throwTypeError(); const QQmlValueTypeWrapper *w = o->as(); if (!w) return b->engine()->throwTypeError(); if (const QQmlValueTypeReference *ref = w->as()) if (!ref->readReferenceValue()) RETURN_UNDEFINED(); QString result; if (!QMetaType::convert(w->d()->valueType()->metaType, w->d()->gadgetPtr(), QMetaType(QMetaType::QString), &result)) { result = QString::fromUtf8(w->d()->valueType()->metaType.name()) + QLatin1Char('('); const QMetaObject *mo = w->d()->metaObject(); const int propCount = mo->propertyCount(); for (int i = 0; i < propCount; ++i) { if (mo->property(i).isDesignable()) { QVariant value = mo->property(i).readOnGadget(w->d()->gadgetPtr()); if (i > 0) result += QLatin1String(", "); result += value.toString(); } } result += QLatin1Char(')'); } return Encode(b->engine()->newString(result)); } ReturnedValue QQmlValueTypeWrapper::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup) { PropertyKey id = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit-> runtimeStrings[lookup->nameIndex]); if (!id.isString()) return Object::virtualResolveLookupGetter(object, engine, lookup); const QQmlValueTypeWrapper *r = static_cast(object); QV4::ExecutionEngine *v4 = r->engine(); Scope scope(v4); ScopedString name(scope, id.asStringOrSymbol()); // Note: readReferenceValue() can change the reference->type. if (const QQmlValueTypeReference *reference = r->as()) { if (!reference->readReferenceValue()) return Value::undefinedValue().asReturnedValue(); } QQmlPropertyData result = r->dataForPropertyKey(id); if (!result.isValid()) return QV4::Object::virtualResolveLookupGetter(object, engine, lookup); lookup->qgadgetLookup.ic = r->internalClass(); // & 1 to tell the gc that this is not heap allocated; see markObjects in qv4lookup_p.h lookup->qgadgetLookup.metaObject = quintptr(r->d()->metaObject()) + 1; lookup->qgadgetLookup.metaType = result.propType().iface(); lookup->qgadgetLookup.coreIndex = result.coreIndex(); lookup->qgadgetLookup.isFunction = result.isFunction(); lookup->qgadgetLookup.isEnum = result.isEnum(); lookup->getter = QQmlValueTypeWrapper::lookupGetter; return lookup->getter(lookup, engine, *object); } ReturnedValue QQmlValueTypeWrapper::lookupGetter(Lookup *lookup, ExecutionEngine *engine, const Value &object) { const auto revertLookup = [lookup, engine, &object]() { lookup->qgadgetLookup.metaObject = quintptr(0); lookup->getter = Lookup::getterGeneric; return Lookup::getterGeneric(lookup, engine, object); }; // we can safely cast to a QV4::Object here. If object is something else, // the internal class won't match Heap::Object *o = static_cast(object.heapObject()); if (!o || o->internalClass != lookup->qgadgetLookup.ic) return revertLookup(); Heap::QQmlValueTypeWrapper *valueTypeWrapper = const_cast(static_cast(o)); if (valueTypeWrapper->metaObject() != reinterpret_cast(lookup->qgadgetLookup.metaObject - 1)) return revertLookup(); if (lookup->qgadgetLookup.ic->vtable == QQmlValueTypeReference::staticVTable()) { Scope scope(engine); Scoped referenceWrapper(scope, valueTypeWrapper); referenceWrapper->readReferenceValue(); } return getGadgetProperty(engine, valueTypeWrapper, QMetaType(lookup->qgadgetLookup.metaType), lookup->qgadgetLookup.coreIndex, lookup->qgadgetLookup.isFunction, lookup->qgadgetLookup.isEnum); } bool QQmlValueTypeWrapper::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value) { return Object::virtualResolveLookupSetter(object, engine, lookup, value); } ReturnedValue QQmlValueTypeWrapper::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { Q_ASSERT(m->as()); if (!id.isString()) return Object::virtualGet(m, id, receiver, hasProperty); const QQmlValueTypeWrapper *r = static_cast(m); QV4::ExecutionEngine *v4 = r->engine(); // Note: readReferenceValue() can change the reference->type. if (const QQmlValueTypeReference *reference = r->as()) { if (!reference->readReferenceValue()) return Value::undefinedValue().asReturnedValue(); } QQmlPropertyData result = r->dataForPropertyKey(id); if (!result.isValid()) return Object::virtualGet(m, id, receiver, hasProperty); if (hasProperty) *hasProperty = true; return getGadgetProperty(v4, r->d(), result.propType(), result.coreIndex(), result.isFunction(), result.isEnum()); } bool QQmlValueTypeWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) { if (!id.isString()) return Object::virtualPut(m, id, value, receiver); Q_ASSERT(m->as()); ExecutionEngine *v4 = static_cast(m)->engine(); Scope scope(v4); if (scope.hasException()) return false; Scoped r(scope, static_cast(m)); Scoped reference(scope, m->d()); QMetaType writeBackPropertyType; if (reference) { QMetaProperty writebackProperty = reference->d()->object->metaObject()->property(reference->d()->property); if (!writebackProperty.isWritable() || !reference->readReferenceValue()) return false; writeBackPropertyType = writebackProperty.metaType(); } const QMetaObject *metaObject = r->d()->metaObject(); const QQmlPropertyData pd = r->dataForPropertyKey(id); if (!pd.isValid()) return false; if (reference) { QV4::ScopedFunctionObject f(scope, value); const QV4QPointer &referenceObject = reference->d()->object; const int referencePropertyIndex = reference->d()->property; if (f) { if (!f->isBinding()) { // assigning a JS function to a non-var-property is not allowed. QString error = QStringLiteral("Cannot assign JavaScript function to value-type property"); ScopedString e(scope, v4->newString(error)); v4->throwError(e); return false; } QQmlRefPointer context = v4->callingQmlContext(); QQmlPropertyData cacheData; cacheData.setWritable(true); cacheData.setPropType(writeBackPropertyType); cacheData.setCoreIndex(referencePropertyIndex); QV4::Scoped bindingFunction(scope, (const Value &)f); QV4::ScopedFunctionObject f(scope, bindingFunction->bindingFunction()); QV4::ScopedContext ctx(scope, f->scope()); QQmlBinding *newBinding = QQmlBinding::create(&cacheData, f->function(), referenceObject, context, ctx); newBinding->setSourceLocation(bindingFunction->currentLocation()); if (f->isBoundFunction()) newBinding->setBoundFunction(static_cast(f.getPointer())); newBinding->setSourceLocation(bindingFunction->currentLocation()); newBinding->setTarget(referenceObject, cacheData, &pd); QQmlPropertyPrivate::setBinding(newBinding); return true; } else { if (Q_UNLIKELY(lcBindingRemoval().isInfoEnabled())) { if (auto binding = QQmlPropertyPrivate::binding(referenceObject, QQmlPropertyIndex(referencePropertyIndex, pd.coreIndex()))) { Q_ASSERT(!binding->isValueTypeProxy()); const auto qmlBinding = static_cast(binding); const auto stackFrame = v4->currentStackFrame; qCInfo(lcBindingRemoval, "Overwriting binding on %s::%s which was initially bound at %s by setting \"%s\" at %s:%d", referenceObject->metaObject()->className(), referenceObject->metaObject()->property(referencePropertyIndex).name(), qPrintable(qmlBinding->expressionIdentifier()), metaObject->property(pd.coreIndex()).name(), qPrintable(stackFrame->source()), stackFrame->lineNumber()); } } QQmlPropertyPrivate::removeBinding(referenceObject, QQmlPropertyIndex(referencePropertyIndex, pd.coreIndex())); } } QMetaProperty property = metaObject->property(pd.coreIndex()); Q_ASSERT(property.isValid()); QVariant v = v4->toVariant(value, property.userType()); if (property.isEnumType() && (QMetaType::Type)v.userType() == QMetaType::Double) v = v.toInt(); void *gadget = r->d()->gadgetPtr(); property.writeOnGadget(gadget, v); if (reference) { if (writeBackPropertyType == QMetaType::fromType()) { QVariant variantReferenceValue = r->d()->toVariant(); int flags = 0; int status = -1; void *a[] = { &variantReferenceValue, nullptr, &status, &flags }; QMetaObject::metacall(reference->d()->object, QMetaObject::WriteProperty, reference->d()->property, a); } else { int flags = 0; int status = -1; void *a[] = { r->d()->gadgetPtr(), nullptr, &status, &flags }; QMetaObject::metacall(reference->d()->object, QMetaObject::WriteProperty, reference->d()->property, a); } } return true; } QT_END_NAMESPACE