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:
parent
bf37860a71
commit
0f4cd43bc2
@ -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
|
||||
|
18
tests/auto/tools/moc/faulty_qml_registration/CMakeLists.txt
Normal file
18
tests/auto/tools/moc/faulty_qml_registration/CMakeLists.txt
Normal 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)
|
||||
|
@ -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() {}
|
@ -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
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user