• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 27731088863

18 Jun 2026 01:39AM UTC coverage: 55.027% (+0.09%) from 54.938%
27731088863

Pull #1570

github

web-flow
Merge 47a7a1c36 into 049c10a52
Pull Request #1570: fix: P1 audit findings — executor crash, storemodel guard, getKeysFromFile, sort order, reencryptPath init

9 of 13 new or added lines in 5 files covered. (69.23%)

58 existing lines in 1 file now uncovered.

3678 of 6684 relevant lines covered (55.03%)

29.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.96
/src/executor.cpp
1
// SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "executor.h"
4
#include <QCoreApplication>
5
#include <QDir>
6
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
7
#include <QTextCodec>
8
#endif
9
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
10
#include <QStringDecoder>
11
#endif
12
#include <utility>
13

14
#ifdef QT_DEBUG
15
#include "debughelper.h"
16
#endif
17

18
/**
19
 * @brief Executor::Executor executes external applications
20
 * @param parent
21
 */
22
Executor::Executor(QObject *parent) : QObject(parent) {
49✔
23
  connect(&m_process,
49✔
24
          static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
25
              &QProcess::finished),
26
          this,
27
          static_cast<void (Executor::*)(int, QProcess::ExitStatus)>(
49✔
28
              &Executor::finished));
29
  connect(&m_process, &QProcess::started, this, &Executor::starting);
49✔
30
}
49✔
31

32
/**
33
 * @brief Executor::startProcess starts the internal process, handling WSL
34
 * prefixes.
35
 * @param app Executable path (may start with "wsl ").
36
 * @param args Arguments to pass to the executable.
37
 */
38
void Executor::startProcess(const QString &app, const QStringList &args) {
40✔
39
  if (app.startsWith("wsl ")) {
80✔
40
    QStringList wslArgs = args;
41
    QString actualApp = app;
42
    wslArgs.prepend(actualApp.remove(0, 4));
×
43
    m_process.start("wsl", wslArgs);
×
44
  } else {
45
    m_process.start(app, args);
40✔
46
  }
47
}
40✔
48

49
/**
50
 * @brief Executor::startProcessBlocking starts a given process, handling WSL
51
 * prefixes.
52
 * @param internal QProcess reference to start.
53
 * @param app Executable path (may start with "wsl ").
54
 * @param args Arguments to pass to the executable.
55
 */
56
void Executor::startProcessBlocking(QProcess &internal, const QString &app,
51✔
57
                                    const QStringList &args) {
58
  if (app.startsWith("wsl ")) {
102✔
59
    QStringList wslArgs = args;
60
    QString actualApp = app;
61
    wslArgs.prepend(actualApp.remove(0, 4));
×
62
    internal.start("wsl", wslArgs);
×
63
  } else {
64
    internal.start(app, args);
51✔
65
  }
66
}
51✔
67

68
/**
69
 * @brief Executor::executeNext consumes executable tasks from the queue
70
 */
71
void Executor::executeNext() {
79✔
72
  if (!running) {
79✔
73
    if (!m_execQueue.isEmpty()) {
75✔
74
      const execQueueItem &i = m_execQueue.head();
75
      running = true;
40✔
76
      if (!i.workingDir.isEmpty()) {
40✔
77
        m_process.setWorkingDirectory(i.workingDir);
32✔
78
      }
79
      startProcess(i.app, i.args);
40✔
80
      if (!i.input.isEmpty()) {
40✔
81
        if (!m_process.waitForStarted(-1)) {
19✔
82
#ifdef QT_DEBUG
83
          dbg() << "Process failed to start:" << i.id << " " << i.app;
84
#endif
85
          m_process.closeWriteChannel();
×
86
          running = false;
×
87
          m_execQueue.dequeue();
×
88
          executeNext();
×
89
          return;
×
90
        }
91
        QByteArray data = i.input.toUtf8();
19✔
92
        if (m_process.write(data) != data.length()) {
19✔
93
#ifdef QT_DEBUG
94
          dbg() << "Not all data written to process:" << i.id << " " << i.app;
95
#endif
96
        }
97
      }
98
      m_process.closeWriteChannel();
40✔
99
    }
100
  }
101
}
102

103
/**
104
 * @brief Executor::execute execute an app
105
 * @param id
106
 * @param app
107
 * @param args
108
 * @param readStdout
109
 * @param readStderr
110
 */
111
void Executor::execute(int id, const QString &app, const QStringList &args,
8✔
112
                       bool readStdout, bool readStderr) {
113
  execute(id, QString(), app, args, QString(), readStdout, readStderr);
16✔
114
}
8✔
115

