GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 88.9% 64 / 12 / 84
Functions: 91.7% 11 / 0 / 12
Branches: 47.8% 66 / 26 / 164

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 <unistd.h>
10 #elif OS_WINDOWS
11 #include <windows.h>
12 #else
13 #error "Unsupported platform"
14 #endif
15
16 #include <driver/Driver.h>
17 #include <exception/CompilerError.h>
18 #include <exception/LinkerError.h>
19
20 #include <llvm/TargetParser/Triple.h>
21
22 namespace spice::compiler {
23
24 /**
25 * Execute external command. Used to execute compiled binaries
26 *
27 * @param command Command to execute
28 * @param redirectStdErrToStdOut Redirect StdErr to StdOut
29 * @return Result struct
30 */
31 444 ExecResult SystemUtil::exec(const std::string &command, bool redirectStdErrToStdOut) {
32 #if OS_UNIX
33
1/2
✓ Branch 2 → 3 taken 444 times.
✗ Branch 2 → 42 not taken.
444 std::string redirectedCommand = command;
34
2/2
✓ Branch 3 → 4 taken 210 times.
✓ Branch 3 → 5 taken 234 times.
444 if (redirectStdErrToStdOut)
35
1/2
✓ Branch 4 → 5 taken 210 times.
✗ Branch 4 → 40 not taken.
210 redirectedCommand += " 2>&1"; // Redirect stderr to stdout
36
1/2
✓ Branch 6 → 7 taken 444 times.
✗ Branch 6 → 40 not taken.
444 FILE *pipe = popen(redirectedCommand.c_str(), "r");
37 #elif OS_WINDOWS
38 std::string redirectedCommand = command;
39 if (redirectStdErrToStdOut)
40 redirectedCommand = "\"" + command + " 2>&1\""; // Redirect stderr to stdout
41 FILE *pipe = _popen(redirectedCommand.c_str(), "r");
42 #else
43 #error "Unsupported platform"
44 #endif
45
46 if (!pipe) // GCOV_EXCL_LINE
47 throw CompilerError(IO_ERROR, "Failed to execute command: " + command); // GCOV_EXCL_LINE
48
49 444 std::array<char, 128> buffer{};
50
1/2
✓ Branch 13 → 14 taken 444 times.
✗ Branch 13 → 40 not taken.
444 std::stringstream result;
51
3/4
✓ Branch 22 → 23 taken 6897 times.
✗ Branch 22 → 38 not taken.
✓ Branch 23 → 15 taken 6453 times.
✓ Branch 23 → 24 taken 444 times.
14238 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
52
1/2
✓ Branch 17 → 18 taken 6453 times.
✗ Branch 17 → 38 not taken.
6453 result << buffer.data();
53
54
1/2
✓ Branch 24 → 25 taken 444 times.
✗ Branch 24 → 38 not taken.
444 const int status = pclose(pipe);
55 444 return {result.str(), transformStatusToExitCode(status)};
56
1/2
✓ Branch 25 → 26 taken 444 times.
✗ Branch 25 → 38 not taken.
444 }
57
58 /**
59 * Checks if a certain command is available on the computer
60 *
61 * @param cmd Command to search for
62 * @return Present or not
63 */
64 3 bool SystemUtil::isCommandAvailable(const std::string &cmd) {
65 #if OS_UNIX
66
2/4
✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 14 not taken.
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 12 not taken.
3 const std::string checkCmd = "which " + cmd + " > /dev/null 2>&1";
67 #elif OS_WINDOWS
68 const std::string checkCmd = "where " + cmd + " > nul 2>&1";
69 #else
70 #error "Unsupported platform"
71 #endif
72
1/2
✓ Branch 6 → 7 taken 3 times.
✗ Branch 6 → 15 not taken.
3 const int status = std::system(checkCmd.c_str());
73 6 return transformStatusToExitCode(status) == EXIT_SUCCESS;
74 3 }
75
76 /**
77 * Checks if Graphviz is installed on the system
78 *
79 * @return Present or not
80 */
81
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"); }
82
83 /**
84 * Search for a supported linker invoker on the system and return the executable name or path.
85 * This function may throw a LinkerError if no linker invoker is found.
86 *
87 * @return Name and path to the linker invoker executable
88 */
89 222 ExternalBinaryFinderResult SystemUtil::findLinkerInvoker() {
90 #if OS_UNIX
91
1/2
✓ Branch 26 → 4 taken 222 times.
✗ Branch 26 → 27 not taken.
222 for (const char *linkerInvokerName : {"clang", "gcc"})
92
2/4
✓ Branch 8 → 9 taken 222 times.
✗ Branch 8 → 36 not taken.
✓ Branch 23 → 6 taken 222 times.
✗ Branch 23 → 24 not taken.
444 for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"})
93
4/8
✓ Branch 10 → 11 taken 222 times.
✗ Branch 10 → 43 not taken.
✓ Branch 11 → 12 taken 222 times.
✗ Branch 11 → 41 not taken.
✓ Branch 12 → 13 taken 222 times.
✗ Branch 12 → 39 not taken.
✓ Branch 15 → 16 taken 222 times.
✗ Branch 15 → 18 not taken.
222 if (std::filesystem::exists(path + linkerInvokerName))
94
2/4
✓ Branch 16 → 17 taken 222 times.
✗ Branch 16 → 45 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 25 taken 222 times.
444 return ExternalBinaryFinderResult{linkerInvokerName, path + linkerInvokerName};
95 #elif OS_WINDOWS
96 for (const char *linkerInvokerName : {"clang", "gcc"})
97 if (isCommandAvailable(std::string(linkerInvokerName) + " -v"))
98 return ExternalBinaryFinderResult{linkerInvokerName, linkerInvokerName};
99 #else
100 #error "Unsupported platform"
101 #endif
102 const auto msg = "No supported linker invoker was found on the system. Supported are: clang and gcc"; // LCOV_EXCL_LINE
103 throw LinkerError(LINKER_INVOKER_NOT_FOUND, msg); // LCOV_EXCL_LINE
104 }
105
106 /**
107 * Search for a supported linker on the system and return the executable name or path.
108 * This function may throw a LinkerError if no linker is found.
109 *
110 * @param cliOptions Command line options
111 * @return Name and path to the linker executable
112 */
113 222 ExternalBinaryFinderResult SystemUtil::findLinker([[maybe_unused]] const CliOptions &cliOptions) {
114 #if OS_UNIX
115 222 std::vector<const char *> linkerList;
116
1/2
✓ Branch 2 → 3 taken 222 times.
✗ Branch 2 → 75 not taken.
222 linkerList.reserve(5);
117 // mold does only support linking for unix and darwin
118
1/2
✓ Branch 4 → 5 taken 222 times.
✗ Branch 4 → 7 not taken.
222 if (!cliOptions.targetTriple.isOSWindows())
119
1/2
✓ Branch 5 → 6 taken 222 times.
✗ Branch 5 → 48 not taken.
222 linkerList.push_back("mold");
120
1/2
✓ Branch 7 → 8 taken 222 times.
✗ Branch 7 → 49 not taken.
222 linkerList.push_back("ld.lld");
121
1/2
✓ Branch 8 → 9 taken 222 times.
✗ Branch 8 → 50 not taken.
222 linkerList.push_back("ld64.ddl");
122
1/2
✓ Branch 9 → 10 taken 222 times.
✗ Branch 9 → 51 not taken.
222 linkerList.push_back("gold");
123
1/2
✓ Branch 10 → 11 taken 222 times.
✗ Branch 10 → 52 not taken.
222 linkerList.push_back("ld");
124
125
1/2
✓ Branch 38 → 13 taken 222 times.
✗ Branch 38 → 39 not taken.
222 for (const char *linkerName : linkerList)
126
2/4
✓ Branch 18 → 19 taken 444 times.
✗ Branch 18 → 53 not taken.
✓ Branch 33 → 16 taken 444 times.
✗ Branch 33 → 34 not taken.
888 for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"})
127
5/8
✓ Branch 20 → 21 taken 444 times.
✗ Branch 20 → 60 not taken.
✓ Branch 21 → 22 taken 444 times.
✗ Branch 21 → 58 not taken.
✓ Branch 22 → 23 taken 444 times.
✗ Branch 22 → 56 not taken.
✓ Branch 25 → 26 taken 222 times.
✓ Branch 25 → 28 taken 222 times.
444 if (std::filesystem::exists(path + linkerName))
128
3/4
✓ Branch 26 → 27 taken 222 times.
✗ Branch 26 → 62 not taken.
✓ Branch 30 → 31 taken 222 times.
✓ Branch 30 → 35 taken 222 times.
666 return ExternalBinaryFinderResult{linkerName, path + linkerName};
129 #elif OS_WINDOWS
130 for (const char *linkerName : {"lld", "ld"})
131 if (isCommandAvailable(std::string(linkerName) + " -v"))
132 return ExternalBinaryFinderResult{linkerName, linkerName};
133 #else
134 #error "Unsupported platform"
135 #endif
136 const auto msg = "No supported linker was found on the system. Supported are: mold, lld, gold and ld"; // LCOV_EXCL_LINE
137 throw LinkerError(LINKER_NOT_FOUND, msg); // LCOV_EXCL_LINE
138 222 }
139
140 /**
141 * Search for a supported archiver on the system and return the executable name or path.
142 * This function may throw a LinkerError if no archiver is found.
143 *
144 * @return Name and path to the archiver executable
145 */
146 ExternalBinaryFinderResult SystemUtil::findArchiver() {
147 #if OS_UNIX
148 for (const char *archiverName : {"llvm-ar", "gcc-ar", "ar"})
149 for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"})
150 if (std::filesystem::exists(path + archiverName))
151 return ExternalBinaryFinderResult{archiverName, path + archiverName};
152 #elif OS_WINDOWS
153 for (const char *archiverName : {"llvm-lib", "lib"})
154 if (isCommandAvailable(std::string(archiverName) + " -v"))
155 return ExternalBinaryFinderResult{archiverName, archiverName};
156 #else
157 #error "Unsupported platform"
158 #endif
159 const auto msg = "No supported archiver was found on the system. Supported are: llvm-ar and ar"; // LCOV_EXCL_LINE
160 throw LinkerError(ARCHIVER_NOT_FOUND, msg); // LCOV_EXCL_LINE
161 }
162
163 /**
164 * Retrieve the file extension of the produced output file, depending on target container format and target OS
165 *
166 * @param cliOptions Command line options
167 * @param outputContainer Output container
168 * @return File extension
169 */
170 690 const char *SystemUtil::getOutputFileExtension(const CliOptions &cliOptions, OutputContainer outputContainer) {
171 static constexpr auto OUTPUT_CONTAINER_COUNT = static_cast<size_t>(OutputContainer::MAX);
172 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_WASM = {"wasm", "o", "a", "so"};
173 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_MACOS = {"", "o", "a", "dylib"};
174 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_WINDOWS = {"exe", "obj", "lib", "dll"};
175 static constexpr std::array<const char *, OUTPUT_CONTAINER_COUNT> OC_EXT_MAP_LINUX = {"", "o", "a", "so"};
176
177 690 const auto outputContainerCasted = static_cast<uint8_t>(outputContainer);
178
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 689 times.
690 if (cliOptions.targetTriple.isWasm())
179 1 return OC_EXT_MAP_WASM[outputContainerCasted];
180
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 10 taken 688 times.
689 if (cliOptions.targetTriple.isOSDarwin())
181 1 return OC_EXT_MAP_MACOS[outputContainerCasted];
182
2/2
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 14 taken 687 times.
688 if (cliOptions.targetTriple.isOSWindows())
183 1 return OC_EXT_MAP_WINDOWS[outputContainerCasted];
184 687 return OC_EXT_MAP_LINUX[outputContainerCasted];
185 }
186
187 /**
188 * Retrieve the dir, where the standard library lives.
189 * Returns an empty string if the std was not found.
190 *
191 * @return Std directory
192 */
193 968 std::filesystem::path SystemUtil::getStdDir() {
194 #if OS_UNIX
195
3/6
✓ Branch 2 → 3 taken 968 times.
✗ Branch 2 → 32 not taken.
✓ Branch 3 → 4 taken 968 times.
✗ Branch 3 → 30 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 968 times.
968 if (exists(std::filesystem::path("/usr/lib/spice/std/")))
196 return "/usr/lib/spice/std/";
197 #endif
198
1/2
✓ Branch 8 → 9 taken 968 times.
✗ Branch 8 → 21 not taken.
968 if (std::getenv("SPICE_STD_DIR"))
199
3/6
✓ Branch 10 → 11 taken 968 times.
✗ Branch 10 → 33 not taken.
✓ Branch 11 → 12 taken 968 times.
✗ Branch 11 → 34 not taken.
✓ Branch 12 → 13 taken 968 times.
✗ Branch 12 → 15 not taken.
968 if (const std::filesystem::path stdPath(std::getenv("SPICE_STD_DIR")); exists(stdPath))
200
2/4
✓ Branch 13 → 14 taken 968 times.
✗ Branch 13 → 34 not taken.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 20 taken 968 times.
968 return stdPath;
201 const auto errMsg = "Standard library could not be found. Check if the env var SPICE_STD_DIR exists"; // GCOV_EXCL_LINE
202 throw CompilerError(STD_NOT_FOUND, errMsg); // GCOV_EXCL_LINE
203 }
204
205 /**
206 * Retrieve the dir, where the bootstrap compiler lives.
207 * Returns an empty string if the bootstrap compiler was not found.
208 *
209 * @return
210 */
211 14 std::filesystem::path SystemUtil::getBootstrapDir() {
212
1/2
✓ Branch 3 → 4 taken 14 times.
✗ Branch 3 → 13 not taken.
14 if (std::getenv("SPICE_BOOTSTRAP_DIR")) {
213
3/8
✓ Branch 5 → 6 taken 14 times.
✗ Branch 5 → 22 not taken.
✓ Branch 6 → 7 taken 14 times.
✗ Branch 6 → 23 not taken.
✓ Branch 7 → 8 taken 14 times.
✗ Branch 7 → 9 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
14 if (const std::filesystem::path stdPath(std::getenv("SPICE_BOOTSTRAP_DIR")); exists(stdPath))
214 28 return stdPath;
215 }
216 const auto errMsg = "Bootstrap compiler could not be found. Check if the env var SPICE_BOOTSTRAP_DIR exists"; // GCOV_EXCL_LINE
217 throw CompilerError(BOOTSTRAP_NOT_FOUND, errMsg); // GCOV_EXCL_LINE
218 }
219
220 /**
221 * Retrieve the dir, where output binaries should go when installing them
222 *
223 * @return Installation directory
224 */
225 2 std::filesystem::path SystemUtil::getSpiceBinDir() {
226 #if OS_UNIX
227 2 return "/usr/local/bin/";
228 #elif OS_WINDOWS
229 const char *userProfile = std::getenv("USERPROFILE");
230 assert(userProfile != nullptr && strlen(userProfile) > 0);
231 return std::filesystem::path(userProfile) / "spice" / "bin";
232 #else
233 #error "Unsupported platform"
234 #endif
235 }
236
237 /**
238 * Get the memory page size of the current system
239 *
240 * @return Page size in bytes
241 */
242 458 size_t SystemUtil::getSystemPageSize() {
243 #if OS_UNIX
244 458 return static_cast<size_t>(sysconf(_SC_PAGESIZE));
245 #elif OS_WINDOWS
246 SYSTEM_INFO si;
247 GetSystemInfo(&si);
248 return static_cast<size_t>(si.dwPageSize);
249 #else
250 #error "Unsupported platform"
251 #endif
252 }
253
254 /**
255 * Transform pclose status to process exit code.
256 * The implementation is OS dependent.
257 *
258 * @param status Result of pclose
259 * @return Process exit code
260 */
261 447 int SystemUtil::transformStatusToExitCode(int status) {
262 #if OS_UNIX
263 // Invalid status -> invalid exit code
264
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 447 times.
447 if (status == -1)
265 return -1;
266 // process terminated by signal
267
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 447 times.
447 if (WIFSIGNALED(status))
268 return 128 + WTERMSIG(status);
269 // Process terminated normally
270
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 447 times.
447 assert(WIFEXITED(status));
271 447 return WEXITSTATUS(status);
272 #elif OS_WINDOWS
273 return status;
274 #else
275 #error "Unsupported platform"
276 #endif
277 }
278
279 } // namespace spice::compiler
280