diff --git a/analyzers/cppcheck/job.cpp b/analyzers/cppcheck/job.cpp index 2f6ef66d9c..f55d924ba8 100644 --- a/analyzers/cppcheck/job.cpp +++ b/analyzers/cppcheck/job.cpp @@ -1,234 +1,217 @@ /* This file is part of KDevelop Copyright 2011 Mathieu Lornac Copyright 2011 Damien Coppel Copyright 2011 Lionel Duc Copyright 2011 Sebastien Rannou Copyright 2011 Lucas Sarie Copyright 2006-2008 Hamish Rodda Copyright 2002 Harald Fernengel Copyright 2013 Christoph Thielecke Copyright 2016-2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "job.h" #include "debug.h" #include "parser.h" #include "utils.h" #include #include #include #include #include #include namespace cppcheck { Job::Job(const Parameters& params, QObject* parent) : KDevelop::OutputExecuteJob(parent) , m_timer(new QElapsedTimer) , m_parser(new CppcheckParser) , m_showXmlOutput(params.showXmlOutput) , m_projectRootPath(params.projectRootPath()) { setJobName(i18n("Cppcheck (%1)", prettyPathName(params.checkPath))); setCapabilities(KJob::Killable); setStandardToolView(KDevelop::IOutputView::TestView); setBehaviours(KDevelop::IOutputView::AutoScroll); setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStdout); setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStderr); setProperties(KDevelop::OutputExecuteJob::JobProperty::PostProcessOutput); *this << params.commandLine(); qCDebug(KDEV_CPPCHECK) << "checking path" << params.checkPath; } Job::~Job() { doKill(); } void Job::postProcessStdout(const QStringList& lines) { static const auto fileNameRegex = QRegularExpression("Checking ([^:]*)\\.{3}"); static const auto percentRegex = QRegularExpression("(\\d+)% done"); QRegularExpressionMatch match; foreach (const QString & line, lines) { match = fileNameRegex.match(line); if (match.hasMatch()) { emit infoMessage(this, match.captured(1)); continue; } match = percentRegex.match(line); if (match.hasMatch()) { setPercent(match.captured(1).toULong()); continue; } } m_standardOutput << lines; if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning) { KDevelop::OutputExecuteJob::postProcessStdout(lines); } } void Job::postProcessStderr(const QStringList& lines) { static const auto xmlStartRegex = QRegularExpression("\\s*<"); for (const QString & line : lines) { // unfortunately sometime cppcheck send non-XML messages to stderr. // For example, if we pass '-I /missing_include_dir' to the argument list, // then stderr output will contains such line (tested on cppcheck 1.72): // // (information) Couldn't find path given by -I '/missing_include_dir' // // Therefore we must 'move' such messages to m_standardOutput. if (line.indexOf(xmlStartRegex) != -1) { // the line contains XML m_xmlOutput << line; m_parser->addData(line); m_problems = m_parser->parse(); emitProblems(); } else { KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem); - problem->setSource(KDevelop::IProblem::Plugin); problem->setSeverity(KDevelop::IProblem::Error); problem->setDescription(line); problem->setExplanation("Check your cppcheck settings"); m_problems = {problem}; emitProblems(); if (m_showXmlOutput) { m_standardOutput << line; } else { postProcessStdout({line}); } } } if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning && m_showXmlOutput) { KDevelop::OutputExecuteJob::postProcessStderr(lines); } } void Job::start() { m_standardOutput.clear(); m_xmlOutput.clear(); qCDebug(KDEV_CPPCHECK) << "executing:" << commandLine().join(' '); m_timer->restart(); KDevelop::OutputExecuteJob::start(); } void Job::childProcessError(QProcess::ProcessError e) { QString message; switch (e) { case QProcess::FailedToStart: message = i18n("Failed to start Cppcheck from \"%1\".", commandLine()[0]); break; case QProcess::Crashed: if (status() != KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { message = i18n("Cppcheck crashed."); } break; case QProcess::Timedout: message = i18n("Cppcheck process timed out."); break; case QProcess::WriteError: message = i18n("Write to Cppcheck process failed."); break; case QProcess::ReadError: message = i18n("Read from Cppcheck process failed."); break; case QProcess::UnknownError: // current cppcheck errors will be displayed in the output view // don't notify the user break; } if (!message.isEmpty()) { KMessageBox::error(qApp->activeWindow(), message, i18n("Cppcheck Error")); } KDevelop::OutputExecuteJob::childProcessError(e); } void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(KDEV_CPPCHECK) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus; postProcessStdout({QString("Elapsed time: %1 s.").arg(m_timer->elapsed()/1000.0)}); if (exitCode != 0) { qCDebug(KDEV_CPPCHECK) << "cppcheck failed, standard output: "; qCDebug(KDEV_CPPCHECK) << m_standardOutput.join('\n'); qCDebug(KDEV_CPPCHECK) << "cppcheck failed, XML output: "; qCDebug(KDEV_CPPCHECK) << m_xmlOutput.join('\n'); } KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus); } void Job::emitProblems() { - if (m_problems.isEmpty()) { - return; + if (!m_problems.isEmpty()) { + emit problemsDetected(m_problems); } - - foreach (auto problem, m_problems) { - problem->setFinalLocationMode(KDevelop::IProblem::TrimmedLine); - - // Fix problems with incorrect range, which produced by cppcheck's errors - // without element. In this case location automatically gets "/" - // which entails showing file dialog after selecting such problem in - // ProblemsView. To avoid this we set project's root path as problem location. - auto range = problem->finalLocation(); - if (range.document.isEmpty()) { - range.document = KDevelop::IndexedString(m_projectRootPath.toLocalFile()); - problem->setFinalLocation(range); - } - } - - emit problemsDetected(m_problems); } } diff --git a/analyzers/cppcheck/parser.cpp b/analyzers/cppcheck/parser.cpp index e64d5c863e..f3273418d5 100644 --- a/analyzers/cppcheck/parser.cpp +++ b/analyzers/cppcheck/parser.cpp @@ -1,304 +1,304 @@ /* This file is part of KDevelop Copyright 2013 Christoph Thielecke Copyright 2016 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parser.h" #include "debug.h" #include #include #include #include namespace cppcheck { class CppcheckProblem : public KDevelop::DetectedProblem { public: CppcheckProblem() {} ~CppcheckProblem() override {} + Source source() const override { return Plugin; }; QString sourceString() const override { return QStringLiteral("Cppcheck"); }; }; /** * Convert the value of \ attribute of \ element from cppcheck's * XML-output to 'good-looking' HTML-version. This is necessary because the * displaying of the original message is performed without line breaks - such * tooltips are uncomfortable to read, and large messages will not fit into the * screen. * * This function put the original message into \ tag that automatically * provides line wrapping by builtin capabilities of Qt library. The source text * also can contain tokens '\012' (line break) - they are present in the case of * source code examples. In such cases, the entire text between the first and * last tokens (i.e. source code) is placed into \ tag. * * @param[in] input the original value of \ attribute * @return HTML version for displaying in problem's tooltip */ QString verboseMessageToHtml( const QString & input ) { QString output(QString("%1").arg(input.toHtmlEscaped())); output.replace("\\012", "\n"); if (output.count('\n') >= 2) { output.replace(output.indexOf('\n'), 1, "
" );
         output.replace(output.lastIndexOf('\n'), 1, "

" ); } return output; } CppcheckParser::CppcheckParser() : m_errorInconclusive(false) { } CppcheckParser::~CppcheckParser() { } void CppcheckParser::clear() { m_stateStack.clear(); } bool CppcheckParser::startElement() { State newState = Unknown; qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: elem: " << qPrintable(name().toString()); if (name() == "results") { newState = Results; } else if (name() == "cppcheck") { newState = CppCheck; } else if (name() == "errors") { newState = Errors; } else if (name() == "location") { newState = Location; if (attributes().hasAttribute("file") && attributes().hasAttribute("line")) { m_errorFiles += attributes().value("file").toString(); m_errorLines += attributes().value("line").toString().toInt(); } } else if (name() == "error") { newState = Error; m_errorSeverity = "unknown"; m_errorInconclusive = false; m_errorFiles.clear(); m_errorLines.clear(); m_errorMessage.clear(); m_errorVerboseMessage.clear(); if (attributes().hasAttribute("msg")) { m_errorMessage = attributes().value("msg").toString(); } if (attributes().hasAttribute("verbose")) { m_errorVerboseMessage = verboseMessageToHtml(attributes().value("verbose").toString()); } if (attributes().hasAttribute("severity")) { m_errorSeverity = attributes().value("severity").toString(); } if (attributes().hasAttribute("inconclusive")) { m_errorInconclusive = true; } } else { m_stateStack.push(m_stateStack.top()); return true; } m_stateStack.push(newState); return true; } bool CppcheckParser::endElement(QVector& problems) { qCDebug(KDEV_CPPCHECK) << "CppcheckParser::endElement: elem: " << qPrintable(name().toString()); State state = m_stateStack.pop(); switch (state) { case CppCheck: if (attributes().hasAttribute("version")) { qCDebug(KDEV_CPPCHECK) << "Cppcheck report version: " << attributes().value("version"); } break; case Errors: // errors finished break; case Error: qCDebug(KDEV_CPPCHECK) << "CppcheckParser::endElement: new error elem: line: " << (m_errorLines.isEmpty() ? "?" : QString::number(m_errorLines.first())) << " at " << (m_errorFiles.isEmpty() ? "?" : m_errorFiles.first()) << ", msg: " << m_errorMessage; storeError(problems); break; case Results: // results finished break; case Location: break; default: break; } return true; } QVector CppcheckParser::parse() { QVector problems; qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse!"; while (!atEnd()) { int readNextVal = readNext(); switch (readNextVal) { case StartDocument: clear(); break; case StartElement: startElement(); break; case EndElement: endElement(problems); break; case Characters: break; default: qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: case: " << readNextVal; break; } } qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse: end"; if (hasError()) { switch (error()) { case CustomError: case UnexpectedElementError: case NotWellFormedError: KMessageBox::error( qApp->activeWindow(), i18n("Cppcheck XML Parsing: error at line %1, column %2: %3", lineNumber(), columnNumber(), errorString()), i18n("Cppcheck Error")); break; case NoError: case PrematureEndOfDocumentError: break; } } return problems; } void CppcheckParser::storeError(QVector& problems) { // Construct problem with using first location element KDevelop::IProblem::Ptr problem = getProblem(); // Adds other elements as diagnostics. // This allows the user to track the problem. for (int locationIdx = 1; locationIdx < m_errorFiles.size(); ++locationIdx) { problem->addDiagnostic(getProblem(locationIdx)); } problems.push_back(problem); } KDevelop::IProblem::Ptr CppcheckParser::getProblem(int locationIdx) const { KDevelop::IProblem::Ptr problem(new CppcheckProblem); QStringList messagePrefix; QString errorMessage(m_errorMessage); - problem->setSource(KDevelop::IProblem::Plugin); - if (m_errorSeverity == "error") { problem->setSeverity(KDevelop::IProblem::Error); } else if (m_errorSeverity == "warning") { problem->setSeverity(KDevelop::IProblem::Warning); } else { problem->setSeverity(KDevelop::IProblem::Hint); messagePrefix.push_back(m_errorSeverity); } if (m_errorInconclusive) { messagePrefix.push_back("inconclusive"); } if (!messagePrefix.isEmpty()) { errorMessage = QString("(%1) %2").arg(messagePrefix.join(", ")).arg(m_errorMessage); } problem->setDescription(errorMessage); problem->setExplanation(m_errorVerboseMessage); KDevelop::DocumentRange range; if (locationIdx < 0 || locationIdx >= m_errorFiles.size()) { range = KDevelop::DocumentRange::invalid(); } else { range.document = KDevelop::IndexedString(m_errorFiles.at(locationIdx)); range.setBothLines(m_errorLines.at(locationIdx) - 1); range.setBothColumns(0); } problem->setFinalLocation(range); + problem->setFinalLocationMode(KDevelop::IProblem::TrimmedLine); return problem; } } diff --git a/analyzers/cppcheck/problemmodel.cpp b/analyzers/cppcheck/problemmodel.cpp index ac58b51be3..1ff188d05e 100644 --- a/analyzers/cppcheck/problemmodel.cpp +++ b/analyzers/cppcheck/problemmodel.cpp @@ -1,120 +1,141 @@ /* This file is part of KDevelop Copyright 2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemmodel.h" #include "plugin.h" #include "utils.h" #include #include +#include #include #include namespace cppcheck { inline KDevelop::ProblemModelSet* problemModelSet() { return KDevelop::ICore::self()->languageController()->problemModelSet(); } static const QString problemModelId = QStringLiteral("Cppcheck"); ProblemModel::ProblemModel(Plugin* plugin) : KDevelop::ProblemModel(plugin) , m_plugin(plugin) , m_project(nullptr) { setFeatures(CanDoFullUpdate | ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter); reset(); problemModelSet()->addModel(problemModelId, i18n("Cppcheck"), this); } ProblemModel::~ProblemModel() { problemModelSet()->removeModel(problemModelId); } KDevelop::IProject* ProblemModel::project() const { return m_project; } +void ProblemModel::fixProblemFinalLocation(KDevelop::IProblem::Ptr problem) +{ + // Fix problems with incorrect range, which produced by cppcheck's errors + // without element. In this case location automatically gets "/". + // To avoid this we set project's root path as problem location. + + Q_ASSERT(m_project); + + auto range = problem->finalLocation(); + if (range.document.isEmpty()) { + range.document = KDevelop::IndexedString(m_project->path().toLocalFile()); + problem->setFinalLocation(range); + } + + for (auto diagnostic : problem->diagnostics()) { + fixProblemFinalLocation(diagnostic); + } +} + void ProblemModel::addProblems(const QVector& problems) { static int maxLength = 0; if (m_problems.isEmpty()) { maxLength = 0; } - m_problems.append(problems); - for (auto p : problems) { - addProblem(p); + for (auto problem : problems) { + fixProblemFinalLocation(problem); + + m_problems.append(problem); + addProblem(problem); // This performs adjusting of columns width in the ProblemsView - if (maxLength < p->description().length()) { - maxLength = p->description().length(); + if (maxLength < problem->description().length()) { + maxLength = problem->description().length(); setProblems(m_problems); - break; } } } void ProblemModel::setProblems() { setProblems(m_problems); } void ProblemModel::reset() { reset(nullptr, QString()); } void ProblemModel::reset(KDevelop::IProject* project, const QString& path) { m_project = project; m_path = path; clearProblems(); m_problems.clear(); QString tooltip = i18nc("@info:tooltip", "Re-run last Cppcheck analyze"); if (m_project) { tooltip += QString(" (%1)").arg(prettyPathName(m_path)); } setFullUpdateTooltip(tooltip); } void ProblemModel::show() { problemModelSet()->showModel(problemModelId); } void ProblemModel::forceFullUpdate() { if (m_project && !m_plugin->isRunning()) { m_plugin->runCppcheck(m_project, m_path); } } } diff --git a/analyzers/cppcheck/problemmodel.h b/analyzers/cppcheck/problemmodel.h index 1e8e71cd0d..b17f684472 100644 --- a/analyzers/cppcheck/problemmodel.h +++ b/analyzers/cppcheck/problemmodel.h @@ -1,65 +1,66 @@ /* This file is part of KDevelop Copyright 2017 Anton Anikin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once #include namespace KDevelop { class IProject; } namespace cppcheck { class Plugin; class ProblemModel : public KDevelop::ProblemModel { public: explicit ProblemModel(Plugin* plugin); ~ProblemModel() override; KDevelop::IProject* project() const; void addProblems(const QVector& problems); void setProblems(); void reset(); void reset(KDevelop::IProject* project, const QString& path); void show(); void forceFullUpdate() override; private: + void fixProblemFinalLocation(KDevelop::IProblem::Ptr problem); using KDevelop::ProblemModel::setProblems; Plugin* m_plugin; KDevelop::IProject* m_project; QString m_path; QVector m_problems; }; }