diff options
Diffstat (limited to 'src/qml')
25 files changed, 257 insertions, 62 deletions
diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake index 3c1d7870e7..49829d8e61 100644 --- a/src/qml/Qt6QmlMacros.cmake +++ b/src/qml/Qt6QmlMacros.cmake @@ -828,7 +828,7 @@ function(_qt_internal_target_enable_qmllint target) ) set(cmd - ${tool_wrapper} + "${tool_wrapper}" $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmllint> @${qmllint_rsp_path} ) @@ -860,7 +860,7 @@ function(_qt_internal_target_enable_qmllint target) ) set(cmd - ${tool_wrapper} + "${tool_wrapper}" $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmllint> @${qmllint_rsp_path} ) @@ -898,7 +898,7 @@ function(_qt_internal_target_enable_qmllint target) ) set(cmd - ${tool_wrapper} + "${tool_wrapper}" $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmllint> @${qmllint_rsp_path} ) @@ -1069,7 +1069,7 @@ function(_qt_internal_target_enable_qmlcachegen target output_targets_var qmlcac _qt_internal_get_tool_wrapper_script_path(tool_wrapper) set(cmd - ${tool_wrapper} + "${tool_wrapper}" ${qmlcachegen} --resource-name "${qmlcache_resource_name}" -o "${qmlcache_loader_cpp}" @@ -2486,6 +2486,9 @@ function(qt6_generate_foreign_qml_types source_target destination_qml_target) VERBATIM ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27") + set_source_files_properties(${additional_sources} PROPERTIES SKIP_LINTING ON) + endif() target_sources(${destination_qml_target} PRIVATE ${additional_sources}) endfunction() diff --git a/src/qml/common/qv4staticvalue_p.h b/src/qml/common/qv4staticvalue_p.h index e9c3554104..5c50df30c6 100644 --- a/src/qml/common/qv4staticvalue_p.h +++ b/src/qml/common/qv4staticvalue_p.h @@ -366,10 +366,10 @@ struct StaticValue QV4_NEARLY_ALWAYS_INLINE void setDouble(double d) { if (qt_is_nan(d)) { // We cannot store just any NaN. It has to be a NaN with only the quiet bit - // set in the upper bits of the mantissa and the sign bit off. + // set in the upper bits of the mantissa and the sign bit either on or off. // qt_qnan() happens to produce such a thing via std::numeric_limits, // but this is actually not guaranteed. Therefore, we make our own. - _val = (quint64(QuickType::NaN) << Tag_Shift); + _val = (quint64(std::signbit(d) ? QuickType::MinusNaN : QuickType::NaN) << Tag_Shift); Q_ASSERT(isNaN()); } else { memcpy(&_val, &d, 8); diff --git a/src/qml/configure.cmake b/src/qml/configure.cmake index 654fc2ec9d..28f9d955cb 100644 --- a/src/qml/configure.cmake +++ b/src/qml/configure.cmake @@ -14,7 +14,7 @@ qt_find_package(LTTngUST PROVIDED_TARGETS LTTng::UST MODULE_NAME qml QMAKE_LIB l qt_find_package(Python REQUIRED) if(Python_Interpreter_FOUND) # Need to make it globally available to the project - set(QT_INTERNAL_DECLARATIVE_PYTHON "${Python_EXECUTABLE}" CACHE STRING "") + set(QT_INTERNAL_DECLARATIVE_PYTHON "${Python_EXECUTABLE}" CACHE STRING "" FORCE) endif() #### Tests diff --git a/src/qml/doc/snippets/qml/function-call-binding.qml b/src/qml/doc/snippets/qml/function-call-binding.qml new file mode 100644 index 0000000000..1dc7d0224c --- /dev/null +++ b/src/qml/doc/snippets/qml/function-call-binding.qml @@ -0,0 +1,18 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick + +//! [rectangle] +Rectangle { + x: rectPosition() + y: rectPosition() + width: 200 + height: 200 + color: "lightblue" + + function rectPosition() { + return enabled ? 0 : 100 + } +} +//! [rectangle] diff --git a/src/qml/doc/src/external-resources.qdoc b/src/qml/doc/src/external-resources.qdoc index c120257247..02c39ee3fa 100644 --- a/src/qml/doc/src/external-resources.qdoc +++ b/src/qml/doc/src/external-resources.qdoc @@ -29,6 +29,10 @@ \title QmlLive Manual */ /*! + \externalpage https://felgo.com/qml-hot-reload + \title Felgo QML Hot Reload Tool +*/ +/*! \externalpage https://doc.qt.io/qtcreator/creator-debugging-qml.html \title Qt Creator: QML Debugger */ diff --git a/src/qml/doc/src/javascript/qmlglobalobject.qdoc b/src/qml/doc/src/javascript/qmlglobalobject.qdoc index 15b9996ff3..1bd03fad54 100644 --- a/src/qml/doc/src/javascript/qmlglobalobject.qdoc +++ b/src/qml/doc/src/javascript/qmlglobalobject.qdoc @@ -13,8 +13,8 @@ additional imports: \list \li The \l{QmlGlobalQtObject}{Qt object}: A QML object that offers helper methods and properties specific to the QML environment. -\li \l {Qt::}{qsTr()}, \l {Qt::}{qsTranslate()}, \l {Qt::}{qsTrId()}, \l {Qt::}{qsTrNoOp()}, - \l {Qt::}{qsTranslateNoOp()}, \l {Qt::}{qsTrIdNoOp()} functions: +\li \l {Qt::}{qsTr()}, \l {Qt::}{qsTranslate()}, \l {Qt::}{qsTrId()}, \l {Qt::}{QT_TR_NOOP()()}, + \l {Qt::}{QT_TRANSLATE_NOOP()}, \l {Qt::}{QT_TRID_NOOP()} functions: QML functions that let you translate \l{Mark Strings for Translation} {strings} and \l{Mark Translatable Data Text Strings}{string literals} in the QML environment. diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index e2dfb6ca77..f6619d8d00 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -1467,6 +1467,10 @@ Returns -1 if no matching type was found or one of the given parameters was invalid. + \note: qmlTypeId tries to make modules available, even if they were not accessed by any + engine yet. This can introduce overhead the first time a module is accessed. Trying to + find types from a module which does not exist always introduces this overhead. + \sa QML_ELEMENT, QML_NAMED_ELEMENT, QML_SINGLETON, qmlRegisterType(), qmlRegisterSingletonType() */ diff --git a/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc b/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc index 65e3b95f8e..b45ad83fb8 100644 --- a/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc +++ b/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc @@ -98,6 +98,13 @@ In the previous example, \li \c bottomRect.color depends on \c myTextInput.text.length \endlist +In addition, any properties referenced within a JavaScript function that is +itself used as a binding will be re-evaluated. For example, in the snippet +below, whenever the \c enabled property of the \c Rectangle changes, the +bindings for the \c x and \c y properties will be re-evaluated: + +\snippet qml/function-call-binding.qml rectangle + Syntactically, bindings are allowed to be of arbitrary complexity. However, if a binding is overly complex - such as involving multiple lines, or imperative loops - it could indicate that the binding is being used for more than describing diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 342f642d89..99fad7e3c3 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -2446,18 +2446,22 @@ bool convertToIterable(QMetaType metaType, void *data, Source *sequence) 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); + QVariant element(elementMetaType); + ExecutionEngine::metaTypeFromJS(sequence->get(i), elementMetaType, element.data()); 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. +/*! + * \internal + * + * Converts a JS value to a meta-type. + * \a data must point to a default-constructed instance of \a metaType. + * Returns \c true if conversion succeeded, \c false otherwise. In the latter case, + * \a data is not modified. + */ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, void *data) { // check if it's one of the types we know diff --git a/src/qml/jsruntime/qv4generatorobject.cpp b/src/qml/jsruntime/qv4generatorobject.cpp index 9f77d83ac3..d5519b49e5 100644 --- a/src/qml/jsruntime/qv4generatorobject.cpp +++ b/src/qml/jsruntime/qv4generatorobject.cpp @@ -52,6 +52,7 @@ Heap::FunctionObject *GeneratorFunction::create(ExecutionContext *context, Funct proto->setPrototypeOf(scope.engine->generatorPrototype()); g->defineDefaultProperty(scope.engine->id_prototype(), proto, Attr_NotConfigurable|Attr_NotEnumerable); g->setPrototypeOf(ScopedObject(scope, scope.engine->generatorFunctionCtor()->get(scope.engine->id_prototype()))); + g->d()->canBeTailCalled = false; return g->d(); } @@ -213,6 +214,7 @@ Heap::FunctionObject *MemberGeneratorFunction::create(ExecutionContext *context, proto->setPrototypeOf(scope.engine->generatorPrototype()); g->defineDefaultProperty(scope.engine->id_prototype(), proto, Attr_NotConfigurable|Attr_NotEnumerable); g->setPrototypeOf(ScopedObject(scope, scope.engine->generatorFunctionCtor()->get(scope.engine->id_prototype()))); + g->d()->canBeTailCalled = false; return g->d(); } diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index 712666c6aa..0851b2f5ab 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -744,6 +744,12 @@ ReturnedValue Object::virtualResolveLookupGetter(const Object *object, Execution Heap::Object *obj = object->d(); PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]); + if (object->as<QV4::ProxyObject>()) { + // proxies invalidate assumptions that we normally maek in lookups + // so we always need to use the fallback path + lookup->getter = Lookup::getterFallback; + return lookup->getter(lookup, engine, *object); + } if (name.isArrayIndex()) { lookup->indexedLookup.index = name.asArrayIndex(); lookup->getter = Lookup::getterIndexed; diff --git a/src/qml/jsruntime/qv4typedarray.cpp b/src/qml/jsruntime/qv4typedarray.cpp index 43dc0fae4f..e87bf957d2 100644 --- a/src/qml/jsruntime/qv4typedarray.cpp +++ b/src/qml/jsruntime/qv4typedarray.cpp @@ -762,8 +762,6 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, fin = static_cast<uint>(std::min(relativeEnd, dlen)); } - double val = argc ? argv[0].toNumber() : std::numeric_limits<double>::quiet_NaN(); - Value value = Value::fromDouble(val); if (scope.hasException() || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); @@ -771,6 +769,14 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, uint bytesPerElement = v->bytesPerElement(); uint byteOffset = v->byteOffset(); + Value value; + if (!argc) + value.setDouble(std::numeric_limits<double>::quiet_NaN()); + else if (argv[0].isNumber()) + value = argv[0]; + else + value.setDouble(argv[0].toNumber()); + while (k < fin) { v->d()->type->write(data + byteOffset + k * bytesPerElement, value); k++; diff --git a/src/qml/memory/qv4heap_p.h b/src/qml/memory/qv4heap_p.h index b4bd3c4cd6..2d0ad3d601 100644 --- a/src/qml/memory/qv4heap_p.h +++ b/src/qml/memory/qv4heap_p.h @@ -221,7 +221,7 @@ struct QV4QPointer { private: QtSharedPointer::ExternalRefCountData *d; - QObject *qObject; + T *qObject; }; Q_STATIC_ASSERT(std::is_trivial_v<QV4QPointer<QObject>>); #endif diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index e914da329e..94a91629f3 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -335,7 +335,24 @@ void qmlUnregisterModuleImport(const char *uri, int moduleMajor, //From qqml.h int qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *qmlName) { - return QQmlMetaType::typeId(uri, QTypeRevision::fromVersion(versionMajor, versionMinor), qmlName); + auto revision = QTypeRevision::fromVersion(versionMajor, versionMinor); + int id = QQmlMetaType::typeId(uri, revision, qmlName); + if (id != -1) + return id; + /* If the module hasn't been imported yet, we might not have the id of a + singleton at this point. To obtain it, we need an engine in order to + to do the resolution steps. + This is expensive, but we assume that users don't constantly query invalid + Types; internal code should use QQmlMetaType API. + */ + QQmlEngine engine; + auto *enginePriv = QQmlEnginePrivate::get(&engine); + auto loadHelper = QQml::makeRefPointer<LoadHelper>(&enginePriv->typeLoader, uri); + auto type = loadHelper->resolveType(qmlName).type; + if (type.availableInVersion(revision)) + return type.index(); + else + return -1; } static bool checkSingletonInstance(QQmlEngine *engine, QObject *instance) diff --git a/src/qml/qml/qqmlabstractbinding_p.h b/src/qml/qml/qqmlabstractbinding_p.h index d4c00ea886..27faad645c 100644 --- a/src/qml/qml/qqmlabstractbinding_p.h +++ b/src/qml/qml/qqmlabstractbinding_p.h @@ -63,7 +63,7 @@ public: void addToObject(); void removeFromObject(); - static void printBindingLoopError(const QQmlProperty &prop); + virtual void printBindingLoopError(const QQmlProperty &prop); inline QQmlAbstractBinding *nextBinding() const; diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index 8bf1a9a7d0..551f55b4be 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -5,6 +5,7 @@ #include "qqmlcontext.h" #include "qqmldata_p.h" +#include "qqmlinfo.h" #include <private/qqmldebugserviceinterfaces_p.h> #include <private/qqmldebugconnector_p.h> @@ -145,7 +146,7 @@ void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) getPropertyData(&d, &vtd); Q_ASSERT(d); QQmlProperty p = QQmlPropertyPrivate::restore(targetObject(), *d, &vtd, nullptr); - QQmlAbstractBinding::printBindingLoopError(p); + printBindingLoopError(p); return; } setUpdatingFlag(true); @@ -167,6 +168,12 @@ void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) setUpdatingFlag(false); } +void QQmlBinding::printBindingLoopError(const QQmlProperty &prop) +{ + qmlWarning(prop.object()) << QString(QLatin1String("Binding loop detected for property \"%1\":\n%2")) + .arg(prop.name(), expressionIdentifier()); +} + QV4::ReturnedValue QQmlBinding::evaluate(bool *isUndefined) { QV4::ExecutionEngine *v4 = engine()->handle(); diff --git a/src/qml/qml/qqmlbinding_p.h b/src/qml/qml/qqmlbinding_p.h index 00aa0d6f58..b2f10b826a 100644 --- a/src/qml/qml/qqmlbinding_p.h +++ b/src/qml/qml/qqmlbinding_p.h @@ -73,6 +73,8 @@ public: QString expression() const override; void update(QQmlPropertyData::WriteFlags flags = QQmlPropertyData::DontRemoveBinding); + void printBindingLoopError(const QQmlProperty &prop) override; + typedef int Identifier; enum { Invalid = -1 diff --git a/src/qml/qml/qqmlbuiltinfunctions.cpp b/src/qml/qml/qqmlbuiltinfunctions.cpp index 99e6f889b7..fdc28d727d 100644 --- a/src/qml/qml/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/qqmlbuiltinfunctions.cpp @@ -31,6 +31,7 @@ #include <private/qv4jsonobject_p.h> #include <private/qv4objectproto_p.h> #include <private/qv4qobjectwrapper_p.h> +#include <private/qv4sequenceobject_p.h> #include <private/qv4stackframe_p.h> #include <QtCore/qstring.h> @@ -230,7 +231,9 @@ The following functions are also on the Qt object. The \c styleHints object provides platform-specific style hints and settings. See the \l QStyleHints documentation for further details. - You should access StyleHints via \l Application::styleHints instead. + You should access StyleHints via \l Application::styleHints instead, as + this provides better type information for tooling such as the + \l {Qt Quick Compiler}. \note The \c styleHints object is only available when using the Qt Quick module. */ @@ -1782,14 +1785,20 @@ static QString serializeArray(Object *array, ExecutionEngine *v4, QSet<QV4::Heap Scope scope(v4); ScopedValue val(scope); QString result; - alreadySeen.insert(array->d()); + + ScopedObject detached(scope); + if (Sequence *reference = array->as<Sequence>()) + detached = ReferenceObject::detached(reference->d()); + else + detached = array; + result += QLatin1Char('['); - const uint length = array->getLength(); + const uint length = detached->getLength(); for (uint i = 0; i < length; ++i) { if (i != 0) result += QLatin1Char(','); - val = array->get(i); + val = detached->get(i); if (val->isManaged() && val->managed()->isArrayLike()) if (!alreadySeen.contains(val->objectValue()->d())) result += serializeArray(val->objectValue(), v4, alreadySeen); @@ -1799,6 +1808,7 @@ static QString serializeArray(Object *array, ExecutionEngine *v4, QSet<QV4::Heap result += val->toQStringNoThrow(); } result += QLatin1Char(']'); + alreadySeen.remove(array->d()); return result; }; @@ -2139,7 +2149,7 @@ ReturnedValue GlobalExtensions::method_qsTranslate(const FunctionObject *b, cons } /*! - \qmlmethod string Qt::qsTranslateNoOp(string context, string sourceText, string disambiguation) + \qmlmethod string Qt::QT_TRANSLATE_NOOP(string context, string sourceText, string disambiguation) Marks \a sourceText for dynamic translation in the given \a context; i.e, the stored \a sourceText will not be altered. @@ -2250,7 +2260,7 @@ ReturnedValue GlobalExtensions::method_qsTr(const FunctionObject *b, const Value } /*! - \qmlmethod string Qt::qsTrNoOp(string sourceText, string disambiguation) + \qmlmethod string Qt::QT_TR_NOOP(string sourceText, string disambiguation) Marks \a sourceText for dynamic translation; i.e, the stored \a sourceText will not be altered. @@ -2331,7 +2341,7 @@ ReturnedValue GlobalExtensions::method_qsTrId(const FunctionObject *b, const Val } /*! - \qmlmethod string Qt::qsTrIdNoOp(string id) + \qmlmethod string Qt::QT_TRID_NOOP(string id) Marks \a id for dynamic translation. diff --git a/src/qml/qml/qqmlcomponent.cpp b/src/qml/qml/qqmlcomponent.cpp index c3f692eeea..5a6fd4c8d3 100644 --- a/src/qml/qml/qqmlcomponent.cpp +++ b/src/qml/qml/qqmlcomponent.cpp @@ -1455,22 +1455,24 @@ void QQmlComponent::create(QQmlIncubator &incubator, QQmlContext *context, QQmlC } /*! - Set top-level \a properties of the \a component. + Set top-level \a properties of the \a object that was created from a + QQmlComponent. This method provides advanced control over component instance creation. In general, programmers should use - \l QQmlComponent::createWithInitialProperties to create a component. + \l QQmlComponent::createWithInitialProperties to create an object instance + from a component. Use this method after beginCreate and before completeCreate has been called. If a provided property does not exist, a warning is issued. \since 5.14 */ -void QQmlComponent::setInitialProperties(QObject *component, const QVariantMap &properties) +void QQmlComponent::setInitialProperties(QObject *object, const QVariantMap &properties) { Q_D(QQmlComponent); for (auto it = properties.constBegin(); it != properties.constEnd(); ++it) - d->setInitialProperty(component, it.key(), it.value()); + d->setInitialProperty(object, it.key(), it.value()); } /* diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 015a00d53b..84347dca11 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -21,6 +21,7 @@ #include <QtCore/qstandardpaths.h> #include <QtCore/qmetaobject.h> #include <QDebug> +#include <private/qqmlcomponent_p.h> #include <QtCore/qcoreapplication.h> #include <QtCore/qcryptographichash.h> #include <QtCore/qdir.h> @@ -1811,6 +1812,24 @@ QJSValue QQmlEnginePrivate::singletonInstance<QJSValue>(const QQmlType &type) return QJSValue(QJSValue::UndefinedValue); } QObject *o = component.beginCreate(q->rootContext()); + auto *compPriv = QQmlComponentPrivate::get(&component); + if (compPriv->state.hasUnsetRequiredProperties()) { + /* We would only get the errors from the component after (complete)Create. + We can't call create, as we need to convertAndInsert before completeCreate (otherwise + tst_qqmllanguage::compositeSingletonCircular fails). + On the other hand, we don't want to call cnovertAndInsert if we have an error + So create the unset required component errors manually. + */ + delete o; + const auto requiredProperties = compPriv->state.requiredProperties(); + QList<QQmlError> errors (requiredProperties->size()); + for (const auto &reqProp: *requiredProperties) + errors.push_back(QQmlComponentPrivate::unsetRequiredPropertyToQQmlError(reqProp)); + warning(errors); + v4engine()->throwError(QLatin1String("Due to the preceding error(s), Singleton \"%1\" could not be loaded.").arg(QString::fromUtf8(type.typeName()))); + return QJSValue(QJSValue::UndefinedValue); + } + value = q->newQObject(o); singletonInstances.convertAndInsert(v4engine(), type, &value); component.completeCreate(); diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index 32377f0502..f8f8b72191 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -1423,7 +1423,7 @@ QTypeRevision QQmlImports::updateQmldirContent( Q_ASSERT(database); Q_ASSERT(errors); - qDebug(lcQmlImport) + qCDebug(lcQmlImport) << "updateQmldirContent:" << qPrintable(baseUrl().toString()) << uri << "to" << qmldirUrl << "as" << prefix; diff --git a/src/qml/qml/qqmllistwrapper.cpp b/src/qml/qml/qqmllistwrapper.cpp index 42f79dcecd..e3c0ba19ae 100644 --- a/src/qml/qml/qqmllistwrapper.cpp +++ b/src/qml/qml/qqmllistwrapper.cpp @@ -17,6 +17,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcIncompatibleElement, "qt.qml.list.incompatible") + using namespace QV4; using namespace Qt::StringLiterals; @@ -127,7 +129,22 @@ bool QmlListWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, QV4::Scope scope(v4); QV4::ScopedObject so(scope, value.toObject(scope.engine)); if (auto *wrapper = so->as<QV4::QObjectWrapper>()) { - prop->replace(prop, index, wrapper->object()); + QObject *object = wrapper->object(); + if (!object) { + prop->replace(prop, index, object); + return true; + } + + const QMetaType elementType = w->d()->elementType(); + const QMetaObject *elementMeta = elementType.metaObject(); + if (Q_UNLIKELY(!elementMeta || !QQmlMetaObject::canConvert(object, elementMeta))) { + qCWarning(lcIncompatibleElement) + << "Cannot insert" << object << "into a QML list of" << elementType.name(); + prop->replace(prop, index, nullptr); + return true; + } + + prop->replace(prop, index, object); return true; } @@ -242,11 +259,28 @@ ReturnedValue PropertyListPrototype::method_push(const FunctionObject *b, const if (!qIsAtMostUintLimit(length, std::numeric_limits<uint>::max() - argc)) return scope.engine->throwRangeError(QString::fromLatin1("List length out of range.")); + const QMetaType elementType = w->d()->elementType(); + const QMetaObject *elementMeta = elementType.metaObject(); for (int i = 0; i < argc; ++i) { - if (argv[i].isNull()) + if (argv[i].isNull()) { property->append(property, nullptr); - else - property->append(property, argv[i].as<QV4::QObjectWrapper>()->object()); + continue; + } + + QObject *object = argv[i].as<QV4::QObjectWrapper>()->object(); + if (!object) { + property->append(property, nullptr); + continue; + } + + if (Q_UNLIKELY(!elementMeta || !QQmlMetaObject::canConvert(object, elementMeta))) { + qCWarning(lcIncompatibleElement) + << "Cannot append" << object << "to a QML list of" << elementType.name(); + property->append(property, nullptr); + continue; + } + + property->append(property, object); } const auto actualLength = property->count(property); @@ -370,12 +404,29 @@ ReturnedValue PropertyListPrototype::method_splice(const FunctionObject *b, cons } } + const QMetaType elementType = w->d()->elementType(); + const QMetaObject *elementMeta = elementType.metaObject(); for (qsizetype i = 0; i < itemCount; ++i) { const auto arg = argv[i + 2]; - if (arg.isNull()) + if (arg.isNull()) { property->replace(property, start + i, nullptr); - else - property->replace(property, start + i, arg.as<QObjectWrapper>()->object()); + continue; + } + + QObject *object = arg.as<QObjectWrapper>()->object(); + if (!object) { + property->replace(property, start + i, nullptr); + continue; + } + + if (Q_UNLIKELY(!elementMeta || !QQmlMetaObject::canConvert(object, elementMeta))) { + qCWarning(lcIncompatibleElement) + << "Cannot splice" << object << "into a QML list of" << elementType.name(); + property->replace(property, start + i, nullptr); + continue; + } + + property->replace(property, start + i, object); } return newArray->asReturnedValue(); @@ -420,9 +471,24 @@ ReturnedValue PropertyListPrototype::method_unshift(const FunctionObject *b, con for (qsizetype k = len; k > 0; --k) property->replace(property, k + argc - 1, property->at(property, k - 1)); + const QMetaType elementType = w->d()->elementType(); + const QMetaObject *elementMeta = elementType.metaObject(); for (int i = 0; i < argc; ++i) { const auto *wrapper = argv[i].as<QObjectWrapper>(); - property->replace(property, i, wrapper ? wrapper->object() : nullptr); + QObject *object = wrapper ? wrapper->object() : nullptr; + if (!object) { + property->replace(property, i, object); + continue; + } + + if (Q_UNLIKELY(!elementMeta || !QQmlMetaObject::canConvert(object, elementMeta))) { + qCWarning(lcIncompatibleElement) + << "Cannot unshift" << object << "into a QML list of" << elementType.name(); + property->replace(property, i, nullptr); + continue; + } + + property->replace(property, i, object); } return Encode(uint(len + argc)); diff --git a/src/qml/qml/qqmllistwrapper_p.h b/src/qml/qml/qqmllistwrapper_p.h index 21240d6b98..78872a61bb 100644 --- a/src/qml/qml/qqmllistwrapper_p.h +++ b/src/qml/qml/qqmllistwrapper_p.h @@ -16,6 +16,7 @@ // #include <QtCore/qglobal.h> +#include <QtCore/qloggingcategory.h> #include <QtCore/qpointer.h> #include <QtQml/qqmllist.h> @@ -25,6 +26,8 @@ QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcIncompatibleElement) + namespace QV4 { namespace Heap { @@ -34,6 +37,7 @@ struct QmlListWrapper : Object { void destroy(); QV4QPointer<QObject> object; + QMetaType elementType() const { return QQmlMetaType::listValueType(QMetaType(propertyType)); } QQmlListProperty<QObject> &property() { return *reinterpret_cast<QQmlListProperty<QObject>*>(propertyData); } diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index 7ebc602fff..1430c20099 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -19,6 +19,7 @@ #include <private/qv4qobjectwrapper_p.h> #include <private/qqmlbuiltinfunctions_p.h> #include <private/qqmlirbuilder_p.h> +#include <private/qqmllistwrapper_p.h> #include <QtQml/private/qqmllist_p.h> #include <QStringList> @@ -867,17 +868,21 @@ static void removeOldBinding(QObject *object, QQmlPropertyIndex index, QQmlPrope oldBinding = data->bindings; while (oldBinding && (oldBinding->targetPropertyIndex().coreIndex() != coreIndex || - oldBinding->targetPropertyIndex().hasValueTypeIndex())) + oldBinding->targetPropertyIndex().hasValueTypeIndex())) { oldBinding = oldBinding->nextBinding(); + } - if (!oldBinding) - return; - - if (valueTypeIndex != -1 && oldBinding->kind() == QQmlAbstractBinding::ValueTypeProxy) + if (valueTypeIndex != -1 + && oldBinding + && oldBinding->kind() == QQmlAbstractBinding::ValueTypeProxy) { oldBinding = static_cast<QQmlValueTypeProxyBinding *>(oldBinding.data())->binding(index); + } - if (!oldBinding) + if (!oldBinding) { + // Clear the binding bit so that the binding doesn't appear later for any reason + data->clearBindingBit(coreIndex); return; + } if (!(flags & QQmlPropertyPrivate::DontEnable)) oldBinding->setEnabled(false, {}); @@ -1402,18 +1407,6 @@ static ConvertAndAssignResult tryConvertAndAssign( return {false, false}; } - if (variantMetaType == QMetaType::fromType<QJSValue>()) { - // Handle Qt.binding bindings here to avoid mistaken conversion below - const QJSValue &jsValue = *static_cast<const QJSValue *>(value.constData()); - const QV4::FunctionObject *f - = QJSValuePrivate::asManagedType<QV4::FunctionObject>(&jsValue); - if (f && f->isBinding()) { - QV4::QObjectWrapper::setProperty( - f->engine(), object, &property, f->asReturnedValue()); - return {true, true}; - } - } - // common cases: switch (propertyMetaType.id()) { case QMetaType::Bool: @@ -1498,6 +1491,21 @@ bool iterateQObjectContainer(QMetaType metaType, const void *data, Op op) return true; } +static bool tryAssignBinding( + QObject *object, const QQmlPropertyData &property, const QVariant &value, + QMetaType variantMetaType) { + if (variantMetaType != QMetaType::fromType<QJSValue>()) + return false; + + const QJSValue &jsValue = *static_cast<const QJSValue *>(value.constData()); + const QV4::FunctionObject *f = QJSValuePrivate::asManagedType<QV4::FunctionObject>(&jsValue); + if (!f || !f->isBinding()) + return false; + + QV4::QObjectWrapper::setProperty(f->engine(), object, &property, f->asReturnedValue()); + return true; +} + bool QQmlPropertyPrivate::write( QObject *object, const QQmlPropertyData &property, const QVariant &value, const QQmlRefPointer<QQmlContextData> &context, QQmlPropertyData::WriteFlags flags) @@ -1523,6 +1531,10 @@ bool QQmlPropertyPrivate::write( QQmlEnginePrivate *enginePriv = QQmlEnginePrivate::get(context); const bool isUrl = propertyMetaType == QMetaType::fromType<QUrl>(); // handled separately + // Handle Qt.binding bindings here to avoid mistaken conversion below + if (tryAssignBinding(object, property, value, variantMetaType)) + return true; + // The cases below are in approximate order of likelyhood: if (propertyMetaType == variantMetaType && !isUrl && propertyMetaType != QMetaType::fromType<QList<QUrl>>() && !property.isQList()) { @@ -1598,8 +1610,11 @@ bool QQmlPropertyPrivate::write( prop.clear(&prop); const auto doAppend = [&](QObject *o) { - if (o && !QQmlMetaObject::canConvert(o, valueMetaObject)) + if (Q_UNLIKELY(o && !QQmlMetaObject::canConvert(o, valueMetaObject))) { + qCWarning(lcIncompatibleElement) + << "Cannot append" << o << "to a QML list of" << listValueType.name(); o = nullptr; + } prop.append(&prop, o); }; diff --git a/src/qml/qml/qqmlpropertytopropertybinding.cpp b/src/qml/qml/qqmlpropertytopropertybinding.cpp index ec77e8c97a..f2edf3b87f 100644 --- a/src/qml/qml/qqmlpropertytopropertybinding.cpp +++ b/src/qml/qml/qqmlpropertytopropertybinding.cpp @@ -91,8 +91,7 @@ void QQmlPropertyToPropertyBinding::update(QQmlPropertyData::WriteFlags flags) // Check for a binding update loop if (Q_UNLIKELY(updatingFlag())) { - QQmlAbstractBinding::printBindingLoopError( - QQmlPropertyPrivate::restore(target, *d, &vtd, nullptr)); + printBindingLoopError(QQmlPropertyPrivate::restore(target, *d, &vtd, nullptr)); return; } |
