aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2022-04-08 09:55:11 +0200
committerChristian Kandeler <christian.kandeler@qt.io>2022-04-13 09:58:21 +0000
commit759527ff7f5e1a379a1572079926f545f558bfb7 (patch)
treeaec7861e11e13f340b1e233b9934a48ffdf31ab3
parentc21fb22da85e1aa818b96187caaca0c9c3616767 (diff)
ProjectExplorer: Add output parser for AddressSanitizer messages
Change-Id: I9107a4f23998ed95f374c7d61c9ee1e6e6e2d437 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: hjk <hjk@qt.io>
-rw-r--r--src/libs/utils/outputformatter.cpp9
-rw-r--r--src/libs/utils/outputformatter.h1
-rw-r--r--src/plugins/projectexplorer/CMakeLists.txt1
-rw-r--r--src/plugins/projectexplorer/buildstep.cpp2
-rw-r--r--src/plugins/projectexplorer/outputparser_test.cpp2
-rw-r--r--src/plugins/projectexplorer/projectexplorer.cpp9
-rw-r--r--src/plugins/projectexplorer/projectexplorer.h1
-rw-r--r--src/plugins/projectexplorer/projectexplorer.qbs1
-rw-r--r--src/plugins/projectexplorer/projectexplorerconstants.h1
-rw-r--r--src/plugins/projectexplorer/sanitizerparser.cpp249
-rw-r--r--src/plugins/projectexplorer/sanitizerparser.h61
11 files changed, 337 insertions, 0 deletions
diff --git a/src/libs/utils/outputformatter.cpp b/src/libs/utils/outputformatter.cpp
index 8ae568204d..14a5a1a39f 100644
--- a/src/libs/utils/outputformatter.cpp
+++ b/src/libs/utils/outputformatter.cpp
@@ -185,6 +185,15 @@ void OutputLineParser::addLinkSpecForAbsoluteFilePath(OutputLineParser::LinkSpec
match.capturedLength(capName));
}
+bool Utils::OutputLineParser::fileExists(const FilePath &fp) const
+{
+#ifdef WITH_TESTS
+ if (d->skipFileExistsCheck)
+ return !fp.isEmpty();
+#endif
+ return fp.exists();
+}
+
QString OutputLineParser::rightTrimmed(const QString &in)
{
int pos = in.length();
diff --git a/src/libs/utils/outputformatter.h b/src/libs/utils/outputformatter.h
index f34b984ce8..d0e13156ba 100644
--- a/src/libs/utils/outputformatter.h
+++ b/src/libs/utils/outputformatter.h
@@ -115,6 +115,7 @@ protected:
static void addLinkSpecForAbsoluteFilePath(LinkSpecs &linkSpecs, const FilePath &filePath,
int lineNo, const QRegularExpressionMatch &match,
const QString &capName);
+ bool fileExists(const Utils::FilePath &fp) const;
signals:
void newSearchDirFound(const Utils::FilePath &dir);
diff --git a/src/plugins/projectexplorer/CMakeLists.txt b/src/plugins/projectexplorer/CMakeLists.txt
index 2ec3d6434e..966f49d1c1 100644
--- a/src/plugins/projectexplorer/CMakeLists.txt
+++ b/src/plugins/projectexplorer/CMakeLists.txt
@@ -154,6 +154,7 @@ add_qtc_plugin(ProjectExplorer
runconfigurationaspects.cpp runconfigurationaspects.h
runcontrol.cpp runcontrol.h
runsettingspropertiespage.cpp runsettingspropertiespage.h
+ sanitizerparser.cpp sanitizerparser.h
selectablefilesmodel.cpp selectablefilesmodel.h
session.cpp session.h
sessiondialog.cpp sessiondialog.h sessiondialog.ui
diff --git a/src/plugins/projectexplorer/buildstep.cpp b/src/plugins/projectexplorer/buildstep.cpp
index d66a3a4e80..ff3102c7ea 100644
--- a/src/plugins/projectexplorer/buildstep.cpp
+++ b/src/plugins/projectexplorer/buildstep.cpp
@@ -33,6 +33,7 @@
#include "project.h"
#include "projectexplorer.h"
#include "projectexplorerconstants.h"
+#include "sanitizerparser.h"
#include "target.h"
#include <utils/algorithm.h>
@@ -283,6 +284,7 @@ void BuildStep::setupOutputFormatter(OutputFormatter *formatter)
formatter->addLineParser(parser);
}
+ formatter->addLineParser(new Internal::SanitizerParser);
formatter->setForwardStdOutToStdError(buildConfiguration()->parseStdOut());
}
Utils::FileInProjectFinder fileFinder;
diff --git a/src/plugins/projectexplorer/outputparser_test.cpp b/src/plugins/projectexplorer/outputparser_test.cpp
index 8cbfb4108a..5cdf5c9924 100644
--- a/src/plugins/projectexplorer/outputparser_test.cpp
+++ b/src/plugins/projectexplorer/outputparser_test.cpp
@@ -60,6 +60,8 @@ void OutputParserTester::testParsing(const QString &lines,
const QString &childStdErrLines,
const QString &outputLines)
{
+ for (Utils::OutputLineParser * const parser : lineParsers())
+ parser->skipFileExistsCheck();
const auto terminator = new TestTerminator(this);
if (!lineParsers().isEmpty())
terminator->setRedirectionDetector(lineParsers().constLast());
diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp
index b3644d0b40..19e7caf0c1 100644
--- a/src/plugins/projectexplorer/projectexplorer.cpp
+++ b/src/plugins/projectexplorer/projectexplorer.cpp
@@ -87,6 +87,7 @@
#include "removetaskhandler.h"
#include "runconfigurationaspects.h"
#include "runsettingspropertiespage.h"
+#include "sanitizerparser.h"
#include "selectablefilesmodel.h"
#include "session.h"
#include "sessiondialog.h"
@@ -736,6 +737,7 @@ public:
cmakeRunConfigFactory.runConfigurationId()}
};
+ SanitizerOutputFormatterFactory sanitizerFormatterFactory;
};
static ProjectExplorerPlugin *m_instance = nullptr;
@@ -2246,6 +2248,8 @@ void ProjectExplorerPlugin::extensionsInitialized()
dd->m_projectFilterString = filterStrings.join(filterSeparator);
BuildManager::extensionsInitialized();
+ TaskHub::addCategory(Constants::TASK_CATEGORY_SANITIZER,
+ tr("Sanitizer", "Category for sanitizer issues listed under 'Issues'"));
QSsh::SshSettings::loadSettings(Core::ICore::settings());
const auto searchPathRetriever = [] {
@@ -2318,6 +2322,11 @@ ExtensionSystem::IPlugin::ShutdownFlag ProjectExplorerPlugin::aboutToShutdown()
return AsynchronousShutdown;
}
+QVector<QObject *> ProjectExplorerPlugin::createTestObjects() const
+{
+ return SanitizerParser::createTestObjects();
+}
+
void ProjectExplorerPlugin::showSessionManager()
{
dd->showSessionManager();
diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h
index 9333367d59..41f4ed3041 100644
--- a/src/plugins/projectexplorer/projectexplorer.h
+++ b/src/plugins/projectexplorer/projectexplorer.h
@@ -131,6 +131,7 @@ public:
void extensionsInitialized() override;
void restoreKits();
ShutdownFlag aboutToShutdown() override;
+ QVector<QObject *> createTestObjects() const override;
static void setProjectExplorerSettings(const Internal::ProjectExplorerSettings &pes);
static const Internal::ProjectExplorerSettings &projectExplorerSettings();
diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs
index a497d1e38c..564c12e3ac 100644
--- a/src/plugins/projectexplorer/projectexplorer.qbs
+++ b/src/plugins/projectexplorer/projectexplorer.qbs
@@ -132,6 +132,7 @@ Project {
"runcontrol.cpp", "runcontrol.h",
"runconfigurationaspects.cpp", "runconfigurationaspects.h",
"runsettingspropertiespage.cpp", "runsettingspropertiespage.h",
+ "sanitizerparser.cpp", "sanitizerparser.h",
"selectablefilesmodel.cpp", "selectablefilesmodel.h",
"session.cpp", "session.h",
"sessionmodel.cpp", "sessionmodel.h",
diff --git a/src/plugins/projectexplorer/projectexplorerconstants.h b/src/plugins/projectexplorer/projectexplorerconstants.h
index fcc2db0072..94cba51859 100644
--- a/src/plugins/projectexplorer/projectexplorerconstants.h
+++ b/src/plugins/projectexplorer/projectexplorerconstants.h
@@ -131,6 +131,7 @@ const char TASK_CATEGORY_COMPILE[] = "Task.Category.Compile";
const char TASK_CATEGORY_BUILDSYSTEM[] = "Task.Category.Buildsystem";
const char TASK_CATEGORY_DEPLOYMENT[] = "Task.Category.Deploy";
const char TASK_CATEGORY_AUTOTEST[] = "Task.Category.Autotest";
+const char TASK_CATEGORY_SANITIZER[] = "Task.Category.Analyzer";
// Wizard categories
const char QT_PROJECT_WIZARD_CATEGORY[] = "H.Project";
diff --git a/src/plugins/projectexplorer/sanitizerparser.cpp b/src/plugins/projectexplorer/sanitizerparser.cpp
new file mode 100644
index 0000000000..33975e6fd7
--- /dev/null
+++ b/src/plugins/projectexplorer/sanitizerparser.cpp
@@ -0,0 +1,249 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "sanitizerparser.h"
+
+#include "projectexplorerconstants.h"
+
+#include <QRegularExpression>
+
+#include <numeric>
+
+#ifdef WITH_TESTS
+#include <QTest>
+#include "outputparser_test.h"
+#endif
+
+using namespace Utils;
+
+namespace ProjectExplorer::Internal {
+
+OutputLineParser::Result SanitizerParser::handleLine(const QString &line, OutputFormat format)
+{
+ if (format != OutputFormat::StdErrFormat)
+ return Status::NotHandled;
+
+ // Non-regex shortcut for the common case.
+ if (m_id == 0 && !line.startsWith('='))
+ return Status::NotHandled;
+
+ static const QRegularExpression idPattern(R"(^==(?<id>\d+)==ERROR: (?<desc>.*)$)");
+ const QRegularExpressionMatch match = idPattern.match(line);
+ if (!match.hasMatch())
+ return m_id == 0 ? Status::NotHandled : handleContinuation(line);
+
+ QTC_ASSERT(m_id == 0, flush());
+ m_id = match.captured("id").toULongLong();
+ QTC_ASSERT(m_id != 0, return Status::NotHandled);
+ const QString description = match.captured("desc");
+ m_task = Task(Task::Error, description, {}, 0, Constants::TASK_CATEGORY_SANITIZER);
+ m_task.details << line;
+ return Status::InProgress;
+}
+
+OutputLineParser::Result SanitizerParser::handleContinuation(const QString &line)
+{
+ m_task.details << line;
+
+ if (line == QString("==%1==ABORTING").arg(m_id)) {
+ flush();
+ return Status::Done;
+ }
+
+ // Locations are either source files with line and sometimes column, or binaries with
+ // a hex offset.
+ static const QString filePathPattern = R"((?<file>(?:[A-Za-z]:)?[\/\\][^:]+))";
+ static const QString numberSuffixPatternTemplate = R"(:(?<%1>\d+))";
+ static const QString lineSuffixPattern = numberSuffixPatternTemplate.arg("line");
+ static const QString columnSuffixPattern = numberSuffixPatternTemplate.arg("column");
+ static const QString offsetSuffixPattern = R"(\+0x[[:xdigit:]]+)";
+ static const QString locationPatternString = QString(R"(%1(?:(?:%2(%3)?)|%4))")
+ .arg(filePathPattern, lineSuffixPattern, columnSuffixPattern, offsetSuffixPattern);
+ static const QRegularExpression filePattern(locationPatternString);
+
+ LinkSpecs linkSpecs;
+ const QString summaryPrefix = "SUMMARY: ";
+ if (line.startsWith(summaryPrefix)) {
+ static const QRegularExpression summaryPatternWithFile(QString(
+ R"(^%1(?<desc>.*?) at %2.*$)").arg(summaryPrefix, locationPatternString));
+ const QRegularExpressionMatch summaryMatch = summaryPatternWithFile.match(line);
+ if (summaryMatch.hasMatch()) {
+ m_task.summary = summaryMatch.captured("desc");
+ const FilePath file = absoluteFilePath(FilePath::fromUserInput(
+ summaryMatch.captured("file")));
+ if (fileExists(file)) {
+ m_task.file = file;
+ m_task.line = summaryMatch.captured("line").toInt();
+ m_task.column = summaryMatch.captured("column").toInt();
+ addLinkSpecForAbsoluteFilePath(linkSpecs, file, m_task.line, summaryMatch, "file");
+ addLinkSpecs(linkSpecs);
+ }
+ } else {
+ m_task.summary = line.mid(summaryPrefix.length());
+ }
+ flush();
+ return {Status::Done, linkSpecs};
+ }
+ const QRegularExpressionMatch fileMatch = filePattern.match(line);
+ if (fileMatch.hasMatch()) {
+ const FilePath file = absoluteFilePath(FilePath::fromUserInput(fileMatch.captured("file")));
+ if (fileExists(file)) {
+ addLinkSpecForAbsoluteFilePath(linkSpecs, file, fileMatch.captured("line").toInt(),
+ fileMatch, "file");
+ addLinkSpecs(linkSpecs);
+ }
+ }
+ return {Status::InProgress, linkSpecs};
+}
+
+void SanitizerParser::addLinkSpecs(const LinkSpecs &linkSpecs)
+{
+ LinkSpecs adaptedLinkSpecs = linkSpecs;
+ const int offset = std::accumulate(m_task.details.cbegin(), m_task.details.cend() - 1,
+ 0, [](int total, const QString &line) { return total + line.length() + 1;});
+ for (LinkSpec &ls : adaptedLinkSpecs)
+ ls.startPos += offset;
+ m_linkSpecs << adaptedLinkSpecs;
+}
+
+void SanitizerParser::flush()
+{
+ if (m_task.isNull())
+ return;
+
+ setDetailsFormat(m_task, m_linkSpecs);
+ scheduleTask(m_task, m_task.details.count());
+ m_task.clear();
+ m_linkSpecs.clear();
+ m_id = 0;
+}
+
+#ifdef WITH_TESTS
+class SanitizerParserTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testParser_data()
+ {
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<Tasks >("tasks");
+ QTest::addColumn<QString>("childStdErrLines");
+
+ const QString odrInput = R"(=================================================================
+==3792966==ERROR: AddressSanitizer: odr-violation (0x55f0cfaeddc0):
+ [1] size=16 'lre_id_continue_table_ascii' /sda/home/christian/dev/qbs/master/src/src/shared/quickjs/libregexp.c:193:16
+ [2] size=16 'lre_id_continue_table_ascii' /sda/home/christian/dev/qbs/master/src/src/shared/quickjs/libregexp.c:193:16
+These globals were registered at these points:
+ [1]:
+ #0 0x7fc07337a3d9 in __asan_register_globals /usr/src/debug/gcc/libsanitizer/asan/asan_globals.cpp:341
+ #1 0x55f0cfa986f2 in _sub_I_00099_1 (/sda/home/christian/dev/qbs/master/qtc_System_with_local_compiler_Debug/install-root/usr/local/bin/tst_language+0x2756f2)
+ #2 0x7fc07198943a in __libc_start_main@GLIBC_2.2.5 (/usr/lib/libc.so.6+0x2d43a)
+
+ [2]:
+ #0 0x7fc07337a3d9 in __asan_register_globals /usr/src/debug/gcc/libsanitizer/asan/asan_globals.cpp:341
+ #1 0x7fc072f456b7 in _sub_I_00099_1 (/sda/home/christian/dev/qbs/master/qtc_System_with_local_compiler_Debug/install-root/usr/local/bin/../lib/libqbscore.so.1.22+0xb926b7)
+ #2 0x7fc073d5cedd in call_init (/lib64/ld-linux-x86-64.so.2+0x5edd)
+
+==3792966==HINT: if you don't care about these errors you may set ASAN_OPTIONS=detect_odr_violation=0
+SUMMARY: AddressSanitizer: odr-violation: global 'lre_id_continue_table_ascii' at /sda/home/christian/dev/qbs/master/src/src/shared/quickjs/libregexp.c:193:16
+==3792966==ABORTING)";
+ const QStringList odrNonMatchedLines{
+ "=================================================================",
+ "==3792966==ABORTING"};
+ Task odrTask(Task::Error,
+ QString("AddressSanitizer: odr-violation: global 'lre_id_continue_table_ascii'")
+ + R"(
+==3792966==ERROR: AddressSanitizer: odr-violation (0x55f0cfaeddc0):
+ [1] size=16 'lre_id_continue_table_ascii' /sda/home/christian/dev/qbs/master/src/src/shared/quickjs/libregexp.c:193:16
+ [2] size=16 'lre_id_continue_table_ascii' /sda/home/christian/dev/qbs/master/src/src/shared/quickjs/libregexp.c:193:16
+These globals were registered at these points:
+ [1]:
+ #0 0x7fc07337a3d9 in __asan_register_globals /usr/src/debug/gcc/libsanitizer/asan/asan_globals.cpp:341
+ #1 0x55f0cfa986f2 in _sub_I_00099_1 (/sda/home/christian/dev/qbs/master/qtc_System_with_local_compiler_Debug/install-root/usr/local/bin/tst_language+0x2756f2)
+ #2 0x7fc07198943a in __libc_start_main@GLIBC_2.2.5 (/usr/lib/libc.so.6+0x2d43a)
+
+ [2]:
+ #0 0x7fc07337a3d9 in __asan_register_globals /usr/src/debug/gcc/libsanitizer/asan/asan_globals.cpp:341
+ #1 0x7fc072f456b7 in _sub_I_00099_1 (/sda/home/christian/dev/qbs/master/qtc_System_with_local_compiler_Debug/install-root/usr/local/bin/../lib/libqbscore.so.1.22+0xb926b7)
+ #2 0x7fc073d5cedd in call_init (/lib64/ld-linux-x86-64.so.2+0x5edd)
+
+==3792966==HINT: if you don't care about these errors you may set ASAN_OPTIONS=detect_odr_violation=0
+SUMMARY: AddressSanitizer: odr-violation: global 'lre_id_continue_table_ascii' at /sda/home/christian/dev/qbs/master/src/src/shared/quickjs/libregexp.c:193:16)",
+ FilePath::fromUserInput("/sda/home/christian/dev/qbs/master/src/src/shared/quickjs/libregexp.c"),
+ 193, Constants::TASK_CATEGORY_SANITIZER);
+ odrTask.column = 16;
+ QTest::newRow("odr violation")
+ << odrInput
+ << QList<Task>{odrTask}
+ << (odrNonMatchedLines.join('\n') + "\n");
+
+ const QString leakInput = R"(
+==61167==ERROR: LeakSanitizer: detected memory leaks
+
+Direct leak of 19 byte(s) in 1 object(s) allocated from:
+ #0 0x7eff1fd87667 in __interceptor_malloc (/lib64/libasan.so.6+0xb0667)
+ #1 0x741c95 in mutt_mem_malloc mutt/memory.c:95
+ #2 0x48f089 in get_hostname /home/mutt/work/neo/init.c:343
+ #3 0x49259e in mutt_init /home/mutt/work/neo/init.c:929
+ #4 0x4a4caa in main /home/mutt/work/neo/main.c:665
+ #5 0x7eff1ea1c041 in __libc_start_main ../csu/libc-start.c:308
+
+SUMMARY: AddressSanitizer: 19 byte(s) leaked in 1 allocation(s).)";
+ const QString leakNonMatchedLines = "\n";
+ const Task leakTask(Task::Error,
+ QString("AddressSanitizer: 19 byte(s) leaked in 1 allocation(s).") + leakInput,
+ {}, -1, Constants::TASK_CATEGORY_SANITIZER);
+ QTest::newRow("leak") << leakInput << QList<Task>{leakTask} << leakNonMatchedLines;
+ }
+
+ void testParser()
+ {
+ OutputParserTester testbench;
+ testbench.setLineParsers({new SanitizerParser});
+ QFETCH(QString, input);
+ QFETCH(Tasks, tasks);
+ QFETCH(QString, childStdErrLines);
+ testbench.testParsing(input, OutputParserTester::STDERR, tasks, {}, childStdErrLines, {});
+ }
+};
+#endif
+
+QVector<QObject *> SanitizerParser::createTestObjects()
+{
+#ifdef WITH_TESTS
+ return {new SanitizerParserTest};
+#endif
+ return {};
+}
+
+SanitizerOutputFormatterFactory::SanitizerOutputFormatterFactory()
+{
+ setFormatterCreator([](Target *) -> QList<OutputLineParser *> {return {new SanitizerParser}; });
+}
+
+} // namespace ProjectExplorer::Internal
+
+#include <sanitizerparser.moc>
diff --git a/src/plugins/projectexplorer/sanitizerparser.h b/src/plugins/projectexplorer/sanitizerparser.h
new file mode 100644
index 0000000000..1cb9674fcc
--- /dev/null
+++ b/src/plugins/projectexplorer/sanitizerparser.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "ioutputparser.h"
+#include "runcontrol.h"
+#include "task.h"
+
+#include <QObject>
+#include <QVector>
+
+namespace ProjectExplorer::Internal {
+
+class SanitizerParser : public OutputTaskParser
+{
+public:
+ static QVector<QObject *> createTestObjects();
+
+private:
+ Result handleLine(const QString &line, Utils::OutputFormat format) override;
+ void flush() override;
+
+ Result handleContinuation(const QString &line);
+ void addLinkSpecs(const LinkSpecs &linkSpecs);
+
+ Task m_task;
+ LinkSpecs m_linkSpecs;
+ quint64 m_id = 0;
+};
+
+class SanitizerOutputFormatterFactory : public ProjectExplorer::OutputFormatterFactory
+{
+public:
+ SanitizerOutputFormatterFactory();
+};
+
+} // namespace ProjectExplorer::Internal
+