diff --git a/analyzers/cppcheck/CMakeLists.txt b/analyzers/cppcheck/CMakeLists.txt index db64ba4b8a..f85d23b419 100644 --- a/analyzers/cppcheck/CMakeLists.txt +++ b/analyzers/cppcheck/CMakeLists.txt @@ -1,49 +1,50 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevcppcheck\") include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) set(kdevcppcheck_SRCS plugin.cpp + problemmodel.cpp config/globalconfigpage.cpp config/projectconfigpage.cpp ) ki18n_wrap_ui(kdevcppcheck_UI_SRCS config/globalconfigpage.ui config/projectconfigpage.ui ) kconfig_add_kcfg_files(kdevcppcheck_CONFIG_SRCS config/globalsettings.kcfgc config/projectsettings.kcfgc ) add_library(kdevcppcheck_core STATIC debug.cpp parser.cpp job.cpp parameters.cpp - problemmodel.cpp + problem.cpp utils.cpp ${kdevcppcheck_CONFIG_SRCS} ) target_link_libraries(kdevcppcheck_core KDev::Language KDev::Project KDev::Shell ) kdevplatform_add_plugin(kdevcppcheck JSON kdevcppcheck.json SOURCES ${kdevcppcheck_SRCS} ${kdevcppcheck_UI_SRCS} ) target_link_libraries(kdevcppcheck kdevcppcheck_core KF5::ItemViews ) install(FILES kdevcppcheck.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/kdevcppcheck) ecm_install_icons(ICONS icons/128-apps-cppcheck.png DESTINATION ${KDE_INSTALL_ICONDIR} THEME hicolor) add_subdirectory(tests) diff --git a/analyzers/cppcheck/job.cpp b/analyzers/cppcheck/job.cpp index f55d924ba8..2228772cf2 100644 --- a/analyzers/cppcheck/job.cpp +++ b/analyzers/cppcheck/job.cpp @@ -1,217 +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 "problem.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); + KDevelop::IProblem::Ptr problem(new CppcheckProblem); 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()) { emit problemsDetected(m_problems); } } } diff --git a/analyzers/cppcheck/parser.cpp b/analyzers/cppcheck/parser.cpp index f3273418d5..d2505cf9f3 100644 --- a/analyzers/cppcheck/parser.cpp +++ b/analyzers/cppcheck/parser.cpp @@ -1,304 +1,294 @@ /* 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 "problem.h" -#include -#include -#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); 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/problem.cpp b/analyzers/cppcheck/problem.cpp new file mode 100644 index 0000000000..f83ea71231 --- /dev/null +++ b/analyzers/cppcheck/problem.cpp @@ -0,0 +1,44 @@ +/* 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 "problem.h" + +namespace cppcheck +{ + +CppcheckProblem::CppcheckProblem() +{ +} + +CppcheckProblem::~CppcheckProblem() +{ +} + +CppcheckProblem::Source CppcheckProblem::source() const +{ + return Plugin; +}; + +QString CppcheckProblem::sourceString() const +{ + return QStringLiteral("Cppcheck"); +}; + +} diff --git a/analyzers/cppcheck/problem.h b/analyzers/cppcheck/problem.h new file mode 100644 index 0000000000..339ddbab04 --- /dev/null +++ b/analyzers/cppcheck/problem.h @@ -0,0 +1,39 @@ +/* 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 cppcheck +{ + +class CppcheckProblem + : public KDevelop::DetectedProblem +{ +public: + CppcheckProblem(); + ~CppcheckProblem() override; + + Source source() const override; + QString sourceString() const override; +}; + +}