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

rieske / trans / 29993228457

23 Jul 2026 08:57AM UTC coverage: 91.09% (-0.6%) from 91.733%
29993228457

push

github

web-flow
Harden process invocation, exit codes, and ownership (#57)

Replace shell system() with an argv-based Process helper that drains
stdout/stderr concurrently and fails hard on non-zero tool exits.
Propagate compile failures from Driver to main, move parsing/scanner
factories to unique_ptr, and throw from unfinished array codegen.
Add focused util/driver/codegen tests; keep production free of coverage
tooling hooks.

130 of 202 new or added lines in 9 files covered. (64.36%)

5643 of 6195 relevant lines covered (91.09%)

186846.55 hits per line

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

60.0
/src/util/Process.cpp
1
#include "util/Process.h"
2

3
#include <array>
4
#include <cerrno>
5
#include <cstring>
6
#include <fcntl.h>
7
#include <poll.h>
8
#include <stdexcept>
9
#include <string>
10
#include <sys/wait.h>
11
#include <unistd.h>
12
#include <vector>
13

14
namespace util {
15
namespace {
16

17
constexpr size_t kIoChunk = 4096;
18

19
void closeFd(int& fd) {
5,352✔
20
    if (fd >= 0) {
5,352✔
21
        ::close(fd);
3,282✔
22
        fd = -1;
3,282✔
23
    }
24
}
5,352✔
25

NEW
26
void closePipe(int pipefds[2]) {
×
NEW
27
    closeFd(pipefds[0]);
×
NEW
28
    closeFd(pipefds[1]);
×
NEW
29
}
×
30

NEW
31
[[noreturn]] void throwErrno(const char* what) {
×
NEW
32
    throw std::runtime_error(std::string(what) + ": " + std::strerror(errno));
×
33
}
34

35
std::string joinArgv(const std::vector<std::string>& argv) {
3✔
36
    std::string joined;
3✔
37
    for (size_t i = 0; i < argv.size(); ++i) {
16✔
38
        if (i != 0) {
13✔
39
            joined.push_back(' ');
10✔
40
        }
41
        joined += argv[i];
13✔
42
    }
43
    return joined;
3✔
NEW
44
}
×
45

46
// Multiplex stdin write + stdout/stderr reads until all pipe ends close.
47
// Avoids the classic deadlock from draining one stream before the other.
48
void pumpIo(int& stdinWriteFd, const std::string& stdinData, size_t& stdinOffset,
892✔
49
        int& stdoutReadFd, std::string* stdoutOut,
50
        int& stderrReadFd, std::string& stderrOut) {
51
    std::array<char, kIoChunk> buf {};
892✔
52

53
    while (stdinWriteFd >= 0 || stdoutReadFd >= 0 || stderrReadFd >= 0) {
2,421✔
54
        std::array<pollfd, 3> pfds {};
1,529✔
55
        nfds_t nfds = 0;
1,529✔
56

57
        int stdinIdx = -1;
1,529✔
58
        int stdoutIdx = -1;
1,529✔
59
        int stderrIdx = -1;
1,529✔
60

61
        if (stdinWriteFd >= 0) {
1,529✔
62
            stdinIdx = static_cast<int>(nfds);
323✔
63
            pfds[nfds++] = pollfd { stdinWriteFd, POLLOUT, 0 };
323✔
64
        }
65
        if (stdoutReadFd >= 0) {
1,529✔
66
            stdoutIdx = static_cast<int>(nfds);
740✔
67
            pfds[nfds++] = pollfd { stdoutReadFd, POLLIN, 0 };
740✔
68
        }
69
        if (stderrReadFd >= 0) {
1,529✔
70
            stderrIdx = static_cast<int>(nfds);
1,527✔
71
            pfds[nfds++] = pollfd { stderrReadFd, POLLIN, 0 };
1,527✔
72
        }
73

74
        int ready = ::poll(pfds.data(), nfds, -1);
1,529✔
75
        if (ready < 0) {
1,529✔
NEW
76
            if (errno == EINTR) {
×
NEW
77
                continue;
×
78
            }
NEW
79
            throwErrno("poll");
×
80
        }
81

82
        auto handleRead = [&](int& fd, short revents, std::string* dest) {
2,267✔
83
            if (fd < 0) {
2,267✔
NEW
84
                return;
×
85
            }
86
            if (!(revents & (POLLIN | POLLHUP | POLLERR))) {
2,267✔
87
                return;
636✔
88
            }
89
            if (revents & POLLIN) {
1,631✔
90
                ssize_t n = ::read(fd, buf.data(), buf.size());
939✔
91
                if (n > 0) {
313✔
92
                    if (dest != nullptr) {
313✔
93
                        dest->append(buf.data(), static_cast<size_t>(n));
626✔
94
                    }
95
                    return;
313✔
96
                }
NEW
97
                if (n < 0 && errno == EINTR) {
×
NEW
98
                    return;
×
99
                }
100
            }
101
            closeFd(fd);
1,318✔
102
        };
1,529✔
103

104
        if (stdinIdx >= 0) {
1,529✔
105
            short revents = pfds[static_cast<size_t>(stdinIdx)].revents;
323✔
106
            if (revents & (POLLOUT | POLLERR | POLLHUP)) {
323✔
107
                if (revents & POLLOUT) {
323✔
108
                    size_t remaining = stdinData.size() - stdinOffset;
323✔
109
                    ssize_t n = ::write(stdinWriteFd, stdinData.data() + stdinOffset, remaining);
323✔
110
                    if (n > 0) {
323✔
111
                        stdinOffset += static_cast<size_t>(n);
323✔
112
                        if (stdinOffset >= stdinData.size()) {
323✔
113
                            closeFd(stdinWriteFd);
323✔
114
                        }
NEW
115
                    } else if (n < 0 && errno == EINTR) {
×
116
                        // retry
117
                    } else {
118
                        // EPIPE or other write error: child closed early.
NEW
119
                        closeFd(stdinWriteFd);
×
120
                    }
121
                } else {
NEW
122
                    closeFd(stdinWriteFd);
×
123
                }
124
            }
125
        }
126

127
        if (stdoutIdx >= 0) {
1,529✔
128
            handleRead(stdoutReadFd, pfds[static_cast<size_t>(stdoutIdx)].revents, stdoutOut);
740✔
129
        }
130
        if (stderrIdx >= 0) {
1,529✔
131
            handleRead(stderrReadFd, pfds[static_cast<size_t>(stderrIdx)].revents, &stderrOut);
1,527✔
132
        }
133
    }
134
}
892✔
135

136
} // namespace
137

138
ProcessResult runProcess(const std::vector<std::string>& argv,
893✔
139
        const std::string& stdinData,
140
        const std::string& stdoutPath) {
141
    if (argv.empty()) {
893✔
142
        throw std::invalid_argument("runProcess: empty argv");
1✔
143
    }
144

145
    int stdinPipe[2] = { -1, -1 };
892✔
146
    int stdoutPipe[2] = { -1, -1 };
892✔
147
    int stderrPipe[2] = { -1, -1 };
892✔
148

149
    const bool captureStdout = stdoutPath.empty();
892✔
150
    const bool feedStdin = !stdinData.empty();
892✔
151

NEW
152
    auto cleanupPipes = [&]() {
×
NEW
153
        closePipe(stdinPipe);
×
NEW
154
        closePipe(stdoutPipe);
×
NEW
155
        closePipe(stderrPipe);
×
NEW
156
    };
×
157

158
    if (feedStdin && ::pipe(stdinPipe) != 0) {
892✔
NEW
159
        throwErrno("pipe(stdin)");
×
160
    }
161
    if (captureStdout && ::pipe(stdoutPipe) != 0) {
892✔
NEW
162
        cleanupPipes();
×
NEW
163
        throwErrno("pipe(stdout)");
×
164
    }
165
    if (::pipe(stderrPipe) != 0) {
892✔
NEW
166
        cleanupPipes();
×
NEW
167
        throwErrno("pipe(stderr)");
×
168
    }
169

170
    pid_t pid = ::fork();
892✔
171
    if (pid < 0) {
892✔
NEW
172
        cleanupPipes();
×
NEW
173
        throwErrno("fork");
×
174
    }
175

176
    if (pid == 0) {
892✔
NEW
177
        if (feedStdin) {
×
NEW
178
            ::dup2(stdinPipe[0], STDIN_FILENO);
×
179
        } else {
NEW
180
            int devnull = ::open("/dev/null", O_RDONLY);
×
NEW
181
            if (devnull >= 0) {
×
NEW
182
                ::dup2(devnull, STDIN_FILENO);
×
NEW
183
                ::close(devnull);
×
184
            }
185
        }
186

NEW
187
        if (captureStdout) {
×
NEW
188
            ::dup2(stdoutPipe[1], STDOUT_FILENO);
×
189
        } else {
NEW
190
            int outFd = ::open(stdoutPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
×
NEW
191
            if (outFd < 0) {
×
NEW
192
                _exit(126);
×
193
            }
NEW
194
            ::dup2(outFd, STDOUT_FILENO);
×
NEW
195
            ::close(outFd);
×
196
        }
197

NEW
198
        ::dup2(stderrPipe[1], STDERR_FILENO);
×
199

NEW
200
        closePipe(stdinPipe);
×
NEW
201
        closePipe(stdoutPipe);
×
NEW
202
        closePipe(stderrPipe);
×
203

NEW
204
        std::vector<char*> cargv;
×
NEW
205
        cargv.reserve(argv.size() + 1);
×
NEW
206
        for (const auto& arg : argv) {
×
NEW
207
            cargv.push_back(const_cast<char*>(arg.c_str()));
×
208
        }
NEW
209
        cargv.push_back(nullptr);
×
210

NEW
211
        ::execvp(cargv[0], cargv.data());
×
NEW
212
        _exit(127);
×
NEW
213
    }
×
214

215
    // Parent: close child-side ends; keep parent-side ends in the pipe arrays.
216
    closeFd(stdinPipe[0]);
892✔
217
    closeFd(stdoutPipe[1]);
892✔
218
    closeFd(stderrPipe[1]);
892✔
219

220
    if (!feedStdin) {
892✔
221
        closeFd(stdinPipe[1]);
569✔
222
    }
223
    if (!captureStdout) {
892✔
224
        closeFd(stdoutPipe[0]);
466✔
225
    }
226

227
    ProcessResult result;
892✔
228
    size_t stdinOffset = 0;
892✔
229

230
    try {
231
        pumpIo(stdinPipe[1], stdinData, stdinOffset,
892✔
232
                stdoutPipe[0], captureStdout ? &result.stdoutOutput : nullptr,
233
                stderrPipe[0], result.stderrOutput);
892✔
NEW
234
    } catch (...) {
×
NEW
235
        closePipe(stdinPipe);
×
NEW
236
        closePipe(stdoutPipe);
×
NEW
237
        closePipe(stderrPipe);
×
NEW
238
        int status = 0;
×
NEW
239
        ::waitpid(pid, &status, 0);
×
NEW
240
        throw;
×
NEW
241
    }
×
242

243
    // pumpIo closes all parent ends it was given.
244
    int status = 0;
892✔
245
    if (::waitpid(pid, &status, 0) < 0) {
892✔
NEW
246
        throwErrno("waitpid");
×
247
    }
248
    if (WIFEXITED(status)) {
892✔
249
        result.exitCode = WEXITSTATUS(status);
892✔
NEW
250
    } else if (WIFSIGNALED(status)) {
×
NEW
251
        result.exitCode = 128 + WTERMSIG(status);
×
252
    } else {
NEW
253
        result.exitCode = -1;
×
254
    }
255
    return result;
1,784✔
NEW
256
}
×
257

258
void runProcessOrThrow(const std::vector<std::string>& argv,
887✔
259
        const std::string& stdinData,
260
        const std::string& stdoutPath) {
261
    ProcessResult result = runProcess(argv, stdinData, stdoutPath);
887✔
262
    if (result.exitCode == 0) {
887✔
263
        return;
884✔
264
    }
265
    std::string message = "command failed (" + std::to_string(result.exitCode) + "): " + joinArgv(argv);
3✔
266
    if (!result.stderrOutput.empty()) {
3✔
267
        message += "\n" + result.stderrOutput;
2✔
268
    } else if (!result.stdoutOutput.empty()) {
1✔
269
        message += "\n" + result.stdoutOutput;
1✔
270
    }
271
    throw std::runtime_error { message };
3✔
272
}
890✔
273

274
} // namespace util
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc