File indexing completed on 2024-04-21 04:38:10

0001 /*
0002     SPDX-FileCopyrightText: 2018 Anton Anikin <anton@anikin.xyz>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "job.h"
0008 
0009 #include "checksdb.h"
0010 #include "debug.h"
0011 #include "globalsettings.h"
0012 #include "plugin.h"
0013 #include "utils.h"
0014 
0015 #include <language/editor/documentrange.h>
0016 #include <shell/problem.h>
0017 // KF
0018 #include <KLocalizedString>
0019 #include <KShell>
0020 // Qt
0021 #include <QMessageBox>
0022 #include <QElapsedTimer>
0023 #include <QFileInfo>
0024 #include <QRegularExpression>
0025 
0026 namespace Clazy
0027 {
0028 
0029 Job::Job()
0030     : KDevelop::CompileAnalyzeJob(nullptr)
0031     , m_db(nullptr)
0032     , m_timer(nullptr)
0033 {
0034 }
0035 
0036 QString commandLineString(const JobParameters& params)
0037 {
0038     QStringList args;
0039 
0040     args << params.executablePath;
0041 
0042     if (!params.checks.isEmpty()) {
0043         args << QLatin1String("-checks=") + params.checks;
0044     }
0045 
0046     if (params.onlyQt) {
0047         args << QStringLiteral("-only-qt");
0048     }
0049 
0050     if (params.qtDeveloper) {
0051         args << QStringLiteral("-qt-developer");
0052     }
0053 
0054     if (params.qt4Compat) {
0055         args << QStringLiteral("-qt4-compat");
0056     }
0057 
0058     if (params.visitImplicitCode) {
0059         args << QStringLiteral("-visit-implicit-code");
0060     }
0061 
0062     if (params.ignoreIncludedFiles) {
0063         args << QStringLiteral("-ignore-included-files");
0064     }
0065 
0066     if (!params.headerFilter.isEmpty()) {
0067         args << QLatin1String("-header-filter=") + params.headerFilter;
0068     }
0069 
0070     if (params.enableAllFixits) {
0071         args << QStringLiteral("-enable-all-fixits");
0072     }
0073 
0074     if (params.noInplaceFixits) {
0075         args << QStringLiteral("-no-inplace-fixits");
0076     }
0077 
0078     if (!params.extraAppend.isEmpty()) {
0079         args << QLatin1String("-extra-arg=") + params.extraAppend;
0080     }
0081 
0082     if (!params.extraPrepend.isEmpty()) {
0083         args << QLatin1String("-extra-arg-before=%1") + params.extraPrepend;
0084     }
0085 
0086     if (!params.extraClazy.isEmpty()) {
0087         args << KShell::splitArgs(params.extraClazy);
0088     }
0089 
0090     args << QLatin1String("-p=\"") + params.buildDir + QLatin1Char('\"');
0091 
0092 //     for (auto it = args.begin(), end = args.end(); it != end; ++it) {
0093 //         QString& commandPart = *it;
0094 //         commandPart = spaceEscapedString(commandPart);
0095 //     }
0096 
0097     return args.join(QLatin1Char(' '));
0098 }
0099 
0100 Job::Job(const JobParameters& params, QSharedPointer<const ChecksDB> db)
0101     : KDevelop::CompileAnalyzeJob(nullptr)
0102     , m_db(db)
0103     , m_timer(new QElapsedTimer)
0104 {
0105     setJobName(i18n("Clazy Analysis (%1)", prettyPathName(params.url)));
0106 
0107     setParallelJobCount(params.parallelJobCount);
0108     setBuildDirectoryRoot(params.buildDir);
0109     setCommand(commandLineString(params), params.verboseOutput);
0110     setToolDisplayName(QStringLiteral("Clazy"));
0111     setSources(params.filePaths);
0112 }
0113 
0114 Job::~Job()
0115 {
0116 }
0117 
0118 void Job::processStdoutLines(const QStringList& lines)
0119 {
0120     m_standardOutput << lines;
0121 }
0122 
0123 void Job::processStderrLines(const QStringList& lines)
0124 {
0125     static const auto errorRegex = QRegularExpression(
0126         QStringLiteral("(.+):(\\d+):(\\d+):\\s+warning:\\s+(.+)\\s+\\[-Wclazy-(.+)\\]$"));
0127 
0128     QVector<KDevelop::IProblem::Ptr> problems;
0129 
0130     for (const QString & line : lines) {
0131         auto match = errorRegex.match(line);
0132         if (match.hasMatch()) {
0133             auto check = m_db ? m_db->checks().value(match.captured(5), nullptr) : nullptr;
0134 
0135             const QString levelName = check ? check->level->displayName : i18n("Unknown Level");
0136             KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(levelName));
0137 
0138             problem->setSeverity(KDevelop::IProblem::Warning);
0139             problem->setDescription(match.captured(4));
0140             if (check) {
0141                 problem->setExplanation(check->description);
0142             }
0143 
0144             // Sometimes warning/error file path contains "." or ".." elements so we should fix
0145             // it and take "real" (canonical) path value. But QFileInfo::canonicalFilePath()
0146             // returns empty string when file does not exists. Unfortunately we can't pass some
0147             // real file path from unit tests, therefore we should skip canonicalFilePath() step.
0148             // To detect such testing cases we are check m_timer value, which is not-null only for
0149             // "real" jobs, created with public constructor.
0150             const auto document = m_timer.isNull() ? match.captured(1) : QFileInfo(match.captured(1)).canonicalFilePath();
0151 
0152             const int line = match.capturedRef(2).toInt() - 1;
0153             const int column = match.capturedRef(3).toInt() - 1;
0154 
0155             // TODO add KDevelop::IProblem::FinalLocationMode::ToEnd type ?
0156             KTextEditor::Range range(line, column, line, 1000);
0157             KDevelop::DocumentRange documentRange(KDevelop::IndexedString(document), range);
0158             problem->setFinalLocation(documentRange);
0159             problem->setFinalLocationMode(KDevelop::IProblem::Range);
0160 
0161             problems.append(problem);
0162         }
0163     }
0164     m_stderrOutput << lines;
0165 
0166     if (problems.size()) {
0167         emit problemsDetected(problems);
0168     }
0169 }
0170 
0171 void Job::postProcessStdout(const QStringList& lines)
0172 {
0173     processStdoutLines(lines);
0174 
0175     KDevelop::CompileAnalyzeJob::postProcessStdout(lines);
0176 }
0177 
0178 void Job::postProcessStderr(const QStringList& lines)
0179 {
0180     processStderrLines(lines);
0181 
0182     KDevelop::CompileAnalyzeJob::postProcessStderr(lines);
0183 }
0184 
0185 void Job::start()
0186 {
0187     m_standardOutput.clear();
0188     m_stderrOutput.clear();
0189 
0190     m_timer->restart();
0191 
0192     KDevelop::CompileAnalyzeJob::start();
0193 }
0194 
0195 void Job::childProcessError(QProcess::ProcessError e)
0196 {
0197     QString message;
0198 
0199     switch (e) {
0200     case QProcess::FailedToStart:
0201         message = i18n("Failed to start Clazy analysis process.");
0202         break;
0203 
0204     case QProcess::Crashed:
0205         if (status() != KDevelop::OutputExecuteJob::JobStatus::JobCanceled) {
0206             message = i18n("Clazy analysis process crashed.");
0207         }
0208         break;
0209 
0210     case QProcess::Timedout:
0211         message = i18n("Clazy analysis process timed out.");
0212         break;
0213 
0214     case QProcess::WriteError:
0215         message = i18n("Write to Clazy analysis process failed.");
0216         break;
0217 
0218     case QProcess::ReadError:
0219         message = i18n("Read from Clazy analysis process failed.");
0220         break;
0221 
0222     case QProcess::UnknownError:
0223         // errors will be displayed in the output view ?
0224         // don't notify the user
0225         break;
0226     }
0227 
0228     if (!message.isEmpty()) {
0229         QMessageBox::critical(nullptr, i18nc("@title:window", "Clazy Error"), message);
0230     }
0231 
0232     KDevelop::CompileAnalyzeJob::childProcessError(e);
0233 }
0234 
0235 void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
0236 {
0237     qCDebug(KDEV_CLAZY) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus;
0238 
0239     setPercent(100);
0240     postProcessStdout({QStringLiteral("Elapsed time: %1 s.").arg(m_timer->elapsed()/1000.0)});
0241 
0242     if (exitCode != 0) {
0243         qCDebug(KDEV_CLAZY) << "clazy failed";
0244         qCDebug(KDEV_CLAZY) << "stdout output: ";
0245         qCDebug(KDEV_CLAZY) << m_standardOutput.join(QLatin1Char('\n'));
0246         qCDebug(KDEV_CLAZY) << "stderr output: ";
0247         qCDebug(KDEV_CLAZY) << m_stderrOutput.join(QLatin1Char('\n'));
0248     }
0249 
0250     KDevelop::CompileAnalyzeJob::childProcessExited(exitCode, exitStatus);
0251 }
0252 
0253 }
0254 
0255 #include "moc_job.cpp"