From 7e08a58fb50d28ba96aedd5f5cd79a9479b4a0ad Mon Sep 17 00:00:00 2001
From: Christoph Cullmann <cullmann@kde.org>
Date: Mon, 24 Jan 2022 19:07:37 +0000
Subject: [PATCH] improve QProcess handling

ensure we take executables from PATH for execution instead possibly from current working directory
or the working directory set for the QProcess
---
 addons/compiler-explorer/compiledbreader.cpp  |  4 +-
 addons/externaltools/katetoolrunner.cpp       |  9 +++-
 addons/gdbplugin/debugview.cpp                | 17 +++++++-
 addons/git-blame/commitfilesview.cpp          | 17 +++++---
 addons/git-blame/kategitblameplugin.cpp       |  8 +++-
 addons/kate-ctags/gotosymbolmodel.cpp         | 15 +++++--
 addons/project/comparebranchesview.cpp        |  4 +-
 addons/project/filehistorywidget.cpp          | 14 +++++--
 addons/project/git/gitutils.cpp               | 41 +++++++++++++++----
 addons/project/gitwidget.cpp                  | 19 +++++++--
 addons/project/kateprojectindex.cpp           |  9 +++-
 .../kateprojectinfoviewcodeanalysis.cpp       |  9 +++-
 addons/project/kateprojectworker.cpp          | 32 +++++++++++----
 addons/project/stashdialog.cpp                | 20 ++++-----
 addons/project/stashdialog.h                  |  2 +-
 addons/replicode/replicodeview.cpp            |  9 ++++
 addons/xmlcheck/plugin_katexmlcheck.cpp       |  8 ++++
 kate/katefileactions.cpp                      | 17 ++++----
 kate/katefileactions.h                        |  4 +-
 kate/katemwmodonhddialog.cpp                  |  6 ++-
 kate/katemwmodonhddialog.h                    |  1 +
 kate/kateviewspace.cpp                        |  7 +++-
 shared/gitprocess.h                           | 16 +++++++-
 23 files changed, 217 insertions(+), 71 deletions(-)

