diff options
Diffstat (limited to 'src')
112 files changed, 1228 insertions, 643 deletions
diff --git a/src/3rdparty/masm/qt_attribution.json b/src/3rdparty/masm/qt_attribution.json index 77b144d738..1c98ab8b26 100644 --- a/src/3rdparty/masm/qt_attribution.json +++ b/src/3rdparty/masm/qt_attribution.json @@ -5,6 +5,7 @@ "QtUsage": "Used in Qt QML.", "Homepage": "https://trac.webkit.org/wiki/JavaScriptCore", + "Comment": "no relevant PURL or CPE set, because the dependency is huge, and would show a lot of false positives, at this point it should be considered as part of Qt", "License": "BSD 2-clause \"Simplified\" License", "LicenseId": "BSD-2-Clause", "LicenseFile": "LICENSE", diff --git a/src/labs/folderlistmodel/qquickfolderlistmodel.cpp b/src/labs/folderlistmodel/qquickfolderlistmodel.cpp index 04d4054cf5..ee05d34904 100644 --- a/src/labs/folderlistmodel/qquickfolderlistmodel.cpp +++ b/src/labs/folderlistmodel/qquickfolderlistmodel.cpp @@ -343,38 +343,39 @@ QVariant QQuickFolderListModel::data(const QModelIndex &index, int role) const Q_D(const QQuickFolderListModel); QVariant rv; - if (index.row() >= d->data.size()) + const int row = index.row(); + if (row < 0 || row >= d->data.size()) return rv; switch (role) { case FileNameRole: - rv = d->data.at(index.row()).fileName(); + rv = d->data.at(row).fileName(); break; case FilePathRole: - rv = d->data.at(index.row()).filePath(); + rv = d->data.at(row).filePath(); break; case FileBaseNameRole: - rv = d->data.at(index.row()).baseName(); + rv = d->data.at(row).baseName(); break; case FileSuffixRole: - rv = d->data.at(index.row()).suffix(); + rv = d->data.at(row).suffix(); break; case FileSizeRole: - rv = d->data.at(index.row()).size(); + rv = d->data.at(row).size(); break; case FileLastModifiedRole: - rv = d->data.at(index.row()).lastModified(); + rv = d->data.at(row).lastModified(); break; case FileLastReadRole: - rv = d->data.at(index.row()).lastRead(); + rv = d->data.at(row).lastRead(); break; case FileIsDirRole: - rv = d->data.at(index.row()).isDir(); + rv = d->data.at(row).isDir(); break; case FileUrlRole: case FileURLRole: - rv = QUrl::fromLocalFile(d->data.at(index.row()).filePath()); + rv = QUrl::fromLocalFile(d->data.at(row).filePath()); break; default: break; diff --git a/src/labs/platform/qquicklabsplatformmenu_p.h b/src/labs/platform/qquicklabsplatformmenu_p.h index b8577dcf49..0665dba937 100644 --- a/src/labs/platform/qquicklabsplatformmenu_p.h +++ b/src/labs/platform/qquicklabsplatformmenu_p.h @@ -134,7 +134,7 @@ Q_SIGNALS: void minimumWidthChanged(); void fontChanged(); void typeChanged(); - Q_REVISION(2, 1) void iconChanged(); + Q_REVISION(1, 1) void iconChanged(); protected: void classBegin() override; diff --git a/src/labs/platform/qquicklabsplatformmenuitem_p.h b/src/labs/platform/qquicklabsplatformmenuitem_p.h index f02f2536fd..632007dd88 100644 --- a/src/labs/platform/qquicklabsplatformmenuitem_p.h +++ b/src/labs/platform/qquicklabsplatformmenuitem_p.h @@ -117,7 +117,7 @@ Q_SIGNALS: void textChanged(); void shortcutChanged(); void fontChanged(); - Q_REVISION(2, 1) void iconChanged(); + Q_REVISION(1, 1) void iconChanged(); protected: void classBegin() override; diff --git a/src/labs/platform/qquicklabsplatformsystemtrayicon_p.h b/src/labs/platform/qquicklabsplatformsystemtrayicon_p.h index 4adac1a8f7..7e5a0057d0 100644 --- a/src/labs/platform/qquicklabsplatformsystemtrayicon_p.h +++ b/src/labs/platform/qquicklabsplatformsystemtrayicon_p.h @@ -80,8 +80,8 @@ Q_SIGNALS: void visibleChanged(); void tooltipChanged(); void menuChanged(); - Q_REVISION(2, 1) void geometryChanged(); - Q_REVISION(2, 1) void iconChanged(); + Q_REVISION(1, 1) void geometryChanged(); + Q_REVISION(1, 1) void iconChanged(); protected: void init(); diff --git a/src/labs/settings/qqmlsettings.cpp b/src/labs/settings/qqmlsettings.cpp index 19ebdaf3c2..48fd37ed9b 100644 --- a/src/labs/settings/qqmlsettings.cpp +++ b/src/labs/settings/qqmlsettings.cpp @@ -30,7 +30,7 @@ QT_BEGIN_NAMESPACE /*! \qmltype Settings -//! \instantiates QQmlSettings +//! \instantiates QQmlSettingsLabs \inqmlmodule Qt.labs.settings \ingroup settings \deprecated [6.5] Use \l [QML] {QtCore::}{Settings} from Qt QML Core instead. @@ -205,12 +205,12 @@ Q_LOGGING_CATEGORY(lcSettings, "qt.labs.settings") static const int settingsWriteDelay = 500; -class QQmlSettingsPrivate +class QQmlSettingsLabsPrivate { - Q_DECLARE_PUBLIC(QQmlSettings) + Q_DECLARE_PUBLIC(QQmlSettingsLabs) public: - QQmlSettingsPrivate(); + QQmlSettingsLabsPrivate(); QSettings *instance() const; @@ -223,7 +223,7 @@ public: void _q_propertyChanged(); QVariant readProperty(const QMetaProperty &property) const; - QQmlSettings *q_ptr = nullptr; + QQmlSettingsLabs *q_ptr = nullptr; int timerId = 0; bool initialized = false; QString category; @@ -232,12 +232,12 @@ public: QHash<const char *, QVariant> changedProperties; }; -QQmlSettingsPrivate::QQmlSettingsPrivate() {} +QQmlSettingsLabsPrivate::QQmlSettingsLabsPrivate() {} -QSettings *QQmlSettingsPrivate::instance() const +QSettings *QQmlSettingsLabsPrivate::instance() const { if (!settings) { - QQmlSettings *q = const_cast<QQmlSettings*>(q_func()); + QQmlSettingsLabs *q = const_cast<QQmlSettingsLabs*>(q_func()); settings = fileName.isEmpty() ? new QSettings(q) : new QSettings(fileName, QSettings::IniFormat, q); if (settings->status() != QSettings::NoError) { // TODO: can't print out the enum due to the following error: @@ -267,25 +267,25 @@ QSettings *QQmlSettingsPrivate::instance() const return settings; } -void QQmlSettingsPrivate::init() +void QQmlSettingsLabsPrivate::init() { if (!initialized) { - qCDebug(lcSettings) << "QQmlSettings: stored at" << instance()->fileName(); + qCDebug(lcSettings) << "QQmlSettingsLabs: stored at" << instance()->fileName(); load(); initialized = true; } } -void QQmlSettingsPrivate::reset() +void QQmlSettingsLabsPrivate::reset() { if (initialized && settings && !changedProperties.isEmpty()) store(); delete settings; } -void QQmlSettingsPrivate::load() +void QQmlSettingsLabsPrivate::load() { - Q_Q(QQmlSettings); + Q_Q(QQmlSettingsLabs); const QMetaObject *mo = q->metaObject(); const int offset = mo->propertyOffset(); const int count = mo->propertyCount(); @@ -306,7 +306,7 @@ void QQmlSettingsPrivate::load() || (currentValue.canConvert(previousValue.metaType()) && previousValue != currentValue))) { property.write(q, currentValue); - qCDebug(lcSettings) << "QQmlSettings: load" << property.name() << "setting:" << currentValue << "default:" << previousValue; + qCDebug(lcSettings) << "QQmlSettingsLabs: load" << property.name() << "setting:" << currentValue << "default:" << previousValue; } // ensure that a non-existent setting gets written @@ -322,20 +322,20 @@ void QQmlSettingsPrivate::load() } } -void QQmlSettingsPrivate::store() +void QQmlSettingsLabsPrivate::store() { QHash<const char *, QVariant>::const_iterator it = changedProperties.constBegin(); while (it != changedProperties.constEnd()) { instance()->setValue(QString::fromUtf8(it.key()), it.value()); - qCDebug(lcSettings) << "QQmlSettings: store" << it.key() << ":" << it.value(); + qCDebug(lcSettings) << "QQmlSettingsLabs: store" << it.key() << ":" << it.value(); ++it; } changedProperties.clear(); } -void QQmlSettingsPrivate::_q_propertyChanged() +void QQmlSettingsLabsPrivate::_q_propertyChanged() { - Q_Q(QQmlSettings); + Q_Q(QQmlSettingsLabs); const QMetaObject *mo = q->metaObject(); const int offset = mo->propertyOffset(); const int count = mo->propertyCount(); @@ -343,32 +343,32 @@ void QQmlSettingsPrivate::_q_propertyChanged() const QMetaProperty &property = mo->property(i); const QVariant value = readProperty(property); changedProperties.insert(property.name(), value); - qCDebug(lcSettings) << "QQmlSettings: cache" << property.name() << ":" << value; + qCDebug(lcSettings) << "QQmlSettingsLabs: cache" << property.name() << ":" << value; } if (timerId != 0) q->killTimer(timerId); timerId = q->startTimer(settingsWriteDelay); } -QVariant QQmlSettingsPrivate::readProperty(const QMetaProperty &property) const +QVariant QQmlSettingsLabsPrivate::readProperty(const QMetaProperty &property) const { - Q_Q(const QQmlSettings); + Q_Q(const QQmlSettingsLabs); QVariant var = property.read(q); if (var.metaType() == QMetaType::fromType<QJSValue>()) var = var.value<QJSValue>().toVariant(); return var; } -QQmlSettings::QQmlSettings(QObject *parent) - : QObject(parent), d_ptr(new QQmlSettingsPrivate) +QQmlSettingsLabs::QQmlSettingsLabs(QObject *parent) + : QObject(parent), d_ptr(new QQmlSettingsLabsPrivate) { - Q_D(QQmlSettings); + Q_D(QQmlSettingsLabs); d->q_ptr = this; } -QQmlSettings::~QQmlSettings() +QQmlSettingsLabs::~QQmlSettingsLabs() { - Q_D(QQmlSettings); + Q_D(QQmlSettingsLabs); d->reset(); // flush pending changes } @@ -379,15 +379,15 @@ QQmlSettings::~QQmlSettings() Categories can be used to group related settings together. */ -QString QQmlSettings::category() const +QString QQmlSettingsLabs::category() const { - Q_D(const QQmlSettings); + Q_D(const QQmlSettingsLabs); return d->category; } -void QQmlSettings::setCategory(const QString &category) +void QQmlSettingsLabs::setCategory(const QString &category) { - Q_D(QQmlSettings); + Q_D(QQmlSettingsLabs); if (d->category != category) { d->reset(); d->category = category; @@ -406,15 +406,15 @@ void QQmlSettings::setCategory(const QString &category) \sa QSettings::fileName, QSettings::IniFormat */ -QString QQmlSettings::fileName() const +QString QQmlSettingsLabs::fileName() const { - Q_D(const QQmlSettings); + Q_D(const QQmlSettingsLabs); return d->fileName; } -void QQmlSettings::setFileName(const QString &fileName) +void QQmlSettingsLabs::setFileName(const QString &fileName) { - Q_D(QQmlSettings); + Q_D(QQmlSettingsLabs); if (d->fileName != fileName) { d->reset(); d->fileName = fileName; @@ -433,9 +433,9 @@ void QQmlSettings::setFileName(const QString &fileName) \sa QSettings::value */ -QVariant QQmlSettings::value(const QString &key, const QVariant &defaultValue) const +QVariant QQmlSettingsLabs::value(const QString &key, const QVariant &defaultValue) const { - Q_D(const QQmlSettings); + Q_D(const QQmlSettingsLabs); return d->instance()->value(key, defaultValue); } @@ -449,11 +449,11 @@ QVariant QQmlSettings::value(const QString &key, const QVariant &defaultValue) c \sa QSettings::setValue */ -void QQmlSettings::setValue(const QString &key, const QVariant &value) +void QQmlSettingsLabs::setValue(const QString &key, const QVariant &value) { - Q_D(const QQmlSettings); + Q_D(const QQmlSettingsLabs); d->instance()->setValue(key, value); - qCDebug(lcSettings) << "QQmlSettings: setValue" << key << ":" << value; + qCDebug(lcSettings) << "QQmlSettingsLabs: setValue" << key << ":" << value; } /*! @@ -469,28 +469,28 @@ void QQmlSettings::setValue(const QString &key, const QVariant &value) \sa QSettings::sync */ -void QQmlSettings::sync() +void QQmlSettingsLabs::sync() { - Q_D(QQmlSettings); + Q_D(QQmlSettingsLabs); d->instance()->sync(); } -void QQmlSettings::classBegin() +void QQmlSettingsLabs::classBegin() { } -void QQmlSettings::componentComplete() +void QQmlSettingsLabs::componentComplete() { - Q_D(QQmlSettings); + Q_D(QQmlSettingsLabs); d->init(); qmlWarning(this) << "The Settings type from Qt.labs.settings is deprecated" " and will be removed in a future release. Please use " "the one from QtCore instead."; } -void QQmlSettings::timerEvent(QTimerEvent *event) +void QQmlSettingsLabs::timerEvent(QTimerEvent *event) { - Q_D(QQmlSettings); + Q_D(QQmlSettingsLabs); if (event->timerId() == d->timerId) { killTimer(d->timerId); d->timerId = 0; diff --git a/src/labs/settings/qqmlsettings_p.h b/src/labs/settings/qqmlsettings_p.h index 895df8c5e3..e1e60d721b 100644 --- a/src/labs/settings/qqmlsettings_p.h +++ b/src/labs/settings/qqmlsettings_p.h @@ -24,9 +24,9 @@ QT_BEGIN_NAMESPACE -class QQmlSettingsPrivate; +class QQmlSettingsLabsPrivate; -class Q_LABSSETTINGS_PRIVATE_EXPORT QQmlSettings : public QObject, public QQmlParserStatus +class Q_LABSSETTINGS_PRIVATE_EXPORT QQmlSettingsLabs : public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) @@ -36,8 +36,8 @@ class Q_LABSSETTINGS_PRIVATE_EXPORT QQmlSettings : public QObject, public QQmlPa QML_ADDED_IN_VERSION(1, 0) public: - explicit QQmlSettings(QObject *parent = nullptr); - ~QQmlSettings(); + explicit QQmlSettingsLabs(QObject *parent = nullptr); + ~QQmlSettingsLabs(); QString category() const; void setCategory(const QString &category); @@ -56,14 +56,14 @@ protected: void componentComplete() override; private: - Q_DISABLE_COPY(QQmlSettings) - Q_DECLARE_PRIVATE(QQmlSettings) - QScopedPointer<QQmlSettingsPrivate> d_ptr; + Q_DISABLE_COPY(QQmlSettingsLabs) + Q_DECLARE_PRIVATE(QQmlSettingsLabs) + QScopedPointer<QQmlSettingsLabsPrivate> d_ptr; Q_PRIVATE_SLOT(d_func(), void _q_propertyChanged()) }; QT_END_NAMESPACE -QML_DECLARE_TYPE(QQmlSettings) +QML_DECLARE_TYPE(QQmlSettingsLabs) #endif // QQMLSETTINGS_P_H diff --git a/src/plugins/qmltooling/qmldbg_messages/qdebugmessageservice.cpp b/src/plugins/qmltooling/qmldbg_messages/qdebugmessageservice.cpp index d3ec1c447a..1d4cf47b64 100644 --- a/src/plugins/qmltooling/qmldbg_messages/qdebugmessageservice.cpp +++ b/src/plugins/qmltooling/qmldbg_messages/qdebugmessageservice.cpp @@ -13,7 +13,8 @@ using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>; void DebugMessageHandler(QtMsgType type, const QMessageLogContext &ctxt, const QString &buf) { - QQmlDebugConnector::service<QDebugMessageServiceImpl>()->sendDebugMessage(type, ctxt, buf); + if (QDebugMessageServiceImpl *service = QQmlDebugConnector::service<QDebugMessageServiceImpl>()) + service->sendDebugMessage(type, ctxt, buf); } QDebugMessageServiceImpl::QDebugMessageServiceImpl(QObject *parent) : @@ -29,6 +30,12 @@ QDebugMessageServiceImpl::QDebugMessageServiceImpl(QObject *parent) : } } +QDebugMessageServiceImpl::~QDebugMessageServiceImpl() +{ + if (oldMsgHandler) + restoreOldMessageHandler(); +} + void QDebugMessageServiceImpl::sendDebugMessage(QtMsgType type, const QMessageLogContext &ctxt, const QString &buf) @@ -50,19 +57,26 @@ void QDebugMessageServiceImpl::stateChanged(State state) { QMutexLocker lock(&initMutex); - if (state != Enabled && prevState == Enabled) { - QtMessageHandler handler = qInstallMessageHandler(oldMsgHandler); - // has our handler been overwritten in between? - if (handler != DebugMessageHandler) - qInstallMessageHandler(handler); - - } else if (state == Enabled && prevState != Enabled) { + if (state != Enabled && prevState == Enabled) + restoreOldMessageHandler(); + else if (state == Enabled && prevState != Enabled) oldMsgHandler = qInstallMessageHandler(DebugMessageHandler); - } prevState = state; } +void QDebugMessageServiceImpl::restoreOldMessageHandler() +{ + QtMessageHandler handler = qInstallMessageHandler(oldMsgHandler); + + // has our handler been overwritten in between? + // In that case, leave the other one alone and try again on destruction. + if (handler == DebugMessageHandler) + oldMsgHandler = nullptr; + else + qInstallMessageHandler(handler); +} + void QDebugMessageServiceImpl::synchronizeTime(const QElapsedTimer &otherTimer) { QMutexLocker lock(&initMutex); diff --git a/src/plugins/qmltooling/qmldbg_messages/qdebugmessageservice.h b/src/plugins/qmltooling/qmldbg_messages/qdebugmessageservice.h index 1ae4bd8224..7fbcd64a4a 100644 --- a/src/plugins/qmltooling/qmldbg_messages/qdebugmessageservice.h +++ b/src/plugins/qmltooling/qmldbg_messages/qdebugmessageservice.h @@ -28,6 +28,7 @@ class QDebugMessageServiceImpl : public QDebugMessageService Q_OBJECT public: QDebugMessageServiceImpl(QObject *parent = nullptr); + ~QDebugMessageServiceImpl(); void sendDebugMessage(QtMsgType type, const QMessageLogContext &ctxt, const QString &buf); void synchronizeTime(const QElapsedTimer &otherTimer) override; @@ -36,6 +37,8 @@ protected: void stateChanged(State) override; private: + void restoreOldMessageHandler(); + friend class QQmlDebuggerServiceFactory; QtMessageHandler oldMsgHandler; 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); diff --git a/src/qmlcompiler/qqmljsbasicblocks.cpp b/src/qmlcompiler/qqmljsbasicblocks.cpp index d5bbae037f..e95ad114e4 100644 --- a/src/qmlcompiler/qqmljsbasicblocks.cpp +++ b/src/qmlcompiler/qqmljsbasicblocks.cpp @@ -216,7 +216,7 @@ void QQmlJSBasicBlocks::populateReaderLocations() m_readerLocations.erase(reader); // If it's not a label and has no side effects, we can drop the instruction. - if (!it->second.hasSideEffects) { + if (!it->second.hasInternalSideEffects) { if (!it->second.readRegisters.isEmpty()) { it->second.readRegisters.clear(); erasedReaders = true; diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp index 01ef6a95e6..709dcbea8f 100644 --- a/src/qmlcompiler/qqmljscodegenerator.cpp +++ b/src/qmlcompiler/qqmljscodegenerator.cpp @@ -2561,7 +2561,7 @@ QV4::Moth::ByteCodeHandler::Verdict QQmlJSCodeGenerator::startInstruction( // If the instruction has no side effects and doesn't write any register, it's dead. // We might still need the label, though, and the source code comment. - if (!m_state.hasSideEffects() && changedRegisterVariable().isEmpty()) { + if (!m_state.hasInternalSideEffects() && changedRegisterVariable().isEmpty()) { generateJumpCodeWithTypeConversions(0); return SkipInstruction; } diff --git a/src/qmlcompiler/qqmljscompilepass_p.h b/src/qmlcompiler/qqmljscompilepass_p.h index b35adfb66a..7ca9744bb1 100644 --- a/src/qmlcompiler/qqmljscompilepass_p.h +++ b/src/qmlcompiler/qqmljscompilepass_p.h @@ -64,7 +64,8 @@ public: QQmlJSRegisterContent changedRegister; int changedRegisterIndex = InvalidRegister; - bool hasSideEffects = false; + bool hasInternalSideEffects = false; + bool hasExternalSideEffects = false; bool isRename = false; }; @@ -158,16 +159,33 @@ public: return m_readRegisters.contains(registerIndex); } - bool hasSideEffects() const { return m_hasSideEffects; } - void setHasSideEffects(bool hasSideEffects) { - m_hasSideEffects = hasSideEffects; - if (!hasSideEffects) + bool hasInternalSideEffects() const { return m_hasInternalSideEffects; } + bool hasExternalSideEffects() const { return m_hasExternalSideEffects; } + + void resetSideEffects() + { + m_hasInternalSideEffects = false; + m_hasExternalSideEffects = false; + } + + void applyExternalSideEffects(bool hasExternalSideEffects) + { + if (!hasExternalSideEffects) return; for (auto it = registers.begin(), end = registers.end(); it != end; ++it) it.value().affectedBySideEffects = true; } + void setHasInternalSideEffects() { m_hasInternalSideEffects = true; } + void setHasExternalSideEffects() + { + m_hasExternalSideEffects = true; + m_hasInternalSideEffects = true; + applyExternalSideEffects(true); + } + + bool isRename() const { return m_isRename; } void setIsRename(bool isRename) { m_isRename = isRename; } @@ -178,11 +196,36 @@ public: return m_readRegisters.begin().key(); } + void applyAnnotation(const InstructionAnnotation &annotation) + { + m_readRegisters = annotation.readRegisters; + + m_hasInternalSideEffects = annotation.hasInternalSideEffects; + m_hasExternalSideEffects = annotation.hasExternalSideEffects; + m_isRename = annotation.isRename; + + for (auto it = annotation.typeConversions.constBegin(), + end = annotation.typeConversions.constEnd(); it != end; ++it) { + Q_ASSERT(it.key() != InvalidRegister); + registers[it.key()] = it.value(); + } + + if (annotation.changedRegisterIndex != InvalidRegister) + setRegister(annotation.changedRegisterIndex, annotation.changedRegister); + } + private: VirtualRegisters m_readRegisters; QQmlJSRegisterContent m_changedRegister; int m_changedRegisterIndex = InvalidRegister; - bool m_hasSideEffects = false; + + // If the instruction's value is unused, we still cannot optimize it out. + bool m_hasInternalSideEffects = false; + + // Side effect created by calls to other functions or writes to properties, + // affects tracked value types and lists. Implies the effects of Internal. + bool m_hasExternalSideEffects = false; + bool m_isRename = false; }; @@ -245,23 +288,12 @@ protected: = oldState.changedRegister(); } - if (instruction == annotations.constEnd()) - return newState; - - newState.setHasSideEffects(instruction->second.hasSideEffects); - newState.setReadRegisters(instruction->second.readRegisters); - newState.setIsRename(instruction->second.isRename); + // Side effects are applied at the end of an instruction: An instruction with side + // effects can still read its registers before the side effects happen. + newState.applyExternalSideEffects(oldState.hasExternalSideEffects()); - for (auto it = instruction->second.typeConversions.begin(), - end = instruction->second.typeConversions.end(); it != end; ++it) { - Q_ASSERT(it.key() != InvalidRegister); - newState.registers[it.key()] = it.value(); - } - - if (instruction->second.changedRegisterIndex != InvalidRegister) { - newState.setRegister(instruction->second.changedRegisterIndex, - instruction->second.changedRegister); - } + if (instruction != annotations.constEnd()) + newState.applyAnnotation(instruction->second); return newState; } diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 4bc215add7..bd693cc619 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -18,6 +18,7 @@ #include <QtQml/private/qqmlirbuilder_p.h> #include "qqmljsscope_p.h" #include "qqmljsutils_p.h" +#include "qqmlsa_p.h" #include <algorithm> #include <variant> @@ -799,8 +800,8 @@ void QQmlJSImportVisitor::checkRequiredProperties() } } - const auto isInComponent = [this](const QQmlJSScope::ConstPtr &requiredScope) { - const auto compType = m_rootScopeImports.type(u"Component"_s).scope; + const auto compType = m_rootScopeImports.type(u"Component"_s).scope; + const auto isInComponent = [&](const QQmlJSScope::ConstPtr &requiredScope) { for (auto s = requiredScope; s; s = s->parentScope()) { if (s->isWrappedInImplicitComponent() || s->baseType() == compType) return true; @@ -811,9 +812,11 @@ void QQmlJSImportVisitor::checkRequiredProperties() const auto requiredHasBinding = [](const QList<QQmlJSScope::ConstPtr> &scopesToSearch, const QString &propName) { for (const auto &scope : scopesToSearch) { + if (scope->property(propName).isAlias()) + continue; const auto &[begin, end] = scope->ownPropertyBindings(propName); for (auto it = begin; it != end; ++it) { - if (!scope->property(propName).isAlias()) + if (QQmlSA::isRegularBindingType(it->bindingType())) return true; } } @@ -880,16 +883,17 @@ void QQmlJSImportVisitor::checkRequiredProperties() : u"here"_s; if (!prevRequiredScope.isNull()) { - auto sourceScope = prevRequiredScope->baseType(); - suggestion = QQmlJSFixSuggestion{ - "%1:%2:%3: Property marked as required in %4."_L1 - .arg(sourceScope->filePath()) - .arg(sourceScope->sourceLocation().startLine) - .arg(sourceScope->sourceLocation().startColumn) - .arg(requiredScopeName), - sourceScope->sourceLocation() - }; - suggestion->setFilename(sourceScope->filePath()); + if (auto sourceScope = prevRequiredScope->baseType()) { + suggestion = QQmlJSFixSuggestion{ + "%1:%2:%3: Property marked as required in %4."_L1 + .arg(sourceScope->filePath()) + .arg(sourceScope->sourceLocation().startLine) + .arg(sourceScope->sourceLocation().startColumn) + .arg(requiredScopeName), + sourceScope->sourceLocation() + }; + suggestion->setFilename(sourceScope->filePath()); + } } else { message += " (marked as required by %1)"_L1.arg(requiredScopeName); } @@ -902,7 +906,8 @@ void QQmlJSImportVisitor::checkRequiredProperties() for (const auto &[_, defScope] : m_scopesByIrLocation.asKeyValueRange()) { if (defScope->parentScope() == m_globalScope || defScope->isInlineComponent() - || defScope->isComponentRootElement()) { + || defScope->isComponentRootElement() + || defScope->scopeType() != QQmlJSScope::QMLScope) { continue; } @@ -917,6 +922,8 @@ void QQmlJSImportVisitor::checkRequiredProperties() for (auto propertyIt = ownProperties.constBegin(); propertyIt != ownProperties.constEnd(); ++propertyIt) { const QString propName = propertyIt.key(); + if (descendant->hasOwnPropertyBindings(propName)) + continue; QQmlJSScope::ConstPtr prevRequiredScope; for (QQmlJSScope::ConstPtr requiredScope : std::as_const(scopesToSearch)) { @@ -1613,7 +1620,7 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember) auto tryParseAlias = [&]() { typeName.clear(); // type name is useless for alias here, so keep it empty if (!publicMember->statement) { - m_logger->log(QStringLiteral("Invalid alias expression – an initalizer is needed."), + m_logger->log(QStringLiteral("Invalid alias expression - an initalizer is needed."), qmlSyntax, publicMember->memberType->firstSourceLocation()); // TODO: extend warning to cover until endSourceLocation return; } diff --git a/src/qmlcompiler/qqmljsscope.cpp b/src/qmlcompiler/qqmljsscope.cpp index 5067210261..29a0db05cf 100644 --- a/src/qmlcompiler/qqmljsscope.cpp +++ b/src/qmlcompiler/qqmljsscope.cpp @@ -361,9 +361,15 @@ qFindInlineComponents(QStringView typeName, const QQmlJSScope::ContextualTypes & if (current->isInlineComponent() && current->inlineComponentName() == inlineComponentName) { return { current, inlineComponentParent.revision }; } + // check alternatively the inline components at layer 1 in current and basetype, then at // layer 2, etc... - candidatesForInlineComponents.append(current->childScopes()); + const auto &childScopes = current->childScopes(); + for (const auto &child : childScopes) { + if (child->scopeType() == QQmlJSScope::ScopeType::QMLScope) + candidatesForInlineComponents.enqueue(child); + } + if (const auto base = current->baseType()) candidatesForInlineComponents.enqueue(base); } diff --git a/src/qmlcompiler/qqmljsshadowcheck.cpp b/src/qmlcompiler/qqmljsshadowcheck.cpp index ead1813afe..600e40d389 100644 --- a/src/qmlcompiler/qqmljsshadowcheck.cpp +++ b/src/qmlcompiler/qqmljsshadowcheck.cpp @@ -72,7 +72,7 @@ void QQmlJSShadowCheck::generate_SetLookup(int index, int base) QV4::Moth::ByteCodeHandler::Verdict QQmlJSShadowCheck::startInstruction(QV4::Moth::Instr::Type) { m_state = nextStateFromAnnotations(m_state, *m_annotations); - return (m_state.hasSideEffects() || m_state.changedRegisterIndex() != InvalidRegister) + return (m_state.hasInternalSideEffects() || m_state.changedRegisterIndex() != InvalidRegister) ? ProcessInstruction : SkipInstruction; } diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index 06b49d067d..bff83817fe 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -105,7 +105,7 @@ void QQmlJSTypePropagator::generate_Ret() addReadAccumulator(m_returnType); } - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); m_state.skipInstructionsUntilNextJumpTarget = true; } @@ -631,7 +631,7 @@ void QQmlJSTypePropagator::generate_StoreNameCommon(int nameIndex) m_function->qmlScope, getCurrentSourceLocation()); } - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); if (m_typeResolver->canHoldUndefined(in) && !m_typeResolver->canHoldUndefined(type)) { if (type.property().reset().isEmpty()) @@ -725,7 +725,7 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index) // Writing to a JS array can have side effects all over the place since it's // passed by reference. - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); return; } @@ -742,7 +742,7 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index) // If we're writing to a list retrieved from a property, that _should_ have side effects, // but currently the QML engine doesn't implement them. // TODO: Figure out the above and accurately set the flag. - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); } void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) @@ -963,7 +963,7 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base) addReadAccumulator(property); addReadRegister(base, callBase); - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); } void QQmlJSTypePropagator::generate_SetLookup(int index, int base) @@ -1000,7 +1000,7 @@ void QQmlJSTypePropagator::generate_Resume(int) void QQmlJSTypePropagator::generate_CallValue(int name, int argc, int argv) { - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); Q_UNUSED(name) Q_UNUSED(argc) Q_UNUSED(argv) @@ -1009,7 +1009,7 @@ void QQmlJSTypePropagator::generate_CallValue(int name, int argc, int argv) void QQmlJSTypePropagator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv) { - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); Q_UNUSED(name) Q_UNUSED(thisObject) Q_UNUSED(argc) @@ -1075,7 +1075,7 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar for (int i = 1; i < argc; ++i) addReadRegister(argv + i, stringType); - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); setAccumulator(voidType); return; } @@ -1087,7 +1087,7 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar for (int i = 0; i < argc; ++i) addReadRegister(argv + i, jsValueType); setAccumulator(jsValueType); - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); return; } @@ -1238,14 +1238,17 @@ void QQmlJSTypePropagator::setRegister(int index, const QQmlJSRegisterContent &c } void QQmlJSTypePropagator::mergeRegister( - int index, const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b) + int index, const VirtualRegister &a, const VirtualRegister &b) { - auto merged = m_typeResolver->merge(a, b); + const VirtualRegister merged = { + m_typeResolver->merge(a.content, b.content), + a.affectedBySideEffects || b.affectedBySideEffects, + }; - Q_ASSERT(merged.isValid()); - Q_ASSERT(merged.isConversion()); + Q_ASSERT(merged.content.isValid()); + Q_ASSERT(merged.content.isConversion()); - auto tryPrevStateConversion = [this](int index, const QQmlJSRegisterContent &merged) -> bool { + auto tryPrevStateConversion = [this](int index, const VirtualRegister &merged) -> bool { auto it = m_prevStateAnnotations.find(currentInstructionOffset()); if (it == m_prevStateAnnotations.end()) return false; @@ -1259,22 +1262,31 @@ void QQmlJSTypePropagator::mergeRegister( Q_ASSERT(lastTry.content.isValid()); Q_ASSERT(lastTry.content.isConversion()); - if (!m_typeResolver->equals(lastTry.content.conversionResult(), merged.conversionResult()) - || lastTry.content.conversionOrigins() != merged.conversionOrigins()) { + if (!m_typeResolver->equals(lastTry.content.conversionResult(), + merged.content.conversionResult()) + || lastTry.content.conversionOrigins() != merged.content.conversionOrigins() + || lastTry.affectedBySideEffects != merged.affectedBySideEffects) { return false; } // We don't need to track it again if we've come to the same conclusion before. m_state.annotations[currentInstructionOffset()].typeConversions[index] = lastTry; + + // Do not reset the side effects + Q_ASSERT(!m_state.registers[index].affectedBySideEffects || lastTry.affectedBySideEffects); + m_state.registers[index] = lastTry; return true; }; if (!tryPrevStateConversion(index, merged)) { - merged = m_typeResolver->tracked(merged); - Q_ASSERT(merged.isValid()); - m_state.annotations[currentInstructionOffset()].typeConversions[index].content = merged; - m_state.registers[index].content = merged; + const VirtualRegister cloned = { + m_typeResolver->tracked(merged.content), + merged.affectedBySideEffects, + }; + Q_ASSERT(cloned.content.isValid()); + m_state.annotations[currentInstructionOffset()].typeConversions[index] = cloned; + m_state.registers[index] = cloned; } } @@ -1316,7 +1328,7 @@ void QQmlJSTypePropagator::propagateCall( if (!m_state.accumulatorOut().isValid()) setError(u"Cannot store return type of method %1()."_s.arg(match.methodName())); - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); const auto types = match.parameters(); for (int i = 0; i < argc; ++i) { if (i < types.size()) { @@ -1478,7 +1490,7 @@ void QQmlJSTypePropagator::generate_CallName(int name, int argc, int argv) void QQmlJSTypePropagator::generate_CallPossiblyDirectEval(int argc, int argv) { - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); Q_UNUSED(argc) Q_UNUSED(argv) INSTR_PROLOGUE_NOT_IMPLEMENTED(); @@ -1523,7 +1535,7 @@ void QQmlJSTypePropagator::generate_CallQmlContextPropertyLookup(int index, int void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int argc, int argv) { - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); Q_UNUSED(func) Q_UNUSED(thisObject) Q_UNUSED(argc) @@ -1533,7 +1545,7 @@ void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc, int argv) { - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); Q_UNUSED(func) Q_UNUSED(thisObject) Q_UNUSED(argc) @@ -1543,7 +1555,7 @@ void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc, void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv) { - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); Q_UNUSED(func) Q_UNUSED(argv) @@ -1554,7 +1566,7 @@ void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv) void QQmlJSTypePropagator::generate_ConstructWithSpread(int func, int argc, int argv) { - m_state.setHasSideEffects(true); + m_state.setHasExternalSideEffects(); Q_UNUSED(func) Q_UNUSED(argc) Q_UNUSED(argv) @@ -1563,20 +1575,20 @@ void QQmlJSTypePropagator::generate_ConstructWithSpread(int func, int argc, int void QQmlJSTypePropagator::generate_SetUnwindHandler(int offset) { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); Q_UNUSED(offset) INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE(); } void QQmlJSTypePropagator::generate_UnwindDispatch() { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE(); } void QQmlJSTypePropagator::generate_UnwindToLabel(int level, int offset) { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); Q_UNUSED(level) Q_UNUSED(offset) INSTR_PROLOGUE_NOT_IMPLEMENTED(); @@ -1591,7 +1603,7 @@ void QQmlJSTypePropagator::generate_DeadTemporalZoneCheck(int name) void QQmlJSTypePropagator::generate_ThrowException() { addReadAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType())); - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); m_state.skipInstructionsUntilNextJumpTarget = true; } @@ -1602,18 +1614,18 @@ void QQmlJSTypePropagator::generate_GetException() void QQmlJSTypePropagator::generate_SetException() { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_CreateCallContext() { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); } void QQmlJSTypePropagator::generate_PushCatchContext(int index, int name) { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); Q_UNUSED(index) Q_UNUSED(name) INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE(); @@ -1621,39 +1633,39 @@ void QQmlJSTypePropagator::generate_PushCatchContext(int index, int name) void QQmlJSTypePropagator::generate_PushWithContext() { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_PushBlockContext(int index) { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); Q_UNUSED(index) INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_CloneBlockContext() { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_PushScriptContext(int index) { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); Q_UNUSED(index) INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_PopScriptContext() { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_PopContext() { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); } void QQmlJSTypePropagator::generate_GetIterator(int iterator) @@ -1784,7 +1796,7 @@ void QQmlJSTypePropagator::generate_Jump(int offset) { saveRegisterStateForJump(offset); m_state.skipInstructionsUntilNextJumpTarget = true; - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); } void QQmlJSTypePropagator::generate_JumpTrue(int offset) @@ -1796,7 +1808,7 @@ void QQmlJSTypePropagator::generate_JumpTrue(int offset) return; } saveRegisterStateForJump(offset); - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType())); } @@ -1809,14 +1821,14 @@ void QQmlJSTypePropagator::generate_JumpFalse(int offset) return; } saveRegisterStateForJump(offset); - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType())); } void QQmlJSTypePropagator::generate_JumpNoException(int offset) { saveRegisterStateForJump(offset); - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); } void QQmlJSTypePropagator::generate_JumpNotUndefined(int offset) @@ -1827,7 +1839,7 @@ void QQmlJSTypePropagator::generate_JumpNotUndefined(int offset) void QQmlJSTypePropagator::generate_CheckException() { - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); } void QQmlJSTypePropagator::recordEqualsNullType() @@ -2199,7 +2211,7 @@ void QQmlJSTypePropagator::generate_InitializeBlockDeadTemporalZone(int firstReg { Q_UNUSED(firstReg) Q_UNUSED(count) - m_state.setHasSideEffects(true); + m_state.setHasInternalSideEffects(); // Ignore. We reject uninitialized values anyway. } @@ -2254,8 +2266,8 @@ QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type) registerIt != end; ++registerIt) { const int registerIndex = registerIt.key(); - auto newType = registerIt.value().content; - if (!newType.isValid()) { + const VirtualRegister &newType = registerIt.value(); + if (!newType.content.isValid()) { setError(u"When reached from offset %1, %2 is undefined"_s .arg(stateToMerge.originatingOffset) .arg(registerName(registerIndex))); @@ -2264,7 +2276,7 @@ QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type) auto currentRegister = m_state.registers.find(registerIndex); if (currentRegister != m_state.registers.end()) - mergeRegister(registerIndex, newType, currentRegister.value().content); + mergeRegister(registerIndex, newType, currentRegister.value()); else mergeRegister(registerIndex, newType, newType); } @@ -2279,7 +2291,8 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr) currentInstruction.changedRegister = m_state.changedRegister(); currentInstruction.changedRegisterIndex = m_state.changedRegisterIndex(); currentInstruction.readRegisters = m_state.takeReadRegisters(); - currentInstruction.hasSideEffects = m_state.hasSideEffects(); + currentInstruction.hasExternalSideEffects = m_state.hasExternalSideEffects(); + currentInstruction.hasInternalSideEffects = m_state.hasInternalSideEffects(); currentInstruction.isRename = m_state.isRename(); switch (instr) { @@ -2323,7 +2336,7 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr) && instr != QV4::Moth::Instr::Type::DeadTemporalZoneCheck) { // An instruction needs to have side effects or write to another register otherwise it's a // noop. DeadTemporalZoneCheck is not needed by the compiler and is ignored. - Q_ASSERT(m_state.hasSideEffects() || m_state.changedRegisterIndex() != -1); + Q_ASSERT(m_state.hasInternalSideEffects() || m_state.changedRegisterIndex() != -1); } if (m_state.changedRegisterIndex() != InvalidRegister) { @@ -2335,7 +2348,7 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr) m_state.clearChangedRegister(); } - m_state.setHasSideEffects(false); + m_state.resetSideEffects(); m_state.setIsRename(false); m_state.setReadRegisters(VirtualRegisters()); } diff --git a/src/qmlcompiler/qqmljstypepropagator_p.h b/src/qmlcompiler/qqmljstypepropagator_p.h index dd0c0ec750..99a421a4a1 100644 --- a/src/qmlcompiler/qqmljstypepropagator_p.h +++ b/src/qmlcompiler/qqmljstypepropagator_p.h @@ -227,7 +227,7 @@ private: void setAccumulator(const QQmlJSRegisterContent &content); void setRegister(int index, const QQmlJSRegisterContent &content); - void mergeRegister(int index, const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b); + void mergeRegister(int index, const VirtualRegister &a, const VirtualRegister &b); void addReadRegister(int index, const QQmlJSRegisterContent &convertTo); void addReadAccumulator(const QQmlJSRegisterContent &convertTo) diff --git a/src/qmlcompiler/qqmlsa.cpp b/src/qmlcompiler/qqmlsa.cpp index 7a209f52c1..388a5fe599 100644 --- a/src/qmlcompiler/qqmlsa.cpp +++ b/src/qmlcompiler/qqmlsa.cpp @@ -360,6 +360,13 @@ void DebugPropertyPass::onWrite(const QQmlSA::Element &element, const QString &p + QString::number(location.startColumn), qmlPlugin, location); } + +bool isRegularBindingType(QQmlJSMetaPropertyBinding::BindingType type) +{ + return type >= QQmlJSMetaPropertyBinding::BindingType::BoolLiteral + && type <= QQmlJSMetaPropertyBinding::BindingType::Object; +} + } QT_END_NAMESPACE diff --git a/src/qmlcompiler/qqmlsa_p.h b/src/qmlcompiler/qqmlsa_p.h index 6bbbe9441b..18caee3640 100644 --- a/src/qmlcompiler/qqmlsa_p.h +++ b/src/qmlcompiler/qqmlsa_p.h @@ -183,6 +183,9 @@ public: const QQmlSA::Element &value, const QQmlSA::Element &writeScope, QQmlJS::SourceLocation location) override; }; + +bool isRegularBindingType(QQmlJSMetaPropertyBinding::BindingType type); + } #define QmlLintPluginInterface_iid "org.qt-project.Qt.Qml.SA.LintPlugin/1.0" diff --git a/src/qmlmodels/qqmllistmodel.cpp b/src/qmlmodels/qqmllistmodel.cpp index c04be1cae7..67ba6339be 100644 --- a/src/qmlmodels/qqmllistmodel.cpp +++ b/src/qmlmodels/qqmllistmodel.cpp @@ -625,7 +625,9 @@ void ListModel::set(int elementIndex, QV4::Object *object, QVector<int> *roles) if (role.type == ListLayout::Role::QObject) roleIndex = e->setQObjectProperty(role, wrapper); } else if (QVariant maybeUrl = QV4::ExecutionEngine::toVariant( - o->asReturnedValue(), QMetaType::fromType<QUrl>(), true); + // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe + QV4::Value::fromReturnedValue(o->asReturnedValue()), + QMetaType::fromType<QUrl>(), true); maybeUrl.metaType() == QMetaType::fromType<QUrl>()) { const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url); QUrl qurl = maybeUrl.toUrl(); @@ -717,7 +719,9 @@ void ListModel::set(int elementIndex, QV4::Object *object, ListModel::SetElement e->setQObjectPropertyFast(r, wrapper); } else { QVariant maybeUrl = QV4::ExecutionEngine::toVariant( - o->asReturnedValue(), QMetaType::fromType<QUrl>(), true); + // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe + QV4::Value::fromReturnedValue(o->asReturnedValue()), + QMetaType::fromType<QUrl>(), true); if (maybeUrl.metaType() == QMetaType::fromType<QUrl>()) { const QUrl qurl = maybeUrl.toUrl(); const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url); @@ -1539,7 +1543,9 @@ int ListElement::setJsProperty(const ListLayout::Role &role, const QV4::Value &d roleIndex = setVariantMapProperty(role, o); } else if (role.type == ListLayout::Role::Url) { QVariant maybeUrl = QV4::ExecutionEngine::toVariant( - o.asReturnedValue(), QMetaType::fromType<QUrl>(), true); + // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe + QV4::Value::fromReturnedValue(o.asReturnedValue()), + QMetaType::fromType<QUrl>(), true); if (maybeUrl.metaType() == QMetaType::fromType<QUrl>()) { roleIndex = setUrlProperty(role, maybeUrl.toUrl()); } @@ -1746,9 +1752,11 @@ PropertyKey ModelObjectOwnPropertyKeyIterator::next(const Object *o, Property *p if (auto recursiveListModel = qvariant_cast<QQmlListModel*>(value)) { auto size = recursiveListModel->count(); auto array = ScopedArrayObject{scope, v4->newArrayObject(size)}; + QV4::ScopedValue val(scope); for (auto i = 0; i < size; i++) { - array->arrayPut(i, QJSValuePrivate::convertToReturnedValue( - v4, recursiveListModel->get(i))); + val = QJSValuePrivate::convertToReturnedValue( + v4, recursiveListModel->get(i)); + array->arrayPut(i, val); } pd->value = array; } else { @@ -2060,7 +2068,11 @@ QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agen m_agent = agent; m_dynamicRoles = orig->m_dynamicRoles; - m_layout = new ListLayout(orig->m_layout); + if (ListLayout *layout = orig->m_layout) + m_layout = new ListLayout(layout); + else + m_layout = new ListLayout; + m_listModel = new ListModel(m_layout, this); if (m_dynamicRoles) @@ -2080,12 +2092,13 @@ QQmlListModel::~QQmlListModel() m_listModel->destroy(); delete m_listModel; - if (m_mainThread && m_agent) { + if (m_mainThread && m_agent) m_agent->modelDestroyed(); - m_agent->release(); - } } + if (m_mainThread && m_agent) + m_agent->release(); + m_listModel = nullptr; delete m_layout; @@ -2355,7 +2368,7 @@ void QQmlListModel::setDynamicRoles(bool enableDynamicRoles) { if (m_mainThread && m_agent == nullptr) { if (enableDynamicRoles) { - if (m_layout->roleCount()) + if (m_layout && m_layout->roleCount()) qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty"); else m_dynamicRoles = true; @@ -2928,7 +2941,7 @@ bool QQmlListModelParser::applyProperty( if (v4->hasException) v4->catchException(); else - QJSValuePrivate::setValue(&v, result->asReturnedValue()); + QJSValuePrivate::setValue(&v, result); value.setValue(v); } else { bool ok; diff --git a/src/qmlworkerscript/qquickworkerscript.cpp b/src/qmlworkerscript/qquickworkerscript.cpp index 8df631c6d3..40e4617382 100644 --- a/src/qmlworkerscript/qquickworkerscript.cpp +++ b/src/qmlworkerscript/qquickworkerscript.cpp @@ -194,6 +194,8 @@ bool QQuickWorkerScriptEnginePrivate::event(QEvent *event) QV4::ExecutionEngine *QQuickWorkerScriptEnginePrivate::workerEngine(int id) { + QMutexLocker locker(&m_lock); + const auto it = workers.find(id); if (it == workers.end()) return nullptr; @@ -347,19 +349,16 @@ QQmlError WorkerErrorEvent::error() const QQuickWorkerScriptEngine::QQuickWorkerScriptEngine(QQmlEngine *parent) : QThread(parent), d(new QQuickWorkerScriptEnginePrivate(parent)) { - d->m_lock.lock(); connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection); + QMutexLocker locker(&d->m_lock); start(QThread::LowestPriority); d->m_wait.wait(&d->m_lock); d->moveToThread(this); - d->m_lock.unlock(); } QQuickWorkerScriptEngine::~QQuickWorkerScriptEngine() { - d->m_lock.lock(); QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QQuickWorkerScriptEnginePrivate::WorkerDestroyEvent)); - d->m_lock.unlock(); //We have to force to cleanup the main thread's event queue here //to make sure the main GUI release all pending locks/wait conditions which @@ -408,23 +407,26 @@ int QQuickWorkerScriptEngine::registerWorkerScript(QQuickWorkerScript *owner) { const int id = d->m_nextId++; - d->m_lock.lock(); + QMutexLocker locker(&d->m_lock); d->workers.insert(id, owner); - d->m_lock.unlock(); return id; } void QQuickWorkerScriptEngine::removeWorkerScript(int id) { - const auto it = d->workers.find(id); - if (it == d->workers.end()) - return; + { + QMutexLocker locker(&d->m_lock); + const auto it = d->workers.find(id); + if (it == d->workers.end()) + return; - if (it->isT1()) { - QV4::ExecutionEngine *engine = it->asT1(); - workerScriptExtension(engine)->owner = nullptr; + if (it->isT1()) + workerScriptExtension(it->asT1())->owner = nullptr; + else + *it = static_cast<QQuickWorkerScript *>(nullptr); } + QCoreApplication::postEvent(d, new WorkerRemoveEvent(id)); } @@ -440,12 +442,14 @@ void QQuickWorkerScriptEngine::sendMessage(int id, const QByteArray &data) void QQuickWorkerScriptEngine::run() { - d->m_lock.lock(); - d->m_wait.wakeAll(); - d->m_lock.unlock(); + { + QMutexLocker locker(&d->m_lock); + d->m_wait.wakeAll(); + } exec(); + QMutexLocker locker(&d->m_lock); for (auto it = d->workers.begin(), end = d->workers.end(); it != end; ++it) { if (it->isT1()) delete it->asT1(); diff --git a/src/quick/doc/src/guidelines/qtquick-tool-qmllint.qdoc b/src/quick/doc/src/guidelines/qtquick-tool-qmllint.qdoc index 1ce20de5f0..876723d66e 100644 --- a/src/quick/doc/src/guidelines/qtquick-tool-qmllint.qdoc +++ b/src/quick/doc/src/guidelines/qtquick-tool-qmllint.qdoc @@ -8,10 +8,10 @@ anti-patterns. \e qmllint is a tool shipped with Qt, that verifies the syntatic validity of -QML files. -It also warns about some QML anti-patterns. If you want to disable a specific -warning type, you can find the appropriate flag for doing so by passing -\c{--help} on the command line. +QML files. You can \l{Using qmllint with CMake}{integrate qmllint into your build system} +for ease of use. It also warns about some QML anti-patterns. If you want to +disable a specific warning type, you can find the appropriate flag for doing so +by passing \c{--help} on the command line. By default, some issues will result in warnings that will be printed and result in a non-zero exit code. @@ -49,6 +49,11 @@ qmllint can warn you about code that cannot be compiled by \l{qmlsc}. These warnigs are not enabled by default. In order to enable them specify \c{--compiler warning} or adjust your settings file accordingly. +\section2 Using qmllint with CMake +For projects using the \l{qt6_add_qml_module}{qt_add_qml_module()} CMake API for creating QML +modules, \l{Linting QML sources}{convenience targets} such as \c all_qmllint are created +automatically. These run qmllint on all the QML files of a specific module or of the project. + \section2 Marking components and properties as deprecated qmllint allows you to mark both properties and components as deprecated: diff --git a/src/quick/items/qquickaccessibleattached.cpp b/src/quick/items/qquickaccessibleattached.cpp index 865fb8bf11..b0c55b4653 100644 --- a/src/quick/items/qquickaccessibleattached.cpp +++ b/src/quick/items/qquickaccessibleattached.cpp @@ -430,6 +430,9 @@ void QQuickAccessibleAttached::setIgnored(bool ignored) { if (this->ignored() != ignored && item()) { item()->d_func()->isAccessible = !ignored; + QAccessibleEvent event(item(), + ignored ? QAccessible::ObjectDestroyed : QAccessible::ObjectCreated); + QAccessible::updateAccessibility(&event); emit ignoredChanged(); } } diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index ec339ccc3b..239b512b9b 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -6425,10 +6425,11 @@ void QQuickItem::setVisible(bool v) This property holds whether the item receives mouse and keyboard events. By default this is true. - Setting this property directly affects the \c enabled value of child - items. When set to \c false, the \c enabled values of all child items also - become \c false. When set to \c true, the \c enabled values of child items - are returned to \c true, unless they have explicitly been set to \c false. + Setting this property affects the \e effective \c enabled states of child items. + When an item's parent is not enabled, the (child) item is effectively disabled and + does not receive input events, even if its own \c enabled property is explicitly + set to \c true. The child \c enabled property keeps its value, but becomes + effective again only if all its ancestor items are enabled. Setting this property to \c false automatically causes \l activeFocus to be set to \c false, and this item will no longer receive keyboard events. diff --git a/src/quick/items/qquickitemview_p_p.h b/src/quick/items/qquickitemview_p_p.h index c1188ac4d7..684aa8f98e 100644 --- a/src/quick/items/qquickitemview_p_p.h +++ b/src/quick/items/qquickitemview_p_p.h @@ -42,7 +42,7 @@ public: }; -class Q_AUTOTEST_EXPORT QQuickItemViewChangeSet +class Q_QUICK_EXPORT QQuickItemViewChangeSet { public: QQuickItemViewChangeSet(); diff --git a/src/quick/items/qquickmultipointtoucharea.cpp b/src/quick/items/qquickmultipointtoucharea.cpp index b0b99334a2..33d801a2a3 100644 --- a/src/quick/items/qquickmultipointtoucharea.cpp +++ b/src/quick/items/qquickmultipointtoucharea.cpp @@ -660,8 +660,8 @@ void QQuickMultiPointTouchArea::updateTouchData(QEvent *event, RemapEventPoints for (const QEventPoint &p : std::as_const(touchPoints)) { if (p.state() == QEventPoint::State::Released) continue; - const QPointF ¤tPos = p.scenePosition(); - const QPointF &startPos = p.scenePressPosition(); + const QPointF currentPos = mapFromScene(p.scenePosition()); + const QPointF startPos = mapFromScene(p.scenePressPosition()); if (qAbs(currentPos.x() - startPos.x()) > dragThreshold) offerGrab = true; else if (qAbs(currentPos.y() - startPos.y()) > dragThreshold) diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 3cc63cece0..dbbdfec6cc 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -4866,8 +4866,8 @@ bool QQuickTableViewPrivate::canEdit(const QModelIndex tappedIndex, bool warn) return false; } - if (auto const qaim = model->abstractItemModel()) { - if (!(qaim->flags(tappedIndex) & Qt::ItemIsEditable)) { + if (auto const sourceModel = qaim(modelImpl())) { + if (!(sourceModel->flags(tappedIndex) & Qt::ItemIsEditable)) { if (warn) qmlWarning(q) << "cannot edit: QAbstractItemModel::flags(index) doesn't contain Qt::ItemIsEditable"; return false; diff --git a/src/quick/items/qquicktextcontrol.cpp b/src/quick/items/qquicktextcontrol.cpp index 510ece726a..c5b87a7c30 100644 --- a/src/quick/items/qquicktextcontrol.cpp +++ b/src/quick/items/qquicktextcontrol.cpp @@ -742,7 +742,7 @@ void QQuickTextControl::processEvent(QEvent *e, const QTransform &transform) case QEvent::ShortcutOverride: if (d->interactionFlags & Qt::TextEditable) { - QKeyEvent* ke = static_cast<QKeyEvent *>(e); + QKeyEvent *ke = static_cast<QKeyEvent *>(e); ke->setAccepted(isCommonTextEditShortcut(ke)); } break; diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index 4471d6e4b9..57c12d2825 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -1751,12 +1751,15 @@ QRectF QQuickTextEdit::cursorRectangle() const bool QQuickTextEdit::event(QEvent *event) { Q_D(QQuickTextEdit); - if (event->type() == QEvent::ShortcutOverride) { - d->control->processEvent(event, QPointF(-d->xoff, -d->yoff)); - if (event->isAccepted()) - return true; + bool state = QQuickImplicitSizeItem::event(event); + if (event->type() == QEvent::ShortcutOverride && !event->isAccepted()) { + QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(this); + if (!itemPriv->extra.isAllocated() || !itemPriv->extra->keyHandler) { + d->control->processEvent(event, QPointF(-d->xoff, -d->yoff)); + } + state = true; } - return QQuickImplicitSizeItem::event(event); + return state; } /*! diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index 08713eb026..495f99505f 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -49,6 +49,13 @@ Q_LOGGING_CATEGORY(lcQuickTextInput, "qt.quick.textInput") and setting \l echoMode to an appropriate value enables TextInput to be used for a password input field. + To react to the user accepting text via the Return or Enter keys, handle + the \l accepted() signal. When Return or Enter are pressed and the text + input loses focus, \l editingFinished() will be emitted. When the text is + edited in any way by the user, \l textEdited() is emitted. These signals + \l {Property change signals}{should be preferred} over \c textChanged() + in most cases. + On \macos, the Up/Down key bindings for Home/End are explicitly disabled. If you want such bindings (on any platform), you will need to construct them in QML. @@ -96,7 +103,8 @@ void QQuickTextInput::componentComplete() the \l preeditText property. To get whole text displayed in the TextInput use \l displayText property. - \sa clear(), displayText, preeditText + \sa clear(), displayText, preeditText, accepted(), editingFinished(), + textEdited() */ QString QQuickTextInput::text() const { @@ -1184,6 +1192,8 @@ bool QQuickTextInput::hasAcceptableInput() const Note that if there is a \l validator or \l inputMask set on the text input, the signal will only be emitted if the input is in an acceptable state. + + \sa editingFinished(), textEdited() */ /*! @@ -1195,6 +1205,8 @@ bool QQuickTextInput::hasAcceptableInput() const inputMask set on the text input and enter/return is pressed, this signal will only be emitted if the input follows the inputMask and the validator returns an acceptable state. + + \sa accepted(), textEdited() */ /*! @@ -1204,6 +1216,8 @@ bool QQuickTextInput::hasAcceptableInput() const This signal is emitted whenever the text is edited. Unlike \c textChanged(), this signal is not emitted when the text is changed programmatically, for example, by changing the value of the \c text property or by calling \c clear(). + + \sa accepted(), editingFinished() */ #if QT_CONFIG(im) diff --git a/src/quick/items/qquicktreeview.cpp b/src/quick/items/qquicktreeview.cpp index 88373a1706..17e21a75f4 100644 --- a/src/quick/items/qquicktreeview.cpp +++ b/src/quick/items/qquicktreeview.cpp @@ -650,8 +650,10 @@ void QQuickTreeView::keyPressEvent(QKeyEvent *event) break; } - if (!event->isAccepted()) + if (!event->isAccepted()) { + event->accept(); QQuickTableView::keyPressEvent(event); + } } QT_END_NAMESPACE diff --git a/src/quick/scenegraph/coreapi/qsgrendernode.cpp b/src/quick/scenegraph/coreapi/qsgrendernode.cpp index eee8316d05..cc229708f1 100644 --- a/src/quick/scenegraph/coreapi/qsgrendernode.cpp +++ b/src/quick/scenegraph/coreapi/qsgrendernode.cpp @@ -371,6 +371,17 @@ QSGRenderNode::RenderState::~RenderState() } /*! + \class QSGRenderNode::RenderState + \brief Provides information about the projection matrix and clipping. + \inmodule QtQuick + + The render state contains information for the renderer when invoking + commands to the scenegraph backends. + + \sa QSGRenderNode::render() + */ + +/*! \fn const QMatrix4x4 *QSGRenderNode::RenderState::projectionMatrix() const \return pointer to the current projection matrix. diff --git a/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp b/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp index 64f862f948..b0ab978908 100644 --- a/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp +++ b/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp @@ -9,7 +9,8 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcSgText, "qt.scenegraph.text") -qint64 QSGDistanceFieldGlyphNode::m_totalAllocation = 0; +// all SG glyph vertices and indices; only for qCDebug metrics +static std::atomic<qint64> s_totalAllocation = 0; QSGDistanceFieldGlyphNode::QSGDistanceFieldGlyphNode(QSGRenderContext *context) : m_glyphNodeType(RootGlyphNode) @@ -314,9 +315,9 @@ void QSGDistanceFieldGlyphNode::updateGeometry() } } - m_totalAllocation += vp.size() * sizeof(QSGGeometry::TexturedPoint2D) + ip.size() * sizeof(quint16); + s_totalAllocation += vp.size() * sizeof(QSGGeometry::TexturedPoint2D) + ip.size() * sizeof(quint16); qCDebug(lcSgText) << "allocating for" << vp.size() << "vtx (reserved" << likelyGlyphCount * 4 << "):" << vp.size() * sizeof(QSGGeometry::TexturedPoint2D) - << "bytes;" << ip.size() << "idx:" << ip.size() * sizeof(quint16) << "bytes; total bytes so far" << m_totalAllocation; + << "bytes;" << ip.size() << "idx:" << ip.size() * sizeof(quint16) << "bytes; total bytes so far" << s_totalAllocation; g->allocate(vp.size(), ip.size()); memcpy(g->vertexDataAsTexturedPoint2D(), vp.constData(), vp.size() * sizeof(QSGGeometry::TexturedPoint2D)); memcpy(g->indexDataAsUShort(), ip.constData(), ip.size() * sizeof(quint16)); diff --git a/src/quick/scenegraph/qsgdistancefieldglyphnode_p.h b/src/quick/scenegraph/qsgdistancefieldglyphnode_p.h index d92bf0a836..04b17f0ab7 100644 --- a/src/quick/scenegraph/qsgdistancefieldglyphnode_p.h +++ b/src/quick/scenegraph/qsgdistancefieldglyphnode_p.h @@ -84,8 +84,6 @@ private: uint m_dirtyGeometry: 1; uint m_dirtyMaterial: 1; - - static qint64 m_totalAllocation; // all SG glyph vertices and indices; only for qCDebug metrics }; QT_END_NAMESPACE diff --git a/src/quick/scenegraph/qsgrenderloop_p.h b/src/quick/scenegraph/qsgrenderloop_p.h index 73d30ffb6b..bbd169708a 100644 --- a/src/quick/scenegraph/qsgrenderloop_p.h +++ b/src/quick/scenegraph/qsgrenderloop_p.h @@ -105,6 +105,9 @@ WM_Obscure = QEvent::User + 1, // (updatePaintNode()) WM_RequestSync = QEvent::User + 2, +// Passed from the RL to the RT when a window is exposed +WM_Exposed = QEvent::User + 3, + // Passed by the RL to the RT to free up maybe release SG and GL contexts // if no windows are rendering. WM_TryRelease = QEvent::User + 4, diff --git a/src/quick/scenegraph/qsgrhishadereffectnode.cpp b/src/quick/scenegraph/qsgrhishadereffectnode.cpp index f1db5804e6..4c9facbb57 100644 --- a/src/quick/scenegraph/qsgrhishadereffectnode.cpp +++ b/src/quick/scenegraph/qsgrhishadereffectnode.cpp @@ -9,6 +9,9 @@ #include <private/qsgplaintexture_p.h> #include <QtGui/private/qshaderdescription_p.h> #include <QQmlFile> + +#include <QtGui/qquaternion.h> + #include <QFile> #include <QFileSelector> #include <QMutexLocker> diff --git a/src/quick/scenegraph/qsgrhisupport.cpp b/src/quick/scenegraph/qsgrhisupport.cpp index 57cd51f171..25b7b620e0 100644 --- a/src/quick/scenegraph/qsgrhisupport.cpp +++ b/src/quick/scenegraph/qsgrhisupport.cpp @@ -1334,7 +1334,11 @@ QImage QSGRhiSupport::grabOffscreen(QQuickWindow *window) // There was no rendercontrol which means a custom render target // should not be set either. Set our own, temporarily. - window->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(rt.data())); + auto renderTarget = QQuickRenderTarget::fromRhiRenderTarget(rt.data()); + // Pass on the scale factor. No need to use effectiveDevicePixelRatio(), + // because there is no rendercontrol anyway. + renderTarget.setDevicePixelRatio(window->devicePixelRatio()); + window->setRenderTarget(renderTarget); QRhiCommandBuffer *cb = nullptr; if (rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess) { diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index d2a160c420..fc92673d8e 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -335,6 +335,17 @@ bool QSGRenderThread::event(QEvent *e) return true; } + + case WM_Exposed: { + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_Exposed"); + + mutex.lock(); + window = static_cast<WMWindowEvent *>(e)->window; + waitCondition.wakeOne(); + mutex.unlock(); + + return true; } + case WM_RequestSync: { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_RequestSync"); WMSyncEvent *se = static_cast<WMSyncEvent *>(e); @@ -632,8 +643,14 @@ void QSGRenderThread::syncAndRender() // An update request could still be delivered right before we get an // unexpose. With Vulkan on Windows for example attempting to render // leads to failures at this stage since the surface size is already 0. - if (effectiveOutputSize.isEmpty()) + if (effectiveOutputSize.isEmpty()) { + if (syncRequested) { + mutex.lock(); + waitCondition.wakeOne(); + mutex.unlock(); + } return; + } const QSize previousOutputSize = cd->swapchain->currentPixelSize(); if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) { @@ -1249,10 +1266,6 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window) } } - // set this early as we'll be rendering shortly anyway and this avoids - // specialcasing exposure in polishAndSync. - w->thread->window = window; - #ifndef QT_NO_DEBUG if (w->window->width() <= 0 || w->window->height() <= 0 || (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) { @@ -1271,6 +1284,10 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window) if (!w->thread->isRunning()) { qCDebug(QSG_LOG_RENDERLOOP, "- starting render thread"); + // set this early as we'll be rendering shortly anyway and this avoids + // specialcasing exposure in polishAndSync. + w->thread->window = window; + if (!w->thread->rhi) { QSGRhiSupport *rhiSupport = QSGRhiSupport::instance(); if (!w->thread->offscreenSurface) @@ -1295,6 +1312,12 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window) } else { qCDebug(QSG_LOG_RENDERLOOP, "- render thread already running"); + + // set w->thread->window here too, but using an event so it's thread-safe + w->thread->mutex.lock(); + w->thread->postEvent(new WMWindowEvent(w->window, QEvent::Type(WM_Exposed))); + w->thread->waitCondition.wait(&w->thread->mutex); + w->thread->mutex.unlock(); } polishAndSync(w, true); diff --git a/src/quick/util/qquickapplication.cpp b/src/quick/util/qquickapplication.cpp index 2cb8fe36ba..f56533e355 100644 --- a/src/quick/util/qquickapplication.cpp +++ b/src/quick/util/qquickapplication.cpp @@ -69,7 +69,7 @@ QT_BEGIN_NAMESPACE \qml Timer { interval: 1000; repeat: true - active: Application.state === Qt.Qt.ApplicationActive + active: Application.state === Qt.ApplicationActive onTriggered: imageFetcher.fetchLatestImages() } \endqml diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index 6fc50aaa5c..9bd2efb279 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -899,7 +899,9 @@ void QQuickDeliveryAgentPrivate::deliverKeyEvent(QKeyEvent *e) e->isAutoRepeat(), e->count()); do { - e->accept(); + Q_ASSERT(e->type() != QEvent::ShortcutOverride || !e->isAccepted()); + if (e->type() != QEvent::ShortcutOverride) + e->accept(); QCoreApplication::sendEvent(item, e); } while (!e->isAccepted() && (item = item->parentItem())); } @@ -1055,6 +1057,7 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEvent( if (subtreeHoverEnabled) { hoveredLeafItemFound = false; + QQuickPointerHandlerPrivate::deviceDeliveryTargets(QPointingDevice::primaryPointingDevice()).clear(); deliverHoverEventRecursive(rootItem, scenePos, lastScenePos, modifiers, timestamp); } diff --git a/src/quick/util/qquickforeignutils.cpp b/src/quick/util/qquickforeignutils.cpp index 34b3225abe..6ca5a42ade 100644 --- a/src/quick/util/qquickforeignutils.cpp +++ b/src/quick/util/qquickforeignutils.cpp @@ -19,32 +19,58 @@ QT_BEGIN_NAMESPACE The following properties are available: \list - \li \l bool \c eventPoint.accepted: see also \l QEventPoint::accepted - \li \l PointerDevice \c eventPoint.device: see also \l QEventPoint::device - \li \l size \c eventPoint.ellipseDiameters: see also \l QEventPoint::ellipseDiameters - \li \l point \c eventPoint.globalGrabPosition: see also \l QEventPoint::globalGrabPosition - \li \l point \c eventPoint.globalLastPosition: see also \l QEventPoint::globalLastPosition - \li \l point \c eventPoint.globalPosition: see also \l QEventPoint::globalPosition - \li \l point \c eventPoint.globalPressPosition: see also \l QEventPoint::globalPressPosition - \li \l bool \c eventPoint.grabPosition: see also \l QEventPoint::grabPosition - \li \l int \c eventPoint.id: see also \l QEventPoint::id - \li \l point \c eventPoint.lastPosition: see also \l QEventPoint::lastPosition - \li \l ulong \c eventPoint.lastTimestamp: see also \l QEventPoint::lastTimestamp - \li \l point \c eventPoint.position: see also \l QEventPoint::position - \li \l point \c eventPoint.pressPosition: see also \l QEventPoint::pressPosition - \li \l int \c eventPoint.pressTimestamp: see also \l QEventPoint::pressTimestamp - \li \l real \c eventPoint.pressure: see also \l QEventPoint::pressure - \li \l real \c eventPoint.rotation: see also \l QEventPoint::rotation - \li \l point \c eventPoint.sceneGrabPosition: see also \l QEventPoint::sceneGrabPosition - \li \l point \c eventPoint.sceneLastPosition: see also \l QEventPoint::sceneLastPosition - \li \l point \c eventPoint.scenePosition: see also \l QEventPoint::scenePosition - \li \l ulong \c eventPoint.pressTimestamp: see also \l QEventPoint::pressTimestamp - \li \l point \c eventPoint.scenePressPosition: see also \l QEventPoint::scenePressPosition - \li \l {QML Enumerations}{enumeration} \c eventPoint.state: see also \l QEventPoint::state - \li \l real \c eventPoint.timeHeld: see also \l QEventPoint::timeHeld - \li \l ulong \c eventPoint.timestamp: see also \l QEventPoint::timestamp - \li \l pointingDeviceUniqueId \c eventPoint.uniqueId: see also \l QEventPoint::uniqueId - \li \l vector2d \c eventPoint.velocity: see also \l QEventPoint::velocity + \li \l bool \c eventPoint.accepted \br + See also \l {QEventPoint::accepted}. + \li \l PointerDevice \c eventPoint.device \br + See also \l {QEventPoint::device}. + \li \l size \c eventPoint.ellipseDiameters \br + See also \l {QEventPoint::ellipseDiameters}. + \li \l point \c eventPoint.globalGrabPosition \br + See also \l {QEventPoint::globalGrabPosition}. + \li \l point \c eventPoint.globalLastPosition \br + See also \l {QEventPoint::globalLastPosition}. + \li \l point \c eventPoint.globalPosition \br + See also \l {QEventPoint::globalPosition}. + \li \l point \c eventPoint.globalPressPosition \br + See also \l {QEventPoint::globalPressPosition}. + \li \l bool \c eventPoint.grabPosition \br + See also \l {QEventPoint::grabPosition}. + \li \l int \c eventPoint.id \br + See also \l {QEventPoint::id}. + \li \l point \c eventPoint.lastPosition \br + See also \l {QEventPoint::lastPosition}. + \li \l ulong \c eventPoint.lastTimestamp \br + See also \l {QEventPoint::lastTimestamp}. + \li \l point \c eventPoint.position \br + See also \l {QEventPoint::position}. + \li \l point \c eventPoint.pressPosition \br + See also \l {QEventPoint::pressPosition}. + \li \l int \c eventPoint.pressTimestamp \br + See also \l {QEventPoint::pressTimestamp}. + \li \l real \c eventPoint.pressure \br + See also \l {QEventPoint::pressure}. + \li \l real \c eventPoint.rotation \br + See also \l {QEventPoint::rotation}. + \li \l point \c eventPoint.sceneGrabPosition \br + See also \l {QEventPoint::sceneGrabPosition}. + \li \l point \c eventPoint.sceneLastPosition \br + See also \l {QEventPoint::sceneLastPosition}. + \li \l point \c eventPoint.scenePosition \br + See also \l {QEventPoint::scenePosition}. + \li \l ulong \c eventPoint.pressTimestamp \br + See also \l {QEventPoint::pressTimestamp}. + \li \l point \c eventPoint.scenePressPosition \br + See also \l {QEventPoint::scenePressPosition}. + \li \l {QML Enumerations}{enumeration} \c eventPoint.state \br + See also \l {QEventPoint::state}. + \li \l real \c eventPoint.timeHeld \br + See also \l {QEventPoint::timeHeld}. + \li \l ulong \c eventPoint.timestamp \br + See also \l {QEventPoint::timestamp}. + \li \l pointingDeviceUniqueId \c eventPoint.uniqueId \br + See also \l {QEventPoint::uniqueId}. + \li \l vector2d \c eventPoint.velocity \br + See also \l {QEventPoint::velocity}. \endlist diff --git a/src/quick/util/qquickpath.cpp b/src/quick/util/qquickpath.cpp index 17529317d5..7562b74d46 100644 --- a/src/quick/util/qquickpath.cpp +++ b/src/quick/util/qquickpath.cpp @@ -1982,8 +1982,6 @@ void QQuickPathArc::addToPath(QPainterPath &path, const QQuickPathData &data) { const QPointF &startPoint = path.currentPosition(); const QPointF &endPoint = positionForCurve(data, startPoint); - if (startPoint == endPoint) - return; QQuickSvgParser::pathArc(path, _radiusX, _radiusY, diff --git a/src/quick/util/qquicksvgparser.cpp b/src/quick/util/qquicksvgparser.cpp index 5f88805ef7..8cca5addbb 100644 --- a/src/quick/util/qquicksvgparser.cpp +++ b/src/quick/util/qquicksvgparser.cpp @@ -159,6 +159,10 @@ void QQuickSvgParser::pathArc(QPainterPath &path, qreal y, qreal curx, qreal cury) { + // Check if the start point is equal to the end point. + if (QPointF(curx, cury) == QPointF(x, y)) + return; + qreal sin_th, cos_th; qreal a00, a01, a10, a11; qreal x0, y0, x1, y1, xc, yc; @@ -169,6 +173,14 @@ void QQuickSvgParser::pathArc(QPainterPath &path, rx = qAbs(rx); ry = qAbs(ry); + // Avoid nans and division by zero. + if (qFuzzyIsNull(rx) || qFuzzyIsNull(ry)) { + // https://www.w3.org/TR/SVG/paths.html#ArcOutOfRangeParameters says: + // "If either rx or ry is 0, then this arc is treated as a straight line + // segment (a "lineto") joining the endpoints." + path.lineTo(x, y); + return; + } sin_th = qSin(qDegreesToRadians(x_axis_rotation)); cos_th = qCos(qDegreesToRadians(x_axis_rotation)); diff --git a/src/quick/util/qquicktransition.cpp b/src/quick/util/qquicktransition.cpp index 09d92ddb36..3c5e847702 100644 --- a/src/quick/util/qquicktransition.cpp +++ b/src/quick/util/qquicktransition.cpp @@ -107,14 +107,20 @@ protected: static qsizetype animation_count(QQmlListProperty<QQuickAbstractAnimation> *list); static QQuickAbstractAnimation* animation_at(QQmlListProperty<QQuickAbstractAnimation> *list, qsizetype pos); static void clear_animations(QQmlListProperty<QQuickAbstractAnimation> *list); - QList<QQuickAbstractAnimation *> animations; + static void removeLast_animation(QQmlListProperty<QQuickAbstractAnimation> *list); + static void replace_animation( + QQmlListProperty<QQuickAbstractAnimation> *list, qsizetype pos, + QQuickAbstractAnimation *a); + + QList<QPointer<QQuickAbstractAnimation>> animations; }; void QQuickTransitionPrivate::append_animation(QQmlListProperty<QQuickAbstractAnimation> *list, QQuickAbstractAnimation *a) { QQuickTransition *q = static_cast<QQuickTransition *>(list->object); q->d_func()->animations.append(a); - a->setDisableUserControl(); + if (a) + a->setDisableUserControl(); } qsizetype QQuickTransitionPrivate::animation_count(QQmlListProperty<QQuickAbstractAnimation> *list) @@ -132,10 +138,25 @@ QQuickAbstractAnimation* QQuickTransitionPrivate::animation_at(QQmlListProperty< void QQuickTransitionPrivate::clear_animations(QQmlListProperty<QQuickAbstractAnimation> *list) { QQuickTransition *q = static_cast<QQuickTransition *>(list->object); - while (q->d_func()->animations.size()) { - QQuickAbstractAnimation *firstAnim = q->d_func()->animations.at(0); - q->d_func()->animations.removeAll(firstAnim); - } + q->d_func()->animations.clear(); +} + +void QQuickTransitionPrivate::removeLast_animation(QQmlListProperty<QQuickAbstractAnimation> *list) +{ + QQuickTransition *q = static_cast<QQuickTransition *>(list->object); + q->d_func()->animations.removeLast(); +} + +void QQuickTransitionPrivate::replace_animation( + QQmlListProperty<QQuickAbstractAnimation> *list, qsizetype pos, QQuickAbstractAnimation *a) +{ + QQuickTransition *q = static_cast<QQuickTransition *>(list->object); + QQuickTransitionPrivate *d = q->d_func(); + if (d->animations.length() <= pos) + d->animations.resize(pos + 1, nullptr); + d->animations[pos] = a; + if (a) + a->setDisableUserControl(); } void QQuickTransitionInstance::animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State) @@ -240,15 +261,19 @@ QQuickTransitionInstance *QQuickTransition::prepare(QQuickStateOperation::Action int start = d->reversed ? d->animations.size() - 1 : 0; int end = d->reversed ? -1 : d->animations.size(); - QAbstractAnimationJob *anim = nullptr; - for (int i = start; i != end;) { - anim = d->animations.at(i)->transition(actions, after, direction, defaultTarget); - if (anim) { - if (d->animations.at(i)->threadingModel() == QQuickAbstractAnimation::RenderThread) - anim = new QQuickAnimatorProxyJob(anim, d->animations.at(i)); - d->reversed ? group->prependAnimation(anim) : group->appendAnimation(anim); - } - d->reversed ? --i : ++i; + for (int i = start; i != end; d->reversed ? --i : ++i) { + QQuickAbstractAnimation *anim = d->animations.at(i); + if (!anim) + continue; + + QAbstractAnimationJob *job = anim->transition(actions, after, direction, defaultTarget); + if (!job) + continue; + + if (anim->threadingModel() == QQuickAbstractAnimation::RenderThread) + job = new QQuickAnimatorProxyJob(job, anim); + + d->reversed ? group->prependAnimation(job) : group->appendAnimation(job); } group->setDirection(d->reversed ? QAbstractAnimationJob::Backward : QAbstractAnimationJob::Forward); @@ -434,10 +459,14 @@ bool QQuickTransition::running() const QQmlListProperty<QQuickAbstractAnimation> QQuickTransition::animations() { Q_D(QQuickTransition); - return QQmlListProperty<QQuickAbstractAnimation>(this, &d->animations, QQuickTransitionPrivate::append_animation, - QQuickTransitionPrivate::animation_count, - QQuickTransitionPrivate::animation_at, - QQuickTransitionPrivate::clear_animations); + return QQmlListProperty<QQuickAbstractAnimation>( + this, &d->animations, + QQuickTransitionPrivate::append_animation, + QQuickTransitionPrivate::animation_count, + QQuickTransitionPrivate::animation_at, + QQuickTransitionPrivate::clear_animations, + QQuickTransitionPrivate::replace_animation, + QQuickTransitionPrivate::removeLast_animation); } QT_END_NAMESPACE diff --git a/src/quickcontrols/doc/src/qtquickcontrols-universal.qdoc b/src/quickcontrols/doc/src/qtquickcontrols-universal.qdoc index 3268b5b11a..871acdf40d 100644 --- a/src/quickcontrols/doc/src/qtquickcontrols-universal.qdoc +++ b/src/quickcontrols/doc/src/qtquickcontrols-universal.qdoc @@ -208,6 +208,8 @@ \image qtquickcontrols-universal-foreground.png \endtable + \endstyleproperty + \styleproperty {Universal.theme} {enumeration} {universal-theme-attached-prop} \target universal-theme-attached-prop This attached property holds whether the theme is light or dark. The property diff --git a/src/quickcontrols/macos/qtquickcontrols2macosstyleplugin.cpp b/src/quickcontrols/macos/qtquickcontrols2macosstyleplugin.cpp index ec67ffa4dc..6b1c12fdab 100644 --- a/src/quickcontrols/macos/qtquickcontrols2macosstyleplugin.cpp +++ b/src/quickcontrols/macos/qtquickcontrols2macosstyleplugin.cpp @@ -4,6 +4,7 @@ #include <QtQml/qqml.h> #include <QtQuickControls2/private/qquickstyleplugin_p.h> #include <QtQuickControls2/qquickstyle.h> +#include <private/qquicktheme_p.h> QT_BEGIN_NAMESPACE @@ -34,8 +35,10 @@ QString QtQuickControls2MacOSStylePlugin::name() const return QStringLiteral("macOS"); } -void QtQuickControls2MacOSStylePlugin::initializeTheme(QQuickTheme */*theme*/) +void QtQuickControls2MacOSStylePlugin::initializeTheme(QQuickTheme *theme) { + Q_ASSERT(theme); + theme->setUsePlatformPalette(true); } QT_END_NAMESPACE diff --git a/src/quickcontrols/material/qt_attribution.json b/src/quickcontrols/material/qt_attribution.json index 9d53b8b014..38f7bac6e7 100644 --- a/src/quickcontrols/material/qt_attribution.json +++ b/src/quickcontrols/material/qt_attribution.json @@ -6,6 +6,7 @@ "Files": "impl/ElevationEffect.qml", "Description": "Shadow values for the elevation effect.", "Homepage": "https://angularjs.org/", + "Comment": "no relevant PURL or CPE set, because the dependency is a single file", "License": "MIT License", "LicenseId": "MIT", "LicenseFile": "LICENSE_ANGULARJS.txt", diff --git a/src/quickcontrols/qquickattachedpropertypropagator.cpp b/src/quickcontrols/qquickattachedpropertypropagator.cpp index 892d347809..57ea1fc782 100644 --- a/src/quickcontrols/qquickattachedpropertypropagator.cpp +++ b/src/quickcontrols/qquickattachedpropertypropagator.cpp @@ -98,7 +98,7 @@ static QQuickAttachedPropertyPropagator *findAttachedParent(const QMetaObject *o */ auto popupItem = qobject_cast<QQuickPopupItem *>(objectWeAreAttachedTo); if (popupItem) { - qCDebug(lcAttached).noquote() << "- attachee belongs to popup item" << popupItem << "- checking if it has an attached object"; + qCDebug(lcAttached).noquote() << "- attachee is a popup item" << popupItem << "- checking if it has an attached object"; auto popupItemPrivate = QQuickPopupItemPrivate::get(popupItem); QQuickAttachedPropertyPropagator *popupAttached = attachedObject(ourAttachedType, popupItemPrivate->popup); if (popupAttached) { @@ -108,7 +108,7 @@ static QQuickAttachedPropertyPropagator *findAttachedParent(const QMetaObject *o qCDebug(lcAttached).noquote() << "- popup item does not have attached object"; } } else { - qCDebug(lcAttached).noquote() << "- attachee does not belong to a popup"; + qCDebug(lcAttached).noquote() << "- attachee is not a popup item"; } QQuickItem *item = qobject_cast<QQuickItem *>(objectWeAreAttachedTo); diff --git a/src/quickcontrolsimpl/qquickiconlabel.cpp b/src/quickcontrolsimpl/qquickiconlabel.cpp index 7d1561795b..612d98aa26 100644 --- a/src/quickcontrolsimpl/qquickiconlabel.cpp +++ b/src/quickcontrolsimpl/qquickiconlabel.cpp @@ -600,6 +600,21 @@ void QQuickIconLabel::componentComplete() completeComponent(d->label); QQuickItem::componentComplete(); d->layout(); + + // Any items declared as children of us will be parented before the label and icon image, + // so they will be drawn below us. This is not intuitive, so move us to the bottom of the pile. + const auto paintOrderChildItems = QQuickItemPrivate::get(this)->paintOrderChildItems(); + const auto bottomMostFosterChildIt = std::find_if(paintOrderChildItems.constBegin(), + paintOrderChildItems.constEnd(), [d](QQuickItem *item) { + return item != d->label && item != d->image; + }); + if (bottomMostFosterChildIt != paintOrderChildItems.constEnd()) { + const QQuickItem *bottomMostFosterChild = *bottomMostFosterChildIt; + if (d->label) + d->label->stackBefore(bottomMostFosterChild); + if (d->image) + d->image->stackBefore(bottomMostFosterChild); + } } void QQuickIconLabel::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) diff --git a/src/quickcontrolstestutils/dialogstestutils.cpp b/src/quickcontrolstestutils/dialogstestutils.cpp index 4a795e42da..2732e6ac96 100644 --- a/src/quickcontrolstestutils/dialogstestutils.cpp +++ b/src/quickcontrolstestutils/dialogstestutils.cpp @@ -41,8 +41,8 @@ bool QQuickDialogTestUtils::verifyFileDialogDelegates(QQuickListView *fileDialog return false; } - // Need to call absoluteFilePath on Windows; see comment in dialogtestutil.h. - actualFiles.append(QFileInfo(delegate->file().toLocalFile()).absoluteFilePath()); + // Need to call canonicalFilePath on Windows; see comment in dialogstestutils_p.h. + actualFiles.append(QFileInfo(delegate->file().toLocalFile()).canonicalFilePath()); } if (actualFiles != expectedFiles) { diff --git a/src/quickcontrolstestutils/dialogstestutils_p.h b/src/quickcontrolstestutils/dialogstestutils_p.h index 963eb4d764..53665327e4 100644 --- a/src/quickcontrolstestutils/dialogstestutils_p.h +++ b/src/quickcontrolstestutils/dialogstestutils_p.h @@ -23,7 +23,7 @@ // We need these for Windows, because FolderListModel returns a lowercase drive letter; e.g.: // "file:///c:/blah.txt", whereas other API returns "file:///C:/blah.txt". #define COMPARE_URL(url1, url2) \ - QCOMPARE(QFileInfo(url1.toLocalFile()).absoluteFilePath(), QFileInfo(url2.toLocalFile()).absoluteFilePath()); + QCOMPARE(QFileInfo(url1.toLocalFile()).canonicalFilePath(), QFileInfo(url2.toLocalFile()).canonicalFilePath()); // Store a copy of the arguments in case { ... } list initializer syntax is used as an argument, // which could result in two different lists being created and passed to std::transform() @@ -33,11 +33,11 @@ const QList<QUrl> actualUrlsCopy = actualUrls; \ QList<QString> actualPaths; \ std::transform(actualUrlsCopy.begin(), actualUrlsCopy.end(), std::back_insert_iterator(actualPaths), \ - [](const QUrl &url) { return QFileInfo(url.toLocalFile()).absoluteFilePath(); }); \ + [](const QUrl &url) { return QFileInfo(url.toLocalFile()).canonicalFilePath(); }); \ const QList<QUrl> expectedUrlsCopy = expectedUrls; \ QList<QString> expectedPaths; \ std::transform(expectedUrlsCopy.begin(), expectedUrlsCopy.end(), std::back_insert_iterator(expectedPaths), \ - [](const QUrl &url) { return QFileInfo(url.toLocalFile()).absoluteFilePath(); }); \ + [](const QUrl &url) { return QFileInfo(url.toLocalFile()).canonicalFilePath(); }); \ QCOMPARE(actualPaths, expectedPaths); \ } diff --git a/src/quickdialogs/quickdialogsquickimpl/qquickabstractcolorpicker.cpp b/src/quickdialogs/quickdialogsquickimpl/qquickabstractcolorpicker.cpp index 5b5b2abdae..1ec04be2f7 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qquickabstractcolorpicker.cpp +++ b/src/quickdialogs/quickdialogsquickimpl/qquickabstractcolorpicker.cpp @@ -94,6 +94,13 @@ QQuickAbstractColorPicker::QQuickAbstractColorPicker(QQuickAbstractColorPickerPr setAcceptedMouseButtons(Qt::LeftButton); } +QQuickAbstractColorPicker::~QQuickAbstractColorPicker() +{ + Q_D(QQuickAbstractColorPicker); + if (d->m_handle) + d->removeImplicitSizeListener(d->m_handle); +} + QColor QQuickAbstractColorPicker::color() const { Q_D(const QQuickAbstractColorPicker); diff --git a/src/quickdialogs/quickdialogsquickimpl/qquickabstractcolorpicker_p.h b/src/quickdialogs/quickdialogsquickimpl/qquickabstractcolorpicker_p.h index b29e488b3a..b2117dac15 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qquickabstractcolorpicker_p.h +++ b/src/quickdialogs/quickdialogsquickimpl/qquickabstractcolorpicker_p.h @@ -43,6 +43,8 @@ class Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT QQuickAbstractColorPicker : public QML_UNCREATABLE("AbstractColorPicker is abstract.") public: + ~QQuickAbstractColorPicker() override; + QColor color() const; void setColor(const QColor &c); diff --git a/src/quickdialogs/quickdialogsquickimpl/qquickdialogimplfactory.cpp b/src/quickdialogs/quickdialogsquickimpl/qquickdialogimplfactory.cpp index 594b148528..4cd0fa73af 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qquickdialogimplfactory.cpp +++ b/src/quickdialogs/quickdialogsquickimpl/qquickdialogimplfactory.cpp @@ -29,25 +29,35 @@ std::unique_ptr<QPlatformDialogHelper> QQuickDialogImplFactory::createPlatformDi std::unique_ptr<QPlatformDialogHelper> dialogHelper; switch (type) { case QQuickDialogType::ColorDialog: { - dialogHelper.reset(new QQuickPlatformColorDialog(parent)); + auto *quickPlatformDialog = new QQuickPlatformColorDialog(parent); + if (quickPlatformDialog->isValid()) + dialogHelper.reset(quickPlatformDialog); break; } #if QT_CONFIG(quick_listview) case QQuickDialogType::FileDialog: { - dialogHelper.reset(new QQuickPlatformFileDialog(parent)); + auto *quickPlatformDialog = new QQuickPlatformFileDialog(parent); + if (quickPlatformDialog->isValid()) + dialogHelper.reset(quickPlatformDialog); break; } case QQuickDialogType::FolderDialog: { - dialogHelper.reset(new QQuickPlatformFolderDialog(parent)); + auto *quickPlatformDialog = new QQuickPlatformFolderDialog(parent); + if (quickPlatformDialog->isValid()) + dialogHelper.reset(quickPlatformDialog); break; } case QQuickDialogType::FontDialog: { - dialogHelper.reset(new QQuickPlatformFontDialog(parent)); + auto *quickPlatformDialog = new QQuickPlatformFontDialog(parent); + if (quickPlatformDialog->isValid()) + dialogHelper.reset(quickPlatformDialog); break; } #endif case QQuickDialogType::MessageDialog: { - dialogHelper.reset(new QQuickPlatformMessageDialog(parent)); + auto *quickPlatformDialog = new QQuickPlatformMessageDialog(parent); + if (quickPlatformDialog->isValid()) + dialogHelper.reset(quickPlatformDialog); break; } default: diff --git a/src/quicklayouts/qquicklinearlayout.cpp b/src/quicklayouts/qquicklinearlayout.cpp index 0aeda72143..a530917557 100644 --- a/src/quicklayouts/qquicklinearlayout.cpp +++ b/src/quicklayouts/qquicklinearlayout.cpp @@ -451,7 +451,8 @@ void QQuickGridLayoutBase::rearrange(const QSizeF &size) if (d->m_recurRearrangeCounter++ == 2) { // allow a recursive depth of two in order to respond to height-for-width // (e.g QQuickText changes implicitHeight when its width gets changed) - qWarning() << "Qt Quick Layouts: Detected recursive rearrange. Aborting after two iterations."; + qmlWarning(this) + << "Qt Quick Layouts: Detected recursive rearrange. Aborting after two iterations."; return; } diff --git a/src/quicktemplates/qquickabstractbutton.cpp b/src/quicktemplates/qquickabstractbutton.cpp index 61c13d749e..733fbf9bb2 100644 --- a/src/quicktemplates/qquickabstractbutton.cpp +++ b/src/quicktemplates/qquickabstractbutton.cpp @@ -15,6 +15,9 @@ #if QT_CONFIG(shortcut) # include <QtGui/private/qshortcutmap_p.h> #endif +#if QT_CONFIG(accessibility) +#include <QtGui/private/qaccessiblehelper_p.h> +#endif #include <QtGui/private/qguiapplication_p.h> #include <QtGui/qpa/qplatformtheme.h> #include <QtQuick/private/qquickevents_p_p.h> @@ -1193,7 +1196,9 @@ void QQuickAbstractButton::buttonChange(ButtonChange change) break; case ButtonTextChange: { const QString txt = text(); - maybeSetAccessibleName(txt); +#if QT_CONFIG(accessibility) + maybeSetAccessibleName(qt_accStripAmp(txt)); +#endif #if QT_CONFIG(shortcut) setShortcut(QKeySequence::mnemonic(txt)); #endif @@ -1232,7 +1237,7 @@ void QQuickAbstractButton::accessibilityActiveChanged(bool active) Q_D(QQuickAbstractButton); if (active) { - maybeSetAccessibleName(text()); + maybeSetAccessibleName(qt_accStripAmp(text())); setAccessibleProperty("pressed", d->pressed); setAccessibleProperty("checked", d->checked); setAccessibleProperty("checkable", d->checkable); diff --git a/src/quicktemplates/qquickcontainer.cpp b/src/quicktemplates/qquickcontainer.cpp index 9d48f3b3a1..499fd621d6 100644 --- a/src/quicktemplates/qquickcontainer.cpp +++ b/src/quicktemplates/qquickcontainer.cpp @@ -299,15 +299,37 @@ void QQuickContainerPrivate::reorderItems() if (!contentItem) return; - QList<QQuickItem *> siblings = effectiveContentItem(contentItem)->childItems(); - - int to = 0; - for (int i = 0; i < siblings.size(); ++i) { - QQuickItem* sibling = siblings.at(i); - if (QQuickItemPrivate::get(sibling)->isTransparentForPositioner()) - continue; - int index = contentModel->indexOf(sibling, nullptr); - q->moveItem(index, to++); + // The item view eventually reparents all the items of the content model + // from the container. At this stage (during component complete), however, due + // to optimisation strategies in the item views, this doesn't happen when the + // visible area of the item view is less than the total content width of the items + // within the content model. This can cause issues while reordering. Thus, it's + // better to skip reordering the item within the content model once it's known + // that it will be reparented to the item views. + bool allowReorder = true; + if (!qobject_cast<QQuickFlickable *>(contentItem)) { + for (int index = 0; index < contentModel->count(); index++) { + if (const auto *item = qobject_cast<QQuickItem *>(contentModel->get(index))) { + const auto *parentItem = item->parentItem(); + if (parentItem && !qobject_cast<QQuickItemView *>(parentItem->parentItem())) { + allowReorder = false; + break; + } + } + } + } + + if (allowReorder) { + QList<QQuickItem *> siblings = effectiveContentItem(contentItem)->childItems(); + int to = 0; + for (int i = 0; i < siblings.size(); ++i) { + QQuickItem* sibling = siblings.at(i); + if (QQuickItemPrivate::get(sibling)->isTransparentForPositioner()) + continue; + const int index = contentModel->indexOf(sibling, nullptr); + if (index >= 0) + q->moveItem(index, to++); + } } } diff --git a/src/quicktemplates/qquickmenu.cpp b/src/quicktemplates/qquickmenu.cpp index f53e9a9091..44a9a67cd3 100644 --- a/src/quicktemplates/qquickmenu.cpp +++ b/src/quicktemplates/qquickmenu.cpp @@ -26,6 +26,8 @@ #include <private/qqmlobjectmodel_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQuick/private/qquickitemview_p_p.h> +#include <QtQuick/private/qquicklistview_p.h> #include <QtQuick/private/qquickevents_p_p.h> #include <QtQuick/private/qquickwindow_p.h> @@ -437,6 +439,22 @@ bool QQuickMenuPrivate::prepareEnterTransition() // the right, it flips on the other side of the parent menu. allowHorizontalFlip = cascade && parentMenu; + // Enter transitions may want to animate the Menu's height based on its implicitHeight. + // The Menu's implicitHeight is typically based on the ListView's contentHeight, + // among other things. The docs for ListView's forceLayout function say: + // "Responding to changes in the model is usually batched to happen only once per frame." + // As e.g. NumberAnimation's from and to values are set before any polishes happen, + // any re-evaluation of their bindings happen too late, and the starting height can be + // out-dated when menu items are added after component completion + // (QQuickItemView::componentComplete does a layout, so items declared as children aren't + // affected by this). To account for this, we force a layout before the transition starts. + // We try to avoid unnecessary re-layouting if we can avoid it. + auto *contentItemAsListView = qobject_cast<QQuickListView *>(contentItem); + if (contentItemAsListView) { + if (QQuickItemViewPrivate::get(contentItemAsListView)->currentChanges.hasPendingChanges()) + contentItemAsListView->forceLayout(); + } + if (!QQuickPopupPrivate::prepareEnterTransition()) return false; diff --git a/src/quicktemplates/qquickmenubar.cpp b/src/quicktemplates/qquickmenubar.cpp index d8ecbb6626..5aa68556c2 100644 --- a/src/quicktemplates/qquickmenubar.cpp +++ b/src/quicktemplates/qquickmenubar.cpp @@ -247,7 +247,8 @@ QQuickMenu *QQuickMenuBarPrivate::menus_at(QQmlListProperty<QQuickMenu> *prop, q void QQuickMenuBarPrivate::menus_clear(QQmlListProperty<QQuickMenu> *prop) { QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object); - QQuickMenuBarPrivate::get(menuBar)->contentModel->clear(); + for (int count = menuBar->count(); count > 0; count = menuBar->count()) + menuBar->takeMenu(count - 1); } QPalette QQuickMenuBarPrivate::defaultPalette() const diff --git a/src/quicktemplates/qquickmonthgrid.cpp b/src/quicktemplates/qquickmonthgrid.cpp index 957c9b8f93..037f8cfaf7 100644 --- a/src/quicktemplates/qquickmonthgrid.cpp +++ b/src/quicktemplates/qquickmonthgrid.cpp @@ -45,7 +45,8 @@ QT_BEGIN_NAMESPACE \snippet qtquickcontrols-monthgrid-localization.qml 1 - \sa DayOfWeekRow, WeekNumberColumn, CalendarModel + \sa DayOfWeekRow, WeekNumberColumn, CalendarModel, + {Qt Quick Controls - Event Calendar} */ /*! diff --git a/src/quicktemplates/qquickoverlay.cpp b/src/quicktemplates/qquickoverlay.cpp index 091b2f8fb3..c9d3cf9892 100644 --- a/src/quicktemplates/qquickoverlay.cpp +++ b/src/quicktemplates/qquickoverlay.cpp @@ -97,32 +97,9 @@ bool QQuickOverlayPrivate::startDrag(QEvent *event, const QPointF &pos) return false; } -static QQuickItem *findRootOfOverlaySubtree(QQuickItem *source, const QQuickOverlay *overlay) -{ - QQuickItem *sourceAncestor = source; - while (sourceAncestor) { - QQuickItem *parentItem = sourceAncestor->parentItem(); - if (parentItem == overlay) - return sourceAncestor; - sourceAncestor = parentItem; - } - // Not an ancestor of the overlay. - return nullptr; -} - bool QQuickOverlayPrivate::handlePress(QQuickItem *source, QEvent *event, QQuickPopup *target) { - Q_Q(const QQuickOverlay); if (target) { - // childMouseEventFilter will cause this function to get called for each active popup. - // If any of those active popups block inputs, the delivery agent won't send the press event to source. - // A popup will block input, if it's modal, and the item isn't an ancestor of the popup's popup item. - // If source doesn't belong to a popup, but exists in an overlay subtree, it makes sense to not filter the event. - const QList<QQuickItem *> childItems = paintOrderChildItems(); - if (childItems.indexOf(findRootOfOverlaySubtree(source, q)) - > childItems.indexOf(QQuickPopupPrivate::get(target)->popupItem)) - return false; - if (target->overlayEvent(source, event)) { setMouseGrabberPopup(target); return true; @@ -166,17 +143,7 @@ bool QQuickOverlayPrivate::handleMove(QQuickItem *source, QEvent *event, QQuickP bool QQuickOverlayPrivate::handleRelease(QQuickItem *source, QEvent *event, QQuickPopup *target) { - Q_Q(const QQuickOverlay); if (target) { - // childMouseEventFilter will cause this function to get called for each active popup. - // If any of those active popups block inputs, the delivery agent won't send the press event to source. - // A popup will block input, if it's modal, and the item isn't an ancestor of the popup's popup item. - // If source doesn't belong to a popup, but exists in an overlay subtree, it makes sense to not filter the event. - const QList<QQuickItem *> childItems = paintOrderChildItems(); - if (childItems.indexOf(findRootOfOverlaySubtree(source, q)) - > childItems.indexOf(QQuickPopupPrivate::get(target)->popupItem)) - return false; - setMouseGrabberPopup(nullptr); if (target->overlayEvent(source, event)) { setMouseGrabberPopup(nullptr); diff --git a/src/quicktemplates/qquickpopup.cpp b/src/quicktemplates/qquickpopup.cpp index deb5651391..207b0a365a 100644 --- a/src/quicktemplates/qquickpopup.cpp +++ b/src/quicktemplates/qquickpopup.cpp @@ -2803,6 +2803,20 @@ void QQuickPopup::mouseUngrabEvent() d->handleUngrab(); } + +static QQuickItem *findRootOfOverlaySubtree(QQuickItem *source, const QQuickOverlay *overlay) +{ + QQuickItem *sourceAncestor = source; + while (sourceAncestor) { + QQuickItem *parentItem = sourceAncestor->parentItem(); + if (parentItem == overlay) + return sourceAncestor; + sourceAncestor = parentItem; + } + // Not an ancestor of the overlay. + return nullptr; +} + /*! \internal @@ -2818,6 +2832,17 @@ void QQuickPopup::mouseUngrabEvent() bool QQuickPopup::overlayEvent(QQuickItem *item, QEvent *event) { Q_D(QQuickPopup); + + // The overlay will normally call this function for each active popup, assuming there is no active mouse grabber. + // If \a item doesn't belong to any of these popups, but exists in an overlay subtree, we shouldn't filter the event, + // since the item is supposed to be independent of any active popups. + auto *overlay = QQuickOverlay::overlay(d->window); + const QList<QQuickItem *> paintOrderChildItems = QQuickOverlayPrivate::get(overlay)->paintOrderChildItems(); + const qsizetype targetItemPaintOrderIndex = paintOrderChildItems.indexOf(findRootOfOverlaySubtree(item, overlay)); + const qsizetype popupItemPaintOrderIndex = paintOrderChildItems.indexOf(d->popupItem); + if (targetItemPaintOrderIndex > popupItemPaintOrderIndex) + return false; + switch (event->type()) { case QEvent::KeyPress: case QEvent::KeyRelease: @@ -2851,7 +2876,7 @@ bool QQuickPopup::overlayEvent(QQuickItem *item, QEvent *event) void QQuickPopup::touchEvent(QTouchEvent *event) { Q_D(QQuickPopup); - d->handleTouchEvent(d->popupItem, event); + event->setAccepted(d->handleTouchEvent(d->popupItem, event)); } void QQuickPopup::touchUngrabEvent() diff --git a/src/quicktemplates/qquickshortcutcontext.cpp b/src/quicktemplates/qquickshortcutcontext.cpp index 2d07eb44f9..7f04583fe4 100644 --- a/src/quicktemplates/qquickshortcutcontext.cpp +++ b/src/quicktemplates/qquickshortcutcontext.cpp @@ -57,8 +57,12 @@ bool QQuickShortcutContext::matcher(QObject *obj, Qt::ShortcutContext context) // so that actions within it can grab shortcuts. if (auto *menu = qobject_cast<QQuickMenu *>(popup)) { auto parentMenu = QQuickMenuPrivate::get(menu)->parentMenu; - while (!obj && parentMenu) + while (parentMenu) { obj = parentMenu->window(); + if (obj) + break; + parentMenu = QQuickMenuPrivate::get(parentMenu)->parentMenu; + } } } break; diff --git a/src/quicktestutils/quick/visualtestutils_p.h b/src/quicktestutils/quick/visualtestutils_p.h index c57868ee68..22e909471e 100644 --- a/src/quicktestutils/quick/visualtestutils_p.h +++ b/src/quicktestutils/quick/visualtestutils_p.h @@ -39,8 +39,16 @@ namespace QQuickVisualTestUtils template<typename F> void forEachStep(int steps, F &&func) { + if (steps == 1) { + // that's odd usage, but cut to the chase then + func(qreal(1)); + return; + } + for (int i = 0; i < steps; ++i) { - const qreal progress = qreal(i) / steps; + // - 1 because that gives us {0, 0.5, 1} for progress (if steps == 3), + // rather than {0, 0.33, 0.66}. + const qreal progress = qreal(i) / (steps - 1); func(progress); } } |
