File indexing completed on 2024-04-21 04:35:59

0001 /* This file is part of KDevelop
0002    Copyright 2011 Mathieu Lornac <mathieu.lornac@gmail.com>
0003    Copyright 2011 Damien Coppel <damien.coppel@gmail.com>
0004    Copyright 2011 Lionel Duc <lionel.data@gmail.com>
0005    Copyright 2011 Sebastien Rannou <mxs@sbrk.org>
0006    Copyright 2011 Lucas Sarie <lucas.sarie@gmail.com>
0007    Copyright 2006-2008 Hamish Rodda <rodda@kde.org>
0008    Copyright 2002 Harald Fernengel <harry@kdevelop.org>
0009    Copyright 2016-2017 Anton Anikin <anton@anikin.xyz>
0010 
0011    This program is free software; you can redistribute it and/or
0012    modify it under the terms of the GNU General Public
0013    License as published by the Free Software Foundation; either
0014    version 2 of the License, or (at your option) any later version.
0015 
0016    This program is distributed in the hope that it will be useful,
0017    but WITHOUT ANY WARRANTY; without even the implied warranty of
0018    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019    General Public License for more details.
0020 
0021    You should have received a copy of the GNU General Public License
0022    along with this program; see the file COPYING.  If not, write to
0023    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0024    Boston, MA 02110-1301, USA.
0025 */
0026 
0027 #include "job.h"
0028 
0029 #include "debug.h"
0030 #include "globalsettings.h"
0031 #include "plugin.h"
0032 #include "tool.h"
0033 #include "utils.h"
0034 #include "private/common_config.h"
0035 
0036 #include <execute/iexecuteplugin.h>
0037 #include <interfaces/icore.h>
0038 #include <interfaces/ilaunchconfiguration.h>
0039 #include <interfaces/iplugincontroller.h>
0040 #include <interfaces/iruncontroller.h>
0041 #include <interfaces/iuicontroller.h>
0042 #include <util/environmentprofilelist.h>
0043 
0044 #include <KConfigGroup>
0045 #include <KLocalizedString>
0046 #include <KMessageBox>
0047 #include <KShell>
0048 
0049 #include <QBuffer>
0050 #include <QFileInfo>
0051 #include <QTcpServer>
0052 #include <QTcpSocket>
0053 
0054 namespace Valgrind
0055 {
0056 
0057 inline QString valgrindErrorsPrefix() { return QStringLiteral("valgrind: "); }
0058 
0059 Job::Job(const Tool* tool, KDevelop::ILaunchConfiguration* launchConfig)
0060     : KDevelop::OutputExecuteJob(KDevelop::ICore::self()->runController())
0061     , m_tool(tool)
0062     , m_configGroup(launchConfig->config())
0063     , m_tcpServerPort(0)
0064 {
0065     Q_ASSERT(tool);
0066     Q_ASSERT(launchConfig);
0067 
0068     setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStdout);
0069     setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStderr);
0070     setProperties(KDevelop::OutputExecuteJob::JobProperty::PostProcessOutput);
0071 
0072     setCapabilities(KJob::Killable);
0073     setStandardToolView(KDevelop::IOutputView::TestView);
0074     setBehaviours(KDevelop::IOutputView::AutoScroll);
0075 
0076     auto pluginController = KDevelop::ICore::self()->pluginController();
0077     auto iface = pluginController->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension<IExecutePlugin>();
0078 
0079     Q_ASSERT(iface);
0080 
0081     QString envProfile = iface->environmentProfileName(launchConfig);
0082     if (envProfile.isEmpty()) {
0083         envProfile = KDevelop::EnvironmentProfileList(KSharedConfig::openConfig()).defaultProfileName();
0084     }
0085     setEnvironmentProfile(envProfile);
0086 
0087     QString errorString;
0088 
0089     m_analyzedExecutable = iface->executable(launchConfig, errorString).toLocalFile();
0090     if (!errorString.isEmpty()) {
0091         setError(-1);
0092         setErrorText(errorString);
0093     }
0094 
0095     m_analyzedExecutableArguments = iface->arguments(launchConfig, errorString);
0096     if (!errorString.isEmpty()) {
0097         setError(-1);
0098         setErrorText(errorString);
0099     }
0100 
0101     QUrl workDir = iface->workingDirectory(launchConfig);
0102     if (workDir.isEmpty() || !workDir.isValid()) {
0103         workDir = QUrl::fromLocalFile(QFileInfo(m_analyzedExecutable).absolutePath());
0104     }
0105     setWorkingDirectory(workDir);
0106 
0107     connect(this, &Job::finished, Plugin::self(), &Plugin::jobFinished);
0108 
0109     auto tcpServer = new QTcpServer(this);
0110     tcpServer->listen(QHostAddress::LocalHost);
0111     m_tcpServerPort = tcpServer->serverPort();
0112 
0113     connect(tcpServer, &QTcpServer::newConnection, this, [this, tcpServer]() {
0114         auto tcpSocket = tcpServer->nextPendingConnection();
0115 
0116         connect(tcpSocket, &QTcpSocket::readyRead, this, [this, tcpSocket]() {
0117             QStringList lines;
0118             while (!tcpSocket->atEnd()) {
0119                 lines += tcpSocket->readLine().trimmed();
0120             }
0121             processValgrindOutput(lines);
0122         });
0123     });
0124 
0125     connect(this, &Job::finished, this, [this]() {
0126         emit hideProgress(this);
0127     });
0128     KDevelop::ICore::self()->uiController()->registerStatus(this);
0129 }
0130 
0131 const Tool* Job::tool() const
0132 {
0133     return m_tool;
0134 }
0135 
0136 QString Job::statusName() const
0137 {
0138     return i18n("%1 Analysis (%2)", m_tool->name(), QFileInfo(m_analyzedExecutable).fileName());
0139 }
0140 
0141 void Job::addLoggingArgs(QStringList& args) const
0142 {
0143     args += QStringLiteral("--log-socket=127.0.0.1:%1").arg(m_tcpServerPort);
0144 }
0145 
0146 QStringList Job::buildCommandLine() const
0147 {
0148     CommonConfig config;
0149     config.setConfigGroup(m_configGroup);
0150     config.load();
0151 
0152     QStringList args;
0153 
0154     args += QStringLiteral("--tool=%1").arg(m_tool->valgrindToolName());
0155     addLoggingArgs(args);
0156 
0157     args += config.cmdArgs();
0158     addToolArgs(args);
0159 
0160     return args;
0161 }
0162 
0163 void Job::start()
0164 {
0165     *this << KDevelop::Path(GlobalSettings::valgrindExecutablePath()).toLocalFile();
0166     *this << buildCommandLine();
0167     *this << m_analyzedExecutable;
0168     *this << m_analyzedExecutableArguments;
0169 
0170     qCDebug(KDEV_VALGRIND) << "executing:" << commandLine().join(' ');
0171 
0172     Plugin::self()->jobReadyToStart(this);
0173 
0174     emit showProgress(this, 0, 0, 0);
0175     KDevelop::OutputExecuteJob::start();
0176 }
0177 
0178 void Job::postProcessStderr(const QStringList& lines)
0179 {
0180     for (const QString& line : lines) {
0181         if (line.startsWith(valgrindErrorsPrefix())) {
0182             m_valgrindOutput += line;
0183         }
0184     }
0185     KDevelop::OutputExecuteJob::postProcessStderr(lines);
0186 }
0187 
0188 void Job::processValgrindOutput(const QStringList& lines)
0189 {
0190     m_valgrindOutput += lines;
0191 
0192     if (GlobalSettings::showValgrindOutput()) {
0193         KDevelop::OutputExecuteJob::postProcessStderr(lines);
0194     }
0195 }
0196 
0197 void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
0198 {
0199     qCDebug(KDEV_VALGRIND) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus;
0200 
0201     bool ok = !exitCode;
0202     if (ok) {
0203         ok = processEnded();
0204     }
0205 
0206     Plugin::self()->jobReadyToFinish(this, ok);
0207     KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus);
0208 }
0209 
0210 void Job::childProcessError(QProcess::ProcessError processError)
0211 {
0212     QString errorMessage;
0213 
0214     switch (processError) {
0215 
0216     case QProcess::FailedToStart:
0217         errorMessage = i18n("Failed to start valgrind from \"%1\".", commandLine().at(0));
0218         break;
0219 
0220     case QProcess::Crashed:
0221         // if the process was killed by the user, the crash was expected
0222         // don't notify the user
0223         if (status() != KDevelop::OutputExecuteJob::JobStatus::JobCanceled) {
0224             errorMessage = i18n("Valgrind crashed.");
0225         }
0226         break;
0227 
0228     case QProcess::Timedout:
0229         errorMessage = i18n("Valgrind process timed out.");
0230         break;
0231 
0232     case QProcess::WriteError:
0233         errorMessage = i18n("Write to Valgrind process failed.");
0234         break;
0235 
0236     case QProcess::ReadError:
0237         errorMessage = i18n("Read from Valgrind process failed.");
0238         break;
0239 
0240     case QProcess::UnknownError:
0241         // Here, check if Valgrind failed (because of bad parameters or whatever).
0242         // Because Valgrind always returns 1 on failure, and the profiled application's return
0243         // on success, we cannot know for sure which process returned != 0.
0244         //
0245         // The only way to guess that it is Valgrind which failed is to check stderr and look for
0246         // "valgrind: " at the beginning of the first line, even though it can still be the
0247         // profiled process that writes it on stderr. It is, however, unlikely enough to be
0248         // reliable in most cases.
0249 
0250         if (!m_valgrindOutput.isEmpty() &&
0251              m_valgrindOutput.at(0).startsWith(valgrindErrorsPrefix())) {
0252 
0253             errorMessage  = m_valgrindOutput.join('\n').remove(valgrindErrorsPrefix());
0254             errorMessage += QStringLiteral("\n\n");
0255             errorMessage += i18n("Please review your Valgrind launch configuration.");
0256         } else {
0257             errorMessage = i18n("Unknown Valgrind process error.");
0258         }
0259         break;
0260     }
0261 
0262     if (!errorMessage.isEmpty()) {
0263         KMessageBox::error(activeMainWindow(), errorMessage, i18n("Valgrind Error"));
0264     }
0265 
0266     KDevelop::OutputExecuteJob::childProcessError(processError);
0267 }
0268 
0269 bool Job::processEnded()
0270 {
0271     return true;
0272 }
0273 
0274 int Job::executeProcess(const QString& executable, const QStringList& args, QByteArray& processOutput)
0275 {
0276     QString commandLine = executable + QLatin1Char(' ') + args.join(QLatin1Char(' '));
0277 
0278     if (GlobalSettings::showValgrindOutput()) {
0279         KDevelop::OutputExecuteJob::postProcessStdout({i18n("Executing command: ") + commandLine });
0280     }
0281 
0282     QProcess process;
0283     process.start(executable, args);
0284     if (!process.waitForFinished()){
0285         return -1;
0286     }
0287 
0288     processOutput = process.readAllStandardOutput();
0289     QString errOutput(process.readAllStandardError());
0290 
0291     if (GlobalSettings::showValgrindOutput()) {
0292         KDevelop::OutputExecuteJob::postProcessStdout(QString(processOutput).split('\n'));
0293     }
0294     KDevelop::OutputExecuteJob::postProcessStderr(errOutput.split('\n'));
0295 
0296     if (process.exitCode()) {
0297         QString message = i18n("Failed to execute the command:");
0298         message += "\n\n";
0299         message += commandLine;
0300         message += "\n\n";
0301         message += i18n("Please review your Valgrind launch configuration.");
0302 
0303         KMessageBox::error(activeMainWindow(), message, i18n("Valgrind Error"));
0304     }
0305 
0306     return process.exitCode();
0307 }
0308 
0309 }