diff options
Diffstat (limited to 'src/qml/jsruntime/qv4engine.cpp')
-rw-r--r-- | src/qml/jsruntime/qv4engine.cpp | 733 |
1 files changed, 415 insertions, 318 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 3d14d504e2..bd6251caa9 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -61,6 +61,7 @@ #include "qv4variantobject_p.h" #include "qv4sequenceobject_p.h" #include "qv4qobjectwrapper_p.h" +#include "qv4qmetaobjectwrapper_p.h" #include "qv4memberdata_p.h" #include "qv4arraybuffer_p.h" #include "qv4dataview_p.h" @@ -92,8 +93,6 @@ #include <valgrind/memcheck.h> #endif -Q_DECLARE_METATYPE(QList<int>) - QT_BEGIN_NAMESPACE DEFINE_BOOL_CONFIG_OPTION(disableDiskCache, QML_DISABLE_DISK_CACHE); @@ -372,6 +371,8 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) const size_t guardPages = 2 * WTF::pageSize(); memoryManager = new QV4::MemoryManager(this); + // we don't want to run the gc while the initial setup is not done; not even in aggressive mode + GCCriticalSection gcCriticalSection(this); // reserve space for the JS stack // we allow it to grow to a bit more than m_maxJSStackSize, as we can overshoot due to ScopedValues // allocated outside of JIT'ed methods. @@ -626,25 +627,23 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) ic = newInternalClass(SequencePrototype::staticVTable(), SequencePrototype::defaultPrototype(this)); jsObjects[SequenceProto] = ScopedValue(scope, memoryManager->allocObject<SequencePrototype>(ic->d())); - ExecutionContext *global = rootContext(); - - jsObjects[Object_Ctor] = memoryManager->allocate<ObjectCtor>(global); - jsObjects[String_Ctor] = memoryManager->allocate<StringCtor>(global); - jsObjects[Symbol_Ctor] = memoryManager->allocate<SymbolCtor>(global); - jsObjects[Number_Ctor] = memoryManager->allocate<NumberCtor>(global); - jsObjects[Boolean_Ctor] = memoryManager->allocate<BooleanCtor>(global); - jsObjects[Array_Ctor] = memoryManager->allocate<ArrayCtor>(global); - jsObjects[Function_Ctor] = memoryManager->allocate<FunctionCtor>(global); - jsObjects[GeneratorFunction_Ctor] = memoryManager->allocate<GeneratorFunctionCtor>(global); - jsObjects[Date_Ctor] = memoryManager->allocate<DateCtor>(global); - jsObjects[RegExp_Ctor] = memoryManager->allocate<RegExpCtor>(global); - jsObjects[Error_Ctor] = memoryManager->allocate<ErrorCtor>(global); - jsObjects[EvalError_Ctor] = memoryManager->allocate<EvalErrorCtor>(global); - jsObjects[RangeError_Ctor] = memoryManager->allocate<RangeErrorCtor>(global); - jsObjects[ReferenceError_Ctor] = memoryManager->allocate<ReferenceErrorCtor>(global); - jsObjects[SyntaxError_Ctor] = memoryManager->allocate<SyntaxErrorCtor>(global); - jsObjects[TypeError_Ctor] = memoryManager->allocate<TypeErrorCtor>(global); - jsObjects[URIError_Ctor] = memoryManager->allocate<URIErrorCtor>(global); + jsObjects[Object_Ctor] = memoryManager->allocate<ObjectCtor>(this); + jsObjects[String_Ctor] = memoryManager->allocate<StringCtor>(this); + jsObjects[Symbol_Ctor] = memoryManager->allocate<SymbolCtor>(this); + jsObjects[Number_Ctor] = memoryManager->allocate<NumberCtor>(this); + jsObjects[Boolean_Ctor] = memoryManager->allocate<BooleanCtor>(this); + jsObjects[Array_Ctor] = memoryManager->allocate<ArrayCtor>(this); + jsObjects[Function_Ctor] = memoryManager->allocate<FunctionCtor>(this); + jsObjects[GeneratorFunction_Ctor] = memoryManager->allocate<GeneratorFunctionCtor>(this); + jsObjects[Date_Ctor] = memoryManager->allocate<DateCtor>(this); + jsObjects[RegExp_Ctor] = memoryManager->allocate<RegExpCtor>(this); + jsObjects[Error_Ctor] = memoryManager->allocate<ErrorCtor>(this); + jsObjects[EvalError_Ctor] = memoryManager->allocate<EvalErrorCtor>(this); + jsObjects[RangeError_Ctor] = memoryManager->allocate<RangeErrorCtor>(this); + jsObjects[ReferenceError_Ctor] = memoryManager->allocate<ReferenceErrorCtor>(this); + jsObjects[SyntaxError_Ctor] = memoryManager->allocate<SyntaxErrorCtor>(this); + jsObjects[TypeError_Ctor] = memoryManager->allocate<TypeErrorCtor>(this); + jsObjects[URIError_Ctor] = memoryManager->allocate<URIErrorCtor>(this); jsObjects[IteratorProto] = memoryManager->allocate<IteratorPrototype>(); ic = newInternalClass(ForInIteratorPrototype::staticVTable(), iteratorPrototype()); @@ -662,9 +661,9 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) // url // - jsObjects[Url_Ctor] = memoryManager->allocate<UrlCtor>(global); + jsObjects[Url_Ctor] = memoryManager->allocate<UrlCtor>(this); jsObjects[UrlProto] = memoryManager->allocate<UrlPrototype>(); - jsObjects[UrlSearchParams_Ctor] = memoryManager->allocate<UrlSearchParamsCtor>(global); + jsObjects[UrlSearchParams_Ctor] = memoryManager->allocate<UrlSearchParamsCtor>(this); jsObjects[UrlSearchParamsProto] = memoryManager->allocate<UrlSearchParamsPrototype>(); str = newString(QStringLiteral("get [Symbol.species]")); @@ -702,19 +701,19 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) sequencePrototype()->cast<SequencePrototype>()->init(); - jsObjects[WeakMap_Ctor] = memoryManager->allocate<WeakMapCtor>(global); + jsObjects[WeakMap_Ctor] = memoryManager->allocate<WeakMapCtor>(this); jsObjects[WeakMapProto] = memoryManager->allocate<WeakMapPrototype>(); static_cast<WeakMapPrototype *>(weakMapPrototype())->init(this, weakMapCtor()); - jsObjects[Map_Ctor] = memoryManager->allocate<MapCtor>(global); + jsObjects[Map_Ctor] = memoryManager->allocate<MapCtor>(this); jsObjects[MapProto] = memoryManager->allocate<MapPrototype>(); static_cast<MapPrototype *>(mapPrototype())->init(this, mapCtor()); - jsObjects[WeakSet_Ctor] = memoryManager->allocate<WeakSetCtor>(global); + jsObjects[WeakSet_Ctor] = memoryManager->allocate<WeakSetCtor>(this); jsObjects[WeakSetProto] = memoryManager->allocate<WeakSetPrototype>(); static_cast<WeakSetPrototype *>(weakSetPrototype())->init(this, weakSetCtor()); - jsObjects[Set_Ctor] = memoryManager->allocate<SetCtor>(global); + jsObjects[Set_Ctor] = memoryManager->allocate<SetCtor>(this); jsObjects[SetProto] = memoryManager->allocate<SetPrototype>(); static_cast<SetPrototype *>(setPrototype())->init(this, setCtor()); @@ -722,33 +721,34 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) // promises // - jsObjects[Promise_Ctor] = memoryManager->allocate<PromiseCtor>(global); + jsObjects[Promise_Ctor] = memoryManager->allocate<PromiseCtor>(this); jsObjects[PromiseProto] = memoryManager->allocate<PromisePrototype>(); static_cast<PromisePrototype *>(promisePrototype())->init(this, promiseCtor()); // typed arrays - jsObjects[SharedArrayBuffer_Ctor] = memoryManager->allocate<SharedArrayBufferCtor>(global); + jsObjects[SharedArrayBuffer_Ctor] = memoryManager->allocate<SharedArrayBufferCtor>(this); jsObjects[SharedArrayBufferProto] = memoryManager->allocate<SharedArrayBufferPrototype>(); static_cast<SharedArrayBufferPrototype *>(sharedArrayBufferPrototype())->init(this, sharedArrayBufferCtor()); - jsObjects[ArrayBuffer_Ctor] = memoryManager->allocate<ArrayBufferCtor>(global); + jsObjects[ArrayBuffer_Ctor] = memoryManager->allocate<ArrayBufferCtor>(this); jsObjects[ArrayBufferProto] = memoryManager->allocate<ArrayBufferPrototype>(); static_cast<ArrayBufferPrototype *>(arrayBufferPrototype())->init(this, arrayBufferCtor()); - jsObjects[DataView_Ctor] = memoryManager->allocate<DataViewCtor>(global); + jsObjects[DataView_Ctor] = memoryManager->allocate<DataViewCtor>(this); jsObjects[DataViewProto] = memoryManager->allocate<DataViewPrototype>(); static_cast<DataViewPrototype *>(dataViewPrototype())->init(this, dataViewCtor()); jsObjects[ValueTypeProto] = (Heap::Base *) nullptr; jsObjects[SignalHandlerProto] = (Heap::Base *) nullptr; + jsObjects[TypeWrapperProto] = (Heap::Base *) nullptr; - jsObjects[IntrinsicTypedArray_Ctor] = memoryManager->allocate<IntrinsicTypedArrayCtor>(global); + jsObjects[IntrinsicTypedArray_Ctor] = memoryManager->allocate<IntrinsicTypedArrayCtor>(this); jsObjects[IntrinsicTypedArrayProto] = memoryManager->allocate<IntrinsicTypedArrayPrototype>(); static_cast<IntrinsicTypedArrayPrototype *>(intrinsicTypedArrayPrototype()) ->init(this, static_cast<IntrinsicTypedArrayCtor *>(intrinsicTypedArrayCtor())); for (int i = 0; i < NTypedArrayTypes; ++i) { - static_cast<Value &>(typedArrayCtors[i]) = memoryManager->allocate<TypedArrayCtor>(global, Heap::TypedArray::Type(i)); + static_cast<Value &>(typedArrayCtors[i]) = memoryManager->allocate<TypedArrayCtor>(this, Heap::TypedArray::Type(i)); static_cast<Value &>(typedArrayPrototype[i]) = memoryManager->allocate<TypedArrayPrototype>(Heap::TypedArray::Type(i)); typedArrayPrototype[i].as<TypedArrayPrototype>()->init(this, static_cast<TypedArrayCtor *>(typedArrayCtors[i].as<Object>())); } @@ -795,14 +795,14 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) globalObject->defineDefaultProperty(QStringLiteral("Math"), (o = memoryManager->allocate<MathObject>())); globalObject->defineDefaultProperty(QStringLiteral("JSON"), (o = memoryManager->allocate<JsonObject>())); globalObject->defineDefaultProperty(QStringLiteral("Reflect"), (o = memoryManager->allocate<Reflect>())); - globalObject->defineDefaultProperty(QStringLiteral("Proxy"), (o = memoryManager->allocate<Proxy>(rootContext()))); + globalObject->defineDefaultProperty(QStringLiteral("Proxy"), (o = memoryManager->allocate<Proxy>(this))); globalObject->defineReadonlyProperty(QStringLiteral("undefined"), Value::undefinedValue()); globalObject->defineReadonlyProperty(QStringLiteral("NaN"), Value::fromDouble(std::numeric_limits<double>::quiet_NaN())); globalObject->defineReadonlyProperty(QStringLiteral("Infinity"), Value::fromDouble(Q_INFINITY)); - jsObjects[Eval_Function] = memoryManager->allocate<EvalFunction>(global); + jsObjects[Eval_Function] = memoryManager->allocate<EvalFunction>(this); globalObject->defineDefaultProperty(QStringLiteral("eval"), *evalFunction()); // ES6: 20.1.2.12 & 20.1.2.13: @@ -831,7 +831,9 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) globalObject->defineDefaultProperty(QStringLiteral("escape"), GlobalFunctions::method_escape, 1); globalObject->defineDefaultProperty(QStringLiteral("unescape"), GlobalFunctions::method_unescape, 1); - ScopedFunctionObject t(scope, memoryManager->allocate<FunctionObject>(rootContext(), nullptr, ::throwTypeError)); + ScopedFunctionObject t( + scope, + memoryManager->allocate<DynamicFunctionObject>(this, nullptr, ::throwTypeError)); t->defineReadonlyProperty(id_length(), Value::fromInt32(0)); t->setInternalClass(t->internalClass()->cryopreserved()); jsObjects[ThrowerObject] = t; @@ -850,7 +852,6 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) ExecutionEngine::~ExecutionEngine() { - modules.clear(); for (auto val : nativeModules) { PersistentValueStorage::free(val); } @@ -861,8 +862,12 @@ ExecutionEngine::~ExecutionEngine() delete identifierTable; delete memoryManager; - while (!compilationUnits.isEmpty()) - (*compilationUnits.begin())->unlink(); + for (const auto &cu : std::as_const(m_compilationUnits)) { + Q_ASSERT(cu->engine == this); + cu->clear(); + cu->engine = nullptr; + } + m_compilationUnits.clear(); delete bumperPointerAllocator; delete regExpCache; @@ -896,7 +901,7 @@ void ExecutionEngine::setProfiler(Profiling::Profiler *profiler) void ExecutionEngine::initRootContext() { Scope scope(this); - Scoped<ExecutionContext> r(scope, memoryManager->allocManaged<ExecutionContext>(sizeof(ExecutionContext::Data))); + Scoped<ExecutionContext> r(scope, memoryManager->allocManaged<ExecutionContext>()); r->d_unchecked()->init(Heap::ExecutionContext::Type_GlobalContext); r->d()->activation.set(this, globalObject->d()); jsObjects[RootContext] = r; @@ -1227,8 +1232,6 @@ QQmlRefPointer<QQmlContextData> ExecutionEngine::callingQmlContext() const StackTrace ExecutionEngine::stackTrace(int frameLimit) const { - Scope scope(const_cast<ExecutionEngine *>(this)); - ScopedString name(scope); StackTrace stack; CppStackFrame *f = currentStackFrame; @@ -1236,8 +1239,8 @@ StackTrace ExecutionEngine::stackTrace(int frameLimit) const QV4::StackFrame frame; frame.source = f->source(); frame.function = f->function(); - frame.line = qAbs(f->lineNumber()); - frame.column = -1; + frame.line = f->lineNumber(); + stack.append(frame); if (f->isJSTypesFrame()) { if (static_cast<JSTypesStackFrame *>(f)->isTailCalling()) { @@ -1274,7 +1277,7 @@ static inline char *v4StackTrace(const ExecutionContext *context) const QString fileName = url.isLocalFile() ? url.toLocalFile() : url.toString(); str << "frame={level=\"" << i << "\",func=\"" << stackTrace.at(i).function << "\",file=\"" << fileName << "\",fullname=\"" << fileName - << "\",line=\"" << stackTrace.at(i).line << "\",language=\"js\"}"; + << "\",line=\"" << qAbs(stackTrace.at(i).line) << "\",language=\"js\"}"; } } str << ']'; @@ -1326,7 +1329,7 @@ void ExecutionEngine::markObjects(MarkStack *markStack) identifierTable->markObjects(markStack); - for (auto compilationUnit: compilationUnits) + for (const auto &compilationUnit : std::as_const(m_compilationUnits)) compilationUnit->markObjects(markStack); } @@ -1469,7 +1472,7 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError() if (!trace.isEmpty()) { QV4::StackFrame frame = trace.constFirst(); error.setUrl(QUrl(frame.source)); - error.setLine(frame.line); + error.setLine(qAbs(frame.line)); error.setColumn(frame.column); } QV4::Scoped<QV4::ErrorObject> errorObj(scope, exception); @@ -1480,20 +1483,21 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError() // Variant conversion code typedef QSet<QV4::Heap::Object *> V4ObjectSet; -static QVariant toVariant(const QV4::Value &value, QMetaType typeHint, bool createJSValueForObjects, V4ObjectSet *visitedObjects); +enum class JSToQVariantConversionBehavior {Never, Safish, Aggressive }; +static QVariant toVariant( + const QV4::Value &value, QMetaType typeHint, JSToQVariantConversionBehavior conversionBehavior, + V4ObjectSet *visitedObjects); static QObject *qtObjectFromJS(const QV4::Value &value); -static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObjects = nullptr); +static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObjects = nullptr, + JSToQVariantConversionBehavior behavior = JSToQVariantConversionBehavior::Safish); static bool convertToNativeQObject(const QV4::Value &value, QMetaType 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); static QV4::ReturnedValue variantToJS(QV4::ExecutionEngine *v4, const QVariant &value) { return v4->metaTypeToJS(value.metaType(), value.constData()); } -static QVariant toVariant( - const QV4::Value &value, QMetaType metaType, bool createJSValueForObjects, +static QVariant toVariant(const QV4::Value &value, QMetaType metaType, JSToQVariantConversionBehavior conversionBehavior, V4ObjectSet *visitedObjects) { Q_ASSERT (!value.isEmpty()); @@ -1504,6 +1508,12 @@ static QVariant toVariant( if (metaType == QMetaType::fromType<bool>()) return QVariant(value.toBoolean()); + if (metaType == QMetaType::fromType<double>()) + return QVariant(value.toNumber()); + + if (metaType == QMetaType::fromType<float>()) + return QVariant(float(value.toNumber())); + if (metaType == QMetaType::fromType<QJsonValue>()) return QVariant::fromValue(QV4::JsonObject::toJsonValue(value)); @@ -1565,9 +1575,9 @@ static QVariant toVariant( QV4::ScopedValue arrayValue(scope); for (qint64 i = 0; i < length; ++i) { arrayValue = a->get(i); - QVariant asVariant(valueMetaType); - if (QQmlValueTypeProvider::createValueType( - arrayValue, valueMetaType, asVariant.data())) { + QVariant asVariant = QQmlValueTypeProvider::createValueType( + arrayValue, valueMetaType); + if (asVariant.isValid()) { retnAsIterable.metaContainer().addValue(retn.data(), asVariant.constData()); continue; } @@ -1584,21 +1594,21 @@ static QVariant toVariant( } } - asVariant = toVariant(arrayValue, valueMetaType, false, visitedObjects); + asVariant = toVariant(arrayValue, valueMetaType, JSToQVariantConversionBehavior::Never, visitedObjects); if (valueMetaType == QMetaType::fromType<QVariant>()) { retnAsIterable.metaContainer().addValue(retn.data(), &asVariant); } else { auto originalType = asVariant.metaType(); bool couldConvert = asVariant.convert(valueMetaType); - if (!couldConvert) { + if (!couldConvert && originalType.isValid()) { + // If the original type was void, we're converting a "hole" in a sparse + // array. There is no point in warning about that. qWarning().noquote() << QLatin1String("Could not convert array value " "at position %1 from %2 to %3") .arg(QString::number(i), QString::fromUtf8(originalType.name()), QString::fromUtf8(valueMetaType.name())); - // create default constructed value - asVariant = QVariant(valueMetaType, nullptr); } retnAsIterable.metaContainer().addValue(retn.data(), asVariant.constData()); } @@ -1625,11 +1635,10 @@ static QVariant toVariant( return str.at(0); return str; } -#if QT_CONFIG(qml_locale) - if (const QV4::QQmlLocaleData *ld = value.as<QV4::QQmlLocaleData>()) - return *ld->d()->locale; -#endif if (const QV4::DateObject *d = value.as<DateObject>()) { + // NOTE: since we convert QTime to JS Date, + // round trip will change the variant type (to QDateTime)! + if (metaType == QMetaType::fromType<QDate>()) return DateObject::dateTimeToDate(d->toQDateTime()); @@ -1645,7 +1654,11 @@ static QVariant toVariant( return d->toQUrl(); if (const ArrayBuffer *d = value.as<ArrayBuffer>()) return d->asByteArray(); - // NOTE: since we convert QTime to JS Date, round trip will change the variant type (to QDateTime)! + if (const Symbol *symbol = value.as<Symbol>()) { + return conversionBehavior == JSToQVariantConversionBehavior::Never + ? QVariant::fromValue(QJSValuePrivate::fromReturnedValue(symbol->asReturnedValue())) + : symbol->descriptiveString(); + } const QV4::Object *object = value.as<QV4::Object>(); Q_ASSERT(object); @@ -1658,24 +1671,53 @@ static QVariant toVariant( #endif if (metaType.isValid() && !(metaType.flags() & QMetaType::PointerToQObject)) { - QVariant result(metaType); - if (QQmlValueTypeProvider::createValueType(value, metaType, result.data())) + const QVariant result = QQmlValueTypeProvider::createValueType(value, metaType); + if (result.isValid()) return result; } - if (createJSValueForObjects) + if (conversionBehavior == JSToQVariantConversionBehavior::Never) return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(o->asReturnedValue())); - return objectToVariant(o, visitedObjects); + return objectToVariant(o, visitedObjects, conversionBehavior); } +QVariant ExecutionEngine::toVariantLossy(const Value &value) +{ + return ::toVariant(value, QMetaType(), JSToQVariantConversionBehavior::Aggressive, nullptr); +} -QVariant ExecutionEngine::toVariant(const Value &value, QMetaType typeHint, bool createJSValueForObjects) +QVariant ExecutionEngine::toVariant( + const Value &value, QMetaType typeHint, bool createJSValueForObjectsAndSymbols) { - return ::toVariant(value, typeHint, createJSValueForObjects, nullptr); + auto behavior = createJSValueForObjectsAndSymbols ? JSToQVariantConversionBehavior::Never + : JSToQVariantConversionBehavior::Safish; + return ::toVariant(value, typeHint, behavior, nullptr); +} + +static QVariantMap objectToVariantMap(const QV4::Object *o, V4ObjectSet *visitedObjects, + JSToQVariantConversionBehavior conversionBehvior) +{ + QVariantMap map; + QV4::Scope scope(o->engine()); + QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly); + QV4::ScopedValue name(scope); + QV4::ScopedValue val(scope); + while (1) { + name = it.nextPropertyNameAsString(val); + if (name->isNull()) + break; + + QString key = name->toQStringNoThrow(); + map.insert(key, ::toVariant( + val, /*type hint*/ QMetaType {}, + conversionBehvior, visitedObjects)); + } + return map; } -static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObjects) +static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObjects, + JSToQVariantConversionBehavior conversionBehvior) { Q_ASSERT(o); @@ -1703,31 +1745,23 @@ static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObject int length = a->getLength(); for (int ii = 0; ii < length; ++ii) { v = a->get(ii); - list << ::toVariant(v, QMetaType {}, /*createJSValueForObjects*/false, visitedObjects); + list << ::toVariant(v, QMetaType {}, conversionBehvior, + visitedObjects); } result = list; - } else if (const FunctionObject *f = o->as<FunctionObject>()) { - // If it's a FunctionObject, we can only save it as QJSValue. - result = QVariant::fromValue(QJSValuePrivate::fromReturnedValue(f->asReturnedValue())); + } else if (o->getPrototypeOf() == o->engine()->objectPrototype()->d() + || (conversionBehvior == JSToQVariantConversionBehavior::Aggressive && + !o->as<QV4::FunctionObject>())) { + /* FunctionObject is excluded for historical reasons, even though + objects with a custom prototype risk losing information + But the Aggressive path is used only in QJSValue::toVariant + which is documented to be lossy + */ + result = objectToVariantMap(o, visitedObjects, conversionBehvior); } else { - QVariantMap map; - QV4::Scope scope(o->engine()); - QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly); - QV4::ScopedValue name(scope); - QV4::ScopedValue val(scope); - while (1) { - name = it.nextPropertyNameAsString(val); - if (name->isNull()) - break; - - QString key = name->toQStringNoThrow(); - map.insert(key, ::toVariant( - val, /*type hint*/ QMetaType {}, - /*createJSValueForObjects*/false, visitedObjects)); - } - - result = map; + // If it's not a plain object, we can only save it as QJSValue. + result = QVariant::fromValue(QJSValuePrivate::fromReturnedValue(o->asReturnedValue())); } visitedObjects->remove(o->d()); @@ -1743,6 +1777,18 @@ QV4::ReturnedValue ExecutionEngine::fromData( QMetaType metaType, const void *ptr, QV4::Heap::Object *container, int property, uint flags) { + const auto createSequence = [&](const QMetaSequence metaSequence) { + QV4::Scope scope(this); + QV4::Scoped<Sequence> sequence(scope); + if (container) { + return QV4::SequencePrototype::newSequence( + this, metaType, metaSequence, ptr, + container, property, Heap::ReferenceObject::Flags(flags)); + } else { + return QV4::SequencePrototype::fromData(this, metaType, metaSequence, ptr); + } + }; + const int type = metaType.id(); if (type < QMetaType::User) { switch (QMetaType::Type(type)) { @@ -1758,6 +1804,10 @@ QV4::ReturnedValue ExecutionEngine::fromData( return QV4::Encode(*reinterpret_cast<const int*>(ptr)); case QMetaType::UInt: return QV4::Encode(*reinterpret_cast<const uint*>(ptr)); + case QMetaType::Long: + return QV4::Encode((double)*reinterpret_cast<const long *>(ptr)); + case QMetaType::ULong: + return QV4::Encode((double)*reinterpret_cast<const ulong *>(ptr)); case QMetaType::LongLong: return QV4::Encode((double)*reinterpret_cast<const qlonglong*>(ptr)); case QMetaType::ULongLong: @@ -1803,15 +1853,9 @@ QV4::ReturnedValue ExecutionEngine::fromData( case QMetaType::QObjectStar: return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(ptr)); case QMetaType::QStringList: - { - QV4::Scope scope(this); - QV4::ScopedValue retn(scope, QV4::SequencePrototype::fromData(this, metaType, ptr)); - if (!retn->isUndefined()) - return retn->asReturnedValue(); - return QV4::Encode(newArrayObject(*reinterpret_cast<const QStringList *>(ptr))); - } + return createSequence(QMetaSequence::fromContainer<QStringList>()); case QMetaType::QVariantList: - return variantListToJS(this, *reinterpret_cast<const QVariantList *>(ptr)); + return createSequence(QMetaSequence::fromContainer<QVariantList>()); case QMetaType::QVariantMap: return variantMapToJS(this, *reinterpret_cast<const QVariantMap *>(ptr)); case QMetaType::QJsonValue: @@ -1820,10 +1864,6 @@ QV4::ReturnedValue ExecutionEngine::fromData( return QV4::JsonObject::fromJsonObject(this, *reinterpret_cast<const QJsonObject *>(ptr)); case QMetaType::QJsonArray: return QV4::JsonObject::fromJsonArray(this, *reinterpret_cast<const QJsonArray *>(ptr)); -#if QT_CONFIG(qml_locale) - case QMetaType::QLocale: - return QQmlLocale::wrap(this, *reinterpret_cast<const QLocale*>(ptr)); -#endif case QMetaType::QPixmap: case QMetaType::QImage: // Scarce value types @@ -1831,106 +1871,95 @@ QV4::ReturnedValue ExecutionEngine::fromData( default: break; } + } - if (const QMetaObject *vtmo = QQmlMetaType::metaObjectForValueType(metaType)) { - if (container) { - return QV4::QQmlValueTypeWrapper::create( - this, ptr, vtmo, metaType, - container, property, Heap::ReferenceObject::Flags(flags)); - } else { - return QV4::QQmlValueTypeWrapper::create(this, ptr, vtmo, metaType); - } - } + if (metaType.flags() & QMetaType::IsEnumeration) + return fromData(metaType.underlyingType(), ptr, container, property, flags); - } else { - QV4::Scope scope(this); - if (metaType == QMetaType::fromType<QQmlListReference>()) { - typedef QQmlListReferencePrivate QDLRP; - QDLRP *p = QDLRP::get((QQmlListReference*)const_cast<void *>(ptr)); - if (p->object) - return QV4::QmlListWrapper::create(scope.engine, p->property, p->propertyType); - else - return QV4::Encode::null(); - } else if (auto flags = metaType.flags(); flags & QMetaType::IsQmlList) { - // casting to QQmlListProperty<QObject> is slightly nasty, but it's the - // same QQmlListReference does. - const auto *p = static_cast<const QQmlListProperty<QObject> *>(ptr); - if (p->object) - return QV4::QmlListWrapper::create(scope.engine, *p, metaType); - else - return QV4::Encode::null(); - } else if (metaType == QMetaType::fromType<QJSValue>()) { - return QJSValuePrivate::convertToReturnedValue( - this, *reinterpret_cast<const QJSValue *>(ptr)); - } else if (metaType == QMetaType::fromType<QList<QObject *> >()) { - // XXX Can this be made more by using Array as a prototype and implementing - // directly against QList<QObject*>? - const QList<QObject *> &list = *(const QList<QObject *>*)ptr; - QV4::ScopedArrayObject a(scope, newArrayObject()); - a->arrayReserve(list.size()); - QV4::ScopedValue v(scope); - for (int ii = 0; ii < list.size(); ++ii) - a->arrayPut(ii, (v = QV4::QObjectWrapper::wrap(this, list.at(ii)))); - a->setArrayLengthUnchecked(list.size()); - return a.asReturnedValue(); - } else if (auto flags = metaType.flags(); flags & QMetaType::PointerToQObject) { - if (flags.testFlag(QMetaType::IsConst)) - return QV4::QObjectWrapper::wrapConst(this, *reinterpret_cast<QObject* const *>(ptr)); - else - return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(ptr)); - } else if (metaType == QMetaType::fromType<QJSPrimitiveValue>()) { - const QJSPrimitiveValue *primitive = static_cast<const QJSPrimitiveValue *>(ptr); - switch (primitive->type()) { - case QJSPrimitiveValue::Boolean: - return Encode(primitive->asBoolean()); - case QJSPrimitiveValue::Integer: - return Encode(primitive->asInteger()); - case QJSPrimitiveValue::String: - return newString(primitive->asString())->asReturnedValue(); - case QJSPrimitiveValue::Undefined: - return Encode::undefined(); - case QJSPrimitiveValue::Null: - return Encode::null(); - case QJSPrimitiveValue::Double: - return Encode(primitive->asDouble()); - } + QV4::Scope scope(this); + if (metaType == QMetaType::fromType<QQmlListReference>()) { + typedef QQmlListReferencePrivate QDLRP; + QDLRP *p = QDLRP::get((QQmlListReference*)const_cast<void *>(ptr)); + if (p->object) + return QV4::QmlListWrapper::create(scope.engine, p->property, p->propertyType); + else + return QV4::Encode::null(); + } else if (auto flags = metaType.flags(); flags & QMetaType::IsQmlList) { + // casting to QQmlListProperty<QObject> is slightly nasty, but it's the + // same QQmlListReference does. + const auto *p = static_cast<const QQmlListProperty<QObject> *>(ptr); + if (p->object) + return QV4::QmlListWrapper::create(scope.engine, *p, metaType); + else + return QV4::Encode::null(); + } else if (metaType == QMetaType::fromType<QJSValue>()) { + return QJSValuePrivate::convertToReturnedValue( + this, *reinterpret_cast<const QJSValue *>(ptr)); + } else if (metaType == QMetaType::fromType<QList<QObject *> >()) { + // XXX Can this be made more by using Array as a prototype and implementing + // directly against QList<QObject*>? + const QList<QObject *> &list = *(const QList<QObject *>*)ptr; + QV4::ScopedArrayObject a(scope, newArrayObject()); + a->arrayReserve(list.size()); + QV4::ScopedValue v(scope); + for (int ii = 0; ii < list.size(); ++ii) + a->arrayPut(ii, (v = QV4::QObjectWrapper::wrap(this, list.at(ii)))); + a->setArrayLengthUnchecked(list.size()); + return a.asReturnedValue(); + } else if (auto flags = metaType.flags(); flags & QMetaType::PointerToQObject) { + if (flags.testFlag(QMetaType::IsConst)) + return QV4::QObjectWrapper::wrapConst(this, *reinterpret_cast<QObject* const *>(ptr)); + else + return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(ptr)); + } else if (metaType == QMetaType::fromType<QJSPrimitiveValue>()) { + const QJSPrimitiveValue *primitive = static_cast<const QJSPrimitiveValue *>(ptr); + switch (primitive->type()) { + case QJSPrimitiveValue::Boolean: + return Encode(primitive->asBoolean()); + case QJSPrimitiveValue::Integer: + return Encode(primitive->asInteger()); + case QJSPrimitiveValue::String: + return newString(primitive->asString())->asReturnedValue(); + case QJSPrimitiveValue::Undefined: + return Encode::undefined(); + case QJSPrimitiveValue::Null: + return Encode::null(); + case QJSPrimitiveValue::Double: + return Encode(primitive->asDouble()); } + } - QV4::Scoped<Sequence> sequence(scope); + if (const QMetaObject *vtmo = QQmlMetaType::metaObjectForValueType(metaType)) { if (container) { - sequence = QV4::SequencePrototype::newSequence( - this, metaType, ptr, - container, property, Heap::ReferenceObject::Flags(flags)); + return QV4::QQmlValueTypeWrapper::create( + this, ptr, vtmo, metaType, + container, property, Heap::ReferenceObject::Flags(flags)); } else { - sequence = QV4::SequencePrototype::fromData(this, metaType, ptr); + return QV4::QQmlValueTypeWrapper::create(this, ptr, vtmo, metaType); } - if (!sequence->isUndefined()) - return sequence->asReturnedValue(); + } - if (QMetaType::canConvert(metaType, QMetaType::fromType<QSequentialIterable>())) { - QSequentialIterable lst; - QMetaType::convert(metaType, ptr, QMetaType::fromType<QSequentialIterable>(), &lst); - return sequentialIterableToJS(this, lst); - } + const QQmlType listType = QQmlMetaType::qmlListType(metaType); + if (listType.isSequentialContainer()) + return createSequence(listType.listMetaSequence()); - if (const QMetaObject *vtmo = QQmlMetaType::metaObjectForValueType(metaType)) { - if (container) { - return QV4::QQmlValueTypeWrapper::create( - this, ptr, vtmo, metaType, - container, property, Heap::ReferenceObject::Flags(flags)); - } else { - return QV4::QQmlValueTypeWrapper::create(this, ptr, vtmo, metaType); - } - } - } + QSequentialIterable iterable; + if (QMetaType::convert(metaType, ptr, QMetaType::fromType<QSequentialIterable>(), &iterable)) { - // XXX TODO: To be compatible, we still need to handle: - // + QObjectList - // + QList<int> + // If the resulting iterable is useful for anything, turn it into a QV4::Sequence. + const QMetaSequence sequence = iterable.metaContainer(); + if (sequence.hasSize() && sequence.canGetValueAtIndex()) + return createSequence(sequence); - // Enumeration types can just be treated as integers for now - if (metaType.flags() & QMetaType::IsEnumeration) - return QV4::Encode(*reinterpret_cast<const int *>(ptr)); + // As a last resort, try to read the contents of the container via an iterator + // and build a JS array from them. + if (sequence.hasConstIterator() && sequence.canGetValueAtConstIterator()) { + QV4::ScopedArrayObject a(scope, newArrayObject()); + for (auto it = iterable.constBegin(), end = iterable.constEnd(); it != end; ++it) + a->push_back(fromVariant(*it)); + return a.asReturnedValue(); + } + } return QV4::Encode(newVariantObject(metaType, ptr)); } @@ -1948,40 +1977,10 @@ ReturnedValue ExecutionEngine::fromVariant( QVariantMap ExecutionEngine::variantMapFromJS(const Object *o) { - return objectToVariant(o).toMap(); -} - - -// Converts a QVariantList to JS. -// The result is a new Array object with length equal to the length -// of the QVariantList, and the elements being the QVariantList's -// elements converted to JS, recursively. -static QV4::ReturnedValue variantListToJS(QV4::ExecutionEngine *v4, const QVariantList &lst) -{ - QV4::Scope scope(v4); - QV4::ScopedArrayObject a(scope, v4->newArrayObject()); - a->arrayReserve(lst.size()); - QV4::ScopedValue v(scope); - for (int i = 0; i < lst.size(); i++) - a->arrayPut(i, (v = variantToJS(v4, lst.at(i)))); - a->setArrayLengthUnchecked(lst.size()); - return a.asReturnedValue(); -} - -// Converts a QSequentialIterable to JS. -// The result is a new Array object with length equal to the length -// of the QSequentialIterable, and the elements being the QSequentialIterable's -// elements converted to JS, recursively. -static QV4::ReturnedValue sequentialIterableToJS(QV4::ExecutionEngine *v4, const QSequentialIterable &lst) -{ - QV4::Scope scope(v4); - QV4::ScopedArrayObject a(scope, v4->newArrayObject()); - a->arrayReserve(lst.size()); - QV4::ScopedValue v(scope); - for (int i = 0; i < lst.size(); i++) - a->arrayPut(i, (v = variantToJS(v4, lst.at(i)))); - a->setArrayLengthUnchecked(lst.size()); - return a.asReturnedValue(); + Q_ASSERT(o); + V4ObjectSet visitedObjects; + visitedObjects.insert(o->d()); + return objectToVariantMap(o, &visitedObjects, JSToQVariantConversionBehavior::Safish); } // Converts a QVariantMap to JS. @@ -2071,10 +2070,10 @@ QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::compileModule(const Q : QQmlMetaType::RequireFullyTyped, &cacheError) : nullptr) { - return ExecutableCompilationUnit::create( - QV4::CompiledData::CompilationUnit( - cachedUnit->qmlData, cachedUnit->aotCompiledFunctions, - url.fileName(), url.toString())); + return executableCompilationUnit( + QQml::makeRefPointer<QV4::CompiledData::CompilationUnit>( + cachedUnit->qmlData, cachedUnit->aotCompiledFunctions, url.fileName(), + url.toString())); } QFile f(QQmlFile::urlToLocalFileOrQrc(url)); @@ -2108,21 +2107,58 @@ QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::compileModule( } } - return ExecutableCompilationUnit::create(std::move(unit)); + return insertCompilationUnit(std::move(unit)); +} + +QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::compilationUnitForUrl(const QUrl &url) const +{ + // Gives the _most recently inserted_ CU of that URL. That's what we want. + return m_compilationUnits.value(url); +} + +QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::executableCompilationUnit( + QQmlRefPointer<CompiledData::CompilationUnit> &&unit) +{ + const QUrl url = unit->finalUrl(); + auto [begin, end] = std::as_const(m_compilationUnits).equal_range(url); + + for (auto it = begin; it != end; ++it) { + if ((*it)->baseCompilationUnit() == unit) + return *it; + } + + auto executableUnit = m_compilationUnits.insert( + url, ExecutableCompilationUnit::create(std::move(unit), this)); + // runtime data should not be initialized yet, so we don't need to mark the CU + Q_ASSERT(!(*executableUnit)->runtimeStrings); + return *executableUnit; +} + +QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::insertCompilationUnit(QQmlRefPointer<CompiledData::CompilationUnit> &&unit) { + QUrl url = unit->finalUrl(); + auto executableUnit = ExecutableCompilationUnit::create(std::move(unit), this); + /* Compilation Units stored in the engine are part of the gc roots, + so we don't trigger any write-barrier when they are added. Use + markCustom to make sure they are still marked when we insert them */ + QV4::WriteBarrier::markCustom(this, [&executableUnit](QV4::MarkStack *ms) { + executableUnit->markObjects(ms); + }); + return *m_compilationUnits.insert(std::move(url), std::move(executableUnit)); } -void ExecutionEngine::injectCompiledModule(const QQmlRefPointer<ExecutableCompilationUnit> &moduleUnit) +void ExecutionEngine::trimCompilationUnits() { - // Injection can happen from the QML type loader thread for example, but instantiation and - // evaluation must be limited to the ExecutionEngine's thread. - QMutexLocker moduleGuard(&moduleMutex); - modules.insert(moduleUnit->finalUrl(), moduleUnit); + for (auto it = m_compilationUnits.begin(); it != m_compilationUnits.end();) { + if ((*it)->count() == 1) + it = m_compilationUnits.erase(it); + else + ++it; + } } ExecutionEngine::Module ExecutionEngine::moduleForUrl( const QUrl &url, const ExecutableCompilationUnit *referrer) const { - QMutexLocker moduleGuard(&moduleMutex); const auto nativeModule = nativeModules.find(url); if (nativeModule != nativeModules.end()) return Module { nullptr, *nativeModule }; @@ -2130,47 +2166,45 @@ ExecutionEngine::Module ExecutionEngine::moduleForUrl( const QUrl resolved = referrer ? referrer->finalUrl().resolved(QQmlTypeLoader::normalize(url)) : QQmlTypeLoader::normalize(url); - auto existingModule = modules.find(resolved); - if (existingModule == modules.end()) + auto existingModule = m_compilationUnits.find(resolved); + if (existingModule == m_compilationUnits.end()) return Module { nullptr, nullptr }; return Module { *existingModule, nullptr }; } ExecutionEngine::Module ExecutionEngine::loadModule(const QUrl &url, const ExecutableCompilationUnit *referrer) { - QMutexLocker moduleGuard(&moduleMutex); - const auto nativeModule = nativeModules.find(url); - if (nativeModule != nativeModules.end()) + const auto nativeModule = nativeModules.constFind(url); + if (nativeModule != nativeModules.cend()) return Module { nullptr, *nativeModule }; const QUrl resolved = referrer ? referrer->finalUrl().resolved(QQmlTypeLoader::normalize(url)) : QQmlTypeLoader::normalize(url); - auto existingModule = modules.find(resolved); - if (existingModule != modules.end()) + auto existingModule = m_compilationUnits.constFind(resolved); + if (existingModule != m_compilationUnits.cend()) return Module { *existingModule, nullptr }; - moduleGuard.unlock(); - auto newModule = compileModule(resolved); - if (newModule) { - moduleGuard.relock(); - modules.insert(resolved, newModule); - } + Q_ASSERT(!newModule || m_compilationUnits.contains(resolved, newModule)); return Module { newModule, nullptr }; } QV4::Value *ExecutionEngine::registerNativeModule(const QUrl &url, const QV4::Value &module) { - QMutexLocker moduleGuard(&moduleMutex); - const auto existingModule = nativeModules.find(url); - if (existingModule != nativeModules.end()) + const auto existingModule = nativeModules.constFind(url); + if (existingModule != nativeModules.cend()) return nullptr; QV4::Value *val = this->memoryManager->m_persistentValues->allocate(); *val = module.asReturnedValue(); nativeModules.insert(url, val); + + // Make sure the type loader doesn't try to resolve the script anymore. + if (m_qmlEngine) + QQmlEnginePrivate::get(m_qmlEngine)->typeLoader.injectScript(url, *val); + return val; } @@ -2437,6 +2471,23 @@ void ExecutionEngine::setExtensionData(int index, Deletable *data) m_extensionData[index] = data; } +template<typename Source> +bool convertToIterable(QMetaType metaType, void *data, Source *sequence) +{ + QSequentialIterable iterable; + if (!QMetaType::view(metaType, data, QMetaType::fromType<QSequentialIterable>(), &iterable)) + return false; + + const QMetaType elementMetaType = iterable.valueMetaType(); + QVariant element(elementMetaType); + for (qsizetype i = 0, end = sequence->getLength(); i < end; ++i) { + if (!ExecutionEngine::metaTypeFromJS(sequence->get(i), elementMetaType, element.data())) + element = QVariant(elementMetaType); + iterable.addValue(element, QSequentialIterable::AtEnd); + } + return true; +} + // 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. @@ -2453,6 +2504,12 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi case QMetaType::UInt: *reinterpret_cast<uint*>(data) = value.toUInt32(); return true; + case QMetaType::Long: + *reinterpret_cast<long*>(data) = long(value.toInteger()); + return true; + case QMetaType::ULong: + *reinterpret_cast<ulong*>(data) = ulong(value.toInteger()); + return true; case QMetaType::LongLong: *reinterpret_cast<qlonglong*>(data) = qlonglong(value.toInteger()); return true; @@ -2471,12 +2528,24 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi *reinterpret_cast<QString*>(data) = value.toQString(); return true; case QMetaType::QByteArray: - if (const ArrayBuffer *ab = value.as<ArrayBuffer>()) + if (const ArrayBuffer *ab = value.as<ArrayBuffer>()) { *reinterpret_cast<QByteArray*>(data) = ab->asByteArray(); - else if (const String *string = value.as<String>()) + } else if (const String *string = value.as<String>()) { *reinterpret_cast<QByteArray*>(data) = string->toQString().toUtf8(); - else + } else if (const ArrayObject *ao = value.as<ArrayObject>()) { + // Since QByteArray is sequentially iterable, we have to construct it from a JS Array. + QByteArray result; + const qint64 length = ao->getLength(); + result.reserve(length); + for (qint64 i = 0; i < length; ++i) { + char value = 0; + ExecutionEngine::metaTypeFromJS(ao->get(i), QMetaType::fromType<char>(), &value); + result.push_back(value); + } + *reinterpret_cast<QByteArray*>(data) = std::move(result); + } else { *reinterpret_cast<QByteArray*>(data) = QByteArray(); + } return true; case QMetaType::Float: *reinterpret_cast<float*>(data) = value.toNumber(); @@ -2493,6 +2562,9 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi case QMetaType::UChar: *reinterpret_cast<unsigned char*>(data) = (unsigned char)(value.toInt32()); return true; + case QMetaType::SChar: + *reinterpret_cast<signed char*>(data) = (signed char)(value.toInt32()); + return true; case QMetaType::QChar: if (String *s = value.stringValue()) { QString str = s->toQString(); @@ -2562,7 +2634,8 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi const QV4::ArrayObject *a = value.as<QV4::ArrayObject>(); if (a) { *reinterpret_cast<QVariantList *>(data) = ExecutionEngine::toVariant( - *a, /*typeHint*/QMetaType{}, /*createJSValueForObjects*/false).toList(); + *a, /*typeHint*/QMetaType{}, /*createJSValueForObjectsAndSymbols*/false) + .toList(); return true; } break; @@ -2578,7 +2651,7 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi case QMetaType::QVariant: if (value.as<QV4::Managed>()) { *reinterpret_cast<QVariant*>(data) = ExecutionEngine::toVariant( - value, /*typeHint*/QMetaType{}, /*createJSValueForObjects*/false); + value, /*typeHint*/QMetaType{}, /*createJSValueForObjectsAndSymbols*/false); } else if (value.isNull()) { *reinterpret_cast<QVariant*>(data) = QVariant::fromValue(nullptr); } else if (value.isUndefined()) { @@ -2606,15 +2679,6 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi } break; } -#if QT_CONFIG(qml_locale) - case QMetaType::QLocale: { - if (const QV4::QQmlLocaleData *l = value.as<QQmlLocaleData>()) { - *reinterpret_cast<QLocale *>(data) = *l->d()->locale; - return true; - } - break; - } -#endif default: break; } @@ -2624,19 +2688,40 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi return true; } - if (metaType == QMetaType::fromType<QQmlListReference>()) { - if (const QV4::QmlListWrapper *wrapper = value.as<QV4::QmlListWrapper>()) { + if (const QV4::QmlListWrapper *wrapper = value.as<QV4::QmlListWrapper>()) { + if (metaType == QMetaType::fromType<QQmlListReference>()) { *reinterpret_cast<QQmlListReference *>(data) = wrapper->toListReference(); return true; } + + const auto wrapperPrivate = wrapper->d(); + if (wrapperPrivate->propertyType() == metaType) { + *reinterpret_cast<QQmlListProperty<QObject> *>(data) = *wrapperPrivate->property(); + return true; + } } if (const QQmlValueTypeWrapper *vtw = value.as<QQmlValueTypeWrapper>()) { const QMetaType valueType = vtw->type(); if (valueType == metaType) return vtw->toGadget(data); - if (QMetaType::canConvert(valueType, metaType)) - return QMetaType::convert(valueType, vtw->d()->gadgetPtr(), metaType, data); + + Heap::QQmlValueTypeWrapper *d = vtw->d(); + if (d->isReference()) + d->readReference(); + + if (void *gadgetPtr = d->gadgetPtr()) { + if (QQmlValueTypeProvider::populateValueType(metaType, data, valueType, gadgetPtr)) + return true; + if (QMetaType::canConvert(valueType, metaType)) + return QMetaType::convert(valueType, gadgetPtr, metaType, data); + } else { + QVariant empty(valueType); + if (QQmlValueTypeProvider::populateValueType(metaType, data, valueType, empty.data())) + return true; + if (QMetaType::canConvert(valueType, metaType)) + return QMetaType::convert(valueType, empty.data(), metaType, data); + } } // Try to use magic; for compatibility with qjsvalue_cast. @@ -2645,43 +2730,62 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi return true; const bool isPointer = (metaType.flags() & QMetaType::IsPointer); - if (value.as<QV4::VariantObject>() && isPointer) { - const QByteArray pointedToTypeName = QByteArray(metaType.name()).chopped(1); - const QMetaType valueType = QMetaType::fromName(pointedToTypeName); - QVariant &var = value.as<QV4::VariantObject>()->d()->data(); - if (valueType == var.metaType()) { - // We have T t, T* is requested, so return &t. - *reinterpret_cast<void* *>(data) = var.data(); + const QV4::VariantObject *variantObject = value.as<QV4::VariantObject>(); + if (variantObject) { + // Actually a reference, because we're poking it for its data() below and we want + // the _original_ data, not some copy. + QVariant &var = variantObject->d()->data(); + + if (var.metaType() == metaType) { + metaType.destruct(data); + metaType.construct(data, var.data()); return true; - } else if (Object *o = value.objectValue()) { - // Look in the prototype chain. - QV4::Scope scope(o->engine()); - QV4::ScopedObject proto(scope, o->getPrototypeOf()); - while (proto) { - bool canCast = false; - if (QV4::VariantObject *vo = proto->as<QV4::VariantObject>()) { - const QVariant &v = vo->d()->data(); - canCast = (metaType == v.metaType()); - } - else if (proto->as<QV4::QObjectWrapper>()) { - QV4::ScopedObject p(scope, proto.getPointer()); - if (QObject *qobject = qtObjectFromJS(p)) { - if (const QMetaObject *metaObject = metaType.metaObject()) - canCast = metaObject->cast(qobject) != nullptr; - else - canCast = qobject->qt_metacast(pointedToTypeName); + } + + if (isPointer) { + const QByteArray pointedToTypeName = QByteArray(metaType.name()).chopped(1); + const QMetaType valueType = QMetaType::fromName(pointedToTypeName); + + if (valueType == var.metaType()) { + // ### Qt7: Remove this. Returning pointers to potentially gc'd data is crazy. + // We have T t, T* is requested, so return &t. + *reinterpret_cast<const void **>(data) = var.data(); + return true; + } else if (Object *o = value.objectValue()) { + // Look in the prototype chain. + QV4::Scope scope(o->engine()); + QV4::ScopedObject proto(scope, o->getPrototypeOf()); + while (proto) { + bool canCast = false; + if (QV4::VariantObject *vo = proto->as<QV4::VariantObject>()) { + const QVariant &v = vo->d()->data(); + canCast = (metaType == v.metaType()); } + else if (proto->as<QV4::QObjectWrapper>()) { + QV4::ScopedObject p(scope, proto.getPointer()); + if (QObject *qobject = qtObjectFromJS(p)) { + if (const QMetaObject *metaObject = metaType.metaObject()) + canCast = metaObject->cast(qobject) != nullptr; + else + canCast = qobject->qt_metacast(pointedToTypeName); + } + } + if (canCast) { + const QMetaType varType = var.metaType(); + if (varType.flags() & QMetaType::IsPointer) { + *reinterpret_cast<const void **>(data) + = *reinterpret_cast<void *const *>(var.data()); + } else { + *reinterpret_cast<const void **>(data) = var.data(); + } + return true; + } + proto = proto->getPrototypeOf(); } - if (canCast) { - const QMetaType varType = var.metaType(); - if (varType.flags() & QMetaType::IsPointer) - *reinterpret_cast<void* *>(data) = *reinterpret_cast<void* *>(var.data()); - else - *reinterpret_cast<void* *>(data) = var.data(); - return true; - } - proto = proto->getPrototypeOf(); } + } else if (QQmlValueTypeProvider::populateValueType( + metaType, data, var.metaType(), var.data())) { + return true; } } else if (value.isNull() && isPointer) { *reinterpret_cast<void* *>(data) = nullptr; @@ -2693,7 +2797,7 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi *reinterpret_cast<QJSPrimitiveValue *>(data) = createPrimitive(&value); return true; } else if (!isPointer) { - if (QQmlValueTypeProvider::createValueType(value, metaType, data)) + if (QQmlValueTypeProvider::populateValueType(metaType, data, value)) return true; } @@ -2704,21 +2808,14 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi metaType.construct(data, result.constData()); return true; } + + if (convertToIterable(metaType, data, sequence)) + return true; } if (const QV4::ArrayObject *array = value.as<ArrayObject>()) { - QSequentialIterable iterable; - if (QMetaType::view( - metaType, data, QMetaType::fromType<QSequentialIterable>(), &iterable)) { - const QMetaType elementMetaType = iterable.valueMetaType(); - QVariant element(elementMetaType); - for (qsizetype i = 0, end = array->getLength(); i < end; ++i) { - if (!metaTypeFromJS(array->get(i), elementMetaType, element.data())) - element = QVariant(elementMetaType); - iterable.addValue(element, QSequentialIterable::AtEnd); - } + if (convertToIterable(metaType, data, array)) return true; - } } return false; |