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
Change-Id: I2f213444993a9d782a027c2dc25ffcbe0824ddca
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
(cherry picked from commit fd88f44c15e969da3228ab2b0a302e78e2ccc8de)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Fabian Kosmale 2025-03-03 17:28:31 +01:00 committed by Qt Cherry-pick Bot
parent bf37860a71
commit 0f4cd43bc2
5 changed files with 78 additions and 0 deletions

View File

@ -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

View File

@ -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)

View File

@ -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() {}

View File

@ -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

View File

@ -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