diff --git a/job.cpp b/job.cpp index bac20fdd9a..5399a36881 100644 --- a/job.cpp +++ b/job.cpp @@ -1,476 +1,477 @@ /* 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 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 #include #include #include #include #include #include #include -#include -#include -#include #include #include #include #include #include #include #include +#include +#include +#include +#include #include #include #include #include "cppcheckmodel.h" #include "cppcheck_file_model.h" #include "cppcheck_severity_model.h" #include "cppcheckparser.h" #include "cppcheckview.h" #include "debug.h" #include "plugin.h" #include "modelwrapper.h" namespace cppcheck { /*! * Creates a model and a parser according to the specified name and * connects the 2 items */ class ModelParserFactoryPrivate { public: void make(const QString& type, cppcheck::Model*& m_model, cppcheck::Parser*& m_parser); }; void ModelParserFactoryPrivate::make(const QString& tool, cppcheck::Model*& m_model, cppcheck::Parser*& m_parser) { Q_UNUSED(tool); ModelWrapper* modelWrapper = 0; KConfig config("kdevcppcheckrc"); KConfigGroup grp = config.group("cppcheck"); int OutputViewMode = grp.readEntry("OutputViewMode", 0); if (OutputViewMode == cppcheck::CppcheckView::flatOutputMode) m_model = new cppcheck::CppcheckModel(); else if (OutputViewMode == cppcheck::CppcheckView::groupedByFileOutputMode) m_model = new cppcheck::CppcheckFileModel(); else if (OutputViewMode == cppcheck::CppcheckView::groupedBySeverityOutputMode) m_model = new cppcheck::CppcheckSeverityModel(); modelWrapper = new ModelWrapper(m_model); m_parser = new cppcheck::CppcheckParser(m_model); if (OutputViewMode == cppcheck::CppcheckView::flatOutputMode) { QObject::connect(m_parser, SIGNAL(newElement(cppcheck::Model::eElementType)), modelWrapper, SLOT(newElement(cppcheck::Model::eElementType))); QObject::connect(m_parser, SIGNAL(newData(cppcheck::Model::eElementType, QString, QString, int, QString, QString, QString, QString, QString)), modelWrapper, SLOT(newData(cppcheck::Model::eElementType, QString, QString, int, QString, QString, QString, QString, QString))); } else if (OutputViewMode == cppcheck::CppcheckView::groupedByFileOutputMode) { QObject::connect(m_parser, SIGNAL(newItem(cppcheck::ModelItem*)), modelWrapper, SLOT(newItem(cppcheck::ModelItem*))); } else if (OutputViewMode == cppcheck::CppcheckView::groupedBySeverityOutputMode) { QObject::connect(m_parser, SIGNAL(newItem(cppcheck::ModelItem*)), modelWrapper, SLOT(newItem(cppcheck::ModelItem*))); } m_model->setModelWrapper(modelWrapper); QObject::connect(m_parser, SIGNAL(reset()), modelWrapper, SLOT(reset())); m_model->reset(); } Job::Job(cppcheck::Plugin* inst, bool allFiles, QObject* parent) : KDevelop::OutputJob(parent) , m_process(new QProcess(this)) , m_pid(0) , m_model(0) , m_parser(0) , m_applicationOutput(new KDevelop::ProcessLineMaker(this)) , m_plugin(inst) , m_killed(false) , allFiles(allFiles) { setCapabilities(KJob::Killable); m_process->setProcessChannelMode(QProcess::SeparateChannels); connect(m_process, SIGNAL(readyReadStandardOutput()), SLOT(readyReadStandardOutput())); connect(m_process, SIGNAL(readyReadStandardError()), SLOT(readyReadStandardError())); connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus))); connect(m_process, SIGNAL(error(QProcess::ProcessError)), SLOT(processErrored(QProcess::ProcessError))); // create the correct model for each tool QString tool = "cppcheck"; ModelParserFactoryPrivate factory; factory.make(tool, m_model, m_parser); m_model->getModelWrapper()->job(this); m_plugin->incomingModel(m_model); } Job::~Job() { doKill(); if (m_model && m_model->getModelWrapper()) m_model->getModelWrapper()->job(0); delete m_process; delete m_applicationOutput; delete m_parser; } void Job::processModeArgs(QStringList& out, const t_cppcheck_cfg_argarray mode_args, int mode_args_count, KConfigGroup& cfg) const { // For each option, set the right string in the arguments list for (int i = 0; i < mode_args_count; ++i) { QString val; QString argtype = mode_args[i][2]; if (argtype == "str") val = cfg.readEntry(mode_args[i][0]); else if (argtype == "int") { int n = cfg.readEntry(mode_args[i][0], 0); if (n) { val.sprintf("%d", n); } } else if (argtype == "bool") { bool n = cfg.readEntry(mode_args[i][0], false); val = n ? "yes" : "no"; } else if (argtype == "float") { int n = cfg.readEntry(mode_args[i][0], 1); val.sprintf("%d.0", n); } if (val.length()) { QString argument = QString(mode_args[i][1]) + val; out << argument; } } } void Job::readyReadStandardError() { QString stderr_content_local = m_process->readAllStandardError(); stderr_output += stderr_content_local; m_applicationOutput->slotReceivedStderr(stderr_content_local.toLocal8Bit()); } void Job::readyReadStandardOutput() { QString stdout_content_local = m_process->readAllStandardOutput(); stdout_output += stdout_content_local; m_applicationOutput->slotReceivedStdout(stdout_content_local.toLocal8Bit()); } QStringList Job::buildCommandLine() const { static const t_cppcheck_cfg_argarray generic_args = { }; static const int generic_args_count = sizeof(generic_args) / sizeof(*generic_args); KConfig config("kdevcppcheckrc"); KConfigGroup cfg = config.group("cppcheck"); QStringList args; args.append(KShell::splitArgs(cfg.readEntry("Cppcheck Arguments", ""))); args.append("--force"); args.append("--xml-version=2"); /* extra parameters */ QString cppcheckParameters(cfg.readEntry("cppcheckParameters", "")); if (!cppcheckParameters.isEmpty()) args.append(cppcheckParameters); bool additionalCheckStyle = cfg.readEntry("AdditionalCheckStyle", false); if (additionalCheckStyle) args.append("--enable=style"); bool additionalCheckPerformance = cfg.readEntry("AdditionalCheckPerformance", false); if (additionalCheckPerformance) args.append("--enable=performance"); bool additionalCheckPortability = cfg.readEntry("AdditionalCheckPortability", false); if (additionalCheckPortability) args.append("--enable=portability"); bool additionalCheckInformation = cfg.readEntry("AdditionalCheckInformation", false); if (additionalCheckInformation) args.append("--enable=information"); bool AdditionalCheckUnusedFunction = cfg.readEntry("AdditionalCheckUnusedFunction", false); if (AdditionalCheckUnusedFunction) args.append("--enable=unusedFunction"); bool additionalCheckMissingInclude = cfg.readEntry("AdditionalCheckMissingInclude", false); if (additionalCheckMissingInclude) args.append("--enable=missingInclude"); if (allFiles == false) { qCDebug(KDEV_CPPCHECK) << "checking file: " << m_plugin->core()->documentController()->activeDocument()->url().toLocalFile() << "(" << "" << ")"; args.append(m_plugin->core()->documentController()->activeDocument()->url().toLocalFile()); } else { qCDebug(KDEV_CPPCHECK) << "checking all files"; // project path for (int i = 0; i < KDevelop::ICore::self()->projectController()->projects().count(); i++) { - args.append(KDevelop::ICore::self()->projectController()->projects().at(i)->folder().toLocalFile()); + args.append(KDevelop::ICore::self()->projectController()->projects().at(i)->path().toUrl().toLocalFile()); } } QString tool = cfg.readEntry("Current Tool", "cppcheck"); processModeArgs(args, generic_args, generic_args_count, cfg); addToolArgs(args, cfg); return args; } void Job::start() { KConfig config("kdevcppcheckrc"); KConfigGroup grp = config.group("cppcheck"); KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); QUrl cppcheckExecutable(grp.readEntry("CppcheckExecutable", QUrl::fromLocalFile("/usr/bin/cppcheck"))); QString err; QString envgrp = ""; setStandardToolView(KDevelop::IOutputView::DebugView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); setModel(new KDevelop::OutputModel()); m_process->setEnvironment(l.createEnvironment(envgrp, m_process->systemEnvironment())); Q_ASSERT(m_process->state() != QProcess::Running); // some tools need to initialize stuff before the process starts beforeStart(); QStringList cppcheckArgs; cppcheckArgs = buildCommandLine(); qCDebug(KDEV_CPPCHECK) << "executing:" << cppcheckExecutable.toLocalFile() << cppcheckArgs; m_process->setProgram(cppcheckExecutable.toLocalFile()); m_process->setArguments(cppcheckArgs); m_process->start(); m_pid = m_process->pid(); //setTitle(QString(i18n("job output (pid=%1)", m_pid))); setTitle(QString(i18n("Cppcheck output"))); startOutput(); connect(m_applicationOutput, SIGNAL(receivedStdoutLines(QStringList)), model(), SLOT(appendLines(QStringList))); connect(m_applicationOutput, SIGNAL(receivedStderrLines(QStringList)), model(), SLOT(appendLines(QStringList))); emit updateTabText(m_model, i18n("job running (pid=%1)", m_pid)); processStarted(); } bool Job::doKill() { if (m_process && m_process->pid()) { m_process->kill(); m_killed = true; m_process = 0; } return true; } cppcheck::Plugin* Job::plugin() const { return m_plugin; } void Job::processErrored(QProcess::ProcessError e) { switch (e) { case QProcess::FailedToStart: KMessageBox::error(qApp->activeWindow(), i18n("Failed to start cppcheck from \"%1\".", m_process->property("executable").toString()), i18n("Failed to start Cppcheck")); break; case QProcess::Crashed: // if the process was killed by the user, the crash was expected // don't notify the user if (!m_killed) KMessageBox::error(qApp->activeWindow(), i18n("Cppcheck crashed."), i18n("Cppcheck Error")); break; case QProcess::Timedout: KMessageBox::error(qApp->activeWindow(), i18n("Cppcheck process timed out."), i18n("Cppcheck Error")); break; case QProcess::WriteError: KMessageBox::error(qApp->activeWindow(), i18n("Write to Cppcheck process failed."), i18n("Cppcheck Error")); break; case QProcess::ReadError: KMessageBox::error(qApp->activeWindow(), i18n("Read from Cppcheck process failed."), i18n("Cppcheck Error")); break; case QProcess::UnknownError: KMessageBox::error(qApp->activeWindow(), i18n("Unknown Cppcheck process error."), i18n("Cppcheck Error")); break; } } void Job::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(KDEV_CPPCHECK) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus; QString tabname = i18n("cppcheck finished (pid=%1,exit=%2)", m_pid, exitCode); if (exitCode != 0) { /* ** Here, check if Cppcheck failed (because of bad parameters or whatever). ** Because Cppcheck always returns 1 on failure, and the profiled application's return ** on success, we cannot know for sure which process returned != 0. ** ** The only way to guess that it is Cppcheck which failed is to check stderr and look for ** "cppcheck:" at the beginning of each line, even though it can still be the profiled ** process that writes it on stderr. It is, however, unlikely enough to be reliable in ** most cases. */ qCDebug(KDEV_CPPCHECK) << "cppcheck failed, output: "; qCDebug(KDEV_CPPCHECK) << stdout_output; qCDebug(KDEV_CPPCHECK) << "cppcheck failed, error output: "; qCDebug(KDEV_CPPCHECK) << stderr_output; const QString& s = stdout_output; QStringList err = s.split("\n"); tabname = i18n("job failed"); model()->appendLines(err); } else { // success string_device = new QBuffer(); string_device->open(QIODevice::ReadWrite); string_device->write(stdout_output.toLocal8Bit()); m_parser->addData(stderr_output); m_parser->parse(); string_device->close(); emit updateTabText(m_model, tabname); } processEnded(); emitResult(); emit jobFinished(); } void Job::beforeStart() { } void Job::processStarted() { } void Job::processEnded() { } KDevelop::OutputModel* Job::model() { return dynamic_cast(KDevelop::OutputJob::model()); } void Job::addToolArgs(QStringList& args, KConfigGroup& cfg) const { static const t_cppcheck_cfg_argarray cppcheck_args = { {"Cppcheck Arguments", "", "str"}, }; static const int count = sizeof(cppcheck_args) / sizeof(*cppcheck_args); processModeArgs(args, cppcheck_args, count, cfg); } /* * KProcessOutputToParser implementation */ KProcessOutputToParser::KProcessOutputToParser(Parser* parser) { m_parser = parser; m_device = new QBuffer(); m_device->open(QIODevice::WriteOnly); m_process = 0L; } KProcessOutputToParser::~KProcessOutputToParser() { if (m_device != 0) delete m_device; if (m_process) delete m_process; } bool KProcessOutputToParser::execute(QString execPath, QStringList args) { m_process = new QProcess(); m_process->setProcessChannelMode(QProcess::SeparateChannels); m_process->setProgram(execPath); m_process->setArguments(args); QObject::connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(newDataFromStdOut())); QObject::connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processEnded(int, QProcess::ExitStatus))); //execute and wait the end of the program m_process->start(); return m_process->waitForFinished(); } void KProcessOutputToParser::processEnded(int , QProcess::ExitStatus status) { qCDebug(KDEV_CPPCHECK) << status; if (status == QProcess::NormalExit) { m_device->close(); } delete m_process; } void KProcessOutputToParser::newDataFromStdOut() { m_device->write(m_process->readAllStandardOutput()); } } diff --git a/parsers/cppcheckparser.cpp b/parsers/cppcheckparser.cpp index dce8f81858..0c2edef094 100644 --- a/parsers/cppcheckparser.cpp +++ b/parsers/cppcheckparser.cpp @@ -1,198 +1,199 @@ /* This file is part of KDevelop * Copyright 2013 Christoph Thielecke 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 #include #include +#include #include #include #include #include "debug.h" #include "cppcheckparser.h" namespace cppcheck { CppcheckParser::CppcheckParser(cppcheck::Model *m_model, QObject* parent) : ErrorLine(0), ErrorFile(""), Message(""), MessageVerbose(""), Severity(Unknown), ProjectPath(""), m_model(m_model) { Q_UNUSED(parent) } CppcheckParser::~CppcheckParser() { } void CppcheckParser::clear() { m_stateStack.clear(); m_buffer.clear(); cppcheckArgs.clear(); programArgs.clear(); } bool CppcheckParser::startElement() { m_buffer.clear(); 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("line")) ErrorLine = attributes().value("line").toString().toInt(); if (attributes().hasAttribute("file")) { ErrorFile = attributes().value("file").toString(); /* get project path */ ProjectPath = ""; for (int i = 0; i < KDevelop::ICore::self()->projectController()->projects().count(); i++) { if (KDevelop::ICore::self()->projectController()->findProjectForUrl(QUrl::fromLocalFile(ErrorFile)) != 0) { - ProjectPath = KDevelop::ICore::self()->projectController()->projects().at(i)->folder().toLocalFile(); + ProjectPath = KDevelop::ICore::self()->projectController()->projects().at(i)->path().toUrl().toLocalFile(); } } ErrorFile.remove(ProjectPath); } } else if (name() == "error") { newState = Error; ErrorLine = -1; ErrorFile = ""; Message = ""; MessageVerbose = ""; ProjectPath = ""; Severity = "unknown"; if (attributes().hasAttribute("msg")) Message = attributes().value("msg").toString(); if (attributes().hasAttribute("verbose")) MessageVerbose = attributes().value("verbose").toString(); if (attributes().hasAttribute("severity")) Severity = attributes().value("severity").toString(); - + if (dynamic_cast(m_model)) emit newElement(cppcheck::CppcheckModel::startError); // if (dynamic_cast(m_model)) // emit newElement(cppcheck::CppcheckFileModel::startError); } else { m_stateStack.push(m_stateStack.top()); return true; } m_stateStack.push(newState); return true; } bool CppcheckParser::endElement() { 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: " << ErrorLine << " at " << ErrorFile << ", msg: " << Message; if (dynamic_cast(m_model)) emit newData(cppcheck::CppcheckModel::error, name().toString(), m_buffer, ErrorLine, ErrorFile, Message, MessageVerbose, ProjectPath, Severity); else if (dynamic_cast(m_model)) { //emit newData(cppcheck::CppcheckFileModel::error, name().toString(), m_buffer, ErrorLine, ErrorFile, Message, MessageVerbose, ProjectPath, Severity); CppcheckFileItem *m_item = new CppcheckFileItem(); m_item->incomingData(name().toString(), m_buffer, ErrorLine, ErrorFile, Message, MessageVerbose, ProjectPath, Severity); emit newItem(m_item); } else if (dynamic_cast(m_model)) { //emit newData(cppcheck::CppcheckSeverityModel::error, name().toString(), m_buffer, ErrorLine, ErrorFile, Message, MessageVerbose, ProjectPath, Severity); CppcheckSeverityItem *m_item = new CppcheckSeverityItem(); m_item->incomingData(name().toString(), m_buffer, ErrorLine, ErrorFile, Message, MessageVerbose, ProjectPath, Severity); emit newItem(m_item); } break; case Results: // results finished break; case Location: break; default: break; } return true; } void CppcheckParser::parse() { qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse!"; while (!atEnd()) { int readNextVal = readNext(); switch (readNextVal) { case StartDocument: clear(); break; case StartElement: startElement(); break; case EndElement: endElement(); break; case Characters: m_buffer += text().toString(); break; default: qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: case: " << readNextVal; break; } } emit newItem(NULL); 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; } } } }