diff options
Diffstat (limited to 'src/plugins/projectexplorer/sanitizerparser.cpp')
-rw-r--r-- | src/plugins/projectexplorer/sanitizerparser.cpp | 249 |
1 files changed, 249 insertions, 0 deletions
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> |