diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2022-04-08 09:55:11 +0200 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2022-04-13 09:58:21 +0000 |
commit | 759527ff7f5e1a379a1572079926f545f558bfb7 (patch) | |
tree | aec7861e11e13f340b1e233b9934a48ffdf31ab3 | |
parent | c21fb22da85e1aa818b96187caaca0c9c3616767 (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.cpp | 9 | ||||
-rw-r--r-- | src/libs/utils/outputformatter.h | 1 | ||||
-rw-r--r-- | src/plugins/projectexplorer/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/plugins/projectexplorer/buildstep.cpp | 2 | ||||
-rw-r--r-- | src/plugins/projectexplorer/outputparser_test.cpp | 2 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectexplorer.cpp | 9 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectexplorer.h | 1 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectexplorer.qbs | 1 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectexplorerconstants.h | 1 | ||||
-rw-r--r-- | src/plugins/projectexplorer/sanitizerparser.cpp | 249 | ||||
-rw-r--r-- | src/plugins/projectexplorer/sanitizerparser.h | 61 |
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 + |