ProjectExplorer: Handle files with no line number in GCC output parser

This necessitated changes in the LdParser, as it turned out that a lot of
linker messages were not actually caught there, but by accident in the
GccParser (mostly by a now-superfluous regex).
Note that the LdParser is still pretty awful; we just did the minimum
that was necessary to keep the tests passing.

Fixes: QTCREATORBUG-30806
Change-Id: I97ef08ca2bb8990841a95621f07368e879734856
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Christian Kandeler
2024-05-23 18:13:09 +02:00
parent 1c6ea674d7
commit f3110327ba
5 changed files with 102 additions and 71 deletions

View File

@@ -342,6 +342,7 @@ OutputLineParser::Result OutputFormatter::handleMessage(const QString &text, Out
= d->nextParser->handleLine(text, outputTypeForParser(d->nextParser, format)); = d->nextParser->handleLine(text, outputTypeForParser(d->nextParser, format));
switch (res.status) { switch (res.status) {
case OutputLineParser::Status::Done: case OutputLineParser::Status::Done:
d->nextParser->flush();
d->nextParser = nullptr; d->nextParser = nullptr;
return res; return res;
case OutputLineParser::Status::InProgress: case OutputLineParser::Status::InProgress:
@@ -359,6 +360,7 @@ OutputLineParser::Result OutputFormatter::handleMessage(const QString &text, Out
= parser->handleLine(text, outputTypeForParser(parser, format)); = parser->handleLine(text, outputTypeForParser(parser, format));
switch (res.status) { switch (res.status) {
case OutputLineParser::Status::Done: case OutputLineParser::Status::Done:
parser->flush();
involvedParsers << parser; involvedParsers << parser;
return res; return res;
case OutputLineParser::Status::InProgress: case OutputLineParser::Status::InProgress:

View File

@@ -51,8 +51,22 @@ public:
if (!match.hasMatch()) if (!match.hasMatch())
return {}; return {};
// Quick plausability test: If there's no slashes or dots, it's probably not a file.
const QString possibleFile = match.captured("file");
if (!possibleFile.contains('/') && !possibleFile.contains("'\\")
&& !possibleFile.contains('.')) {
return {};
}
// Defer to the LdParser for some file types
if (possibleFile.endsWith(".o") || possibleFile.endsWith(".a")
|| possibleFile.endsWith("dll") || possibleFile.contains(".so")
|| possibleFile.endsWith("ld") || possibleFile.endsWith("ranlib")) {
return {};
}
Data data; Data data;
data.rawFilePath = match.captured("file"); data.rawFilePath = possibleFile;
data.fileOffset = match.capturedStart("file"); data.fileOffset = match.capturedStart("file");
data.line = match.captured("line").toInt(); data.line = match.captured("line").toInt();
data.column = match.captured("column").toInt(); data.column = match.captured("column").toInt();
@@ -90,13 +104,13 @@ private:
const QString typePrefix = "(?:fatal |#)"; const QString typePrefix = "(?:fatal |#)";
const QString fullTypeString const QString fullTypeString
= QString::fromLatin1("(?<fullTypeString>%1?%2:?\\s)").arg(typePrefix, type); = QString::fromLatin1("(?<fullTypeString>%1?%2:?\\s)").arg(typePrefix, type);
const QString lineAndOptionalColumn = "(?:(?<line>\\d+)(?::(?<column>\\d+))?)"; const QString optionalLineAndColumn = "(?:(?:(?<line>\\d+)(?::(?<column>\\d+))?):)?";
const QString binaryLocation = "\\(.*\\)"; // E.g. "(.text+0x40)" const QString binaryLocation = "\\(.*\\)"; // E.g. "(.text+0x40)"
const QString fullLocation = QString::fromLatin1("%1(?:%2|%3)") const QString fullLocation = QString::fromLatin1("%1(?:%2|%3)")
.arg(filePattern(), lineAndOptionalColumn, binaryLocation); .arg(filePattern(), optionalLineAndColumn, binaryLocation);
const QString description = "(?<description>[^\\s].+)"; const QString description = "(?<description>[^\\s].+)";
return QString::fromLatin1("^%1:\\s+%2?%3$").arg(fullLocation, fullTypeString, description); return QString::fromLatin1("^%1\\s+%2?%3$").arg(fullLocation, fullTypeString, description);
} }
}; };
} // namespace } // namespace
@@ -105,18 +119,10 @@ GccParser::GccParser()
{ {
setObjectName(QLatin1String("GCCParser")); setObjectName(QLatin1String("GCCParser"));
m_regExpScope.setPattern(QLatin1Char('^') + filePattern()
+ "(?:(\\d+):)?(\\d+:)?\\s+((?:In .*(?:function|constructor) .*|At global scope|At top level):)$");
QTC_CHECK(m_regExpScope.isValid());
m_regExpIncluded.setPattern(QString::fromLatin1("\\bfrom\\s") + filePattern() m_regExpIncluded.setPattern(QString::fromLatin1("\\bfrom\\s") + filePattern()
+ QLatin1String("(\\d+)(:\\d+)?[,:]?$")); + QLatin1String("(\\d+)(:\\d+)?[,:]?$"));
QTC_CHECK(m_regExpIncluded.isValid()); QTC_CHECK(m_regExpIncluded.isValid());
m_regExpInlined.setPattern(QString::fromLatin1("\\binlined from\\s.* at ")
+ filePattern() + "(\\d+)(:\\d+)?[,:]?$");
QTC_CHECK(m_regExpInlined.isValid());
m_regExpCc1plus.setPattern(QLatin1Char('^') + "cc1plus.*(error|warning): ((?:" m_regExpCc1plus.setPattern(QLatin1Char('^') + "cc1plus.*(error|warning): ((?:"
+ filePattern() + " No such file or directory)?.*)"); + filePattern() + " No such file or directory)?.*)");
QTC_CHECK(m_regExpCc1plus.isValid()); QTC_CHECK(m_regExpCc1plus.isValid());
@@ -177,8 +183,13 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat
const QString lne = rightTrimmed(line); const QString lne = rightTrimmed(line);
// Blacklist some lines to not handle them: // Blacklist some lines to not handle them:
if (lne.startsWith(QLatin1String("TeamBuilder ")) || if (lne.startsWith(QLatin1String("TeamBuilder "))
lne.startsWith(QLatin1String("distcc["))) { || lne.startsWith(QLatin1String("distcc["))
|| lne.contains("undefined reference")
|| lne.contains("undefined symbol")
|| lne.contains("duplicate symbol")
|| lne.contains("multiple definition")
|| lne.contains("ar: creating")) {
return Status::NotHandled; return Status::NotHandled;
} }
@@ -205,10 +216,6 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat
qCDebug(gccParserLog) << "checking regex" << m_regExpIncluded.pattern(); qCDebug(gccParserLog) << "checking regex" << m_regExpIncluded.pattern();
match = m_regExpIncluded.match(lne); match = m_regExpIncluded.match(lne);
if (!match.hasMatch()) {
qCDebug(gccParserLog) << "checking regex" << m_regExpInlined.pattern();
match = m_regExpInlined.match(lne);
}
if (match.hasMatch()) { if (match.hasMatch()) {
const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured("file"))); const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured("file")));
const int lineNo = match.captured(2).toInt(); const int lineNo = match.captured(2).toInt();
@@ -229,7 +236,6 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat
if (!filePath.isEmpty()) if (!filePath.isEmpty())
addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, -1, match, 3); addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, -1, match, 3);
gccCreateOrAmendTask(type, match.captured(2), lne, false, filePath, -1, 0, linkSpecs); gccCreateOrAmendTask(type, match.captured(2), lne, false, filePath, -1, 0, linkSpecs);
flush();
return {Status::Done, linkSpecs}; return {Status::Done, linkSpecs};
} }
@@ -243,20 +249,6 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat
return {Status::InProgress, linkSpecs}; return {Status::InProgress, linkSpecs};
} }
qCDebug(gccParserLog) << "checking regex" << m_regExpScope.pattern();
match = m_regExpScope.match(lne);
if (match.hasMatch()) {
const int lineno = match.captured(2).toInt();
const int column = match.captured(3).toInt();
const QString description = match.captured(4);
const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured("file")));
LinkSpecs linkSpecs;
addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineno, match, "file");
gccCreateOrAmendTask(
Task::Unknown, description, lne, false, filePath, lineno, column, linkSpecs);
return {Status::InProgress, linkSpecs};
}
if ((lne.startsWith(' ') && !currentTask().isNull()) || isContinuation(lne)) { if ((lne.startsWith(' ') && !currentTask().isNull()) || isContinuation(lne)) {
gccCreateOrAmendTask(Task::Unknown, lne, lne, true); gccCreateOrAmendTask(Task::Unknown, lne, lne, true);
return Status::InProgress; return Status::InProgress;
@@ -439,8 +431,7 @@ void ProjectExplorerTest::testGccOutputParsers_data()
FilePath::fromUserInput("C:\\temp\\test\\untitled8/main.cpp"), FilePath::fromUserInput("C:\\temp\\test\\untitled8/main.cpp"),
8, 0, 8, 0,
formatRanges) formatRanges)
<< CompileTask(Task::Error, << CompileTask(Task::Error, "collect2: ld returned 1 exit status"))
"collect2: ld returned 1 exit status"))
<< QString(); << QString();
formatRanges.clear(); formatRanges.clear();
@@ -465,8 +456,7 @@ void ProjectExplorerTest::testGccOutputParsers_data()
FilePath::fromUserInput("C:\\temp\\test\\untitled8/main.cpp"), FilePath::fromUserInput("C:\\temp\\test\\untitled8/main.cpp"),
-1, 0, -1, 0,
formatRanges) formatRanges)
<< CompileTask(Task::Error, << CompileTask(Task::Error, "collect2: ld returned 1 exit status"))
"collect2: ld returned 1 exit status"))
<< QString(); << QString();
QTest::newRow("linker: dll format not recognized") QTest::newRow("linker: dll format not recognized")
@@ -989,7 +979,7 @@ void ProjectExplorerTest::testGccOutputParsers_data()
<< formatRange(31, 28) << formatRange(31, 28)
<< formatRange(59, 23, "olpfile:///home/user/test/foo.cpp::2::-1") << formatRange(59, 23, "olpfile:///home/user/test/foo.cpp::2::-1")
<< formatRange(82, 34)) << formatRange(82, 34))
<< CompileTask(Task::Unknown, << CompileTask(Task::Error,
"first defined here", "first defined here",
FilePath::fromUserInput("/home/user/test/bar.cpp"), FilePath::fromUserInput("/home/user/test/bar.cpp"),
4) 4)
@@ -1007,7 +997,7 @@ void ProjectExplorerTest::testGccOutputParsers_data()
<< CompileTask(Task::Error, << CompileTask(Task::Error,
"multiple definition of `foo'", "multiple definition of `foo'",
FilePath::fromUserInput("foo.o"), -1) FilePath::fromUserInput("foo.o"), -1)
<< CompileTask(Task::Unknown, << CompileTask(Task::Error,
"first defined here", "first defined here",
FilePath::fromUserInput("bar.o"), -1) FilePath::fromUserInput("bar.o"), -1)
<< CompileTask(Task::Error, << CompileTask(Task::Error,
@@ -1465,6 +1455,37 @@ void ProjectExplorerTest::testGccOutputParsers_data()
<< Tasks{CompileTask(Task::Error, "blubb", "/home/tim/path/to/sources/and/more.h", << Tasks{CompileTask(Task::Error, "blubb", "/home/tim/path/to/sources/and/more.h",
15, 22)} 15, 22)}
<< QString(); << QString();
QTest::newRow("no line number")
<< QString::fromUtf8("In file included from /data/dev/creator/src/libs/utils/aspects.cpp:12:\n"
"/data/dev/creator/src/libs/utils/layoutbuilder.h: In instantiation of Layouting::BuilderItem<X, XInterface>::I::I(const Inner&) [with Inner = Utils::BaseAspect; X = Layouting::Row; XInterface = Layouting::LayoutInterface]:\n"
"/data/dev/creator/src/libs/utils/aspects.cpp:3454:13: required from here\n"
"/data/dev/creator/src/libs/utils/layoutbuilder.h:79:51: error: use of deleted function Utils::BaseAspect::BaseAspect(const Utils::BaseAspect&)\n"
" 79 | apply = [p](XInterface *x) { doit_nest(x, p); };\n"
" | ~~~~~~~~^~~~~")
<< OutputParserTester::STDERR
<< QString() << QString()
<< (Tasks{compileTask(
Task::Error,
"use of deleted function Utils::BaseAspect::BaseAspect(const Utils::BaseAspect&)\n"
"In file included from /data/dev/creator/src/libs/utils/aspects.cpp:12:\n"
"/data/dev/creator/src/libs/utils/layoutbuilder.h: In instantiation of Layouting::BuilderItem<X, XInterface>::I::I(const Inner&) [with Inner = Utils::BaseAspect; X = Layouting::Row; XInterface = Layouting::LayoutInterface]:\n"
"/data/dev/creator/src/libs/utils/aspects.cpp:3454:13: required from here\n"
"/data/dev/creator/src/libs/utils/layoutbuilder.h:79:51: error: use of deleted function Utils::BaseAspect::BaseAspect(const Utils::BaseAspect&)\n"
" 79 | apply = [p](XInterface *x) { doit_nest(x, p); };\n"
" | ~~~~~~~~^~~~~",
FilePath::fromUserInput("/data/dev/creator/src/libs/utils/aspects.cpp"), 3454, 13,
QVector<QTextLayout::FormatRange>{
formatRange(82, 22),
formatRange(104, 44, "olpfile:///data/dev/creator/src/libs/utils/aspects.cpp::12::-1"),
formatRange(148, 5),
formatRange(153, 48, "olpfile:///data/dev/creator/src/libs/utils/layoutbuilder.h::0::-1"),
formatRange(201, 177),
formatRange(378, 44, "olpfile:///data/dev/creator/src/libs/utils/aspects.cpp::3454::-1"),
formatRange(422, 31),
formatRange(453, 48, "olpfile:///data/dev/creator/src/libs/utils/layoutbuilder.h::79::-1"),
formatRange(501, 228)})})
<< QString();
} }
void ProjectExplorerTest::testGccOutputParsers() void ProjectExplorerTest::testGccOutputParsers()

