summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <[email protected]>2025-03-03 17:28:31 +0100
committerFabian Kosmale <[email protected]>2025-03-05 22:41:05 +0100
commitfd88f44c15e969da3228ab2b0a302e78e2ccc8de (patch)
tree5b92cee36f8aaaa18df17333b75dd0fd84e059ae
parent38e39902c6b2056d4600c2f8b425fde26cca179c (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.cpp23
-rw-r--r--tests/auto/tools/moc/faulty_qml_registration/CMakeLists.txt18
-rw-r--r--tests/auto/tools/moc/faulty_qml_registration/faulty_registration.cpp6
-rw-r--r--tests/auto/tools/moc/faulty_qml_registration/faulty_registration.h13
-rw-r--r--tests/auto/tools/moc/tst_moc.cpp18
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