GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 82.4% 61 / 14 / 88
Functions: 84.6% 11 / 0 / 13
Branches: 45.1% 64 / 34 / 176

src/util/SystemUtil.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "SystemUtil.h"
4
5 #include <array>
6 #include <iostream> // IWYU pragma: keep (usage in Windows-only code)
7 #include <vector>
8 #if OS_UNIX
9 #include <spawn.h>
10 #include <sys/wait.h>
11 #include <unistd.h>
12 #if OS_MACOS
13 extern char **environ;
14 #endif
15 #elif OS_WINDOWS
16 #include <process.h>
17 #include <windows.h>
18 #else
19 #error "Unsupported platform"
20 #endif
21
22 #include <driver/Driver.h>
23 #include <exception/CompilerError.h>
24 #include <exception/LinkerError.h>
25
26 #include <llvm/TargetParser/Triple.h>
27
28 namespace spice::compiler {
29
30 /**
31 * Execute external command. Used to execute compiled binaries
32 *
33 * @param command Command to execute
34 * @param redirectStdErrToStdOut Redirect StdErr to StdOut
35 * @return Result struct
36 */
37 658 ExecResult SystemUtil::exec(const std::string &command, bool redirectStdErrToStdOut) {
38 #if OS_UNIX
39
1/2
✓ Branch 2 → 3 taken 658 times.
✗ Branch 2 → 45 not taken.
658 std::string redirectedCommand = command;
40
2/2
✓ Branch 3 → 4 taken 315 times.
✓ Branch 3 → 5 taken 343 times.
658 if (redirectStdErrToStdOut)
41
1/2
✓ Branch 4 → 5 taken 315 times.
✗ Branch 4 → 43 not taken.
315 redirectedCommand += " 2>&1"; // Redirect stderr to stdout
42
1/2
✓ Branch 6 → 7 taken 658 times.
✗ Branch 6 → 43 not taken.
658 FILE *pipe = popen(redirectedCommand.c_str(), "r");
43 #elif OS_WINDOWS
44 std::string redirectedCommand = command;
45 if (redirectStdErrToStdOut)
46 redirectedCommand = "\"" + command + " 2>&1\""; // Redirect stderr to stdout
47 FILE *pipe = _popen(redirectedCommand.c_str(), "r");
48 #else
49 #error "Unsupported platform"
50 #endif
51
52 if (!pipe) // GCOV_EXCL_LINE
53 throw CompilerError(IO_ERROR, "Failed to execute command: " + command); // GCOV_EXCL_LINE
54
55 658 std::array<char, 128> buffer{};
56
1/2
✓ Branch 13 → 14 taken 658 times.
✗ Branch 13 → 43 not taken.
658 std::stringstream result;
57
3/4
✓ Branch 20 → 21 taken 7074 times.
✗ Branch 20 → 41 not taken.
✓ Branch 21 → 15 taken 6416 times.
✓ Branch 21 → 22 taken 658 times.
7732 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
58
1/2
✓ Branch 17 → 18 taken 6416 times.
✗ Branch 17 → 41 not taken.
6416 result << buffer.data();
59
60
1/2
✓ Branch 22 → 23 taken 658 times.
✗ Branch 22 → 41 not taken.
658 const int status = pclose(pipe);
61 1316 return {.output = result.str(), .exitCode = transformStatusToExitCode(status)};
62
2/4
✓ Branch 23 → 24 taken 658 times.
✗ Branch 23 → 41 not taken.
✓ Branch 26 → 27 taken 658 times.
✗ Branch 26 → 28 not taken.
1316 }
63
64 /**
65 * Execute an external binary, inheriting the parent's standard streams.
66 * Unlike exec(), this does not capture the child's output: its stdin, stdout and stderr are
67 * connected directly to ours, so the program behaves as if invoked from the terminal.
68 *
69 * @param executablePath Path to the executable to run
70 * @return Exit code of the executed binary
71 */
72 int SystemUtil::run(const std::string &executablePath) {
73 #if OS_UNIX
74 // No file actions are given, so the child inherits our stdin/stdout/stderr
75 const char *argv[] = {executablePath.c_str(), nullptr};
76 pid_t pid;
77 if (posix_spawn(&pid, executablePath.c_str(), nullptr, nullptr, const_cast<char *const *>(argv), environ) != 0)
78 throw CompilerError(IO_ERROR, "Failed to execute: " + executablePath); // GCOV_EXCL_LINE
79 int status;
80 if (waitpid(pid, &status, 0) == -1)
81 throw CompilerError(IO_ERROR, "Failed to wait for: " + executablePath); // GCOV_EXCL_LINE
82 return transformStatusToExitCode(status);
83 #elif OS_WINDOWS
84 // _P_WAIT inherits the parent's standard streams and returns the child's exit code
85 const intptr_t exitCode = _spawnl(_P_WAIT, executablePath.c_str(), executablePath.c_str(), nullptr);
86 if (exitCode == -1)
87 throw CompilerError(IO_ERROR, "Failed to execute: " + executablePath); // GCOV_EXCL_LINE
88 return static_cast<int>(exitCode);
89 #else
90 #error "Unsupported platform"
91 #endif
92 }
93
94 /**
95 * Checks if a certain command is available on the computer
96 *
97 * @param cmd Command to search for
98 * @return Present or not
99 */
100 3 bool SystemUtil::isCommandAvailable(const std::string &cmd) {
101 #if OS_UNIX
102
2/4
✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 13 not taken.
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 11 not taken.
3 const std::string checkCmd = "which " + cmd + " > /dev/null 2>&1";
103 #elif OS_WINDOWS
104 const std::string checkCmd = "where " + cmd + " > nul 2>&1";
105 #else
106 #error "Unsupported platform"
107 #endif
108
1/2
✓ Branch 6 → 7 taken 3 times.
✗ Branch 6 → 14 not taken.
3 const int status = std::system(checkCmd.c_str());
109 6 return transformStatusToExitCode(status) == EXIT_SUCCESS;
110 3 }
111
112 /**
113 * Checks if Graphviz is installed on the system
114 *
115 * @return Present or not
116 */
117
2/4
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 13 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 11 not taken.
3 bool SystemUtil::isGraphvizInstalled() { return isCommandAvailable("dot"); }
118
119 /**
120 * Search for a supported linker invoker on the system and return the executable name or path.
121 * This function may throw a LinkerError if no linker invoker is found.
122 *
123 * @return Name and path to the linker invoker executable
124 */
125 329 ExternalBinaryFinderResult SystemUtil::findLinkerInvoker() {
126 #if OS_UNIX
127
1/2
✓ Branch 24 → 3 taken 329 times.
✗ Branch 24 → 25 not taken.
329 for (const char *linkerInvokerName : LINKER_INVOKER_NAMES)
128
2/4
✓ Branch 6 → 7 taken 329 times.
✗ Branch 6 → 34 not taken.
✓ Branch 21 → 4 taken 329 times.
✗ Branch 21 → 22 not taken.
658 for (const std::string path : BINARY_SEARCH_DIRS)
129
4/8
✓ Branch 8 → 9 taken 329 times.
✗ Branch 8 → 41 not taken.
✓ Branch 9 → 10 taken 329 times.
✗ Branch 9 → 39 not taken.
✓ Branch 10 → 11 taken 329 times.
✗ Branch 10 → 37 not taken.
✓ Branch 13 → 14 taken 329 times.
✗ Branch 13 → 16 not taken.
329 if (std::filesystem::exists(path + linkerInvokerName))
130
2/4
✓ Branch 14 → 15 taken 329 times.
✗ Branch 14 → 43 not taken.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 23 taken 329 times.
658 return ExternalBinaryFinderResult{.name = linkerInvokerName, .path = path + linkerInvokerName};
131 #elif OS_WINDOWS
132 for (const char *linkerInvokerName : LINKER_INVOKER_NAMES)
133 if (isCommandAvailable(std::string(linkerInvokerName) + " -v"))
134 return ExternalBinaryFinderResult{linkerInvokerName, linkerInvokerName};
135 #else
136 #error "Unsupported platform"
137 #endif
138 constexpr auto msg = "No supported linker invoker was found on the system. Supported are: clang and gcc"; // LCOV_EXCL_LINE
139 throw LinkerError(LINKER_INVOKER_NOT_FOUND, msg); // LCOV_EXCL_LINE
140 }
141
142 /**
143 * Search for a supported linker on the system and return the executable name or path.
144 * This function may throw a LinkerError if no linker is found.
145 *
146 * @param cliOptions Command line options
147 * @return Name and path to the linker executable
148 */
149 329 ExternalBinaryFinderResult SystemUtil::findLinker([[maybe_unused]] const CliOptions &cliOptions) {
150 #if OS_UNIX
151 329 std::vector<const char *> linkerList;
152
1/2
✓ Branch 2 → 3 taken 329 times.
✗ Branch 2 → 81 not taken.
329 linkerList.reserve(1 + LINKER_NAMES_UNIX.size());
153 // mold does only support linking for unix and darwin
154
1/2
✓ Branch 4 → 5 taken 329 times.
✗ Branch 4 → 6 not taken.
329 if (!cliOptions.targetTriple.isOSWindows())
155
1/2
✓ Branch 5 → 6 taken 329 times.
✗ Branch 5 → 81 not taken.
329 linkerList.push_back(LINKER_NAME_MOLD);
156
1/2
✓ Branch 12 → 13 taken 329 times.
✗ Branch 12 → 57 not taken.
658 linkerList.insert(linkerList.end(), LINKER_NAMES_UNIX.begin(), LINKER_NAMES_UNIX.end());
157
158
1/2
✓ Branch 47 → 15 taken 329 times.
✗ Branch 47 → 48 not taken.
658 for (const char *linkerName : linkerList)
159
2/4
✓ Branch 20 → 21 taken 658 times.
✗ Branch 20 → 59 not taken.
✓ Branch 35 → 18 taken 658 times.
✗ Branch 35 → 36 not taken.
1316 for (const std::string path : BINARY_SEARCH_DIRS)
160
5/8
✓ Branch 22 → 23 taken 658 times.
✗ Branch 22 → 66 not taken.
✓ Branch 23 → 24 taken 658 times.
✗ Branch 23 → 64 not taken.
✓ Branch 24 → 25 taken 658 times.
✗ Branch 24 → 62 not taken.
✓ Branch 27 → 28 taken 329 times.
✓ Branch 27 → 30 taken 329 times.
658 if (std::filesystem::exists(path + linkerName))
161
3/4
✓ Branch 28 → 29 taken 329 times.
✗ Branch 28 → 68 not taken.
✓ Branch 32 → 33 taken 329 times.
✓ Branch 32 → 39 taken 329 times.
987 return ExternalBinaryFinderResult{.name = linkerName, .path = path + linkerName};
162 #elif OS_WINDOWS
163 for (const char *linkerName : LINKER_NAMES_WINDOWS)
164 if (isCommandAvailable(std::string(linkerName) + " -v"))
165 return ExternalBinaryFinderResult{linkerName, linkerName};
166 #else
167 #error "Unsupported platform"
168 #endif
169 constexpr auto msg = "No supported linker was found on the system. Supported are: mold, lld, gold and ld"; // LCOV_EXCL_LINE
170 throw LinkerError(LINKER_NOT_FOUND, msg); // LCOV_EXCL_LINE
171 329 }
172
173 /**
174 * Search for a supported archiver on the system and return the executable name or path.
175 * This function may throw a LinkerError if no archiver is found.
176 *
177 * @return Name and path to the archiver executable
178 */
179 ExternalBinaryFinderResult SystemUtil::findArchiver() {
180 #if OS_UNIX
181 for (const char *archiverName : ARCHIVER_NAMES_UNIX)
182 for (const std::string path : BINARY_SEARCH_DIRS)
183 if (std::filesystem::exists(path + archiverName))
184 return ExternalBinaryFinderResult{.name = archiverName, .path = path + archiverName};
185 #elif OS_WINDOWS
186 for (const char *archiverName : ARCHIVER_NAMES_WINDOWS)
187 if (isCommandAvailable(std::string(archiverName) + " -v"))
188 return ExternalBinaryFinderResult{archiverName, archiverName};
189 #else
190 #error "Unsupported platform"
191 #endif
192 constexpr auto msg = "No supported archiver was found on the system. Supported are: llvm-ar and ar"; // LCOV_EXCL_LINE
193 throw LinkerError(ARCHIVER_NOT_FOUND, msg); // LCOV_EXCL_LINE
194 }
195
196 /**
197 * Retrieve the file extension of the produced output file, depending on target container format and target OS
198 *
199 * @param cliOptions Command line options
200 * @param outputContainer Output container
201 * @return File extension
202 */
203 947 const char *SystemUtil::getOutputFileExtension(const CliOptions &cliOptions, OutputContainer outputContainer) {
204 static constexpr auto OUTPUT_CONTAINER_COUNT = static_cast<size_t>(OutputContainer::MAX);
205 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_WASM = {"wasm", "o", "a", "so"};
206 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_MACOS = {"", "o", "a", "dylib"};
207 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_WINDOWS = {"exe", "obj", "lib", "dll"};
208 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_LINUX = {"", "o", "a", "so"};
209
210 947 const auto outputContainerCasted = static_cast<uint8_t>(outputContainer);
211
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 946 times.
947 if (cliOptions.targetTriple.isWasm())
212 1 return OC_EXT_MAP_WASM[outputContainerCasted];
213
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 10 taken 945 times.
946 if (cliOptions.targetTriple.isOSDarwin())
214 1 return OC_EXT_MAP_MACOS[outputContainerCasted];
215
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 14 taken 943 times.
945 if (cliOptions.targetTriple.isOSWindows())
216 2 return OC_EXT_MAP_WINDOWS[outputContainerCasted];
217 943 return OC_EXT_MAP_LINUX[outputContainerCasted];
218 }
219
220 /**
221 * Retrieve the dir, where the standard library lives.
222 * Returns an empty string if the std was not found.
223 *
224 * @return Std directory
225 */
226 2361 std::filesystem::path SystemUtil::getStdDir() {
227 #if OS_UNIX
228
3/6
✓ Branch 2 → 3 taken 2361 times.
✗ Branch 2 → 32 not taken.
✓ Branch 3 → 4 taken 2361 times.
✗ Branch 3 → 30 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 2361 times.
2361 if (exists(std::filesystem::path("/usr/lib/spice/std/")))
229 return "/usr/lib/spice/std/";
230 #endif
231
1/2
✓ Branch 8 → 9 taken 2361 times.
✗ Branch 8 → 21 not taken.
2361 if (std::getenv("SPICE_STD_DIR"))
232
3/6
✓ Branch 10 → 11 taken 2361 times.
✗ Branch 10 → 33 not taken.
✓ Branch 11 → 12 taken 2361 times.
✗ Branch 11 → 34 not taken.
✓ Branch 12 → 13 taken 2361 times.
✗ Branch 12 → 15 not taken.
2361 if (const std::filesystem::path stdPath(std::getenv("SPICE_STD_DIR")); exists(stdPath))
233
2/4
✓ Branch 13 → 14 taken 2361 times.
✗ Branch 13 → 34 not taken.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 20 taken 2361 times.
2361 return stdPath;
234 constexpr auto msg = "Standard library could not be found. Check if the env var SPICE_STD_DIR exists"; // GCOV_EXCL_LINE
235 throw CompilerError(STD_NOT_FOUND, msg); // GCOV_EXCL_LINE
236 }
237
238 /**
239 * Retrieve the dir, where the bootstrap compiler lives.
240 * Returns an empty string if the bootstrap compiler was not found.
241 *
242 * @return
243 */
244 797 std::filesystem::path SystemUtil::getBootstrapDir() {
245
1/2
✓ Branch 3 → 4 taken 797 times.
✗ Branch 3 → 13 not taken.
797 if (std::getenv("SPICE_BOOTSTRAP_DIR")) {
246
3/8
✓ Branch 5 → 6 taken 797 times.
✗ Branch 5 → 22 not taken.
✓ Branch 6 → 7 taken 797 times.
✗ Branch 6 → 23 not taken.
✓ Branch 7 → 8 taken 797 times.
✗ Branch 7 → 9 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
797 if (const std::filesystem::path stdPath(std::getenv("SPICE_BOOTSTRAP_DIR")); exists(stdPath))
247 1594 return stdPath;
248 }
249 constexpr auto msg = "Bootstrap compiler could not be found. Check if the env var SPICE_BOOTSTRAP_DIR exists"; // GCOV_EXCL_LINE
250 throw CompilerError(BOOTSTRAP_NOT_FOUND, msg); // GCOV_EXCL_LINE
251 }
252
253 /**
254 * Retrieve the dir, where output binaries should go when installing them
255 *
256 * @return Installation directory
257 */
258 2 std::filesystem::path SystemUtil::getSpiceBinDir() {
259 #if OS_UNIX
260 2 return "/usr/local/bin/";
261 #elif OS_WINDOWS
262 const char *userProfile = std::getenv("USERPROFILE");
263 assert(userProfile != nullptr && strlen(userProfile) > 0);
264 return std::filesystem::path(userProfile) / "spice" / "bin";
265 #else
266 #error "Unsupported platform"
267 #endif
268 }
269
270 /**
271 * Get the memory page size of the current system
272 *
273 * @return Page size in bytes
274 */
275 575 size_t SystemUtil::getSystemPageSize() {
276 #if OS_UNIX
277 575 return static_cast<size_t>(sysconf(_SC_PAGESIZE));
278 #elif OS_WINDOWS
279 SYSTEM_INFO si;
280 GetSystemInfo(&si);
281 return static_cast<size_t>(si.dwPageSize);
282 #else
283 #error "Unsupported platform"
284 #endif
285 }
286
287 /**
288 * Transform pclose status to process exit code.
289 * The implementation is OS dependent.
290 *
291 * @param status Result of pclose
292 * @return Process exit code
293 */
294 661 int SystemUtil::transformStatusToExitCode(int status) {
295 #if OS_UNIX
296 // Invalid status -> invalid exit code
297
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 661 times.
661 if (status == -1)
298 return -1;
299 // process terminated by signal
300
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 661 times.
661 if (WIFSIGNALED(status))
301 return 128 + WTERMSIG(status);
302 // Process terminated normally
303
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 661 times.
661 assert(WIFEXITED(status));
304 661 return WEXITSTATUS(status);
305 #elif OS_WINDOWS
306 return status;
307 #else
308 #error "Unsupported platform"
309 #endif
310 }
311
312 } // namespace spice::compiler
313