View File

@@ -38,9 +38,7 @@ private:
bool isContinuation(const QString &newLine) const override; bool isContinuation(const QString &newLine) const override;
QRegularExpression m_regExpScope;
QRegularExpression m_regExpIncluded; QRegularExpression m_regExpIncluded;
QRegularExpression m_regExpInlined;
QRegularExpression m_regExpGccNames; QRegularExpression m_regExpGccNames;
QRegularExpression m_regExpCc1plus; QRegularExpression m_regExpCc1plus;
}; };

View File

@@ -39,46 +39,49 @@ Utils::OutputLineParser::Result LdParser::handleLine(const QString &line, Utils:
return Status::NotHandled; return Status::NotHandled;
QString lne = rightTrimmed(line); QString lne = rightTrimmed(line);
if (!lne.isEmpty() && !lne.at(0).isSpace() && !currentTask().isNull()) {
flush();
return Status::NotHandled;
}
if (lne.startsWith(QLatin1String("TeamBuilder ")) if (lne.startsWith(QLatin1String("TeamBuilder "))
|| lne.startsWith(QLatin1String("distcc[")) || lne.startsWith(QLatin1String("distcc["))
|| lne.contains(QLatin1String("ar: creating "))) { || lne.contains(QLatin1String("ar: creating "))) {
return Status::NotHandled; return Status::NotHandled;
} }
const auto getStatus = [&lne] { return lne.endsWith(':') ? Status::InProgress : Status::Done; };
// ld on macOS // ld on macOS
if (lne.startsWith("Undefined symbols for architecture") && lne.endsWith(":")) { if (lne.startsWith("Undefined symbols for architecture") && getStatus() == Status::InProgress) {
createOrAmendTask(Task::Error, lne, line); createOrAmendTask(Task::Error, lne, line);
return Status::InProgress; return Status::InProgress;
} }
if (!currentTask().isNull() && lne.startsWith(" ")) {
if (!currentTask().isNull() && isContinuation(line)) {
static const QRegularExpression locRegExp(" (?<symbol>\\S+) in (?<file>\\S+)"); static const QRegularExpression locRegExp(" (?<symbol>\\S+) in (?<file>\\S+)");
const QRegularExpressionMatch match = locRegExp.match(lne); const QRegularExpressionMatch match = locRegExp.match(lne);
LinkSpecs linkSpecs; LinkSpecs linkSpecs;
Utils::FilePath filePath; Utils::FilePath filePath;
bool handle = false;
if (match.hasMatch()) { if (match.hasMatch()) {
handle = true;
filePath = absoluteFilePath(Utils::FilePath::fromString(match.captured("file"))); filePath = absoluteFilePath(Utils::FilePath::fromString(match.captured("file")));
addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, 0, match, "file"); addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, 0, match, "file");
currentTask().setFile(filePath); currentTask().setFile(filePath);
} else {
handle = !lne.isEmpty() && lne.at(0).isSpace();
} }
if (handle) {
createOrAmendTask(Task::Unknown, {}, line, true, filePath); createOrAmendTask(Task::Unknown, {}, line, true, filePath);
return {Status::InProgress, linkSpecs}; return {Status::InProgress, linkSpecs};
} }
}
if (lne.startsWith("collect2:") || lne.startsWith("collect2.exe:")) { if (lne.startsWith("collect2:") || lne.startsWith("collect2.exe:")) {
scheduleTask(CompileTask(Task::Error, lne /* description */), 1); createOrAmendTask(Task::Error, lne, line);
return Status::Done; return getStatus();
} }
QRegularExpressionMatch match = m_ranlib.match(lne); QRegularExpressionMatch match = m_ranlib.match(lne);
if (match.hasMatch()) { if (match.hasMatch()) {
QString description = match.captured(2); createOrAmendTask(Task::Warning, match.captured(2), line);
scheduleTask(CompileTask(Task::Warning, description), 1); return getStatus();
return Status::Done;
} }
match = m_regExpGccNames.match(lne); match = m_regExpGccNames.match(lne);
@@ -91,8 +94,8 @@ Utils::OutputLineParser::Result LdParser::handleLine(const QString &line, Utils:
} else if (description.startsWith(QLatin1String("fatal: "))) { } else if (description.startsWith(QLatin1String("fatal: "))) {
description = description.mid(7); description = description.mid(7);
} }
scheduleTask(CompileTask(type, description), 1); createOrAmendTask(type, description, line);
return Status::Done; return getStatus();
} }
match = m_regExpLinker.match(lne); match = m_regExpLinker.match(lne);
@@ -101,41 +104,47 @@ Utils::OutputLineParser::Result LdParser::handleLine(const QString &line, Utils:
int lineno = match.captured(7).toInt(&ok); int lineno = match.captured(7).toInt(&ok);
if (!ok) if (!ok)
lineno = -1; lineno = -1;
Utils::FilePath filename Utils::FilePath filePath
= absoluteFilePath(Utils::FilePath::fromUserInput(match.captured(1))); = absoluteFilePath(Utils::FilePath::fromUserInput(match.captured(1)));
int capIndex = 1; int capIndex = 1;
const QString sourceFileName = match.captured(4); const QString sourceFileName = match.captured(4);
if (!sourceFileName.isEmpty() if (!sourceFileName.isEmpty()
&& !sourceFileName.startsWith(QLatin1String("(.text")) && !sourceFileName.startsWith(QLatin1String("(.text"))
&& !sourceFileName.startsWith(QLatin1String("(.data"))) { && !sourceFileName.startsWith(QLatin1String("(.data"))) {
filename = absoluteFilePath(Utils::FilePath::fromUserInput(sourceFileName)); filePath = absoluteFilePath(Utils::FilePath::fromUserInput(sourceFileName));
capIndex = 4; capIndex = 4;
} }
QString description = match.captured(8).trimmed(); QString description = match.captured(8).trimmed();
Task::TaskType type = Task::Error;
if (description.startsWith(QLatin1String("first defined here")) ||
description.startsWith(QLatin1String("note:"), Qt::CaseInsensitive)) {
type = Task::Unknown;
} else if (description.startsWith(QLatin1String("warning: "), Qt::CaseInsensitive)) {
type = Task::Warning;
description = description.mid(9);
}
static const QStringList keywords{ static const QStringList keywords{
"File format not recognized", "File format not recognized",
"undefined reference", "undefined reference",
"multiple definition",
"first defined here", "first defined here",
"feupdateenv is not implemented and will always fail", // yes, this is quite special ... "feupdateenv is not implemented and will always fail", // yes, this is quite special ...
}; };
const auto descriptionContainsKeyword = [&description](const QString &keyword) { const auto descriptionContainsKeyword = [&description](const QString &keyword) {
return description.contains(keyword); return description.contains(keyword);
}; };
if (Utils::anyOf(keywords, descriptionContainsKeyword)) { Task::TaskType type = Task::Unknown;
const bool hasKeyword = Utils::anyOf(keywords, descriptionContainsKeyword);
if (description.startsWith(QLatin1String("warning: "), Qt::CaseInsensitive)) {
type = Task::Warning;
description = description.mid(9);
} else if (hasKeyword) {
type = Task::Error;
}
if (hasKeyword || filePath.fileName().endsWith(".o")) {
LinkSpecs linkSpecs; LinkSpecs linkSpecs;
addLinkSpecForAbsoluteFilePath(linkSpecs, filename, lineno, match, capIndex); addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineno, match, capIndex);
scheduleTask(CompileTask(type, description, filename, lineno), 1); createOrAmendTask(type, description, line, false, filePath, lineno, 0, linkSpecs);
return {Status::Done, linkSpecs}; return {getStatus(), linkSpecs};
} }
} }
return Status::NotHandled; return Status::NotHandled;
} }
bool LdParser::isContinuation(const QString &line) const
{
return currentTask().details.last().endsWith(':') || (!line.isEmpty() && line.at(0).isSpace());
}

View File

@@ -17,6 +17,7 @@ public:
LdParser(); LdParser();
private: private:
Result handleLine(const QString &line, Utils::OutputFormat type) override; Result handleLine(const QString &line, Utils::OutputFormat type) override;
bool isContinuation(const QString &line) const override;
QRegularExpression m_ranlib; QRegularExpression m_ranlib;
QRegularExpression m_regExpLinker; QRegularExpression m_regExpLinker;