GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 83.1% 64 / 14 / 91
Functions: 84.6% 11 / 0 / 13
Branches: 45.3% 67 / 34 / 182

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 640 ExecResult SystemUtil::exec(const std::string &command, bool redirectStdErrToStdOut) {
38 #if OS_UNIX
39
1/2
✓ Branch 2 → 3 taken 640 times.
✗ Branch 2 → 45 not taken.
640 std::string redirectedCommand = command;
40
2/2
✓ Branch 3 → 4 taken 306 times.
✓ Branch 3 → 5 taken 334 times.
640 if (redirectStdErrToStdOut)
41
1/2
✓ Branch 4 → 5 taken 306 times.
✗ Branch 4 → 43 not taken.
306 redirectedCommand += " 2>&1"; // Redirect stderr to stdout
42
1/2
✓ Branch 6 → 7 taken 640 times.
✗ Branch 6 → 43 not taken.
640 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 640 std::array<char, 128> buffer{};
56
1/2
✓ Branch 13 → 14 taken 640 times.
✗ Branch 13 → 43 not taken.
640 std::stringstream result;
57
3/4
✓ Branch 20 → 21 taken 7017 times.
✗ Branch 20 → 41 not taken.
✓ Branch 21 → 15 taken 6377 times.
✓ Branch 21 → 22 taken 640 times.
7657 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
58
1/2
✓ Branch 17 → 18 taken 6377 times.
✗ Branch 17 → 41 not taken.
6377 result << buffer.data();
59
60
1/2
✓ Branch 22 → 23 taken 640 times.
✗ Branch 22 → 41 not taken.
640 const int status = pclose(pipe);
61 1280 return {result.str(), transformStatusToExitCode(status)};
62
2/4
✓ Branch 23 → 24 taken 640 times.
✗ Branch 23 → 41 not taken.
✓ Branch 26 → 27 taken 640 times.
✗ Branch 26 → 28 not taken.
1280 }
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 320 ExternalBinaryFinderResult SystemUtil::findLinkerInvoker() {
126 #if OS_UNIX
127
1/2
✓ Branch 26 → 4 taken 320 times.
✗ Branch 26 → 27 not taken.
320 for (const char *linkerInvokerName : {"clang", "gcc"})
128
2/4
✓ Branch 8 → 9 taken 320 times.
✗ Branch 8 → 36 not taken.
✓ Branch 23 → 6 taken 320 times.
✗ Branch 23 → 24 not taken.
640 for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"})
129
4/8
✓ Branch 10 → 11 taken 320 times.
✗ Branch 10 → 43 not taken.
✓ Branch 11 → 12 taken 320 times.
✗ Branch 11 → 41 not taken.
✓ Branch 12 → 13 taken 320 times.
✗ Branch 12 → 39 not taken.
✓ Branch 15 → 16 taken 320 times.
✗ Branch 15 → 18 not taken.
320 if (std::filesystem::exists(path + linkerInvokerName))
130
2/4
✓ Branch 16 → 17 taken 320 times.
✗ Branch 16 → 45 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 25 taken 320 times.
640 return ExternalBinaryFinderResult{linkerInvokerName, path + linkerInvokerName};
131 #elif OS_WINDOWS
132 for (const char *linkerInvokerName : {"clang", "gcc"})
133 if (isCommandAvailable(std::string(linkerInvokerName) + " -v"))
134 return ExternalBinaryFinderResult{linkerInvokerName, linkerInvokerName};
135 #else
136 #error "Unsupported platform"
137 #endif
138 const 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 320 ExternalBinaryFinderResult SystemUtil::findLinker([[maybe_unused]] const CliOptions &cliOptions) {
150 #if OS_UNIX
151 320 std::vector<const char *> linkerList;
152
1/2
✓ Branch 2 → 3 taken 320 times.
✗ Branch 2 → 83 not taken.
320 linkerList.reserve(5);
153 // mold does only support linking for unix and darwin
154
1/2
✓ Branch 4 → 5 taken 320 times.
✗ Branch 4 → 7 not taken.
320 if (!cliOptions.targetTriple.isOSWindows())
155
1/2
✓ Branch 5 → 6 taken 320 times.
✗ Branch 5 → 56 not taken.
320 linkerList.push_back("mold");
156
1/2
✓ Branch 7 → 8 taken 320 times.
✗ Branch 7 → 57 not taken.
320 linkerList.push_back("ld.lld");
157
1/2
✓ Branch 8 → 9 taken 320 times.
✗ Branch 8 → 58 not taken.
320 linkerList.push_back("ld64.ddl");
158
1/2
✓ Branch 9 → 10 taken 320 times.
✗ Branch 9 → 59 not taken.
320 linkerList.push_back("gold");
159
1/2
✓ Branch 10 → 11 taken 320 times.
✗ Branch 10 → 60 not taken.
320 linkerList.push_back("ld");
160
161
1/2
✓ Branch 46 → 13 taken 320 times.
✗ Branch 46 → 47 not taken.
640 for (const char *linkerName : linkerList)
162
2/4
✓ Branch 19 → 20 taken 640 times.
✗ Branch 19 → 61 not taken.
✓ Branch 34 → 17 taken 640 times.
✗ Branch 34 → 35 not taken.
1280 for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"})
163
5/8
✓ Branch 21 → 22 taken 640 times.
✗ Branch 21 → 68 not taken.
✓ Branch 22 → 23 taken 640 times.
✗ Branch 22 → 66 not taken.
✓ Branch 23 → 24 taken 640 times.
✗ Branch 23 → 64 not taken.
✓ Branch 26 → 27 taken 320 times.
✓ Branch 26 → 29 taken 320 times.
640 if (std::filesystem::exists(path + linkerName))
164
3/4
✓ Branch 27 → 28 taken 320 times.
✗ Branch 27 → 70 not taken.
✓ Branch 31 → 32 taken 320 times.
✓ Branch 31 → 38 taken 320 times.
960 return ExternalBinaryFinderResult{linkerName, path + linkerName};
165 #elif OS_WINDOWS
166 for (const char *linkerName : {"lld", "ld"})
167 if (isCommandAvailable(std::string(linkerName) + " -v"))
168 return ExternalBinaryFinderResult{linkerName, linkerName};
169 #else
170 #error "Unsupported platform"
171 #endif
172 const auto msg = "No supported linker was found on the system. Supported are: mold, lld, gold and ld"; // LCOV_EXCL_LINE
173 throw LinkerError(LINKER_NOT_FOUND, msg); // LCOV_EXCL_LINE
174 320 }
175
176 /**
177 * Search for a supported archiver on the system and return the executable name or path.
178 * This function may throw a LinkerError if no archiver is found.
179 *
180 * @return Name and path to the archiver executable
181 */
182 ExternalBinaryFinderResult SystemUtil::findArchiver() {
183 #if OS_UNIX
184 for (const char *archiverName : {"llvm-ar", "gcc-ar", "ar"})
185 for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"})
186 if (std::filesystem::exists(path + archiverName))
187 return ExternalBinaryFinderResult{archiverName, path + archiverName};
188 #elif OS_WINDOWS
189 for (const char *archiverName : {"llvm-lib", "lib"})
190 if (isCommandAvailable(std::string(archiverName) + " -v"))
191 return ExternalBinaryFinderResult{archiverName, archiverName};
192 #else
193 #error "Unsupported platform"
194 #endif
195 const auto msg = "No supported archiver was found on the system. Supported are: llvm-ar and ar"; // LCOV_EXCL_LINE
196 throw LinkerError(ARCHIVER_NOT_FOUND, msg); // LCOV_EXCL_LINE
197 }
198
199 /**
200 * Retrieve the file extension of the produced output file, depending on target container format and target OS
201 *
202 * @param cliOptions Command line options
203 * @param outputContainer Output container
204 * @return File extension
205 */
206 929 const char *SystemUtil::getOutputFileExtension(const CliOptions &cliOptions, OutputContainer outputContainer) {
207 static constexpr auto OUTPUT_CONTAINER_COUNT = static_cast<size_t>(OutputContainer::MAX);
208 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_WASM = {"wasm", "o", "a", "so"};
209 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_MACOS = {"", "o", "a", "dylib"};
210 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_WINDOWS = {"exe", "obj", "lib", "dll"};
211 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_LINUX = {"", "o", "a", "so"};
212
213 929 const auto outputContainerCasted = static_cast<uint8_t>(outputContainer);
214
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 928 times.
929 if (cliOptions.targetTriple.isWasm())
215 1 return OC_EXT_MAP_WASM[outputContainerCasted];
216
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 10 taken 927 times.
928 if (cliOptions.targetTriple.isOSDarwin())
217 1 return OC_EXT_MAP_MACOS[outputContainerCasted];
218
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 14 taken 925 times.
927 if (cliOptions.targetTriple.isOSWindows())
219 2 return OC_EXT_MAP_WINDOWS[outputContainerCasted];
220 925 return OC_EXT_MAP_LINUX[outputContainerCasted];
221 }
222
223 /**
224 * Retrieve the dir, where the standard library lives.
225 * Returns an empty string if the std was not found.
226 *
227 * @return Std directory
228 */
229 2287 std::filesystem::path SystemUtil::getStdDir() {
230 #if OS_UNIX
231
3/6
✓ Branch 2 → 3 taken 2287 times.
✗ Branch 2 → 32 not taken.
✓ Branch 3 → 4 taken 2287 times.
✗ Branch 3 → 30 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 2287 times.
2287 if (exists(std::filesystem::path("/usr/lib/spice/std/")))
232 return "/usr/lib/spice/std/";
233 #endif
234
1/2
✓ Branch 8 → 9 taken 2287 times.
✗ Branch 8 → 21 not taken.
2287 if (std::getenv("SPICE_STD_DIR"))
235
3/6
✓ Branch 10 → 11 taken 2287 times.
✗ Branch 10 → 33 not taken.
✓ Branch 11 → 12 taken 2287 times.
✗ Branch 11 → 34 not taken.
✓ Branch 12 → 13 taken 2287 times.
✗ Branch 12 → 15 not taken.
2287 if (const std::filesystem::path stdPath(std::getenv("SPICE_STD_DIR")); exists(stdPath))
236
2/4
✓ Branch 13 → 14 taken 2287 times.
✗ Branch 13 → 34 not taken.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 20 taken 2287 times.
2287 return stdPath;
237 const auto errMsg = "Standard library could not be found. Check if the env var SPICE_STD_DIR exists"; // GCOV_EXCL_LINE
238 throw CompilerError(STD_NOT_FOUND, errMsg); // GCOV_EXCL_LINE
239 }
240
241 /**
242 * Retrieve the dir, where the bootstrap compiler lives.
243 * Returns an empty string if the bootstrap compiler was not found.
244 *
245 * @return
246 */
247 797 std::filesystem::path SystemUtil::getBootstrapDir() {
248
1/2
✓ Branch 3 → 4 taken 797 times.
✗ Branch 3 → 13 not taken.
797 if (std::getenv("SPICE_BOOTSTRAP_DIR")) {
249
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))
250 1594 return stdPath;
251 }
252 const auto errMsg = "Bootstrap compiler could not be found. Check if the env var SPICE_BOOTSTRAP_DIR exists"; // GCOV_EXCL_LINE
253 throw CompilerError(BOOTSTRAP_NOT_FOUND, errMsg); // GCOV_EXCL_LINE
254 }
255
256 /**
257 * Retrieve the dir, where output binaries should go when installing them
258 *
259 * @return Installation directory
260 */
261 2 std::filesystem::path SystemUtil::getSpiceBinDir() {
262 #if OS_UNIX
263 2 return "/usr/local/bin/";
264 #elif OS_WINDOWS
265 const char *userProfile = std::getenv("USERPROFILE");
266 assert(userProfile != nullptr && strlen(userProfile) > 0);
267 return std::filesystem::path(userProfile) / "spice" / "bin";
268 #else
269 #error "Unsupported platform"
270 #endif
271 }
272
273 /**
274 * Get the memory page size of the current system
275 *
276 * @return Page size in bytes
277 */
278 566 size_t SystemUtil::getSystemPageSize() {
279 #if OS_UNIX
280 566 return static_cast<size_t>(sysconf(_SC_PAGESIZE));
281 #elif OS_WINDOWS
282 SYSTEM_INFO si;
283 GetSystemInfo(&si);
284 return static_cast<size_t>(si.dwPageSize);
285 #else
286 #error "Unsupported platform"
287 #endif
288 }
289
290 /**
291 * Transform pclose status to process exit code.
292 * The implementation is OS dependent.
293 *
294 * @param status Result of pclose
295 * @return Process exit code
296 */
297 643 int SystemUtil::transformStatusToExitCode(int status) {
298 #if OS_UNIX
299 // Invalid status -> invalid exit code
300
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 643 times.
643 if (status == -1)
301 return -1;
302 // process terminated by signal
303
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 643 times.
643 if (WIFSIGNALED(status))
304 return 128 + WTERMSIG(status);
305 // Process terminated normally
306
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 643 times.
643 assert(WIFEXITED(status));
307 643 return WEXITSTATUS(status);
308 #elif OS_WINDOWS
309 return status;
310 #else
311 #error "Unsupported platform"
312 #endif
313 }
314
315 } // namespace spice::compiler
316