diff options
Diffstat (limited to 'src/qml')
48 files changed, 553 insertions, 303 deletions
diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake index 91229d8d1f..ac9165711b 100644 --- a/src/qml/Qt6QmlMacros.cmake +++ b/src/qml/Qt6QmlMacros.cmake @@ -3262,7 +3262,7 @@ function(qt6_generate_deploy_qml_app_script) qt_deploy_qml_imports(TARGET ${arg_TARGET} PLUGINS_FOUND plugins_found) if(NOT DEFINED __QT_DEPLOY_POST_BUILD) qt_deploy_runtime_dependencies( - EXECUTABLE $<TARGET_FILE_NAME:${arg_TARGET}>.app + EXECUTABLE \"$<TARGET_FILE_NAME:${arg_TARGET}>.app\" ADDITIONAL_MODULES \${plugins_found} ${common_deploy_args}) endif()") @@ -3290,7 +3290,7 @@ endif()") CONTENT " qt_deploy_qml_imports(TARGET ${arg_TARGET} PLUGINS_FOUND plugins_found) qt_deploy_runtime_dependencies( - EXECUTABLE $<TARGET_FILE:${arg_TARGET}> + EXECUTABLE \"$<TARGET_FILE:${arg_TARGET}>\" ADDITIONAL_MODULES \${plugins_found} GENERATE_QT_CONF ${common_deploy_args})") @@ -3303,7 +3303,7 @@ ${common_deploy_args})") CONTENT " qt_deploy_qml_imports(TARGET ${arg_TARGET} PLUGINS_FOUND plugins_found) qt_deploy_runtime_dependencies( - EXECUTABLE $<TARGET_FILE:${arg_TARGET}> + EXECUTABLE \"$<TARGET_FILE:${arg_TARGET}>\" ADDITIONAL_MODULES \${plugins_found} GENERATE_QT_CONF ${common_deploy_args})") diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 5df9b3fa4c..b9f784a761 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1422,6 +1422,7 @@ bool Codegen::visit(BinaryExpression *ast) return false; } else if (ast->op == QSOperator::Assign) { + bytecodeGenerator->setLocation(ast->left->firstSourceLocation()); if (AST::Pattern *p = ast->left->patternCast()) { RegisterScope scope(this); Reference right = expression(ast->right); diff --git a/src/qml/doc/src/cppintegration/data.qdoc b/src/qml/doc/src/cppintegration/data.qdoc index cba6a122b9..e89cca452f 100644 --- a/src/qml/doc/src/cppintegration/data.qdoc +++ b/src/qml/doc/src/cppintegration/data.qdoc @@ -497,7 +497,7 @@ Q_ENUM(Status) \endcode Enum classes are registered in QML as scoped and unscoped properties. -The \c Ready value will be registered at \c Message.Status.Ready and \c Message.Ready . +The \c Ready value will be registered as \c Message.Status.Ready and \c Message.Ready . When using enum classes, there can be multiple enums using the same identifiers. The unscoped registration will be overwriten by the last registered enum. For classes diff --git a/src/qml/doc/src/cppintegration/exposecppattributes.qdoc b/src/qml/doc/src/cppintegration/exposecppattributes.qdoc index 6031d0eebb..83b7155636 100644 --- a/src/qml/doc/src/cppintegration/exposecppattributes.qdoc +++ b/src/qml/doc/src/cppintegration/exposecppattributes.qdoc @@ -521,6 +521,12 @@ pragma NativeMethodBehavior: AcceptThisObject With this line added, the example above will work as expected. +\section2 Overriding toString() + +If you provide a Q_INVOKABLE method called \e{toString} (with no arguments), that +method will be used to convert the object to a string in place of JavaScript's +native \e{toString} implementation. + \section1 Exposing Signals Any public \l{Signals & Slots}{signal} of a QObject-derived type is accessible diff --git a/src/qml/doc/src/javascript/hostenvironment.qdoc b/src/qml/doc/src/javascript/hostenvironment.qdoc index c267371bbd..26705f9e19 100644 --- a/src/qml/doc/src/javascript/hostenvironment.qdoc +++ b/src/qml/doc/src/javascript/hostenvironment.qdoc @@ -47,6 +47,10 @@ to use from C++. See \l {qtqml-cppintegration-interactqmlfromcpp.html}{Interacting with QML Objects from C++} for more information. +The exception to this rule are functions assigned to signal handlers: There, type annotations are +forbidden to avoid a potential mismatch with the types of the signal. This does not cause issues for +tooling, as the signal already provides the necessary information. + By default, type annotations are ignored by the interpreter and the JIT compiler, but enforced by \l{qmlcachegen} and \l{qmlsc} when compiling to C++. This can lead to differences in behavior if you either pass values that diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index ca63c9c03a..e61dfe0ae7 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -1181,11 +1181,11 @@ This returns the attached object instance that has been attached to the specified \a attachee by the attaching type \e T. - If \a create is true and type \e T is a valid attaching type, this creates and returns a new - attached object instance. + If \a attachee is \nullptr, returns \nullptr. - Returns 0 if type \e T is not a valid attaching type, or if \a create is false and no - attachment object instance has previously been created for \a attachee. + If an existing attached object instance of type \e T exists, it will return + it. Otherwise, it will return a newly created instance if \a create is + \c true and \e T is a valid attaching type, or \nullptr if it's not. \sa QML_ATTACHED(), {Providing Attached Properties} */ diff --git a/src/qml/jit/qv4assemblercommon_p.h b/src/qml/jit/qv4assemblercommon_p.h index cccd90fab8..d02f57b42b 100644 --- a/src/qml/jit/qv4assemblercommon_p.h +++ b/src/qml/jit/qv4assemblercommon_p.h @@ -583,12 +583,12 @@ public: for (Jump j : catchyJumps) j.link(this); + loadUndefined(); // We don't need to check for isInterrupted here because if that is set, // then the first checkException() in any exception handler will find another "exception" // and jump out of the exception handler. loadPtr(exceptionHandlerAddress(), ScratchRegister); Jump exitFunction = branchPtr(Equal, ScratchRegister, TrustedImmPtr(0)); - loadUndefined(); jump(ScratchRegister); exitFunction.link(this); diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index 374d170019..45990f6996 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -928,7 +928,8 @@ bool QJSEngine::convertV2(const QJSValue &value, QMetaType metaType, void *ptr) return convertString(*string, metaType, ptr); // Does not need scoping since QJSValue still holds on to the value. - return QV4::ExecutionEngine::metaTypeFromJS(QJSValuePrivate::asReturnedValue(&value), metaType, ptr); + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(&value)), + metaType, ptr); } bool QJSEngine::convertVariant(const QVariant &value, QMetaType metaType, void *ptr) @@ -1155,7 +1156,8 @@ void QJSEngine::throwError(QJSValue::ErrorType errorType, const QString &message */ void QJSEngine::throwError(const QJSValue &error) { - m_v4Engine->throwError(QJSValuePrivate::asReturnedValue(&error)); + // safe, QJSValue holds a persistent reference + m_v4Engine->throwError(QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(&error))); } /*! diff --git a/src/qml/jsapi/qjsengine.h b/src/qml/jsapi/qjsengine.h index dbeecb45ae..a16ac60603 100644 --- a/src/qml/jsapi/qjsengine.h +++ b/src/qml/jsapi/qjsengine.h @@ -145,6 +145,18 @@ public: } } + if constexpr (std::is_same_v<T, double>) { + if (sourceType == QMetaType::fromType<int>()) + return double(*static_cast<const int *>(value.constData())); + } + + if constexpr (std::is_same_v<T, int>) { + if (sourceType == QMetaType::fromType<double>()) { + return QJSNumberCoercion::toInteger( + *static_cast<const double *>(value.constData())); + } + } + if constexpr (std::is_same_v<QObject, std::remove_const_t<std::remove_pointer_t<T>>>) { if (sourceType.flags() & QMetaType::PointerToQObject) { return *static_cast<QObject *const *>(value.constData()); @@ -254,6 +266,16 @@ public: return from; } + if constexpr (std::is_same_v<To, double>) { + if constexpr (std::is_same_v<From, int>) + return double(from); + } + + if constexpr (std::is_same_v<To, int>) { + if constexpr (std::is_same_v<From, double>) + return QJSNumberCoercion::toInteger(from); + } + { const QMetaType sourceType = QMetaType::fromType<From>(); const QMetaType targetType = QMetaType::fromType<To>(); diff --git a/src/qml/jsapi/qjsmanagedvalue.cpp b/src/qml/jsapi/qjsmanagedvalue.cpp index a4f2f97432..b429784a96 100644 --- a/src/qml/jsapi/qjsmanagedvalue.cpp +++ b/src/qml/jsapi/qjsmanagedvalue.cpp @@ -787,7 +787,8 @@ void QJSManagedValue::setProperty(const QString &name, const QJSValue &value) return; } QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); - obj->put(key, QJSValuePrivate::convertToReturnedValue(scope.engine, value)); + QV4::ScopedValue val(scope, QJSValuePrivate::convertToReturnedValue(scope.engine, value)); + obj->put(key, val); } } @@ -905,7 +906,10 @@ void QJSManagedValue::setProperty(quint32 arrayIndex, const QJSValue &value) "Value was created in different engine."); return; } - obj->put(arrayIndex, QJSValuePrivate::convertToReturnedValue(v4, value)); + v4 = obj->engine(); // in case value was primitive + QV4::Scope scope(v4); + QV4::ScopedValue v(scope, QJSValuePrivate::convertToReturnedValue(v4, value)); + obj->put(arrayIndex, v); } } @@ -1120,6 +1124,8 @@ QJSManagedValue QJSManagedValue::jsMetaInstantiate(const QJSValueList &values) c *result.d = c->engine()->newObject(c->d()); QV4::Object *o = result.d->as<QV4::Object>(); + QV4::Scope scope(engine); + QV4::ScopedValue val(scope); for (uint i = 0, end = qMin(qsizetype(c->d()->size), values.size()); i < end; ++i) { const QJSValue &arg = values[i]; if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { @@ -1127,7 +1133,8 @@ QJSManagedValue QJSManagedValue::jsMetaInstantiate(const QJSValueList &values) c "Argument was created in different engine."); return QJSManagedValue(); } - o->setProperty(i, QJSValuePrivate::convertToReturnedValue(engine, arg)); + val = QJSValuePrivate::convertToReturnedValue(engine, arg); + o->setProperty(i, val); } return result; diff --git a/src/qml/jsapi/qjsprimitivevalue.h b/src/qml/jsapi/qjsprimitivevalue.h index 49a00ed180..ea65aa5610 100644 --- a/src/qml/jsapi/qjsprimitivevalue.h +++ b/src/qml/jsapi/qjsprimitivevalue.h @@ -777,10 +777,14 @@ private: } union { - bool m_bool = false; + bool m_bool; int m_int; double m_double; QString m_string; + + // Dummy value to trigger initialization of the whole storage. + // We don't want to see maybe-uninitialized warnings every time we access m_string. + std::byte m_zeroInitialize[sizeof(QString)] = {}; }; Type m_type = Undefined; diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index 8717462729..943808b322 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -902,7 +902,10 @@ QJSValue& QJSValue::operator=(const QJSValue& other) if (const QString *string = QJSValuePrivate::asQString(&other)) QJSValuePrivate::setString(this, *string); else - QJSValuePrivate::setValue(this, QJSValuePrivate::asReturnedValue(&other)); + // fomReturnedValue is safe, as the QJSValue still has a persistent reference + QJSValuePrivate::setValue( + this, + QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(&other))); return *this; } @@ -990,17 +993,25 @@ static bool js_equal(const QString &string, const QV4::Value &value) */ bool QJSValue::equals(const QJSValue& other) const { + // QJSValue stores heap items in persistent values, which already ensures marking + // therefore, fromReturnedValue below is safe if (const QString *string = QJSValuePrivate::asQString(this)) { if (const QString *otherString = QJSValuePrivate::asQString(&other)) return *string == *otherString; - return js_equal(*string, QJSValuePrivate::asReturnedValue(&other)); + return js_equal( + *string, + QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(&other))); } - if (const QString *otherString = QJSValuePrivate::asQString(&other)) - return js_equal(*otherString, QJSValuePrivate::asReturnedValue(this)); + if (const QString *otherString = QJSValuePrivate::asQString(&other)) { + return js_equal( + *otherString, + QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(this))); + } - return Runtime::CompareEqual::call(QJSValuePrivate::asReturnedValue(this), - QJSValuePrivate::asReturnedValue(&other)); + return Runtime::CompareEqual::call( + QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(this)), + QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(&other))); } /*! @@ -1041,8 +1052,11 @@ bool QJSValue::strictlyEquals(const QJSValue& other) const return false; } - return RuntimeHelpers::strictEqual(QJSValuePrivate::asReturnedValue(this), - QJSValuePrivate::asReturnedValue(&other)); + // QJSValue stores heap objects persistently, so we can be sure that they'll be marked + // thus we can safely use fromReturnedValue + return RuntimeHelpers::strictEqual( + QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(this)), + QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(&other))); } /*! diff --git a/src/qml/jsapi/qjsvalue_p.h b/src/qml/jsapi/qjsvalue_p.h index b95e76e76e..6bc731aace 100644 --- a/src/qml/jsapi/qjsvalue_p.h +++ b/src/qml/jsapi/qjsvalue_p.h @@ -189,7 +189,7 @@ public: static QJSValue fromReturnedValue(QV4::ReturnedValue d) { QJSValue result; - setValue(&result, d); + setValue(&result, QV4::Value::fromReturnedValue(d)); return result; } diff --git a/src/qml/jsruntime/qv4arrayobject.cpp b/src/qml/jsruntime/qv4arrayobject.cpp index 05e73d0295..e4170b459e 100644 --- a/src/qml/jsruntime/qv4arrayobject.cpp +++ b/src/qml/jsruntime/qv4arrayobject.cpp @@ -576,6 +576,7 @@ ReturnedValue ArrayPrototype::method_join(const FunctionObject *functionObject, const Value *thisObject, const Value *argv, int argc) { Scope scope(functionObject); + CHECK_STACK_LIMITS(scope.engine) ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index a807cc42df..3bb140a4fd 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -2453,9 +2453,12 @@ bool convertToIterable(QMetaType metaType, void *data, Source *sequence) return false; const QMetaType elementMetaType = iterable.valueMetaType(); + QV4::Scope scope(sequence->engine()); + QV4::ScopedValue v(scope); for (qsizetype i = 0, end = sequence->getLength(); i < end; ++i) { QVariant element(elementMetaType); - ExecutionEngine::metaTypeFromJS(sequence->get(i), elementMetaType, element.data()); + v = sequence->get(i); + ExecutionEngine::metaTypeFromJS(v, elementMetaType, element.data()); iterable.addValue(element, QSequentialIterable::AtEnd); } return true; @@ -2515,9 +2518,12 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi QByteArray result; const qint64 length = ao->getLength(); result.reserve(length); + QV4::Scope scope(ao->engine()); + QV4::ScopedValue v(scope); for (qint64 i = 0; i < length; ++i) { char value = 0; - ExecutionEngine::metaTypeFromJS(ao->get(i), QMetaType::fromType<char>(), &value); + v = ao->get(i); + ExecutionEngine::metaTypeFromJS(v, QMetaType::fromType<char>(), &value); result.push_back(value); } *reinterpret_cast<QByteArray*>(data) = std::move(result); @@ -2778,7 +2784,7 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi *reinterpret_cast<void* *>(data) = nullptr; return true; } else if (metaType == QMetaType::fromType<QJSValue>()) { - QJSValuePrivate::setValue(reinterpret_cast<QJSValue*>(data), value.asReturnedValue()); + QJSValuePrivate::setValue(reinterpret_cast<QJSValue*>(data), value); return true; } else if (metaType == QMetaType::fromType<QJSPrimitiveValue>()) { *reinterpret_cast<QJSPrimitiveValue *>(data) = createPrimitive(&value); diff --git a/src/qml/jsruntime/qv4executablecompilationunit.cpp b/src/qml/jsruntime/qv4executablecompilationunit.cpp index dc24f2d2a9..aed1a84f63 100644 --- a/src/qml/jsruntime/qv4executablecompilationunit.cpp +++ b/src/qml/jsruntime/qv4executablecompilationunit.cpp @@ -695,8 +695,10 @@ const Value *ExecutableCompilationUnit::resolveExportRecursively( return fragment.native; ScopedObject o(scope, dependentModule.native); - if (o) - return engine->registerNativeModule(request, o->get(importName)); + if (o) { + ScopedValue nativeModule(scope, o->get(importName)); + return engine->registerNativeModule(request, nativeModule); + } return nullptr; } else { @@ -727,8 +729,10 @@ const Value *ExecutableCompilationUnit::resolveExportRecursively( resolution = fragment.native; } else { ScopedObject o(scope, dependentModule.native); - if (o) - resolution = engine->registerNativeModule(request, o->get(exportName)); + if (o) { + ScopedValue nativeModule(scope, o->get(exportName)); + resolution = engine->registerNativeModule(request, nativeModule); + } } } } diff --git a/src/qml/jsruntime/qv4globalobject.cpp b/src/qml/jsruntime/qv4globalobject.cpp index 32e93cedb4..ec74fbceed 100644 --- a/src/qml/jsruntime/qv4globalobject.cpp +++ b/src/qml/jsruntime/qv4globalobject.cpp @@ -301,15 +301,15 @@ ReturnedValue EvalFunction::evalCall(const Value *, const Value *argv, int argc, return Encode::undefined(); ExecutionEngine *v4 = engine(); - bool isStrict = v4->currentStackFrame->v4Function->isStrict(); + const Function *v4Function = v4->currentStackFrame + ? v4->currentStackFrame->v4Function + : v4->globalCode; + const bool isStrict = v4Function && v4Function->isStrict(); Scope scope(v4); - ScopedContext ctx(scope, v4->currentContext()); - if (!directCall) { - // the context for eval should be the global scope - ctx = v4->scriptContext(); - } + // In case of !directCall, the context for eval should be the global scope + ScopedContext ctx(scope, directCall ? v4->currentContext() : v4->scriptContext()); String *scode = argv[0].stringValue(); if (!scode) @@ -330,14 +330,14 @@ ReturnedValue EvalFunction::evalCall(const Value *, const Value *argv, int argc, return Encode::undefined(); function->kind = Function::Eval; + ScopedValue thisObject(scope, directCall + ? scope.engine->currentStackFrame->thisObject() + : scope.engine->globalObject->asReturnedValue()); if (function->isStrict() || isStrict) { ScopedFunctionObject e(scope, FunctionObject::createScriptFunction(ctx, function)); - ScopedValue thisObject(scope, directCall ? scope.engine->currentStackFrame->thisObject() : scope.engine->globalObject->asReturnedValue()); return checkedResult(v4, e->call(thisObject, nullptr, 0)); } - ScopedValue thisObject(scope, scope.engine->currentStackFrame->thisObject()); - return checkedResult(v4, function->call(thisObject, nullptr, 0, ctx)); } diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index 0851b2f5ab..2071e39520 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -42,7 +42,9 @@ void Object::setInternalClass(Heap::InternalClass *ic) for (uint i = 0; i < ic->size; ++i) { // Note that some members might have been deleted. The key may be invalid. const PropertyKey key = ic->nameMap.at(i); - newMembers->set(scope.engine, i, key.isValid() ? get(key) : Encode::undefined()); + // fromReturnedValue is safe, we directly write it through the WriteBarrier + newMembers->set(scope.engine, i, + QV4::Value::fromReturnedValue(key.isValid() ? get(key) : Encode::undefined())); } p->internalClass.set(scope.engine, ic); diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index e19c57fd40..6d08764a11 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -2219,7 +2219,9 @@ bool CallArgument::fromValue(QMetaType metaType, ExecutionEngine *engine, const default: if (type == qMetaTypeId<QJSValue>()) { qjsValuePtr = new (&allocData) QJSValue; - QJSValuePrivate::setValue(qjsValuePtr, value.asReturnedValue()); + Scope scope(engine); + ScopedValue v(scope, value); + QJSValuePrivate::setValue(qjsValuePtr, v); return true; } diff --git a/src/qml/jsruntime/qv4sequenceobject.cpp b/src/qml/jsruntime/qv4sequenceobject.cpp index 1dd2654382..2cafd9a741 100644 --- a/src/qml/jsruntime/qv4sequenceobject.cpp +++ b/src/qml/jsruntime/qv4sequenceobject.cpp @@ -709,7 +709,8 @@ QVariant SequencePrototype::toVariant(const QV4::Value &array, QMetaType typeHin QV4::ScopedValue v(scope); for (quint32 i = 0; i < quint32(length); ++i) { const QMetaType valueMetaType = priv->typeId; - QVariant variant = ExecutionEngine::toVariant(a->get(i), valueMetaType, false); + QV4::ScopedValue element(scope, a->get(i)); + QVariant variant = ExecutionEngine::toVariant(element, valueMetaType, false); if (valueMetaType == QMetaType::fromType<QVariant>()) { meta->addValueAtEnd(result.data(), &variant); } else { diff --git a/src/qml/jsruntime/qv4urlobject.cpp b/src/qml/jsruntime/qv4urlobject.cpp index 637d033bd4..aa0d47c766 100644 --- a/src/qml/jsruntime/qv4urlobject.cpp +++ b/src/qml/jsruntime/qv4urlobject.cpp @@ -806,7 +806,8 @@ ReturnedValue UrlSearchParamsCtor::virtualCallAsConstructor(const FunctionObject uint len = argArray->getLength(); for (uint i = 0; i < len; i++) { - QV4::Value pair = argArray->get(i); + // safe to user fromReturnedValue: Normal array, which will ensure marking + QV4::Value pair = Value::fromReturnedValue(argArray->get(i)); auto *pairArrayObject = pair.as<ArrayObject>(); if (pairArrayObject == nullptr) { @@ -895,11 +896,14 @@ void UrlSearchParamsObject::initializeParams(ScopedArrayObject& params) for (uint i = 0; i < len; i++) { - QV4::Value pair = params->get(i); + // fromReturnedValue is safe; everything is reachable via params + // so the gc won't collect it; we control params, so there can't be + // any weird proxy magic + QV4::Value pair = Value::fromReturnedValue(params->get(i)); auto *pairArrayObject = pair.as<ArrayObject>(); - QV4::Value key = pairArrayObject->get(uint(0)); - QV4::Value value = pairArrayObject->get(uint(1)); + QV4::Value key = Value::fromReturnedValue(pairArrayObject->get(uint(0))); + QV4::Value value = Value::fromReturnedValue(pairArrayObject->get(uint(1))); scopedKeys->put(i, key); scopedValues->put(i, value); @@ -1014,11 +1018,11 @@ QList<QStringList> UrlSearchParamsObject::params() const uint len = scopedArray->getLength(); for (uint i = 0; i < len; i++) { - QV4::Value pair = scopedArray->get(i); + QV4::Value pair = Value::fromReturnedValue(scopedArray->get(i)); auto *pairArrayObject = pair.as<ArrayObject>(); - QV4::Value key = pairArrayObject->get(uint(0)); - QV4::Value value = pairArrayObject->get(uint(1)); + QV4::Value key = Value::fromReturnedValue(pairArrayObject->get(uint(0))); + QV4::Value value = Value::fromReturnedValue(pairArrayObject->get(uint(1))); result << QStringList { key.toQString(), value.toQString() }; } @@ -1063,10 +1067,11 @@ int UrlSearchParamsObject::indexOf(QString name, int last) const int len = scopedArray->getLength(); for (int i = last + 1; i < len; i++) { - QV4::Value pair = scopedArray->get(i); + // fromReturnedValue is safe, scopedArray is a normal array and takes care of marking + QV4::Value pair = Value::fromReturnedValue(scopedArray->get(i)); auto *pairArrayObject = pair.as<ArrayObject>(); - QV4::Value key = pairArrayObject->get(uint(0)); + QV4::Value key = Value::fromReturnedValue(pairArrayObject->get(uint(0))); if (key.toQString() == name) return i; @@ -1084,10 +1089,10 @@ QString UrlSearchParamsObject::stringAt(int index, int pairIndex) const if (index >= scopedArray->getLength()) return {}; - QV4::Value pair = scopedArray->get(index); + QV4::Value pair = Value::fromReturnedValue(scopedArray->get(index)); auto *pairArrayObject = pair.as<ArrayObject>(); - QV4::Value value = pairArrayObject->get(pairIndex); + QV4::Value value = Value::fromReturnedValue(pairArrayObject->get(pairIndex)); return value.toQString(); } @@ -1101,10 +1106,10 @@ QV4::Heap::String * UrlSearchParamsObject::stringAtRaw(int index, int pairIndex) if (index >= scopedArray->getLength()) return nullptr; - QV4::Value pair = scopedArray->get(index); + QV4::Value pair = Value::fromReturnedValue(scopedArray->get(index)); auto *pairArrayObject = pair.as<ArrayObject>(); - QV4::Value value = pairArrayObject->get(pairIndex); + QV4::Value value = Value::fromReturnedValue(pairArrayObject->get(pairIndex)); return value.as<String>()->d(); } diff --git a/src/qml/jsruntime/qv4value_p.h b/src/qml/jsruntime/qv4value_p.h index 643d6c16a9..36fe261dea 100644 --- a/src/qml/jsruntime/qv4value_p.h +++ b/src/qml/jsruntime/qv4value_p.h @@ -38,9 +38,6 @@ struct Q_QML_PRIVATE_EXPORT Value : public StaticValue { using ManagedPtr = Managed *; - Value() = default; - constexpr Value(quint64 val) : StaticValue(val) {} - static constexpr Value fromStaticValue(StaticValue staticValue) { return {staticValue._val}; diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 44e8955b7a..f6bb6d4082 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -1267,7 +1267,9 @@ void AOTCompiledContext::storeNameSloppy(uint nameIndex, void *value, QMetaType } else { QVariant var(propType); QV4::ExecutionEngine *v4 = engine->handle(); - v4->metaTypeFromJS(v4->metaTypeToJS(type, value), propType, var.data()); + QV4::Scope scope(v4); + QV4::ScopedValue val(scope, v4->metaTypeToJS(type, value)); + v4->metaTypeFromJS(val, propType, var.data()); storeResult = storeObjectProperty(&l, qmlScopeObject, var.data()); } @@ -1286,7 +1288,9 @@ void AOTCompiledContext::storeNameSloppy(uint nameIndex, void *value, QMetaType } else { QVariant var(propType); QV4::ExecutionEngine *v4 = engine->handle(); - v4->metaTypeFromJS(v4->metaTypeToJS(type, value), propType, var.data()); + QV4::Scope scope(v4); + QV4::ScopedValue val(scope, v4->metaTypeToJS(type, value)); + v4->metaTypeFromJS(val, propType, var.data()); storeResult = storeFallbackProperty(&l, qmlScopeObject, var.data()); } break; @@ -1504,7 +1508,9 @@ void AOTCompiledContext::initCallGlobalLookup(uint index) const bool AOTCompiledContext::loadGlobalLookup(uint index, void *target, QMetaType type) const { QV4::Lookup *l = compilationUnit->runtimeLookups + index; - if (!QV4::ExecutionEngine::metaTypeFromJS(l->globalGetter(l, engine->handle()), type, target)) { + QV4::Scope scope(engine->handle()); + QV4::ScopedValue val(scope, l->globalGetter(l, engine->handle())); + if (!QV4::ExecutionEngine::metaTypeFromJS(val, type, target)) { engine->handle()->throwTypeError(); return false; } diff --git a/src/qml/qml/qqmlabstractbinding.cpp b/src/qml/qml/qqmlabstractbinding.cpp index 78d1d68f55..d1c0a855d1 100644 --- a/src/qml/qml/qqmlabstractbinding.cpp +++ b/src/qml/qml/qqmlabstractbinding.cpp @@ -49,16 +49,15 @@ void QQmlAbstractBinding::addToObject() // Find the value type proxy (if there is one) QQmlValueTypeProxyBinding *proxy = nullptr; - if (data->hasBindingBit(coreIndex)) { - QQmlAbstractBinding *b = data->bindings; - while (b && (b->targetPropertyIndex().coreIndex() != coreIndex || - b->targetPropertyIndex().hasValueTypeIndex())) - b = b->nextBinding(); - Q_ASSERT(b && b->kind() == QQmlAbstractBinding::ValueTypeProxy); - proxy = static_cast<QQmlValueTypeProxyBinding *>(b); + QQmlAbstractBinding *b = data->bindings; + while (b && (b->targetPropertyIndex().coreIndex() != coreIndex || + b->targetPropertyIndex().hasValueTypeIndex())) { + b = b->nextBinding(); } - if (!proxy) { + if (b && b->kind() == QQmlAbstractBinding::ValueTypeProxy) { + proxy = static_cast<QQmlValueTypeProxyBinding *>(b); + } else { proxy = new QQmlValueTypeProxyBinding(obj, QQmlPropertyIndex(coreIndex)); Q_ASSERT(proxy->targetPropertyIndex().coreIndex() == coreIndex); diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index 551f55b4be..31b3c28690 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -218,7 +218,9 @@ protected: } // If the type didn't match, we need to do JavaScript conversion. This should be rare. - return write(engine()->handle()->metaTypeToJS(type, result), isUndefined, flags); + QV4::Scope scope(engine()->handle()); + QV4::ScopedValue value(scope, engine()->handle()->metaTypeToJS(type, result)); + return write(value, isUndefined, flags); } // Returns true if successful, false if an error description was set on expression @@ -439,8 +441,10 @@ bool QQmlBinding::slowWrite( if (core.isVarProperty()) { QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(m_target.data()); Q_ASSERT(vmemo); + QV4::Scope scope(qmlEngine->handle()); + QV4::ScopedValue value(scope, qmlEngine->handle()->metaTypeToJS(resultType, result)); vmemo->setVMEProperty(core.coreIndex(), - qmlEngine->handle()->metaTypeToJS(resultType, result)); + value); } else if (isUndefined && core.isResettable()) { void *args[] = { nullptr }; QMetaObject::metacall(m_target.data(), QMetaObject::ResetProperty, core.coreIndex(), args); @@ -697,7 +701,7 @@ void QQmlBinding::doUpdate(const DeleteWatcher &watcher, QQmlPropertyData::Write error = !write(result, returnType, isUndefined, flags); returnType.destruct(result); } else if (canWrite()) { - error = !write(QV4::Encode::undefined(), true, flags); + error = !write(QV4::Value::undefinded(), true, flags); } } } else { diff --git a/src/qml/qml/qqmlcomponent.cpp b/src/qml/qml/qqmlcomponent.cpp index 60e5dd4c9b..9cc57eed13 100644 --- a/src/qml/qml/qqmlcomponent.cpp +++ b/src/qml/qml/qqmlcomponent.cpp @@ -382,7 +382,8 @@ bool QQmlComponentPrivate::setInitialProperty( } const QString lastProperty = properties.last(); segment = scope.engine->newString(lastProperty); - object->put(segment, scope.engine->metaTypeToJS(value.metaType(), value.constData())); + QV4::ScopedValue v(scope, scope.engine->metaTypeToJS(value.metaType(), value.constData())); + object->put(segment, v); if (scope.engine->hasException) { qmlWarning(base, scope.engine->catchExceptionAsQmlError()); scope.engine->hasException = false; @@ -927,6 +928,11 @@ QObject *QQmlComponent::create(QQmlContext *context) there are unset required properties, the object creation fails and returns \c nullptr, in which case \l isError() will return \c true. + If \a context is \nullptr (the default), it will create the instance in the + \l {QQmlEngine::rootContext()}{root context} of the engine. + + The ownership of the returned object instance is transferred to the caller. + \sa QQmlComponent::create \since 5.14 */ @@ -1419,8 +1425,8 @@ void QQmlComponentPrivate::completeLoadFromModule(QAnyStringView uri, QAnyString \a incubator. \a context specifies the context within which to create the object instance. - If \a context is \nullptr (by default), it will create the instance in the - engine's \l {QQmlEngine::rootContext()}{root context}. + If \a context is \nullptr (the default), it will create the instance in the + \l {QQmlEngine::rootContext()}{root context} of the engine. \a forContext specifies a context that this object creation depends upon. If the \a forContext is being created asynchronously, and the diff --git a/src/qml/qml/qqmlcontextdata.cpp b/src/qml/qml/qqmlcontextdata.cpp index 8667a1ce81..22a359d93f 100644 --- a/src/qml/qml/qqmlcontextdata.cpp +++ b/src/qml/qml/qqmlcontextdata.cpp @@ -100,7 +100,6 @@ void QQmlContextData::invalidate() } m_importedScripts.clear(); - m_engine = nullptr; clearParent(); } @@ -111,6 +110,8 @@ void QQmlContextData::clearContextRecursively() for (auto ctxIt = m_childContexts; ctxIt; ctxIt = ctxIt->m_nextChild) ctxIt->clearContextRecursively(); + + m_engine = nullptr; } void QQmlContextData::clearContext() @@ -137,8 +138,7 @@ QQmlContextData::~QQmlContextData() // avoid recursion addref(); - if (m_engine) - invalidate(); + invalidate(); m_linkedContext.reset(); Q_ASSERT(refCount() == 1); diff --git a/src/qml/qml/qqmlcontextdata_p.h b/src/qml/qml/qqmlcontextdata_p.h index f5d54c8fd2..ae95123d8c 100644 --- a/src/qml/qml/qqmlcontextdata_p.h +++ b/src/qml/qml/qqmlcontextdata_p.h @@ -277,6 +277,15 @@ public: QV4::PersistentValue importedScripts() const { return m_importedScripts; } void setImportedScripts(const QV4::PersistentValue &scripts) { m_importedScripts = scripts; } + /* + we can safely pass a ReturnedValue here, as setImportedScripts will directly store + scripts in a persistentValue, without any intermediate allocation that could trigger + a gc run + */ + void setImportedScripts(QV4::ExecutionEngine *engine, QV4::ReturnedValue scripts) { + m_importedScripts.set(engine, scripts); + } + QQmlRefPointer<QQmlContextData> linkedContext() const { return m_linkedContext; } void setLinkedContext(const QQmlRefPointer<QQmlContextData> &context) { m_linkedContext = context; } diff --git a/src/qml/qml/qqmldelayedcallqueue.cpp b/src/qml/qml/qqmldelayedcallqueue.cpp index 96a5679599..655264435c 100644 --- a/src/qml/qml/qqmldelayedcallqueue.cpp +++ b/src/qml/qml/qqmldelayedcallqueue.cpp @@ -153,7 +153,8 @@ void QQmlDelayedCallQueue::storeAnyArguments(DelayedFunctionCall &dfc, QQmlV4Fun QV4::ScopedArrayObject array(scope, engine->newArrayObject(length)); uint i = 0; for (int j = offset, ej = args->length(); j < ej; ++i, ++j) - array->put(i, (*args)[j]); + // fromReturnedValue is safe, args should already ensure marking + array->put(i, QV4::Value::fromReturnedValue((*args)[j])); dfc.m_args.set(engine, array); } diff --git a/src/qml/qml/qqmlextensionplugin.cpp b/src/qml/qml/qqmlextensionplugin.cpp index 441fc6200d..09f400c173 100644 --- a/src/qml/qml/qqmlextensionplugin.cpp +++ b/src/qml/qml/qqmlextensionplugin.cpp @@ -35,7 +35,7 @@ QT_BEGIN_NAMESPACE The \l {Writing QML Extensions with C++} tutorial also contains a chapter on creating QML plugins. - \sa QQmlEngine::importPlugin(), {How to Create Qt Plugins} + \sa {How to Create Qt Plugins} */ /*! diff --git a/src/qml/qml/qqmlglobal.cpp b/src/qml/qml/qqmlglobal.cpp index 398011226f..3dfbe9c85a 100644 --- a/src/qml/qml/qqmlglobal.cpp +++ b/src/qml/qml/qqmlglobal.cpp @@ -363,7 +363,7 @@ static bool byProperties( if (sourceMetaType == QMetaType::fromType<QJSValue>()) { const QJSValue *val = static_cast<const QJSValue *>(source); return byProperties( - targetMetaObject, target, QV4::Value(QJSValuePrivate::asReturnedValue(val))); + targetMetaObject, target, QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(val))); } if (sourceMetaType == QMetaType::fromType<QVariantMap>()) { @@ -431,7 +431,8 @@ bool QQmlValueTypeProvider::createValueType( if (targetType.canPopulateValueType() && byProperties( targetMetaObject, target, - QV4::Value(QJSValuePrivate::asReturnedValue(&source)))) { + // fromReturnedValue is OK here because QJSValue holds a PersistentValue + QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(&source)))) { return true; } @@ -440,7 +441,9 @@ bool QQmlValueTypeProvider::createValueType( targetMetaObject, targetMetaType, target, [&](QMetaType parameterType, auto callback) { QVariant variant = QV4::ExecutionEngine::toVariant( - QJSValuePrivate::asReturnedValue(&source), parameterType); + // fromReturnedValue is OK here because QJSValue holds a PersistentValue + QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(&source)), + parameterType); return callback(variant.metaType(), variant.data()); })) { return true; diff --git a/src/qml/qml/qqmlimport_p.h b/src/qml/qml/qqmlimport_p.h index fe8ae5295c..fbe0459343 100644 --- a/src/qml/qml/qqmlimport_p.h +++ b/src/qml/qml/qqmlimport_p.h @@ -389,6 +389,16 @@ QQmlImportDatabase::LocalQmldirResult QQmlImportDatabase::locateLocalQmldir( QString qmldirAbsoluteFilePath; for (QString qmldirPath : qmlDirPaths) { if (hasInterceptors) { + // TODO: + // 1. This is inexact. It triggers only on the existence of interceptors, not on + // actual interception. If the URL was remote to begin with but no interceptor + // actually changes it, we still clear the qmldirPath and consider it + // QmldirInterceptedToRemote. + // 2. This misdiagnosis makes addLibraryImport do the right thing and postpone + // the loading of pre-registered types for any QML engine that has interceptors + // (even if they don't do anything in this case). + // Fixing this would open the door to follow-up problems but wouldn't result in any + // significant benefit. const QUrl intercepted = engine->interceptUrl( QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), QQmlAbstractUrlInterceptor::QmldirFile); diff --git a/src/qml/qml/qqmlincubator.cpp b/src/qml/qml/qqmlincubator.cpp index 90571fdf50..4fc3f1a737 100644 --- a/src/qml/qml/qqmlincubator.cpp +++ b/src/qml/qml/qqmlincubator.cpp @@ -103,6 +103,8 @@ QQmlIncubatorPrivate::~QQmlIncubatorPrivate() void QQmlIncubatorPrivate::clear() { + if (!enginePriv) + return; // reset the tagged pointer if (requiredPropertiesFromComponent) requiredPropertiesFromComponent = decltype(requiredPropertiesFromComponent){}; diff --git a/src/qml/qml/qqmllistwrapper.cpp b/src/qml/qml/qqmllistwrapper.cpp index e3c0ba19ae..497fe43a73 100644 --- a/src/qml/qml/qqmllistwrapper.cpp +++ b/src/qml/qml/qqmllistwrapper.cpp @@ -377,9 +377,11 @@ ReturnedValue PropertyListPrototype::method_splice(const FunctionObject *b, cons ScopedArrayObject newArray(scope, scope.engine->newArrayObject()); newArray->arrayReserve(deleteCount); ScopedValue v(scope); + QV4::ScopedValue wrappedObject(scope); for (qsizetype i = 0; i < deleteCount; ++i) { + wrappedObject = QObjectWrapper::wrap(scope.engine, property->at(property, start + i)); newArray->arrayPut( - i, QObjectWrapper::wrap(scope.engine, property->at(property, start + i))); + i, wrappedObject); } newArray->setArrayLengthUnchecked(deleteCount); diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index f09024d330..5caaafdc5c 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -1169,10 +1169,22 @@ QVariant QQmlPropertyPrivate::readValueProperty() } return QVariant(); } else if (core.isQList()) { + auto coreMetaType = core.propType(); - QQmlListProperty<QObject> prop; - core.readProperty(object, &prop); - return QVariant::fromValue(QQmlListReferencePrivate::init(prop, core.propType())); + // IsQmlList is set for QQmlListPropery and list<ObjectType> + if (coreMetaType.flags() & QMetaType::IsQmlList) { + QQmlListProperty<QObject> prop; + core.readProperty(object, &prop); + return QVariant::fromValue(QQmlListReferencePrivate::init(prop, coreMetaType)); + } else { + // but not for lists of value types + QVariant result(coreMetaType); + // TODO: ideally, we would not default construct and copy assign, + // but do a single copy-construct; we don't have API for that, though + coreMetaType.construct(result.data()); + core.readProperty(object, result.data()); + return result; + } } else if (core.isQObject()) { @@ -1504,7 +1516,8 @@ static bool tryAssignBinding( if (!f || !f->isBinding()) return false; - QV4::QObjectWrapper::setProperty(f->engine(), object, &property, f->asReturnedValue()); + // fromReturnedValue is safe! f is stored in the QJSValue, so htere's already a persistent reference to it + QV4::QObjectWrapper::setProperty(f->engine(), object, &property, QV4::Value::fromReturnedValue(f->asReturnedValue())); return true; } diff --git a/src/qml/qml/qqmlpropertybinding.cpp b/src/qml/qml/qqmlpropertybinding.cpp index 5f646b62de..4de4ce8ccc 100644 --- a/src/qml/qml/qqmlpropertybinding.cpp +++ b/src/qml/qml/qqmlpropertybinding.cpp @@ -131,6 +131,9 @@ QUntypedPropertyBinding QQmlPropertyBinding::createFromBoundFunction(const QQmlP void QQmlPropertyBindingJS::expressionChanged() { + if (!hasValidContext()) + return; + auto binding = asBinding(); if (!binding->propertyDataPtr) return; diff --git a/src/qml/qml/qqmlpropertycachecreator.cpp b/src/qml/qml/qqmlpropertycachecreator.cpp index c49ce14351..c7d187ef3a 100644 --- a/src/qml/qml/qqmlpropertycachecreator.cpp +++ b/src/qml/qml/qqmlpropertycachecreator.cpp @@ -140,8 +140,8 @@ void QQmlPendingGroupPropertyBindings::resolveMissingPropertyCaches( if (!pendingBinding.resolveInstantiatingProperty()) continue; - auto cache = pendingBinding.instantiatingPropertyCache(); - propertyCaches->set(groupPropertyObjectIndex, cache); + if (auto cache = pendingBinding.instantiatingPropertyCache()) + propertyCaches->set(groupPropertyObjectIndex, cache); } } diff --git a/src/qml/qml/qqmlscriptblob.cpp b/src/qml/qml/qqmlscriptblob.cpp index 4ea3816ed4..1b038a34ee 100644 --- a/src/qml/qml/qqmlscriptblob.cpp +++ b/src/qml/qml/qqmlscriptblob.cpp @@ -150,7 +150,7 @@ void QQmlScriptBlob::done() for (int scriptIndex = 0; scriptIndex < m_scripts.size(); ++scriptIndex) { const ScriptReference &script = m_scripts.at(scriptIndex); - m_scriptData->scripts.append(script.script); + m_scriptData->scripts.append(script.script->scriptData()); if (!script.nameSpace.isNull()) { if (!ns.contains(script.nameSpace)) { diff --git a/src/qml/qml/qqmlscriptdata.cpp b/src/qml/qml/qqmlscriptdata.cpp index 1a6a8157d0..5ed9345a4f 100644 --- a/src/qml/qml/qqmlscriptdata.cpp +++ b/src/qml/qml/qqmlscriptdata.cpp @@ -63,7 +63,7 @@ QQmlRefPointer<QQmlContextData> QQmlScriptData::qmlContextDataForContext( } QV4::ScopedValue v(scope); for (int ii = 0; ii < scripts.size(); ++ii) { - v = scripts.at(ii)->scriptData()->scriptValueForContext(qmlContextData); + v = scripts.at(ii)->scriptValueForContext(qmlContextData); scriptsArray->put(ii, v); } diff --git a/src/qml/qml/qqmlscriptdata_p.h b/src/qml/qml/qqmlscriptdata_p.h index c7100861e2..208e55d13f 100644 --- a/src/qml/qml/qqmlscriptdata_p.h +++ b/src/qml/qml/qqmlscriptdata_p.h @@ -39,7 +39,7 @@ public: QUrl url; QString urlString; QQmlRefPointer<QQmlTypeNameCache> typeNameCache; - QVector<QQmlRefPointer<QQmlScriptBlob>> scripts; + QVector<QQmlRefPointer<QQmlScriptData>> scripts; QV4::ReturnedValue scriptValueForContext(const QQmlRefPointer<QQmlContextData> &parentCtxt); diff --git a/src/qml/qml/qqmltypedata.cpp b/src/qml/qml/qqmltypedata.cpp index d3cfeb3108..e18101af2b 100644 --- a/src/qml/qml/qqmltypedata.cpp +++ b/src/qml/qml/qqmltypedata.cpp @@ -350,104 +350,167 @@ void QQmlTypeData::setCompileUnit(const Container &container) } } -void QQmlTypeData::done() +bool QQmlTypeData::checkScripts() { - auto cleanup = qScopeGuard([this]{ - m_backupSourceCode = SourceCodeData(); - m_document.reset(); - m_typeReferences.clear(); - if (isError()) { - const auto encounteredErrors = errors(); - for (const QQmlError &e : encounteredErrors) - qCDebug(DBG_DISK_CACHE) << e.toString(); - m_compiledData.reset(); - } - }); - - if (isError()) - return; - // Check all script dependencies for errors for (int ii = 0; ii < m_scripts.size(); ++ii) { const ScriptReference &script = m_scripts.at(ii); Q_ASSERT(script.script->isCompleteOrError()); if (script.script->isError()) { - QList<QQmlError> errors = script.script->errors(); - QQmlError error; - error.setUrl(url()); - error.setLine(qmlConvertSourceCoordinate<quint32, int>(script.location.line())); - error.setColumn(qmlConvertSourceCoordinate<quint32, int>(script.location.column())); - error.setDescription(QQmlTypeLoader::tr("Script %1 unavailable").arg(script.script->urlString())); - errors.prepend(error); - setError(errors); - return; + createError( + script, + QQmlTypeLoader::tr("Script %1 unavailable").arg(script.script->urlString())); + return false; } } + return true; +} + +void QQmlTypeData::createError(const TypeReference &type, const QString &message) +{ + createError(type, message, type.typeData ? type.typeData->errors() : QList<QQmlError>()); +} + +void QQmlTypeData::createError(const ScriptReference &script, const QString &message) +{ + createError(script, message, script.script ? script.script->errors() : QList<QQmlError>()); +} +bool QQmlTypeData::checkDependencies() +{ // Check all type dependencies for errors - for (auto it = std::as_const(m_resolvedTypes).begin(), end = std::as_const(m_resolvedTypes).end(); it != end; - ++it) { + for (auto it = std::as_const(m_resolvedTypes).begin(), end = std::as_const(m_resolvedTypes).end(); + it != end; ++it) { const TypeReference &type = *it; - Q_ASSERT(!type.typeData || type.typeData->isCompleteOrError() || type.type.isInlineComponentType()); + Q_ASSERT(!type.typeData + || type.typeData->isCompleteOrError() + || type.type.isInlineComponentType()); if (type.errorWhenNotFound - && type.type.isInlineComponentType() - && !type.type.pendingResolutionName().isEmpty()) { + && type.type.isInlineComponentType() + && !type.type.pendingResolutionName().isEmpty()) { auto containingType = type.type.containingType(); - auto objectId = containingType.lookupInlineComponentIdByName(type.type.pendingResolutionName()); - if (objectId < 0) { // can be any negative number if we tentatively resolved it in QQmlImport but it actually was not an inline component - const QString typeName = stringAt(it.key()); - int lastDot = typeName.lastIndexOf(u'.'); + auto objectId + = containingType.lookupInlineComponentIdByName(type.type.pendingResolutionName()); - QList<QQmlError> errors = type.typeData ? type.typeData->errors() : QList<QQmlError>{}; - QQmlError error; - error.setUrl(url()); - error.setLine(qmlConvertSourceCoordinate<quint32, int>(type.location.line())); - error.setColumn(qmlConvertSourceCoordinate<quint32, int>(type.location.column())); - error.setDescription(QQmlTypeLoader::tr("Type %1 has no inline component type called %2").arg(QStringView{typeName}.left(lastDot), type.type.pendingResolutionName())); - errors.prepend(error); - setError(errors); - return; + // can be any negative number if we tentatively resolved it in QQmlImport but it + // actually was not an inline component + if (objectId < 0) { + const QString &typeName = stringAt(it.key()); + int lastDot = typeName.lastIndexOf(u'.'); + createError( + type, + QQmlTypeLoader::tr("Type %1 has no inline component type called %2") + .arg(QStringView{typeName}.left(lastDot), type.type.elementName())); + return false; } else { type.type.setInlineComponentObjectId(objectId); } } if (type.errorWhenNotFound && type.typeData && type.typeData->isError()) { - const QString typeName = stringAt(it.key()); - - QList<QQmlError> errors = type.typeData->errors(); - QQmlError error; - error.setUrl(url()); - error.setLine(qmlConvertSourceCoordinate<quint32, int>(type.location.line())); - error.setColumn(qmlConvertSourceCoordinate<quint32, int>(type.location.column())); - error.setDescription(QQmlTypeLoader::tr("Type %1 unavailable").arg(typeName)); - errors.prepend(error); - setError(errors); - return; + const QString &typeName = stringAt(it.key()); + createError(type, QQmlTypeLoader::tr("Type %1 unavailable").arg(typeName)); + return false; } } + return true; +} + +bool QQmlTypeData::checkCompositeSingletons() +{ // Check all composite singleton type dependencies for errors for (int ii = 0; ii < m_compositeSingletons.size(); ++ii) { const TypeReference &type = m_compositeSingletons.at(ii); Q_ASSERT(!type.typeData || type.typeData->isCompleteOrError()); if (type.typeData && type.typeData->isError()) { QString typeName = type.type.qmlTypeName(); - - QList<QQmlError> errors = type.typeData->errors(); - QQmlError error; - error.setUrl(url()); - error.setLine(qmlConvertSourceCoordinate<quint32, int>(type.location.line())); - error.setColumn(qmlConvertSourceCoordinate<quint32, int>(type.location.column())); - error.setDescription(QQmlTypeLoader::tr("Type %1 unavailable").arg(typeName)); - errors.prepend(error); - setError(errors); - return; + createError(type, QQmlTypeLoader::tr("Type %1 unavailable").arg(typeName)); + return false; } } + return true; +} + +void QQmlTypeData::createQQmlType() +{ m_typeClassName = QQmlPropertyCacheCreatorBase::createClassNameTypeByUrl(finalUrl()); if (!m_typeClassName.isEmpty()) m_typeIds = CompositeMetaTypeIds::fromCompositeName(m_typeClassName); +} + +bool QQmlTypeData::rebuildFromSource() +{ + // Clear and re-build everything. + + m_typeReferences.clear(); + m_scripts.clear(); + m_namespaces.clear(); + m_compositeSingletons.clear(); + + m_resolvedTypes.clear(); + m_typesResolved = false; + + m_typeClassName.clear(); + + m_inlineComponentData.clear(); + m_compiledData.reset(); + + m_implicitImportLoaded = false; + + m_importCache.adopt(new QQmlImports(m_typeLoader)); + m_unresolvedImports.clear(); + + if (!loadFromSource()) + return false; + + continueLoadFromIR(); + + if (!resolveTypes()) + return false; + + if (!checkScripts()) + return false; + + if (!checkDependencies()) + return false; + + if (!checkCompositeSingletons()) + return false; + + createQQmlType(); + + setupICs(m_document, &m_inlineComponentData, finalUrl()); + return true; +} + +void QQmlTypeData::done() +{ + auto cleanup = qScopeGuard([this]{ + m_backupSourceCode = SourceCodeData(); + m_document.reset(); + m_typeReferences.clear(); + if (isError()) { + const auto encounteredErrors = errors(); + for (const QQmlError &e : encounteredErrors) + qCDebug(DBG_DISK_CACHE) << e.toString(); + m_compiledData.reset(); + } + }); + + if (isError()) + return; + + if (!checkScripts()) + return; + + if (!checkDependencies()) + return; + + if (!checkCompositeSingletons()) + return; + + createQQmlType(); if (m_document) { setupICs(m_document, &m_inlineComponentData, finalUrl()); @@ -493,28 +556,18 @@ void QQmlTypeData::done() << m_compiledData->fileName(); } - if (!loadFromSource()) + resolvedTypeCache.clear(); + typeNameCache.reset(); + + if (!rebuildFromSource()) return; - // We want to keep our resolve types ... - m_compiledData->resolvedTypes.clear(); - // ... but we don't want the property caches we've created for the broken CU. - for (QV4::ResolvedTypeReference *ref: std::as_const(resolvedTypeCache)) { - const auto compilationUnit = ref->compilationUnit(); - if (compilationUnit.isNull()) { - // Inline component references without CU belong to the surrounding CU. - // We have to clear them. Inline component references to other documents - // have a CU. - if (!ref->type().isInlineComponentType()) - continue; - } else if (compilationUnit != m_compiledData) { - continue; - } - ref->setTypePropertyCache(QQmlPropertyCache::ConstPtr()); - ref->setCompilationUnit(QQmlRefPointer<QV4::ExecutableCompilationUnit>()); + const QQmlError error = buildTypeResolutionCaches(&typeNameCache, &resolvedTypeCache); + if (error.isValid()) { + setError(error); + qDeleteAll(resolvedTypeCache); + return; } - - m_compiledData.reset(); } } @@ -809,36 +862,8 @@ void QQmlTypeData::allDependenciesDone() { QQmlTypeLoader::Blob::allDependenciesDone(); - if (!m_typesResolved) { - // Check that all imports were resolved - QList<QQmlError> errors; - auto it = m_unresolvedImports.constBegin(), end = m_unresolvedImports.constEnd(); - for ( ; it != end; ++it) { - if ((*it)->priority == 0) { - // This import was not resolved - for (auto keyIt = m_unresolvedImports.constBegin(), - keyEnd = m_unresolvedImports.constEnd(); - keyIt != keyEnd; ++keyIt) { - const PendingImportPtr &import = *keyIt; - QQmlError error; - error.setDescription(QQmlTypeLoader::tr("module \"%1\" is not installed").arg(import->uri)); - error.setUrl(m_importCache->baseUrl()); - error.setLine(qmlConvertSourceCoordinate<quint32, int>( - import->location.line())); - error.setColumn(qmlConvertSourceCoordinate<quint32, int>( - import->location.column())); - errors.prepend(error); - } - } - } - if (errors.size()) { - setError(errors); - return; - } - + if (!m_typesResolved) resolveTypes(); - m_typesResolved = true; - } } void QQmlTypeData::downloadProgressChanged(qreal p) @@ -889,8 +914,39 @@ void QQmlTypeData::compile(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCach } } -void QQmlTypeData::resolveTypes() +bool QQmlTypeData::resolveTypes() { + Q_ASSERT(!m_typesResolved); + + // Check that all imports were resolved + QList<QQmlError> errors; + auto it = m_unresolvedImports.constBegin(), end = m_unresolvedImports.constEnd(); + for ( ; it != end; ++it) { + const PendingImportPtr &import = *it; + if (import->priority != 0) + continue; + + // If the import was potentially remote and all the network requests have failed, + // we now know that there is no qmldir. We can register its types. + if (registerPendingTypes(import)) + continue; + + // This import was not resolved + QQmlError error; + error.setDescription(QQmlTypeLoader::tr("module \"%1\" is not installed").arg(import->uri)); + error.setUrl(m_importCache->baseUrl()); + error.setLine(qmlConvertSourceCoordinate<quint32, int>( + import->location.line())); + error.setColumn(qmlConvertSourceCoordinate<quint32, int>( + import->location.column())); + errors.prepend(error); + } + + if (errors.size()) { + setError(errors); + return false; + } + // Add any imported scripts to our resolved set const auto resolvedScripts = m_importCache->resolvedScripts(); for (const QQmlImports::ScriptReference &script : resolvedScripts) { @@ -927,7 +983,7 @@ void QQmlTypeData::resolveTypes() QTypeRevision version = csRef.version; if (!resolveType(typeName, version, ref, -1, -1, true, QQmlType::CompositeSingletonType)) - return; + return false; if (ref.type.isCompositeSingleton()) { ref.typeData = typeLoader()->getType(ref.type.sourceUrl()); @@ -943,7 +999,7 @@ void QQmlTypeData::resolveTypes() } } - for (QV4::CompiledData::TypeReferenceMap::ConstIterator unresolvedRef = m_typeReferences.constBegin(), end = m_typeReferences.constEnd(); + for (auto unresolvedRef = m_typeReferences.constBegin(), end = m_typeReferences.constEnd(); unresolvedRef != end; ++unresolvedRef) { TypeReference ref; // resolved reference @@ -959,7 +1015,7 @@ void QQmlTypeData::resolveTypes() if (!resolveType(name, version, ref, unresolvedRef->location.line(), unresolvedRef->location.column(), reportErrors, QQmlType::AnyRegistrationType, selfReferenceDetection) && reportErrors) - return; + return false; if (ref.type.isComposite() && !ref.selfReference) { ref.typeData = typeLoader()->getType(ref.type.sourceUrl()); @@ -969,7 +1025,7 @@ void QQmlTypeData::resolveTypes() auto containingType = ref.type.containingType(); if (containingType.isValid()) { auto const url = containingType.sourceUrl(); - if (url.isValid()) { + if (!url.isEmpty() && url != finalUrl()) { auto typeData = typeLoader()->getType(url); ref.typeData = typeData; addDependency(typeData.data()); @@ -985,8 +1041,11 @@ void QQmlTypeData::resolveTypes() } // ### this allows enums to work without explicit import or instantiation of the type - if (!m_implicitImportLoaded) - loadImplicitImport(); + if (!m_implicitImportLoaded && !loadImplicitImport()) + return false; + + m_typesResolved = true; + return true; } QQmlError QQmlTypeData::buildTypeResolutionCaches( diff --git a/src/qml/qml/qqmltypedata_p.h b/src/qml/qml/qqmltypedata_p.h index b067dea5bf..00007e1433 100644 --- a/src/qml/qml/qqmltypedata_p.h +++ b/src/qml/qml/qqmltypedata_p.h @@ -15,6 +15,7 @@ // We mean it. // +#include <private/qqmlsourcecoordinate_p.h> #include <private/qqmltypeloader_p.h> #include <private/qv4executablecompilationunit_p.h> @@ -87,7 +88,7 @@ private: bool loadFromSource(); void restoreIR(QV4::CompiledData::CompilationUnit &&unit); void continueLoadFromIR(); - void resolveTypes(); + bool resolveTypes(); QQmlError buildTypeResolutionCaches( QQmlRefPointer<QQmlTypeNameCache> *typeNameCache, QV4::ResolvedTypeReferenceMap *resolvedTypeCache @@ -136,6 +137,27 @@ private: bool m_implicitImportLoaded; bool loadImplicitImport(); + bool checkScripts(); + bool checkDependencies(); + + template<typename Reference> + void createError(const Reference &ref, const QString &message, QList<QQmlError> errors) + { + QQmlError error; + error.setUrl(url()); + error.setLine(qmlConvertSourceCoordinate<quint32, int>(ref.location.line())); + error.setColumn(qmlConvertSourceCoordinate<quint32, int>(ref.location.column())); + error.setDescription(message); + errors.prepend(std::move(error)); + setError(errors); + } + + void createError(const TypeReference &type, const QString &message); + void createError(const ScriptReference &script, const QString &message); + + bool checkCompositeSingletons(); + void createQQmlType(); + bool rebuildFromSource(); }; QT_END_NAMESPACE diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 68c1163c8c..cf861cfd65 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -681,67 +681,59 @@ bool QQmlTypeLoader::Blob::addLibraryImport(const QQmlTypeLoader::Blob::PendingI // If there is a qmldir we cannot see, yet, then we have to wait. // The qmldir might contain import directives. - if (qmldirResult != QQmlImportDatabase::QmldirInterceptedToRemote && ( - // Major version of module already registered: - // We believe that the registration is complete. - QQmlMetaType::typeModule(import->uri, import->version) + // TODO: This should trigger on any potentially remote URLs, not only intercepted ones. + // However, fixing this would open the door for follow-up problems while providing + // rather limited benefits. + if (qmldirResult != QQmlImportDatabase::QmldirInterceptedToRemote + && registerPendingTypes(import)) { + if (m_importCache->addLibraryImport( + importDatabase, import->uri, import->qualifier, import->version, QString(), + QString(), import->flags, import->precedence, errors).isValid()) { + return true; + } - // Otherwise, try to register further module types. - || QQmlMetaType::qmlRegisterModuleTypes(import->uri) + return false; + } - // Otherwise, there is no way to register any further types. - // Try with any module of that name. - || QQmlMetaType::latestModuleVersion(import->uri).isValid())) { + // We haven't yet resolved this import + m_unresolvedImports << import; - if (!m_importCache->addLibraryImport( - importDatabase, import->uri, import->qualifier, import->version, - QString(), QString(), import->flags, import->precedence, errors).isValid()) { - return false; - } - } else { - // We haven't yet resolved this import - m_unresolvedImports << import; - - const QQmlEngine *engine = typeLoader()->engine(); - const bool hasInterceptors - = !(QQmlEnginePrivate::get(engine)->urlInterceptors.isEmpty()); - - // Query any network import paths for this library. - // Interceptor might redirect local paths. - QStringList remotePathList = importDatabase->importPathList( - hasInterceptors ? QQmlImportDatabase::LocalOrRemote - : QQmlImportDatabase::Remote); - if (!remotePathList.isEmpty()) { - // Add this library and request the possible locations for it - const QTypeRevision version = m_importCache->addLibraryImport( - importDatabase, import->uri, import->qualifier, import->version, - QString(), QString(), import->flags | QQmlImports::ImportIncomplete, - import->precedence, errors); - - if (!version.isValid()) - return false; + // Add this library and request the possible locations for it + const QTypeRevision version = m_importCache->addLibraryImport( + importDatabase, import->uri, import->qualifier, import->version, QString(), + QString(), import->flags | QQmlImports::ImportIncomplete, import->precedence, + errors); + + if (!version.isValid()) + return false; - // Use more specific version for finding the qmldir if possible - if (version.hasMajorVersion()) - import->version = version; - - // Probe for all possible locations - int priority = 0; - const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths( - import->uri, remotePathList, import->version); - for (const QString &qmldirPath : qmlDirPaths) { - if (hasInterceptors) { - QUrl url = engine->interceptUrl( - QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), - QQmlAbstractUrlInterceptor::QmldirFile); - if (!QQmlFile::isLocalFile(url) - && !fetchQmldir(url, import, ++priority, errors)) { - return false; - } - } else if (!fetchQmldir(QUrl(qmldirPath), import, ++priority, errors)) { + // Use more specific version for finding the qmldir if possible + if (version.hasMajorVersion()) + import->version = version; + + const QQmlEngine *engine = typeLoader()->engine(); + const bool hasInterceptors = !(QQmlEnginePrivate::get(engine)->urlInterceptors.isEmpty()); + + // Query any network import paths for this library. + // Interceptor might redirect local paths. + QStringList remotePathList = importDatabase->importPathList( + hasInterceptors ? QQmlImportDatabase::LocalOrRemote : QQmlImportDatabase::Remote); + if (!remotePathList.isEmpty()) { + // Probe for all possible locations + int priority = 0; + const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths( + import->uri, remotePathList, import->version); + for (const QString &qmldirPath : qmlDirPaths) { + if (hasInterceptors) { + QUrl url = engine->interceptUrl( + QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), + QQmlAbstractUrlInterceptor::QmldirFile); + if (!QQmlFile::isLocalFile(url) + && !fetchQmldir(url, import, ++priority, errors)) { return false; } - + } else if (!fetchQmldir(QUrl(qmldirPath), import, ++priority, errors)) { + return false; } } } @@ -749,6 +741,21 @@ bool QQmlTypeLoader::Blob::addLibraryImport(const QQmlTypeLoader::Blob::PendingI return true; } +bool QQmlTypeLoader::Blob::registerPendingTypes(const PendingImportPtr &import) +{ + return + // Major version of module already registered: + // We believe that the registration is complete. + QQmlMetaType::typeModule(import->uri, import->version) + + // Otherwise, try to register further module types. + || QQmlMetaType::qmlRegisterModuleTypes(import->uri) + + // Otherwise, there is no way to register any further types. + // Try with any module of that name. + || QQmlMetaType::latestModuleVersion(import->uri).isValid(); +} + bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import, QQmlImports::ImportFlags flags, QList<QQmlError> *errors) { diff --git a/src/qml/qml/qqmltypeloader_p.h b/src/qml/qml/qqmltypeloader_p.h index 854778125a..0c6df56d5a 100644 --- a/src/qml/qml/qqmltypeloader_p.h +++ b/src/qml/qml/qqmltypeloader_p.h @@ -102,6 +102,9 @@ public: QQmlImports::ImportFlags flags, QList<QQmlError> *errors); protected: + + bool registerPendingTypes(const PendingImportPtr &import); + bool loadDependentImports( const QList<QQmlDirParser::Import> &imports, const QString &qualifier, QTypeRevision version, quint16 precedence, QQmlImports::ImportFlags flags, diff --git a/src/qml/qml/qqmlvaluetypewrapper.cpp b/src/qml/qml/qqmlvaluetypewrapper.cpp index 6bd26f65dd..b036066f2f 100644 --- a/src/qml/qml/qqmlvaluetypewrapper.cpp +++ b/src/qml/qml/qqmlvaluetypewrapper.cpp @@ -570,6 +570,13 @@ QMetaType QQmlValueTypeWrapper::type() const bool QQmlValueTypeWrapper::write(QObject *target, int propertyIndex) const { bool destructGadgetOnExit = false; + auto cleanup = qScopeGuard([&]() { + if (destructGadgetOnExit) { + d()->valueType()->metaType().destruct(d()->gadgetPtr()); + d()->setGadgetPtr(nullptr); + } + }); + Q_ALLOCA_DECLARE(void, gadget); if (d()->isReference()) { if (!d()->gadgetPtr()) { @@ -586,11 +593,6 @@ bool QQmlValueTypeWrapper::write(QObject *target, int propertyIndex) const 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; } diff --git a/src/qml/qml/qqmlvmemetaobject.cpp b/src/qml/qml/qqmlvmemetaobject.cpp index 94c2c254c0..e27ab165be 100644 --- a/src/qml/qml/qqmlvmemetaobject.cpp +++ b/src/qml/qml/qqmlvmemetaobject.cpp @@ -119,8 +119,9 @@ void ResolvedList::append(QObject *o) const QV4::ScopedObject object(scope, m_list); QV4::ArrayData::realloc(object, QV4::Heap::ArrayData::Simple, length + 1, false); + QV4::ScopedValue wrappedObject(scope, QV4::QObjectWrapper::wrap(scope.engine, o)); arrayData->vtable()->put( - object, length, QV4::QObjectWrapper::wrap(scope.engine, o)); + object, length, wrappedObject); } QObject *ResolvedList::at(qsizetype i) const @@ -134,7 +135,8 @@ void ResolvedList::replace(qsizetype i, QObject *o) const { QV4::Scope scope(m_list->internalClass->engine); QV4::ScopedObject object(scope, m_list); - m_list->arrayData->vtable()->put(object, i, QV4::QObjectWrapper::wrap(scope.engine, o)); + QV4::ScopedValue wrappedObject(scope, QV4::QObjectWrapper::wrap(scope.engine, o)); + m_list->arrayData->vtable()->put(object, i, wrappedObject); } static void list_append(QQmlListProperty<QObject> *prop, QObject *o) diff --git a/src/qml/qmltc/supportlibrary/qqmlcpptypehelpers_p.h b/src/qml/qmltc/supportlibrary/qqmlcpptypehelpers_p.h index 2470d87efe..bd3b987624 100644 --- a/src/qml/qmltc/supportlibrary/qqmlcpptypehelpers_p.h +++ b/src/qml/qmltc/supportlibrary/qqmlcpptypehelpers_p.h @@ -1,8 +1,8 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QQMLCPPTYPEHELPERS_H -#define QQMLCPPTYPEHELPERS_H +#ifndef QQMLCPPTYPEHELPERS_P_H +#define QQMLCPPTYPEHELPERS_P_H // // W A R N I N G @@ -25,4 +25,4 @@ using passByConstRefOrValue = std::conditional_t<((sizeof(T) > 3 * sizeof(void *)) || !std::is_trivial_v<T>), const T &, T>; -#endif // QQMLCPPTYPEHELPERS_H +#endif // QQMLCPPTYPEHELPERS_P_H diff --git a/src/qml/types/qqmlbind.cpp b/src/qml/types/qqmlbind.cpp index f6513c60a1..437d9b06f0 100644 --- a/src/qml/types/qqmlbind.cpp +++ b/src/qml/types/qqmlbind.cpp @@ -57,10 +57,8 @@ public: silentDestroy(oldKind); switch (newKind) { case QQmlBindEntryKind::V4Value: - new (&v4Value) QV4::PersistentValue(std::move(other.v4Value)); - break; case QQmlBindEntryKind::Variant: - new (&variant) QVariant(std::move(other.variant)); + new (&v4Value) QV4::PersistentValue(std::move(other.v4Value)); break; case QQmlBindEntryKind::Binding: new (&binding) QQmlAnyBinding(std::move(other.binding)); @@ -77,10 +75,8 @@ public: silentDestroy(oldKind); switch (newKind) { case QQmlBindEntryKind::V4Value: - new (&v4Value) QV4::PersistentValue(other.v4Value); - break; case QQmlBindEntryKind::Variant: - new (&variant) QVariant(other.variant); + new (&v4Value) QV4::PersistentValue(other.v4Value); break; case QQmlBindEntryKind::Binding: new (&binding) QQmlAnyBinding(other.binding); @@ -95,10 +91,8 @@ public: { switch (kind) { case QQmlBindEntryKind::V4Value: - v4Value.~PersistentValue(); - break; case QQmlBindEntryKind::Variant: - variant.~QVariant(); + v4Value.~PersistentValue(); break; case QQmlBindEntryKind::Binding: binding.~QQmlAnyBinding(); @@ -109,10 +103,11 @@ public: return QQmlBindEntryKind::None; } - [[nodiscard]] QQmlBindEntryKind set(QVariant v, QQmlBindEntryKind oldKind) + [[nodiscard]] QQmlBindEntryKind set( + QV4::ExecutionEngine *engine, const QVariant &v, QQmlBindEntryKind oldKind) { silentDestroy(oldKind); - new (&variant) QVariant(std::move(v)); + new (&v4Value) QV4::PersistentValue(engine, engine->fromVariant(v)); return QQmlBindEntryKind::Variant; } @@ -123,6 +118,13 @@ public: return QQmlBindEntryKind::V4Value; } + [[nodiscard]] QQmlBindEntryKind setVariant(QV4::PersistentValue v, QQmlBindEntryKind oldKind) + { + silentDestroy(oldKind); + new (&v4Value) QV4::PersistentValue(std::move(v)); + return QQmlBindEntryKind::Variant; + } + [[nodiscard]] QQmlBindEntryKind set(QQmlAnyBinding v, QQmlBindEntryKind oldKind) { silentDestroy(oldKind); @@ -131,7 +133,6 @@ public: } QV4::PersistentValue v4Value; - QVariant variant; QQmlAnyBinding binding; private: @@ -558,14 +559,22 @@ QVariant QQmlBind::value() const if (!d->lastIsTarget) return QVariant(); Q_ASSERT(d->entries.last().currentKind == QQmlBindEntryKind::Variant); - return d->entries.last().current.variant; + const auto &v4Value = d->entries.last().current.v4Value; + QV4::ExecutionEngine *engine = v4Value.engine(); + return engine->toVariant(*v4Value.valueRef(), QMetaType()); } void QQmlBind::setValue(const QVariant &v) { Q_D(QQmlBind); QQmlBindEntry *targetEntry = d->targetEntry(); - targetEntry->currentKind = targetEntry->current.set(v, targetEntry->currentKind); + QQmlEngine *engine = qmlEngine(this); + if (!engine) { + qWarning() << "QQmlBind must be created in a QML context"; + return; + } + targetEntry->currentKind + = targetEntry->current.set(engine->handle(), v, targetEntry->currentKind); prepareEval(); } @@ -843,8 +852,8 @@ void QQmlBindPrivate::decodeBinding( return; } - const auto setVariant = [&entry](QVariant var) { - entry.currentKind = entry.current.set(std::move(var), entry.currentKind); + const auto setVariant = [&entry](QV4::PersistentValue value) { + entry.currentKind = entry.current.setVariant(value, entry.currentKind); }; const auto setBinding = [&entry](QQmlAnyBinding binding) { @@ -874,16 +883,20 @@ void QQmlBindPrivate::decodeBinding( } break; case QV4::CompiledData::Binding::Type_String: - setVariant(compilationUnit->bindingValueAsString(binding)); + setVariant(QV4::PersistentValue( + compilationUnit->engine, + compilationUnit->runtimeStrings[binding->stringIndex]->asReturnedValue())); break; case QV4::CompiledData::Binding::Type_Number: - setVariant(compilationUnit->bindingValueAsNumber(binding)); + setVariant(QV4::PersistentValue( + compilationUnit->engine, + compilationUnit->constants[binding->value.constantValueIndex].asReturnedValue())); break; case QV4::CompiledData::Binding::Type_Boolean: - setVariant(binding->valueAsBoolean()); + setVariant(QV4::PersistentValue(compilationUnit->engine, QV4::Encode(binding->value.b))); break; case QV4::CompiledData::Binding::Type_Null: - setVariant(QVariant::fromValue(nullptr)); + setVariant(QV4::PersistentValue(compilationUnit->engine, QV4::Encode::null())); break; case QV4::CompiledData::Binding::Type_Object: case QV4::CompiledData::Binding::Type_Invalid: @@ -1038,7 +1051,9 @@ void QQmlBind::eval() case QQmlBindEntryKind::Variant: if (d->restoreValue) { QQmlAnyBinding::takeFrom(entry.prop); // we don't want to have a binding active - entry.prop.write(entry.previous.variant); + const QV4::PersistentValue &v4Value = entry.previous.v4Value; + entry.prop.write(v4Value.engine()->toVariant( + *v4Value.valueRef(), entry.prop.propertyMetaType())); entry.clearPrev(); } break; @@ -1066,7 +1081,8 @@ void QQmlBind::eval() QV4::PersistentValue(vmemo->engine, retVal), entry.previousKind); } else { // nope, use the meta object to get a QVariant - entry.previousKind = entry.previous.set(entry.prop.read(), entry.previousKind); + entry.previousKind = entry.previous.set( + propPriv->engine->handle(), entry.prop.read(), entry.previousKind); } } } @@ -1085,9 +1101,12 @@ void QQmlBind::eval() if (!entry.prop.isValid()) continue; switch (entry.currentKind) { - case QQmlBindEntryKind::Variant: - entry.prop.write(entry.current.variant); + case QQmlBindEntryKind::Variant: { + const QV4::PersistentValue &v4Value = entry.current.v4Value; + entry.prop.write( + v4Value.engine()->toVariant(*v4Value.valueRef(), entry.prop.propertyMetaType())); break; + } case QQmlBindEntryKind::Binding: Q_ASSERT(!d->delayed); entry.current.binding.installOn(entry.prop); |