116
/**
117
 * @brief Executor::execute executes an app from a workDir
118
 * @param id
119
 * @param workDir
120
 * @param app
121
 * @param args
122
 * @param readStdout
123
 * @param readStderr
124
 */
125
void Executor::execute(int id, const QString &workDir, const QString &app,
1✔
126
                       const QStringList &args, bool readStdout,
127
                       bool readStderr) {
128
  execute(id, workDir, app, args, QString(), readStdout, readStderr);
1✔
129
}
1✔
130

131
/**
132
 * @brief Executor::execute an app, takes input and presents it as stdin
133
 * @param id
134
 * @param app
135
 * @param args
136
 * @param input
137
 * @param readStdout
138
 * @param readStderr
139
 */
140
void Executor::execute(int id, const QString &app, const QStringList &args,
×
141
                       QString input, bool readStdout, bool readStderr) {
142
  execute(id, QString(), app, args, std::move(input), readStdout, readStderr);
×
143
}
×
144

145
/**
146
 * @brief Executor::execute  executes an app from a workDir, takes input and
147
 * presents it as stdin
148
 * @param id
149
 * @param workDir
150
 * @param app
151
 * @param args
152
 * @param input
153
 * @param readStdout
154
 * @param readStderr
155
 */
156
void Executor::execute(int id, const QString &workDir, const QString &app,
40✔
157
                       const QStringList &args, QString input, bool readStdout,
158
                       bool readStderr) {
159
  // Happens a lot if e.g. git binary is not set.
160
  // This will result in bogus "QProcess::FailedToStart" messages,
161
  // also hiding legitimate errors from the gpg commands.
162
  if (app.isEmpty()) {
40✔
163
#ifdef QT_DEBUG
164
    dbg() << "Trying to execute nothing...";
165
#endif
166
    return;
×
167
  }
168
  QString appPath = app;
169
  if (!appPath.startsWith("wsl ")) {
80✔
170
    appPath =
171
        QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(app);
120✔
172
  }
173
  m_execQueue.push_back(
40✔
174
      {id, appPath, args, std::move(input), readStdout, readStderr, workDir});
175
  executeNext();
40✔
176
}
80✔
177

178
/**
179
 * @brief decodes the input into a string assuming UTF-8 encoding.
180
 * If this fails (which is likely if it is not actually UTF-8)
181
 * it will then fall back to Qt's decoding function, which
182
 * will try based on BOM and if that fails fall back to local encoding.
183
 * This should not be needed in Qt6
184
 *
185
 * @param in input data
186
 * @return Input bytes decoded to string
187
 */
188
static auto decodeAssumingUtf8(const QByteArray &in) -> QString {
138✔
189
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
190
  QTextCodec *codec = QTextCodec::codecForName("UTF-8");
191
  QTextCodec::ConverterState state;
192
  QString out = codec->toUnicode(in.constData(), in.size(), &state);
193
  if (state.invalidChars == 0) {
194
    return out;
195
  }
196
  codec = QTextCodec::codecForUtfText(in);
197
  return codec->toUnicode(in);
198
#else
199
  auto converter = QStringDecoder(QStringDecoder::Utf8);
200
  QString out = converter(in);
138✔
201
  if (!converter.hasError()) {
138✔
202
    return out;
203
  }
204
  // Fallback if UTF-8 decoding failed - try system encoding
205
  auto fallback = QStringDecoder(QStringDecoder::System);
206
  return fallback(in);
×
207
#endif
208
}
209

210
/**
211
 * @brief Executor::executeBlocking blocking version of the executor,
212
 * takes input and presents it as stdin
213
 * @param app
214
 * @param args
215
 * @param input
216
 * @param process_out
217
 * @param process_err
218
 * @return
219
 *
220
 * Note: Returning error code instead of throwing to maintain compatibility
221
 * with the existing error handling pattern used throughout QtPass.
222
 */
223
auto Executor::runBlocking(QProcess &process, const QString &app,
51✔
224
                           const QStringList &args, const QString &input,
225
                           QString *process_out, QString *process_err) -> int {
226
  startProcessBlocking(process, app, args);
51✔
227
  if (!process.waitForStarted(-1)) {
51✔
228
#ifdef QT_DEBUG
229
    dbg() << "Process failed to start:" << app;
230
#endif
231
    return -1;
232
  }
233
  if (!input.isEmpty()) {
48✔
234
    QByteArray data = input.toUtf8();
235
    if (process.write(data) != data.length()) {
3✔
236
#ifdef QT_DEBUG
237
      dbg() << "Not all input written:" << app;
238
#endif
239
    }
240
  }
241
  // Always close stdin so a child blocking on EOF doesn't hang when no
242
  // input is written (these are one-shot blocking runs that never stream).
243
  process.closeWriteChannel();
48✔
244
  process.waitForFinished(-1);
48✔
245
  if (process.exitStatus() != QProcess::NormalExit) {
48✔
246
    // Process failed to start or crashed; return -1 to indicate error.
247
    // The calling code checks for non-zero exit codes for error handling.
248
    return -1;
249
  }
250
  if (process_out != nullptr) {
48✔
251
    *process_out = decodeAssumingUtf8(process.readAllStandardOutput());
92✔
252
  }
253
  if (process_err != nullptr) {
48✔
254
    *process_err = decodeAssumingUtf8(process.readAllStandardError());
48✔
255
  }
256
  return process.exitCode();
48✔
257
}
258

