qmlcachegen: fix crash on unresolved type with required property

qmlcachegen can't resolve all types when importing QtQuick.Controls, so
scopes from QtQuick.Controls might be unresolved.
Check the scope before creating a fix suggesion when checking the
required properties, and add a test that tests a file with required
properties on an unresolved base type "Tumbler".

This also fixes the crashes from QTBUG-137196 and QTBUG-136998 it seems.

Pick-to: 6.10 6.9 6.8 6.5
Fixes: QTBUG-137411
Fixes: QTBUG-137196
Fixes: QTBUG-136998
Change-Id: Ibf461b54abf84ba13bff8c4833940c7359cf2d8e
Reviewed-by: Olivier De Cannière <olivier.decanniere@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Sami Shalayel 2025-06-05 10:51:46 +02:00
parent f2e8a9b49f
commit c9713681e8
3 changed files with 90 additions and 10 deletions

View File

@ -1055,16 +1055,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);
}

View File

@ -0,0 +1,25 @@
import QtQuick
Item {
id: root
Item {
id: inner
Tumbler {
id: year
delegate: Rectangle {
required property var modelData
}
}
Tumbler {
id: month
delegate: Rectangle {
required property var modelData
}
}
}
}

View File

@ -74,6 +74,9 @@ private slots:
void aotstatsGeneration_data();
void aotstatsGeneration();
void aotstatsFormatRevisionMissmatch();
void crash_data();
void crash();
};
// A wrapper around QQmlComponent to ensure the temporary reference counts
@ -122,6 +125,36 @@ static bool generateCache(const QString &qmlFileName, QByteArray *capturedStderr
return proc.exitCode() == 0;
}
static bool generateCpp(const QString &qmlFileName, QByteArray *capturedStderr = nullptr)
{
#if defined(QTEST_CROSS_COMPILED)
QTest::qFail("You cannot call qmlcachegen on the target.", __FILE__, __LINE__);
return false;
#endif
QProcess proc;
if (capturedStderr == nullptr)
proc.setProcessChannelMode(QProcess::ForwardedChannels);
proc.setProgram(QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath)
+ QLatin1String("/qmlcachegen"));
QTemporaryDir outputDir;
const QString outputFile = outputDir.filePath("output.cpp"_L1);
proc.setArguments(QStringList{ "--resource-path"_L1, "qrc:/qt/qml/Crashes/testFile.qml"_L1,
"-o"_L1, outputFile, qmlFileName });
proc.start();
if (!proc.waitForFinished())
return false;
if (capturedStderr)
*capturedStderr = proc.readAllStandardError();
if (!QFile::exists(outputFile))
return false;
if (proc.exitStatus() != QProcess::NormalExit)
return false;
return proc.exitCode() == 0;
}
tst_qmlcachegen::tst_qmlcachegen()
: QQmlDataTest(QT_QMLTEST_DATADIR)
{
@ -1049,6 +1082,27 @@ void tst_qmlcachegen::aotstatsFormatRevisionMissmatch()
QVERIFY(!QQmlJS::AotStats::fromJsonDocument(document).has_value());
}
void tst_qmlcachegen::crash_data()
{
QTest::addColumn<QString>("fileName");
QTest::addRow("buggyFixSuggestion") << u"buggyFixSuggestion.qml"_s;
}
void tst_qmlcachegen::crash()
{
#if defined(QTEST_CROSS_COMPILED)
QSKIP("Cannot call qmlcachegen on cross-compiled target.");
#endif
QFETCH(QString, fileName);
const QString filePath = testFile("crashes/" + fileName);
QFile file(filePath);
QVERIFY(file.exists());
QVERIFY(generateCpp(filePath));
}
const QQmlScriptString &ScriptStringProps::undef() const
{
return m_undef;