File indexing completed on 2024-05-05 04:39:30

0001 /*
0002     SPDX-FileCopyrightText: 2011 Mathieu Lornac <mathieu.lornac@gmail.com>
0003     SPDX-FileCopyrightText: 2011 Damien Coppel <damien.coppel@gmail.com>
0004     SPDX-FileCopyrightText: 2011 Lionel Duc <lionel.data@gmail.com>
0005     SPDX-FileCopyrightText: 2011 Sebastien Rannou <mxs@sbrk.org>
0006     SPDX-FileCopyrightText: 2011 Lucas Sarie <lucas.sarie@gmail.com>
0007     SPDX-FileCopyrightText: 2006-2008 Hamish Rodda <rodda@kde.org>
0008     SPDX-FileCopyrightText: 2002 Harald Fernengel <harry@kdevelop.org>
0009     SPDX-FileCopyrightText: 2013 Christoph Thielecke <crissi99@gmx.de>
0010     SPDX-FileCopyrightText: 2016-2017 Anton Anikin <anton.anikin@htower.ru>
0011 
0012     SPDX-License-Identifier: GPL-2.0-or-later
0013 */
0014 
0015 #include "job.h"
0016 
0017 #include "debug.h"
0018 #include "parser.h"
0019 #include "utils.h"
0020 
0021 #include <interfaces/icore.h>
0022 #include <interfaces/iuicontroller.h>
0023 #include <sublime/message.h>
0024 #include <shell/problem.h>
0025 // KF
0026 #include <KLocalizedString>
0027 // Qt
0028 #include <QApplication>
0029 #include <QElapsedTimer>
0030 #include <QRegularExpression>
0031 
0032 namespace cppcheck
0033 {
0034 
0035 Job::Job(const Parameters& params, QObject* parent)
0036     : KDevelop::OutputExecuteJob(parent)
0037     , m_timer(new QElapsedTimer)
0038     , m_parser(new CppcheckParser)
0039     , m_showXmlOutput(params.showXmlOutput)
0040     , m_projectRootPath(params.projectRootPath())
0041 {
0042     setJobName(i18n("Cppcheck Analysis (%1)", prettyPathName(params.checkPath)));
0043 
0044     setCapabilities(KJob::Killable);
0045     setStandardToolView(KDevelop::IOutputView::TestView);
0046     setBehaviours(KDevelop::IOutputView::AutoScroll);
0047 
0048     setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStdout);
0049     setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStderr);
0050     setProperties(KDevelop::OutputExecuteJob::JobProperty::PostProcessOutput);
0051 
0052     *this << params.commandLine();
0053     qCDebug(KDEV_CPPCHECK) << "checking path" << params.checkPath;
0054 }
0055 
0056 Job::~Job()
0057 {
0058     doKill();
0059 }
0060 
0061 void Job::postProcessStdout(const QStringList& lines)
0062 {
0063     static const auto fileNameRegex = QRegularExpression(QStringLiteral("Checking ([^:]*)\\.{3}"));
0064     static const auto percentRegex  = QRegularExpression(QStringLiteral("(\\d+)% done"));
0065 
0066     QRegularExpressionMatch match;
0067 
0068     for (const QString& line : lines) {
0069         match = fileNameRegex.match(line);
0070         if (match.hasMatch()) {
0071             emit infoMessage(this, match.captured(1));
0072             continue;
0073         }
0074 
0075         match = percentRegex.match(line);
0076         if (match.hasMatch()) {
0077             setPercent(match.capturedRef(1).toULong());
0078             continue;
0079         }
0080     }
0081 
0082     m_standardOutput << lines;
0083 
0084     if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning) {
0085         KDevelop::OutputExecuteJob::postProcessStdout(lines);
0086     }
0087 }
0088 
0089 void Job::postProcessStderr(const QStringList& lines)
0090 {
0091     static const auto xmlStartRegex = QRegularExpression(QStringLiteral("\\s*<"));
0092 
0093     for (const QString & line : lines) {
0094         // unfortunately sometime cppcheck send non-XML messages to stderr.
0095         // For example, if we pass '-I /missing_include_dir' to the argument list,
0096         // then stderr output will contains such line (tested on cppcheck 1.72):
0097         //
0098         // (information) Couldn't find path given by -I '/missing_include_dir'
0099         //
0100         // Therefore we must 'move' such messages to m_standardOutput.
0101 
0102         if (line.indexOf(xmlStartRegex) != -1) { // the line contains XML
0103             m_xmlOutput << line;
0104 
0105             m_parser->addData(line);
0106 
0107             m_problems = m_parser->parse();
0108             emitProblems();
0109         }
0110         else {
0111             KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(i18n("Cppcheck")));
0112 
0113             problem->setSeverity(KDevelop::IProblem::Error);
0114             problem->setDescription(line);
0115             problem->setExplanation(QStringLiteral("Check your cppcheck settings"));
0116 
0117             m_problems = {problem};
0118             emitProblems();
0119 
0120             if (m_showXmlOutput) {
0121                 m_standardOutput << line;
0122             } else {
0123                 postProcessStdout({line});
0124             }
0125         }
0126     }
0127 
0128     if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning && m_showXmlOutput) {
0129         KDevelop::OutputExecuteJob::postProcessStderr(lines);
0130     }
0131 }
0132 
0133 void Job::start()
0134 {
0135     m_standardOutput.clear();
0136     m_xmlOutput.clear();
0137 
0138     qCDebug(KDEV_CPPCHECK) << "executing:" << commandLine().join(QLatin1Char(' '));
0139 
0140     m_timer->restart();
0141     KDevelop::OutputExecuteJob::start();
0142 }
0143 
0144 void Job::childProcessError(QProcess::ProcessError e)
0145 {
0146     QString messageText;
0147 
0148     switch (e) {
0149     case QProcess::FailedToStart:
0150         messageText = i18n("Failed to start Cppcheck from \"%1\".", commandLine()[0]);
0151         break;
0152 
0153     case QProcess::Crashed:
0154         if (status() != KDevelop::OutputExecuteJob::JobStatus::JobCanceled) {
0155             messageText = i18n("Cppcheck crashed.");
0156         }
0157         break;
0158 
0159     case QProcess::Timedout:
0160         messageText = i18n("Cppcheck process timed out.");
0161         break;
0162 
0163     case QProcess::WriteError:
0164         messageText = i18n("Write to Cppcheck process failed.");
0165         break;
0166 
0167     case QProcess::ReadError:
0168         messageText = i18n("Read from Cppcheck process failed.");
0169         break;
0170 
0171     case QProcess::UnknownError:
0172         // current cppcheck errors will be displayed in the output view
0173         // don't notify the user
0174         break;
0175     }
0176 
0177     if (!messageText.isEmpty()) {
0178         auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
0179         KDevelop::ICore::self()->uiController()->postMessage(message);
0180     }
0181 
0182     KDevelop::OutputExecuteJob::childProcessError(e);
0183 }
0184 
0185 void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
0186 {
0187     qCDebug(KDEV_CPPCHECK) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus;
0188 
0189     postProcessStdout({QStringLiteral("Elapsed time: %1 s.").arg(m_timer->elapsed()/1000.0)});
0190 
0191     if (exitCode != 0) {
0192         qCDebug(KDEV_CPPCHECK) << "cppcheck failed, standard output: ";
0193         qCDebug(KDEV_CPPCHECK) << m_standardOutput.join(QLatin1Char('\n'));
0194         qCDebug(KDEV_CPPCHECK) << "cppcheck failed, XML output: ";
0195         qCDebug(KDEV_CPPCHECK) << m_xmlOutput.join(QLatin1Char('\n'));
0196     }
0197 
0198     KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus);
0199 }
0200 
0201 void Job::emitProblems()
0202 {
0203     if (!m_problems.isEmpty()) {
0204         emit problemsDetected(m_problems);
0205     }
0206 }
0207 
0208 }
0209 
0210 #include "moc_job.cpp"