From 748411fa64412db1650e04ee7b4405b8fbc53d42 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 4 Mar 2020 16:46:42 +0100 Subject: Store a QV4::ReturnedValue in QJSValue Being careful, we can now save primitive values inline. We use the heap pointer of QV4::Value as either QString* or QV4::Value* for complex types. We cannot store persistent managed QV4::Value without the double indirection as those need to be allocated in a special place. The generic QVariant case is not supported anymore. The only place where it was actually needed were the stream operators for QJSValue. Those were fundamentally broken: * A managed QJSValue saved and loaded from a stream was converted to a QVariant-type QJSValue * QVariant-type QJSValues were not callable, could not be objects or arrays, or any of the special types. * Cyclic references were forcibly broken when saving to a data stream. In general the support for saving and loading of managed types to/from a data stream was so abysmally bad that we don't lose much by dropping it. [ChangeLog][QML][Important Behavior Changes] When saving a QJSValue to a QDataStream only primitive values or strings will be retained. Support for objects and arrays was incomplete and unreliable already before. It cannot work correctly as we don't necessarily have a JavaScript heap when loading a QJSValue from a stream. Therefore, we don't have a proper place to keep any managed values. Using QVariant to keep them instead is a bad idea because QVariant cannot represent everything a QJSValue can contain. Fixes: QTBUG-75174 Change-Id: I75697670639bca8d4b1668763d7020c4cf871bda Reviewed-by: Fabian Kosmale --- src/qml/jsruntime/qv4engine.cpp | 163 ++++++++++++++++++++++++++-------------- 1 file changed, 105 insertions(+), 58 deletions(-) (limited to 'src/qml/jsruntime/qv4engine.cpp') diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 1efe09c59f..d4725ce7c2 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -165,8 +165,23 @@ static void saveJSValue(QDataStream &stream, const void *data) if (jsv->isUndefined()) isNullOrUndefined |= 0x2; stream << isNullOrUndefined; - if (!isNullOrUndefined) - reinterpret_cast(data)->toVariant().save(stream); + if (!isNullOrUndefined) { + const QVariant v = reinterpret_cast(data)->toVariant(); + switch (v.userType()) { + case QMetaType::Bool: + case QMetaType::Double: + case QMetaType::Int: + case QMetaType::QString: + v.save(stream); + break; + default: + qWarning() << "QDataStream::operator<< was to save a non-trivial QJSValue." + << "This is not supported anymore, please stream a QVariant instead."; + QVariant().save(stream); + break; + } + + } } static void restoreJSValue(QDataStream &stream, void *data) @@ -183,7 +198,25 @@ static void restoreJSValue(QDataStream &stream, void *data) } else { QVariant v; v.load(stream); - QJSValuePrivate::setVariant(jsv, v); + + switch (v.userType()) { + case QMetaType::Bool: + *jsv = QJSValue(v.toBool()); + break; + case QMetaType::Double: + *jsv = QJSValue(v.toDouble()); + break; + case QMetaType::Int: + *jsv = QJSValue(v.toInt()); + break; + case QMetaType::QString: + *jsv = QJSValue(v.toString()); + break; + default: + qWarning() << "QDataStream::operator>> to restore a non-trivial QJSValue." + << "This is not supported anymore, please stream a QVariant instead."; + break; + } } } @@ -1428,11 +1461,9 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError() typedef QSet V4ObjectSet; static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int typeHint, bool createJSValueForObjects, V4ObjectSet *visitedObjects); -static QObject *qtObjectFromJS(QV4::ExecutionEngine *engine, const QV4::Value &value); +static QObject *qtObjectFromJS(const QV4::Value &value); static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V4ObjectSet *visitedObjects = nullptr); -static bool convertToNativeQObject(QV4::ExecutionEngine *e, const QV4::Value &value, - const QByteArray &targetType, - void **result); +static bool convertToNativeQObject(const QV4::Value &value, const QByteArray &targetType, void **result); static QV4::ReturnedValue variantListToJS(QV4::ExecutionEngine *v4, const QVariantList &lst); static QV4::ReturnedValue sequentialIterableToJS(QV4::ExecutionEngine *v4, const QSequentialIterable &lst); static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVariantMap &vmap); @@ -1463,7 +1494,7 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int return QVariant::fromValue(QV4::JsonObject::toJsonValue(value)); if (typeHint == qMetaTypeId()) - return QVariant::fromValue(QJSValue(e, value.asReturnedValue())); + return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(value.asReturnedValue())); if (value.as()) { QV4::ScopedObject object(scope, value); @@ -1583,7 +1614,7 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int } if (createJSValueForObjects) - return QVariant::fromValue(QJSValue(scope.engine, o->asReturnedValue())); + return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(o->asReturnedValue())); return objectToVariant(e, o, visitedObjects); } @@ -1743,8 +1774,8 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) return QV4::Encode::null(); } } else if (type == qMetaTypeId()) { - const QJSValue *value = reinterpret_cast(ptr); - return QJSValuePrivate::convertedToValue(this, *value); + return QJSValuePrivate::convertToReturnedValue( + this, *reinterpret_cast(ptr)); } else if (type == qMetaTypeId >()) { // XXX Can this be made more by using Array as a prototype and implementing // directly against QList? @@ -2093,94 +2124,98 @@ void ExecutionEngine::setExtensionData(int index, Deletable *data) // Converts a JS value to a meta-type. // data must point to a place that can store a value of the given type. // Returns true if conversion succeeded, false otherwise. -bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) +bool ExecutionEngine::metaTypeFromJS(const Value &value, int type, void *data) { // check if it's one of the types we know switch (QMetaType::Type(type)) { case QMetaType::Bool: - *reinterpret_cast(data) = value->toBoolean(); + *reinterpret_cast(data) = value.toBoolean(); return true; case QMetaType::Int: - *reinterpret_cast(data) = value->toInt32(); + *reinterpret_cast(data) = value.toInt32(); return true; case QMetaType::UInt: - *reinterpret_cast(data) = value->toUInt32(); + *reinterpret_cast(data) = value.toUInt32(); return true; case QMetaType::LongLong: - *reinterpret_cast(data) = qlonglong(value->toInteger()); + *reinterpret_cast(data) = qlonglong(value.toInteger()); return true; case QMetaType::ULongLong: - *reinterpret_cast(data) = qulonglong(value->toInteger()); + *reinterpret_cast(data) = qulonglong(value.toInteger()); return true; case QMetaType::Double: - *reinterpret_cast(data) = value->toNumber(); + *reinterpret_cast(data) = value.toNumber(); return true; case QMetaType::QString: - if (value->isUndefined() || value->isNull()) + if (value.isUndefined() || value.isNull()) *reinterpret_cast(data) = QString(); else - *reinterpret_cast(data) = value->toQString(); + *reinterpret_cast(data) = value.toQString(); return true; case QMetaType::QByteArray: - if (const ArrayBuffer *ab = value->as()) + if (const ArrayBuffer *ab = value.as()) *reinterpret_cast(data) = ab->asByteArray(); else *reinterpret_cast(data) = QByteArray(); return true; case QMetaType::Float: - *reinterpret_cast(data) = value->toNumber(); + *reinterpret_cast(data) = value.toNumber(); return true; case QMetaType::Short: - *reinterpret_cast(data) = short(value->toInt32()); + *reinterpret_cast(data) = short(value.toInt32()); return true; case QMetaType::UShort: - *reinterpret_cast(data) = value->toUInt16(); + *reinterpret_cast(data) = value.toUInt16(); return true; case QMetaType::Char: - *reinterpret_cast(data) = char(value->toInt32()); + *reinterpret_cast(data) = char(value.toInt32()); return true; case QMetaType::UChar: - *reinterpret_cast(data) = (unsigned char)(value->toInt32()); + *reinterpret_cast(data) = (unsigned char)(value.toInt32()); return true; case QMetaType::QChar: - if (String *s = value->stringValue()) { + if (String *s = value.stringValue()) { QString str = s->toQString(); *reinterpret_cast(data) = str.isEmpty() ? QChar() : str.at(0); } else { - *reinterpret_cast(data) = QChar(ushort(value->toUInt16())); + *reinterpret_cast(data) = QChar(ushort(value.toUInt16())); } return true; case QMetaType::QDateTime: - if (const QV4::DateObject *d = value->as()) { + if (const QV4::DateObject *d = value.as()) { *reinterpret_cast(data) = d->toQDateTime(); return true; } break; case QMetaType::QDate: - if (const QV4::DateObject *d = value->as()) { + if (const QV4::DateObject *d = value.as()) { *reinterpret_cast(data) = d->toQDateTime().date(); return true; } break; case QMetaType::QRegExp: - if (const QV4::RegExpObject *r = value->as()) { + if (const QV4::RegExpObject *r = value.as()) { *reinterpret_cast(data) = r->toQRegExp(); return true; } break; #if QT_CONFIG(regularexpression) case QMetaType::QRegularExpression: - if (const QV4::RegExpObject *r = value->as()) { + if (const QV4::RegExpObject *r = value.as()) { *reinterpret_cast(data) = r->toQRegularExpression(); return true; } break; #endif case QMetaType::QObjectStar: { - const QV4::QObjectWrapper *qobjectWrapper = value->as(); - if (qobjectWrapper || value->isNull()) { - *reinterpret_cast(data) = qtObjectFromJS(this, *value); + if (value.isNull()) { + *reinterpret_cast(data) = nullptr; return true; - } break; + } + if (value.as()) { + *reinterpret_cast(data) = qtObjectFromJS(value); + return true; + } + break; } case QMetaType::QStringList: { - const QV4::ArrayObject *a = value->as(); + const QV4::ArrayObject *a = value.as(); if (a) { *reinterpret_cast(data) = a->toQStringList(); return true; @@ -2188,33 +2223,45 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) break; } case QMetaType::QVariantList: { - const QV4::ArrayObject *a = value->as(); + const QV4::ArrayObject *a = value.as(); if (a) { - *reinterpret_cast(data) = toVariant(*a, /*typeHint*/-1, /*createJSValueForObjects*/false).toList(); + *reinterpret_cast(data) = a->engine()->toVariant( + *a, /*typeHint*/-1, /*createJSValueForObjects*/false).toList(); return true; } break; } case QMetaType::QVariantMap: { - const QV4::Object *o = value->as(); + const QV4::Object *o = value.as(); if (o) { - *reinterpret_cast(data) = variantMapFromJS(o); + *reinterpret_cast(data) = o->engine()->variantMapFromJS(o); return true; } break; } case QMetaType::QVariant: - *reinterpret_cast(data) = toVariant(*value, /*typeHint*/-1, /*createJSValueForObjects*/false); + if (const QV4::Managed *m = value.as()) + *reinterpret_cast(data) = m->engine()->toVariant(value, /*typeHint*/-1, /*createJSValueForObjects*/false); + else if (value.isNull()) + *reinterpret_cast(data) = QVariant::fromValue(nullptr); + else if (value.isUndefined()) + *reinterpret_cast(data) = QVariant(); + else if (value.isBoolean()) + *reinterpret_cast(data) = QVariant(value.booleanValue()); + else if (value.isInteger()) + *reinterpret_cast(data) = QVariant(value.integerValue()); + else if (value.isDouble()) + *reinterpret_cast(data) = QVariant(value.doubleValue()); return true; case QMetaType::QJsonValue: - *reinterpret_cast(data) = QV4::JsonObject::toJsonValue(*value); + *reinterpret_cast(data) = QV4::JsonObject::toJsonValue(value); return true; case QMetaType::QJsonObject: { - *reinterpret_cast(data) = QV4::JsonObject::toJsonObject(value->as()); + *reinterpret_cast(data) = QV4::JsonObject::toJsonObject(value.as()); return true; } case QMetaType::QJsonArray: { - const QV4::ArrayObject *a = value->as(); + const QV4::ArrayObject *a = value.as(); if (a) { *reinterpret_cast(data) = JsonObject::toJsonArray(a); return true; @@ -2226,7 +2273,7 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) } { - const QQmlValueTypeWrapper *vtw = value->as(); + const QQmlValueTypeWrapper *vtw = value.as(); if (vtw && vtw->typeId() == type) { return vtw->toGadget(data); } @@ -2254,18 +2301,18 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) // Try to use magic; for compatibility with qjsvalue_cast. QByteArray name = QMetaType::typeName(type); - if (convertToNativeQObject(this, *value, name, reinterpret_cast(data))) + if (convertToNativeQObject(value, name, reinterpret_cast(data))) return true; - if (value->as() && name.endsWith('*')) { + if (value.as() && name.endsWith('*')) { int valueType = QMetaType::type(name.left(name.size()-1)); - QVariant &var = value->as()->d()->data(); + QVariant &var = value.as()->d()->data(); if (valueType == var.userType()) { // We have T t, T* is requested, so return &t. *reinterpret_cast(data) = var.data(); return true; - } else if (Object *o = value->objectValue()) { + } else if (Object *o = value.objectValue()) { // Look in the prototype chain. - QV4::Scope scope(this); + QV4::Scope scope(o->engine()); QV4::ScopedObject proto(scope, o->getPrototypeOf()); while (proto) { bool canCast = false; @@ -2276,7 +2323,7 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) else if (proto->as()) { QByteArray className = name.left(name.size()-1); QV4::ScopedObject p(scope, proto.getPointer()); - if (QObject *qobject = qtObjectFromJS(this, p)) + if (QObject *qobject = qtObjectFromJS(p)) canCast = qobject->qt_metacast(className) != nullptr; } if (canCast) { @@ -2290,22 +2337,22 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) proto = proto->getPrototypeOf(); } } - } else if (value->isNull() && name.endsWith('*')) { + } else if (value.isNull() && name.endsWith('*')) { *reinterpret_cast(data) = nullptr; return true; } else if (type == qMetaTypeId()) { - *reinterpret_cast(data) = QJSValue(this, value->asReturnedValue()); + QJSValuePrivate::setValue(reinterpret_cast(data), value.asReturnedValue()); return true; } return false; } -static bool convertToNativeQObject(QV4::ExecutionEngine *e, const QV4::Value &value, const QByteArray &targetType, void **result) +static bool convertToNativeQObject(const QV4::Value &value, const QByteArray &targetType, void **result) { if (!targetType.endsWith('*')) return false; - if (QObject *qobject = qtObjectFromJS(e, value)) { + if (QObject *qobject = qtObjectFromJS(value)) { int start = targetType.startsWith("const ") ? 6 : 0; QByteArray className = targetType.mid(start, targetType.size()-start-1); if (void *instance = qobject->qt_metacast(className)) { @@ -2316,12 +2363,12 @@ static bool convertToNativeQObject(QV4::ExecutionEngine *e, const QV4::Value &va return false; } -static QObject *qtObjectFromJS(QV4::ExecutionEngine *engine, const QV4::Value &value) +static QObject *qtObjectFromJS(const QV4::Value &value) { if (!value.isObject()) return nullptr; - QV4::Scope scope(engine); + QV4::Scope scope(value.as()->engine()); QV4::Scoped v(scope, value); if (v) { -- cgit v1.2.3