diff --git a/addons/compiler-explorer/compiledbreader.cpp b/addons/compiler-explorer/compiledbreader.cpp
index 74e83638e..ab9ebc483 100644
--- a/addons/compiler-explorer/compiledbreader.cpp
+++ b/addons/compiler-explorer/compiledbreader.cpp
@@ -21,7 +21,9 @@ std::optional<QString> getDotGitPath(const QString &repo)
 {
     /* This call is intentionally blocking because we need git path for everything else */
     QProcess git;
-    setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--absolute-git-dir")});
+    if (!setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--absolute-git-dir")})) {
+        return std::nullopt;
+    }
     git.start(QProcess::ReadOnly);
     if (git.waitForStarted() && git.waitForFinished(-1)) {
         if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
diff --git a/addons/externaltools/katetoolrunner.cpp b/addons/externaltools/katetoolrunner.cpp
index 10a5d7226..e14940ad7 100644
--- a/addons/externaltools/katetoolrunner.cpp
+++ b/addons/externaltools/katetoolrunner.cpp
@@ -14,6 +14,7 @@
 #include <KTextEditor/View>
 #include <QFileInfo>
 #include <QRegularExpression>
+#include <QStandardPaths>
 
 KateToolRunner::KateToolRunner(std::unique_ptr<KateExternalTool> tool, KTextEditor::View *view, QObject *parent)
     : QObject(parent)
@@ -40,6 +41,12 @@ KateExternalTool *KateToolRunner::tool() const
 
 void KateToolRunner::run()
 {
+    // always only execute the tool from PATH
+    const auto fullExecutable = QStandardPaths::findExecutable(m_tool->executable);
+    if (fullExecutable.isEmpty()) {
+        return;
+    }
+
     if (!m_tool->workingDir.isEmpty()) {
         m_process->setWorkingDirectory(m_tool->workingDir);
     } else if (m_view) {
@@ -72,7 +79,7 @@ void KateToolRunner::run()
     });
 
     const QStringList args = KShell::splitArgs(m_tool->arguments);
-    m_process->start(m_tool->executable, args);
+    m_process->start(fullExecutable, args);
 }
 
 void KateToolRunner::waitForFinished()
diff --git a/addons/gdbplugin/debugview.cpp b/addons/gdbplugin/debugview.cpp
index 9505daa25..d8c868d7a 100644
--- a/addons/gdbplugin/debugview.cpp
+++ b/addons/gdbplugin/debugview.cpp
@@ -12,7 +12,9 @@
 #include "debugview.h"
 
 #include <QFile>
+#include <QFileInfo>
 #include <QRegularExpression>
+#include <QStandardPaths>
 #include <QTimer>
 
 #include <KLocalizedString>
@@ -48,7 +50,20 @@ void DebugView::runDebugger(const GDBTargetConf &conf, const QStringList &ioFifo
     if (conf.executable.isEmpty()) {
         return;
     }
+
     m_targetConf = conf;
+
+    // no chance if no debugger configured
+    if (m_targetConf.gdbCmd.isEmpty()) {
+        return;
+    }
+
+    // only run debugger from PATH or the absolute executable path we specified
+    const auto fullExecutable = QFileInfo(m_targetConf.gdbCmd).isAbsolute() ? m_targetConf.gdbCmd : QStandardPaths::findExecutable(m_targetConf.gdbCmd);
+    if (fullExecutable.isEmpty()) {
+        return;
+    }
+
     if (ioFifos.size() == 3) {
         m_ioPipeString = QStringLiteral("< %1 1> %2 2> %3").arg(ioFifos[0], ioFifos[1], ioFifos[2]);
     }
@@ -69,7 +84,7 @@ void DebugView::runDebugger(const GDBTargetConf &conf, const QStringList &ioFifo
 
         connect(&m_debugProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &DebugView::slotDebugFinished);
 
-        m_debugProcess.start(m_targetConf.gdbCmd, QStringList());
+        m_debugProcess.start(fullExecutable, QStringList());
 
         m_nextCommands << QStringLiteral("set pagination off");
         m_state = ready;
diff --git a/addons/git-blame/commitfilesview.cpp b/addons/git-blame/commitfilesview.cpp
index 26e484a4a..667b423b2 100644
--- a/addons/git-blame/commitfilesview.cpp
+++ b/addons/git-blame/commitfilesview.cpp
@@ -263,7 +263,9 @@ static void createFileTree(QStandardItem *parent, const QString &basePath, const
 static std::optional<QString> getGitCmdOutput(const QString &workDir, const QStringList &args)
 {
     QProcess git;
-    setupGitProcess(git, workDir, args);
+    if (!setupGitProcess(git, workDir, args)) {
+        return {};
+    }
     git.start(QProcess::ReadOnly);
     if (git.waitForStarted() && git.waitForFinished(-1)) {
         if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
@@ -365,9 +367,12 @@ void CommitDiffTreeView::openCommit(const QString &hash, const QString &filePath
     m_commitHash = hash;
 
     QProcess *git = new QProcess(this);
-    setupGitProcess(*git,
-                    QFileInfo(filePath).absolutePath(),
-                    {QStringLiteral("show"), hash, QStringLiteral("--numstat"), QStringLiteral("--pretty=oneline"), QStringLiteral("-z")});
+    if (!setupGitProcess(*git,
+                         QFileInfo(filePath).absolutePath(),
+                         {QStringLiteral("show"), hash, QStringLiteral("--numstat"), QStringLiteral("--pretty=oneline"), QStringLiteral("-z")})) {
+        delete git;
+        return;
+    }
     connect(git, &QProcess::finished, this, [this, git, filePath](int e, QProcess::ExitStatus s) {
         git->deleteLater();
         if (e != 0 || s != QProcess::NormalExit) {
@@ -440,7 +445,9 @@ void CommitDiffTreeView::showDiff(const QModelIndex &idx)
 {
     const QString file = idx.data(FileItem::Path).toString();
     QProcess git;
-    setupGitProcess(git, m_gitDir, {QStringLiteral("show"), m_commitHash, QStringLiteral("--"), file});
+    if (!setupGitProcess(git, m_gitDir, {QStringLiteral("show"), m_commitHash, QStringLiteral("--"), file})) {
+        return;
+    }
     git.start(QProcess::ReadOnly);
 
     if (git.waitForStarted() && git.waitForFinished(-1)) {
diff --git a/addons/git-blame/kategitblameplugin.cpp b/addons/git-blame/kategitblameplugin.cpp
index d0354cc75..ae0f8c106 100644
--- a/addons/git-blame/kategitblameplugin.cpp
+++ b/addons/git-blame/kategitblameplugin.cpp
@@ -255,7 +255,9 @@ void KateGitBlamePluginView::startBlameProcess(const QUrl &url)
     QDir dir{url.toLocalFile()};
     dir.cdUp();
 
-    setupGitProcess(m_blameInfoProc, dir.absolutePath(), {QStringLiteral("blame"), QStringLiteral("-p"), QStringLiteral("./%1").arg(fileName)});
+    if (!setupGitProcess(m_blameInfoProc, dir.absolutePath(), {QStringLiteral("blame"), QStringLiteral("-p"), QStringLiteral("./%1").arg(fileName)})) {
+        return;
+    }
     m_blameInfoProc.start(QIODevice::ReadOnly);
     m_blameUrl = url;
 }
@@ -270,7 +272,9 @@ void KateGitBlamePluginView::startShowProcess(const QUrl &url, const QString &ha
     QDir dir{url.toLocalFile()};
     dir.cdUp();
 
-    setupGitProcess(m_showProc, dir.absolutePath(), {QStringLiteral("show"), hash, QStringLiteral("--numstat")});
+    if (!setupGitProcess(m_showProc, dir.absolutePath(), {QStringLiteral("show"), hash, QStringLiteral("--numstat")})) {
+        return;
+    }
     m_showProc.start(QIODevice::ReadOnly);
 }
 
diff --git a/addons/kate-ctags/gotosymbolmodel.cpp b/addons/kate-ctags/gotosymbolmodel.cpp
index 6c547e379..0c116090f 100644
--- a/addons/kate-ctags/gotosymbolmodel.cpp
+++ b/addons/kate-ctags/gotosymbolmodel.cpp
@@ -8,6 +8,7 @@
 #include <KLocalizedString>
 #include <QDebug>
 #include <QProcess>
+#include <QStandardPaths>
 
 GotoSymbolModel::GotoSymbolModel(QObject *parent)
     : QAbstractTableModel(parent)
@@ -58,16 +59,24 @@ void GotoSymbolModel::refresh(const QString &filePath)
     m_rows.clear();
     endResetModel();
 
+    // only use ctags from PATH
+    static const auto fullExecutablePath = QStandardPaths::findExecutable(QStringLiteral("ctags"));
+    if (fullExecutablePath.isEmpty()) {
+        beginResetModel();
+        m_rows.append(SymbolItem{i18n("CTags executable not found."), -1, QIcon()});
+        endResetModel();
+        return;
+    }
+
     QProcess p;
-    p.start(QStringLiteral("ctags"), {QStringLiteral("-x"), QStringLiteral("--_xformat=%{name}%{signature}\t%{kind}\t%{line}"), filePath});
+    p.start(fullExecutablePath, {QStringLiteral("-x"), QStringLiteral("--_xformat=%{name}%{signature}\t%{kind}\t%{line}"), filePath});
 
     QByteArray out;
     if (p.waitForFinished()) {
         out = p.readAllStandardOutput();
     } else {
-        qWarning() << "Ctags failed";
         beginResetModel();
-        m_rows.append(SymbolItem{i18n("CTags executable not found."), -1, QIcon()});
+        m_rows.append(SymbolItem{i18n("CTags executable failed to execute."), -1, QIcon()});
         endResetModel();
         return;
     }
diff --git a/addons/project/comparebranchesview.cpp b/addons/project/comparebranchesview.cpp
index 48d1d2633..7cf585f66 100644
--- a/addons/project/comparebranchesview.cpp
+++ b/addons/project/comparebranchesview.cpp
@@ -158,7 +158,9 @@ void CompareBranchesView::showDiff(const QModelIndex &idx)
 {
     auto file = idx.data(Qt::UserRole).toString().remove(m_gitDir + QLatin1Char('/'));
     QProcess git;
-    setupGitProcess(git, m_gitDir, {QStringLiteral("diff"), QStringLiteral("%1...%2").arg(m_fromBr).arg(m_toBr), QStringLiteral("--"), file});
+    if (!setupGitProcess(git, m_gitDir, {QStringLiteral("diff"), QStringLiteral("%1...%2").arg(m_fromBr).arg(m_toBr), QStringLiteral("--"), file})) {
+        return;
+    }
     git.start(QProcess::ReadOnly);
 
     if (git.waitForStarted() && git.waitForFinished(-1)) {
diff --git a/addons/project/filehistorywidget.cpp b/addons/project/filehistorywidget.cpp
index 626016a6b..14857e178 100644
--- a/addons/project/filehistorywidget.cpp
+++ b/addons/project/filehistorywidget.cpp
@@ -231,9 +231,12 @@ FileHistoryWidget::~FileHistoryWidget()
 // git log --format=%H%n%aN%n%aE%n%at%n%ct%n%P%n%B --author-date-order
 void FileHistoryWidget::getFileHistory(const QString &file)
 {
-    setupGitProcess(m_git,
-                    QFileInfo(file).absolutePath(),
-                    {QStringLiteral("log"), QStringLiteral("--format=%H%n%aN%n%aE%n%at%n%ct%n%P%n%B"), QStringLiteral("-z"), file});
+    if (!setupGitProcess(m_git,
+                         QFileInfo(file).absolutePath(),
+                         {QStringLiteral("log"), QStringLiteral("--format=%H%n%aN%n%aE%n%at%n%ct%n%P%n%B"), QStringLiteral("-z"), file})) {
+        Q_EMIT errorMessage(i18n("Failed to get file history: git executable not found in PATH"), true);
+        return;
+    }
 
     connect(&m_git, &QProcess::readyReadStandardOutput, this, [this] {
         auto commits = parseCommits(m_git.readAllStandardOutput().split(0x00));
@@ -258,7 +261,10 @@ void FileHistoryWidget::itemClicked(const QModelIndex &idx)
 
     const auto commit = idx.data(CommitListModel::CommitRole).value<Commit>();
 
-    setupGitProcess(git, fi.absolutePath(), {QStringLiteral("show"), QString::fromUtf8(commit.hash), QStringLiteral("--"), m_file});
+    if (!setupGitProcess(git, fi.absolutePath(), {QStringLiteral("show"), QString::fromUtf8(commit.hash), QStringLiteral("--"), m_file})) {
+        return;
+    }
+
     git.start(QProcess::ReadOnly);
     if (git.waitForStarted() && git.waitForFinished(-1)) {
         if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
diff --git a/addons/project/git/gitutils.cpp b/addons/project/git/gitutils.cpp
index ea8dd8823..8b494c16f 100644
--- a/addons/project/git/gitutils.cpp
+++ b/addons/project/git/gitutils.cpp
@@ -15,7 +15,10 @@
 bool GitUtils::isGitRepo(const QString &repo)
 {
     QProcess git;
-    setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--is-inside-work-tree")});
+    if (!setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--is-inside-work-tree")})) {
+        return false;
+    }
+
     git.start(QProcess::ReadOnly);
     if (git.waitForStarted() && git.waitForFinished(-1)) {
         return git.readAll().trimmed() == "true";
@@ -27,7 +30,10 @@ std::optional<QString> GitUtils::getDotGitPath(const QString &repo)
 {
     /* This call is intentionally blocking because we need git path for everything else */
     QProcess git;
-    setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--absolute-git-dir")});
+    if (!setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--absolute-git-dir")})) {
+        return std::nullopt;
+    }
+
     git.start(QProcess::ReadOnly);
     if (git.waitForStarted() && git.waitForFinished(-1)) {
         if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
@@ -57,7 +63,10 @@ QString GitUtils::getCurrentBranchName(const QString &repo)
 
     for (int i = 0; i < 3; ++i) {
         QProcess git;
-        setupGitProcess(git, repo, argsList[i]);
+        if (!setupGitProcess(git, repo, argsList[i])) {
+            return QString();
+        }
+
         git.start(QProcess::ReadOnly);
         if (git.waitForStarted() && git.waitForFinished(-1)) {
             if (git.exitStatus() == QProcess::NormalExit && git.exitCode() == 0) {
@@ -73,7 +82,10 @@ QString GitUtils::getCurrentBranchName(const QString &repo)
 GitUtils::CheckoutResult GitUtils::checkoutBranch(const QString &repo, const QString &branch)
 {
     QProcess git;
-    setupGitProcess(git, repo, {QStringLiteral("checkout"), branch});
+    if (!setupGitProcess(git, repo, {QStringLiteral("checkout"), branch})) {
+        return CheckoutResult{};
+    }
+
     git.start(QProcess::ReadOnly);
     CheckoutResult res;
     res.branch = branch;
@@ -91,7 +103,11 @@ GitUtils::CheckoutResult GitUtils::checkoutNewBranch(const QString &repo, const
     if (!fromBranch.isEmpty()) {
         args.append(fromBranch);
     }
-    setupGitProcess(git, repo, args);
+
+    if (!setupGitProcess(git, repo, args)) {
+        return CheckoutResult{};
+    }
+
     git.start(QProcess::ReadOnly);
     CheckoutResult res;
     res.branch = newBranch;
@@ -132,7 +148,10 @@ QVector<GitUtils::Branch> GitUtils::getAllBranchesAndTags(const QString &repo, R
         args.append(QStringLiteral("--sort=-taggerdate"));
     }
 
-    setupGitProcess(git, repo, args);
+    if (!setupGitProcess(git, repo, args)) {
+        return {};
+    }
+
     git.start(QProcess::ReadOnly);
     QVector<Branch> branches;
     if (git.waitForStarted() && git.waitForFinished(-1)) {
@@ -166,7 +185,10 @@ std::pair<QString, QString> GitUtils::getLastCommitMessage(const QString &repo)
 {
     // git log -1 --pretty=%B
     QProcess git;
-    setupGitProcess(git, repo, {QStringLiteral("log"), QStringLiteral("-1"), QStringLiteral("--pretty=%B")});
+    if (!setupGitProcess(git, repo, {QStringLiteral("log"), QStringLiteral("-1"), QStringLiteral("--pretty=%B")})) {
+        return {};
+    }
+
     git.start(QProcess::ReadOnly);
     if (git.waitForStarted() && git.waitForFinished(-1)) {
         if (git.exitCode() != 0 || git.exitStatus() != QProcess::NormalExit) {
@@ -197,7 +219,10 @@ GitUtils::Result GitUtils::deleteBranches(const QStringList &branches, const QSt
     args << branches;
 
     QProcess git;
-    setupGitProcess(git, repo, args);
+    if (!setupGitProcess(git, repo, args)) {
+        return {};
+    }
+
     git.start(QProcess::ReadOnly);
     if (git.waitForStarted() && git.waitForFinished(-1)) {
         QString out = QString::fromLatin1(git.readAllStandardError()) + QString::fromLatin1(git.readAllStandardOutput());
diff --git a/addons/project/gitwidget.cpp b/addons/project/gitwidget.cpp
index 2b19781c0..77499dad8 100644
--- a/addons/project/gitwidget.cpp
+++ b/addons/project/gitwidget.cpp
@@ -514,8 +514,9 @@ void GitWidget::launchExternalDiffTool(const QString &file, bool staged)
     args.append(file);
 
     QProcess git;
-    setupGitProcess(git, m_gitPath, args);
-    git.startDetached();
+    if (setupGitProcess(git, m_gitPath, args)) {
+        git.startDetached();
+    }
 }
 
 void GitWidget::commitChanges(const QString &msg, const QString &desc, bool signOff, bool amend)
@@ -745,7 +746,12 @@ void GitWidget::branchCompareFiles(const QString &from, const QString &to)
     auto args = QStringList{QStringLiteral("diff"), QStringLiteral("%1...%2").arg(from).arg(to), QStringLiteral("--name-status")};
 
     QProcess git;
-    setupGitProcess(git, m_gitPath, args);
+
+    // early out if we can't find git
+    if (!setupGitProcess(git, m_gitPath, args)) {
+        return;
+    }
+
     git.start(QProcess::ReadOnly);
     if (git.waitForStarted() && git.waitForFinished(-1)) {
         if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
@@ -767,7 +773,12 @@ void GitWidget::branchCompareFiles(const QString &from, const QString &to)
 
     // get --num-stat
     args = QStringList{QStringLiteral("diff"), QStringLiteral("%1...%2").arg(from).arg(to), QStringLiteral("--numstat"), QStringLiteral("-z")};
-    setupGitProcess(git, m_gitPath, args);
+
+    // early out if we can't find git
+    if (!setupGitProcess(git, m_gitPath, args)) {
+        return;
+    }
+
     git.start(QProcess::ReadOnly);
     if (git.waitForStarted() && git.waitForFinished(-1)) {
         if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
diff --git a/addons/project/kateprojectindex.cpp b/addons/project/kateprojectindex.cpp
index a7d9ec9c1..9fc5b64cb 100644
--- a/addons/project/kateprojectindex.cpp
+++ b/addons/project/kateprojectindex.cpp
@@ -9,6 +9,7 @@
 
 #include <QDir>
 #include <QProcess>
+#include <QStandardPaths>
 
 /**
  * include ctags reading
@@ -73,6 +74,12 @@ void KateProjectIndex::loadCtags(const QStringList &files, const QVariantMap &ct
      */
     m_ctagsIndexFile->close();
 
+    // only use ctags from PATH
+    static const auto fullExecutablePath = QStandardPaths::findExecutable(QStringLiteral("ctags"));
+    if (fullExecutablePath.isEmpty()) {
+        return;
+    }
+
     /**
      * try to run ctags for all files in this project
      * output to our ctags index file
@@ -85,7 +92,7 @@ void KateProjectIndex::loadCtags(const QStringList &files, const QVariantMap &ct
     for (const QVariant &optVariant : opts) {
         args << optVariant.toString();
     }
-    ctags.start(QStringLiteral("ctags"), args);
+    ctags.start(fullExecutablePath, args);
     if (!ctags.waitForStarted()) {
         return;
     }
diff --git a/addons/project/kateprojectinfoviewcodeanalysis.cpp b/addons/project/kateprojectinfoviewcodeanalysis.cpp
index 21cd26a84..23b82c45e 100644
--- a/addons/project/kateprojectinfoviewcodeanalysis.cpp
+++ b/addons/project/kateprojectinfoviewcodeanalysis.cpp
@@ -13,6 +13,7 @@
 
 #include <QFileInfo>
 #include <QHBoxLayout>
+#include <QStandardPaths>
 #include <QToolTip>
 #include <QVBoxLayout>
 
@@ -134,14 +135,18 @@ void KateProjectInfoViewCodeAnalysis::slotStartStopClicked()
     connect(m_analyzer, &QProcess::readyRead, this, &KateProjectInfoViewCodeAnalysis::slotReadyRead);
     connect(m_analyzer, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &KateProjectInfoViewCodeAnalysis::finished);
 
-    m_analyzer->start(m_analysisTool->path(), m_analysisTool->arguments());
+    // ensure we only run the code analyzer from PATH
+    const QString fullExecutable = QStandardPaths::findExecutable(m_analysisTool->path());
+    if (!fullExecutable.isEmpty()) {
+        m_analyzer->start(fullExecutable, m_analysisTool->arguments());
+    }
 
     if (m_messageWidget) {
         delete m_messageWidget;
         m_messageWidget = nullptr;
     }
 
-    if (!m_analyzer->waitForStarted()) {
+    if (fullExecutable.isEmpty() || !m_analyzer->waitForStarted()) {
         m_messageWidget = new KMessageWidget(this);
         m_messageWidget->setCloseButtonVisible(true);
         m_messageWidget->setMessageType(KMessageWidget::Warning);
diff --git a/addons/project/kateprojectworker.cpp b/addons/project/kateprojectworker.cpp
index d1979d1ec..831dae89b 100644
--- a/addons/project/kateprojectworker.cpp
+++ b/addons/project/kateprojectworker.cpp
@@ -18,6 +18,7 @@
 #include <QRegularExpression>
 #include <QSet>
 #include <QSettings>
+#include <QStandardPaths>
 #include <QThread>
 #include <QTime>
 #include <QtConcurrent>
@@ -442,10 +443,12 @@ QVector<QString> KateProjectWorker::filesFromGit(const QDir &dir, bool recursive
 
 QVector<QString> KateProjectWorker::gitFiles(const QDir &dir, bool recursive, const QStringList &args)
 {
+    QVector<QString> files;
     QProcess git;
-    setupGitProcess(git, dir.absolutePath(), args);
+    if (!setupGitProcess(git, dir.absolutePath(), args)) {
+        return files;
+    }
     git.start(QProcess::ReadOnly);
-    QVector<QString> files;
     if (!git.waitForStarted() || !git.waitForFinished(-1)) {
         return files;
     }
@@ -466,13 +469,18 @@ QVector<QString> KateProjectWorker::gitFiles(const QDir &dir, bool recursive, co
 
 QVector<QString> KateProjectWorker::filesFromMercurial(const QDir &dir, bool recursive)
 {
+    // only use version control from PATH
     QVector<QString> files;
+    static const auto fullExecutablePath = QStandardPaths::findExecutable(QStringLiteral("hg"));
+    if (fullExecutablePath.isEmpty()) {
+        return files;
+    }
 
     QProcess hg;
     hg.setWorkingDirectory(dir.absolutePath());
     QStringList args;
     args << QStringLiteral("manifest") << QStringLiteral(".");
-    hg.start(QStringLiteral("hg"), args, QProcess::ReadOnly);
+    hg.start(fullExecutablePath, args, QProcess::ReadOnly);
     if (!hg.waitForStarted() || !hg.waitForFinished(-1)) {
         return files;
     }
@@ -493,7 +501,12 @@ QVector<QString> KateProjectWorker::filesFromMercurial(const QDir &dir, bool rec
 
 QVector<QString> KateProjectWorker::filesFromSubversion(const QDir &dir, bool recursive)
 {
+    // only use version control from PATH
     QVector<QString> files;
+    static const auto fullExecutablePath = QStandardPaths::findExecutable(QStringLiteral("svn"));
+    if (fullExecutablePath.isEmpty()) {
+        return files;
+    }
 
     QProcess svn;
     svn.setWorkingDirectory(dir.absolutePath());
@@ -504,7 +517,7 @@ QVector<QString> KateProjectWorker::filesFromSubversion(const QDir &dir, bool re
     } else {
         args << QStringLiteral("--depth=files");
     }
-    svn.start(QStringLiteral("svn"), args, QProcess::ReadOnly);
+    svn.start(fullExecutablePath, args, QProcess::ReadOnly);
     if (!svn.waitForStarted() || !svn.waitForFinished(-1)) {
         return files;
     }
@@ -555,18 +568,21 @@ QVector<QString> KateProjectWorker::filesFromSubversion(const QDir &dir, bool re
 
 QVector<QString> KateProjectWorker::filesFromDarcs(const QDir &dir, bool recursive)
 {
+    // only use version control from PATH
     QVector<QString> files;
+    static const auto fullExecutablePath = QStandardPaths::findExecutable(QStringLiteral("darcs"));
+    if (fullExecutablePath.isEmpty()) {
+        return files;
+    }
 
-    const QString cmd = QStringLiteral("darcs");
     QString root;
-
     {
         QProcess darcs;
         darcs.setWorkingDirectory(dir.absolutePath());
         QStringList args;
         args << QStringLiteral("list") << QStringLiteral("repo");
 
-        darcs.start(cmd, args, QProcess::ReadOnly);
+        darcs.start(fullExecutablePath, args, QProcess::ReadOnly);
 
         if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) {
             return files;
@@ -590,7 +606,7 @@ QVector<QString> KateProjectWorker::filesFromDarcs(const QDir &dir, bool recursi
         darcs.setWorkingDirectory(dir.absolutePath());
         args << QStringLiteral("list") << QStringLiteral("files") << QStringLiteral("--no-directories") << QStringLiteral("--pending");
 
-        darcs.start(cmd, args, QProcess::ReadOnly);
+        darcs.start(fullExecutablePath, args, QProcess::ReadOnly);
 
         if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) {
             return files;
diff --git a/addons/project/stashdialog.cpp b/addons/project/stashdialog.cpp
index c623182a8..bddedf709 100644
--- a/addons/project/stashdialog.cpp
+++ b/addons/project/stashdialog.cpp
@@ -32,6 +32,8 @@
 
 #include <kfts_fuzzy_match.h>
 
+#include <gitprocess.h>
+
 constexpr int StashIndexRole = Qt::UserRole + 2;
 
 class StashFilterModel final : public QSortFilterProxyModel
@@ -218,11 +220,10 @@ void StashDialog::slotReturnPressed()
     hide();
 }
 
-QProcess *StashDialog::gitp()
+QProcess *StashDialog::gitp(const QStringList &arguments)
 {
     auto git = new QProcess(this);
-    git->setProgram(QStringLiteral("git"));
-    git->setWorkingDirectory(m_gitPath);
+    setupGitProcess(*git, m_gitPath, arguments);
     return git;
 }
 
@@ -242,7 +243,7 @@ void StashDialog::stash(bool keepIndex, bool includeUntracked)
         args.append(m_lineEdit.text());
     }
 
-    auto git = gitp();
+    auto git = gitp(args);
     connect(git, &QProcess::finished, this, [this, git](int exitCode, QProcess::ExitStatus es) {
         if (es != QProcess::NormalExit || exitCode != 0) {
             qWarning() << git->errorString();
@@ -253,14 +254,12 @@ void StashDialog::stash(bool keepIndex, bool includeUntracked)
         Q_EMIT done();
         git->deleteLater();
     });
-    git->setArguments(args);
     git->start(QProcess::ReadOnly);
 }
 
 void StashDialog::getStashList()
 {
-    auto git = gitp();
-    git->setArguments({QStringLiteral("stash"), QStringLiteral("list")});
+    auto git = gitp({QStringLiteral("stash"), QStringLiteral("list")});
     git->start(QProcess::ReadOnly);
 
     QList<QByteArray> stashList;
@@ -293,11 +292,11 @@ void StashDialog::getStashList()
 
 void StashDialog::popStash(const QByteArray &index, const QString &command)
 {
-    auto git = gitp();
     QStringList args{QStringLiteral("stash"), command};
     if (!index.isEmpty()) {
         args.append(QString::fromUtf8(index));
     }
+    auto git = gitp(args);
 
     connect(git, &QProcess::finished, this, [this, command, git](int exitCode, QProcess::ExitStatus es) {
         if (es != QProcess::NormalExit || exitCode != 0) {
@@ -320,7 +319,6 @@ void StashDialog::popStash(const QByteArray &index, const QString &command)
         Q_EMIT done();
         git->deleteLater();
     });
-    git->setArguments(args);
     git->start(QProcess::ReadOnly);
 }
 
@@ -339,9 +337,8 @@ void StashDialog::showStash(const QByteArray &index)
     if (index.isEmpty()) {
         return;
     }
-    auto git = gitp();
 
-    QStringList args{QStringLiteral("stash"), QStringLiteral("show"), QStringLiteral("-p"), QString::fromUtf8(index)};
+    auto git = gitp({QStringLiteral("stash"), QStringLiteral("show"), QStringLiteral("-p"), QString::fromUtf8(index)});
 
     connect(git, &QProcess::finished, this, [this, git](int exitCode, QProcess::ExitStatus es) {
         if (es != QProcess::NormalExit || exitCode != 0) {
@@ -353,6 +350,5 @@ void StashDialog::showStash(const QByteArray &index)
         git->deleteLater();
     });
 
-    git->setArguments(args);
     git->start(QProcess::ReadOnly);
 }
diff --git a/addons/project/stashdialog.h b/addons/project/stashdialog.h
index a18d42ab9..417690757 100644
--- a/addons/project/stashdialog.h
+++ b/addons/project/stashdialog.h
@@ -56,7 +56,7 @@ protected Q_SLOTS:
     void slotReturnPressed() override;
 
 private:
-    QProcess *gitp();
+    QProcess *gitp(const QStringList &arguments);
     void stash(bool keepIndex, bool includeUntracked);
     void getStashList();
     void popStash(const QByteArray &index, const QString &command = QStringLiteral("pop"));
diff --git a/addons/replicode/replicodeview.cpp b/addons/replicode/replicodeview.cpp
index 0199f46ce..7f70ee1ea 100644
--- a/addons/replicode/replicodeview.cpp
+++ b/addons/replicode/replicodeview.cpp
@@ -8,7 +8,9 @@
 
 #include "replicodeconfig.h"
 #include "replicodesettings.h"
+
 #include <QPushButton>
+#include <QStandardPaths>
 #include <QTemporaryFile>
 #include <QtGlobal>
 
@@ -116,7 +118,14 @@ void ReplicodeView::runReplicode()
     }
 
     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Replicode"));
+
     QString executorPath = config.readEntry<QString>("replicodePath", QString());
+
+    // ensure we only call replicode from PATH if not given as absolute path already
+    if (!executorPath.isEmpty() && !QFileInfo(executorPath).isAbsolute()) {
+        executorPath = QStandardPaths::findExecutable(executorPath);
+    }
+
     if (executorPath.isEmpty()) {
         QMessageBox::warning(m_mainWindow->window(),
                              i18nc("@title:window", "Replicode Executable Not Found"),
diff --git a/addons/xmlcheck/plugin_katexmlcheck.cpp b/addons/xmlcheck/plugin_katexmlcheck.cpp
index f1d52f3a7..3971550cd 100644
--- a/addons/xmlcheck/plugin_katexmlcheck.cpp
+++ b/addons/xmlcheck/plugin_katexmlcheck.cpp
@@ -304,10 +304,18 @@ bool PluginKateXMLCheckView::slotValidate()
     s << kv->document()->text();
     s.flush();
 
+    // ensure we only execute xmllint from PATH or application package
     QString exe = QStandardPaths::findExecutable(QStringLiteral("xmllint"));
     if (exe.isEmpty()) {
         exe = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, QStringLiteral("xmllint"));
     }
+    if (exe.isEmpty()) {
+        KMessageBox::error(nullptr,
+                           i18n("<b>Error:</b> Failed to find xmllint. Please make "
+                                "sure that xmllint is installed. It is part of libxml2."));
+        return false;
+    }
+
     // qDebug() << "exe=" <<exe;
     // 	// use catalogs for KDE docbook:
     // 	if( ! getenv("XML_CATALOG_FILES") ) {
diff --git a/kate/katefileactions.cpp b/kate/katefileactions.cpp
index c56c7e1c8..09a23686c 100644
--- a/kate/katefileactions.cpp
+++ b/kate/katefileactions.cpp
@@ -23,6 +23,7 @@
 #include <QDebug>
 #include <QInputDialog>
 #include <QProcess>
+#include <QStandardPaths>
 #include <QUrl>
 
 void KateFileActions::copyFilePathToClipboard(KTextEditor::Document *doc)
@@ -137,17 +138,13 @@ void KateFileActions::deleteDocumentFile(QWidget *parent, KTextEditor::Document
     }
 }
 
-QStringList KateFileActions::supportedDiffTools()
+QVector<std::pair<QString, QString>> KateFileActions::supportedDiffTools()
 {
-    // LATER: check for program existence and set some boolean value accordingly
-    // Can this be even done in an easy way when we don't use the absolute path to the executable?
-    // See https://stackoverflow.com/questions/42444055/how-to-check-if-a-program-exists-in-path-using-qt
-
-    QStringList resultList;
-    resultList.push_back(QStringLiteral("kdiff3"));
-    resultList.push_back(QStringLiteral("kompare"));
-    resultList.push_back(QStringLiteral("meld"));
-
+    // query once if the tools are there in the path and store that
+    // we will disable the actions for the tools not found
+    static QVector<std::pair<QString, QString>> resultList{{QStringLiteral("kdiff3"), QStandardPaths::findExecutable(QStringLiteral("kdiff3"))},
+                                                           {QStringLiteral("kompare"), QStandardPaths::findExecutable(QStringLiteral("kompare"))},
+                                                           {QStringLiteral("meld"), QStandardPaths::findExecutable(QStringLiteral("meld"))}};
     return resultList;
 }
 
diff --git a/kate/katefileactions.h b/kate/katefileactions.h
index 524d81097..77cc5b0bf 100644
--- a/kate/katefileactions.h
+++ b/kate/katefileactions.h
@@ -51,9 +51,9 @@ void openFilePropertiesDialog(KTextEditor::Document *document);
 void deleteDocumentFile(QWidget *parent, KTextEditor::Document *document);
 
 /**
- * @returns a list of supported diff tools (names of the executables)
+ * @returns a list of supported diff tools (names of the executables + paths to them, empty if not found in PATH)
  */
-QStringList supportedDiffTools();
+QVector<std::pair<QString, QString>> supportedDiffTools();
 
 /**
  * Runs an external program to compare the underlying files of two given documents.
diff --git a/kate/katemwmodonhddialog.cpp b/kate/katemwmodonhddialog.cpp
index e0041d858..d7c79e4d4 100644
--- a/kate/katemwmodonhddialog.cpp
+++ b/kate/katemwmodonhddialog.cpp
@@ -22,6 +22,7 @@
 #include <QHeaderView>
 #include <QLabel>
 #include <QPushButton>
+#include <QStandardPaths>
 #include <QStyle>
 #include <QTemporaryFile>
 #include <QTextStream>
@@ -52,6 +53,7 @@ public:
 
 KateMwModOnHdDialog::KateMwModOnHdDialog(DocVector docs, QWidget *parent, const char *name)
     : QDialog(parent)
+    , m_fullDiffPath(QStandardPaths::findExecutable(QStringLiteral("diff")))
     , m_proc(nullptr)
     , m_diffFile(nullptr)
     , m_blockAddDocument(false)
@@ -108,6 +110,7 @@ KateMwModOnHdDialog::KateMwModOnHdDialog(DocVector docs, QWidget *parent, const
              "file for the selected document, and shows the difference with the "
              "default application. Requires diff(1)."));
     hb->addWidget(btnDiff);
+    btnDiff->setEnabled(!m_fullDiffPath.isEmpty());
     connect(btnDiff, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotDiff);
 
     // Dialog buttons
@@ -288,9 +291,10 @@ void KateMwModOnHdDialog::slotDiff()
     m_diffFile->open();
 
     // Start a KProcess that creates a diff
+    // We use the full path to don't launch some random "diff" in current working directory
     m_proc = new KProcess(this);
     m_proc->setOutputChannelMode(KProcess::MergedChannels);
-    *m_proc << QStringLiteral("diff") << QStringLiteral("-ub") << QStringLiteral("-") << doc->url().toLocalFile();
+    *m_proc << m_fullDiffPath << QStringLiteral("-ub") << QStringLiteral("-") << doc->url().toLocalFile();
     connect(m_proc, &KProcess::readyRead, this, &KateMwModOnHdDialog::slotDataAvailable);
     connect(m_proc, static_cast<void (KProcess::*)(int, QProcess::ExitStatus)>(&KProcess::finished), this, &KateMwModOnHdDialog::slotPDone);
 
diff --git a/kate/katemwmodonhddialog.h b/kate/katemwmodonhddialog.h
index 11c09eab7..6fa245726 100644
--- a/kate/katemwmodonhddialog.h
+++ b/kate/katemwmodonhddialog.h
@@ -51,6 +51,7 @@ private:
     class QTreeWidget *twDocuments;
     class QDialogButtonBox *dlgButtons;
     class QPushButton *btnDiff;
+    QString m_fullDiffPath;
     KProcess *m_proc;
     QTemporaryFile *m_diffFile;
     QStringList m_stateTexts;
diff --git a/kate/kateviewspace.cpp b/kate/kateviewspace.cpp
index dba2fb973..af3bb8d34 100644
--- a/kate/kateviewspace.cpp
+++ b/kate/kateviewspace.cpp
@@ -678,8 +678,11 @@ void KateViewSpace::showContextMenu(int idx, const QPoint &globalPos)
 
     if (mCompareWithActive->isEnabled()) {
         for (auto &&diffTool : KateFileActions::supportedDiffTools()) {
-            QAction *compareAction = mCompareWithActive->addAction(diffTool);
-            compareAction->setData(diffTool);
+            QAction *compareAction = mCompareWithActive->addAction(diffTool.first);
+
+            // we use the full path to safely execute the tool, disable action if no full path => tool not found
+            compareAction->setData(diffTool.second);
+            compareAction->setEnabled(!diffTool.second.isEmpty());
         }
     }
 
diff --git a/shared/gitprocess.h b/shared/gitprocess.h
index 47b98b696..b0d79fac6 100644
--- a/shared/gitprocess.h
+++ b/shared/gitprocess.h
@@ -7,6 +7,7 @@
 #pragma once
 
 #include <QProcess>
+#include <QStandardPaths>
 
 /**
  * small helper function to setup a QProcess based "git" command.
@@ -17,10 +18,20 @@
  * @param process process to setup for git
  * @param workingDirectory working directory to use for process
  * @param arguments arguments to pass to git
+ * @return could set setup the process or did that fail, e.g. because the git executable is not available?
  */
-inline void setupGitProcess(QProcess &process, const QString &workingDirectory, const QStringList &arguments)
+inline bool setupGitProcess(QProcess &process, const QString &workingDirectory, const QStringList &arguments)
 {
-    process.setProgram(QStringLiteral("git"));
+    // only use git from PATH
+    static const auto gitExecutable = QStandardPaths::findExecutable(QStringLiteral("git"));
+    if (gitExecutable.isEmpty()) {
+        // ensure we have no valid QProcess setup
+        process.setProgram(QString());
+        return false;
+    }
+
+    // setup program and arguments, ensure we do run git in the right working directory
+    process.setProgram(gitExecutable);
     process.setWorkingDirectory(workingDirectory);
     process.setArguments(arguments);
 
@@ -37,4 +48,5 @@ inline void setupGitProcess(QProcess &process, const QString &workingDirectory,
     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
     env.insert(QStringLiteral("GIT_OPTIONAL_LOCKS"), QStringLiteral("0"));
     process.setProcessEnvironment(env);
+    return true;
 }
-- 
GitLab