259
auto Executor::executeBlocking(const QString &app, const QStringList &args,
36✔
260
                               const QString &input, QString *process_out,
261
                               QString *process_err) -> int {
262
  QProcess internal;
36✔
263
  return runBlocking(internal, app, args, input, process_out, process_err);
72✔
264
}
36✔
265

266
/**
267
 * @brief Executor::executeBlocking blocking version of the executor
268
 * @param app
269
 * @param args
270
 * @param process_out
271
 * @param process_err
272
 * @return
273
 */
274
auto Executor::executeBlocking(const QString &app, const QStringList &args,
21✔
275
                               QString *process_out, QString *process_err)
276
    -> int {
277
  return executeBlocking(app, args, QString(), process_out, process_err);
42✔
278
}
279

280
/**
281
 * @brief Executor::executeBlocking blocking version with custom environment
282
 * @param env Environment variables to set
283
 * @param app Executable path
284
 * @param args Arguments
285
 * @param process_out Standard output
286
 * @param process_err Standard error
287
 * @return Exit code
288
 */
289
auto Executor::executeBlocking(const QProcessEnvironment &env,
15✔
290
                               const QString &app, const QStringList &args,
291
                               QString *process_out, QString *process_err)
292
    -> int {
293
  QProcess process;
15✔
294
  process.setProcessEnvironment(env);
15✔
295
  return runBlocking(process, app, args, QString(), process_out, process_err);
30✔
296
}
15✔
297

298
/**
299
 * @brief Executor::setEnvironment set environment variables
300
 * for executor processes
301
 * @param env
302
 */
303
void Executor::setEnvironment(const QProcessEnvironment &env) {
36✔
304
  m_process.setProcessEnvironment(env);
36✔
305
}
36✔
306

307
auto Executor::environment() const -> QProcessEnvironment {
14✔
308
  return m_process.processEnvironment();
14✔
309
}
310

311
/**
312
 * @brief Executor::cancelNext  cancels execution of first process in queue
313
 *                              if it's not already running
314
 *
315
 * @return  id of the cancelled process or -1 on error
316
 */
317
auto Executor::cancelNext() -> int {
2✔
318
  if (running || m_execQueue.isEmpty()) {
2✔
319
    return -1; // Return -1 to indicate no process was cancelled
320
               // (queue empty or currently executing).
321
  }
322
  return m_execQueue.dequeue().id;
×
323
}
324

325
/**
326
 * @brief Executor::finished called when an executed process finishes
327
 * @param exitCode
328
 * @param exitStatus
329
 */
330
void Executor::finished(int exitCode, QProcess::ExitStatus exitStatus) {
39✔
331
  execQueueItem i = m_execQueue.dequeue();
332
  running = false;
39✔
333
  if (exitStatus == QProcess::NormalExit) {
39✔
334
    QString output;
38✔
335
    QString err;
38✔
336
    if (i.readStdout) {
38✔
337
      output = decodeAssumingUtf8(m_process.readAllStandardOutput());
72✔
338
    }
339
    if (i.readStderr || exitCode != 0) {
38✔
340
      err = decodeAssumingUtf8(m_process.readAllStandardError());
64✔
341
      if (exitCode != 0) {
342
#ifdef QT_DEBUG
343
        dbg() << exitCode << err;
344
#endif
345
      }
346
    }
347
    emit finished(i.id, exitCode, output, err);
38✔
348
  } else {
349
    QString output;
1✔
350
    QString err;
1✔
351
    if (i.readStdout) {
1✔
NEW
352
      output = decodeAssumingUtf8(m_process.readAllStandardOutput());
×
353
    }
354
    if (i.readStderr) {
1✔
NEW
355
      err = decodeAssumingUtf8(m_process.readAllStandardError());
×
356
    }
357
    emit error(i.id, exitCode, output, err);
1✔
358
  }
359
  executeNext();
39✔
360
}
39✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc