diff options
author | Fabian Kosmale <[email protected]> | 2025-03-03 17:28:31 +0100 |
---|---|---|
committer | Fabian Kosmale <[email protected]> | 2025-03-05 22:41:05 +0100 |
commit | fd88f44c15e969da3228ab2b0a302e78e2ccc8de (patch) | |
tree | 5b92cee36f8aaaa18df17333b75dd0fd84e059ae | |
parent | 38e39902c6b2056d4600c2f8b425fde26cca179c (diff) |
Warn about QML registration macro without include
In some setups, e.g. when using precompiled headers, the compiler will
see all definitions necessary to compile a class containing a QML
registration macro. However, moc won't be able to resolve the macro to
its expanded text, and thus miss the information.
This leads to a runtime failure when the QML engine cannot find the
type, because it has not been registered (as qmltyperegistrar relies on
the information collected by moc).
To avoid this, teach moc about the registration macros, and warn
when encountering them. We do this by comparing identifiers inside
classes against the names of the QML registration macros.
We do not error out, as a user might use the names of the macros as
normal identifiers, especially in a non-QML project (as unlikely as this
might be).
Warning out when finding a registration macro might sound
counter-intuitive, but works because moc should not actually see the
macros at this stage: When the header is included, macro expansion will
replace the macro with its expansion; only when the header is missing
will we ever see the macro as an identifier.
Task-number: QTBUG-134148
Pick-to: 6.8 6.9
Change-Id: I2f213444993a9d782a027c2dc25ffcbe0824ddca
Reviewed-by: Ulf Hermann <[email protected]>
Reviewed-by: Sami Shalayel <[email protected]>
-rw-r--r-- | src/tools/moc/moc.cpp | 23 | ||||
-rw-r--r-- | tests/auto/tools/moc/faulty_qml_registration/CMakeLists.txt | 18 | ||||
-rw-r--r-- | tests/auto/tools/moc/faulty_qml_registration/faulty_registration.cpp | 6 | ||||
-rw-r--r-- | tests/auto/tools/moc/faulty_qml_registration/faulty_registration.h | 13 | ||||
-rw-r--r-- | tests/auto/tools/moc/tst_moc.cpp | 18 |
5 files changed, 78 insertions, 0 deletions
diff --git a/src/tools/moc/moc.cpp b/src/tools/moc/moc.cpp index 283a75dad20..27655dbe2aa 100644 --- a/src/tools/moc/moc.cpp +++ b/src/tools/moc/moc.cpp @@ -869,6 +869,7 @@ void Moc::parse() continue; ClassDef def; if (parseClassHead(&def)) { + Symbol qmlRegistrationMacroSymbol = {}; prependNamespaces(def, namespaceList); FunctionDef::Access access = FunctionDef::Private; @@ -984,6 +985,17 @@ void Moc::parse() case SEMIC: case COLON: break; + case IDENTIFIER: + { + const QByteArray lex = lexem(); + if (lex.startsWith("QML_")) { + if ( lex == "QML_ELEMENT" || lex == "QML_NAMED_ELEMENT" + || lex == "QML_ANONYMOUS" || lex == "QML_VALUE_TYPE") { + qmlRegistrationMacroSymbol = symbol(); + } + } + } + Q_FALLTHROUGH(); default: FunctionDef funcDef; funcDef.access = access; @@ -1024,6 +1036,17 @@ void Moc::parse() next(RBRACE); + /* if the header is available, moc will see a Q_CLASSINFO entry; the + token is only visible if the header is missing + To avoid false positives, we only warn when encountering the token in a QObject or gadget + */ + if ((def.hasQObject || def.hasQGadget) && qmlRegistrationMacroSymbol.token != NOTOKEN) { + QByteArray msg("Potential QML registration macro was found, but no header containing it was included.\n" + "This might cause runtime errors in QML applications\n" + "Include <QtQmlIntegration/qqmlintegration.h> or <QtQml/qqmlregistration.h> to fix this."); + warning(qmlRegistrationMacroSymbol, msg.constData()); + } + if (!def.hasQObject && !def.hasQGadget && def.signalList.isEmpty() && def.slotList.isEmpty() && def.propertyList.isEmpty() && def.enumDeclarations.isEmpty()) continue; // no meta object code required diff --git a/tests/auto/tools/moc/faulty_qml_registration/CMakeLists.txt b/tests/auto/tools/moc/faulty_qml_registration/CMakeLists.txt new file mode 100644 index 00000000000..c0a60687ee4 --- /dev/null +++ b/tests/auto/tools/moc/faulty_qml_registration/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# This folder is intentionally not added in the parent directory +# The project structure here is only there to support running it as +# a manual test +cmake_minimum_required(VERSION 3.16) + +project(test_add_resource_options) + +find_package(Qt6Core REQUIRED) + +qt_wrap_cpp(faulty_registration.h) + + +qt6_add_executable(faulty_registration faulty_registration.h faulty_registration.cpp) +target_link_libraries(faulty_registration PRIVATE Qt::Core) + diff --git a/tests/auto/tools/moc/faulty_qml_registration/faulty_registration.cpp b/tests/auto/tools/moc/faulty_qml_registration/faulty_registration.cpp new file mode 100644 index 00000000000..d42f20871dd --- /dev/null +++ b/tests/auto/tools/moc/faulty_qml_registration/faulty_registration.cpp @@ -0,0 +1,6 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "faulty_registration.h" + +int main() {} diff --git a/tests/auto/tools/moc/faulty_qml_registration/faulty_registration.h b/tests/auto/tools/moc/faulty_qml_registration/faulty_registration.h new file mode 100644 index 00000000000..abefcd7d4f5 --- /dev/null +++ b/tests/auto/tools/moc/faulty_qml_registration/faulty_registration.h @@ -0,0 +1,13 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#ifndef FAULTY_REGISTRATION +#define FAULTY_REGISTRATION + +#include <QObject> + +struct Faulty : QObject { + Q_OBJECT + QML_ELEMENT +}; + +#endif diff --git a/tests/auto/tools/moc/tst_moc.cpp b/tests/auto/tools/moc/tst_moc.cpp index 6ba72d2fbe0..09f7a9c75de 100644 --- a/tests/auto/tools/moc/tst_moc.cpp +++ b/tests/auto/tools/moc/tst_moc.cpp @@ -786,6 +786,7 @@ private slots: void dontStripNamespaces(); void oldStyleCasts(); + void faultyQmlRegistration(); void warnOnExtraSignalSlotQualifiaction(); void uLongLong(); void inputFileNameWithDotsButNoExtension(); @@ -1020,6 +1021,23 @@ void tst_Moc::oldStyleCasts() #endif } +void tst_Moc::faultyQmlRegistration() +{ +#ifdef MOC_CROSS_COMPILED + QSKIP("Not tested when cross-compiled"); +#endif +#if QT_CONFIG(process) + QProcess proc; + proc.start(m_moc, QStringList(m_sourceDirectory + QStringLiteral("/faulty_qml_registration/faulty_registration.h"))); + QVERIFY(proc.waitForFinished()); + QCOMPARE(proc.exitCode(), 0); + QByteArray errorMsg = proc.readAllStandardError(); + QVERIFY2(errorMsg.contains("QML registration macro"), errorMsg.constData()); +#else + QSKIP("Requires QProcess"); +#endif +} + void tst_Moc::warnOnExtraSignalSlotQualifiaction() { #ifdef MOC_CROSS_COMPILED |