GCC Code Coverage Report


Directory: ../
File: src/util/FileUtil.cpp
Date: 2025-11-14 16:08:18
Coverage Exec Excl Total
Lines: 97.4% 74 10 86
Functions: 100.0% 11 0 11
Branches: 51.8% 86 22 188

Line Branch Exec Source
1 // Copyright (c) 2021-2025 ChilliBits. All rights reserved.
2
3 #include "FileUtil.h"
4
5 #include <array>
6 #include <filesystem>
7 #include <iostream> // IWYU pragma: keep (usage in Windows-only code)
8
9 #include <driver/Driver.h>
10 #include <exception/CompilerError.h>
11 #include <exception/LinkerError.h>
12 #include <util/CommonUtil.h>
13
14 namespace spice::compiler {
15
16 /**
17 * Creates a file and writes fileContent to it.
18 *
19 * @param filePath File path
20 * @param fileContent String to write into the file
21 */
22 2 void FileUtil::writeToFile(const std::filesystem::path &filePath, const std::string &fileContent) {
23
1/2
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 28 not taken.
2 std::ofstream file(filePath);
24
2/4
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 26 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 12 taken 2 times.
2 if (!file)
25 throw CompilerError(IO_ERROR, "Failed to open file: " + filePath.string());
26
1/2
✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 26 not taken.
2 file << fileContent;
27
1/2
✓ Branch 13 → 14 taken 2 times.
✗ Branch 13 → 26 not taken.
2 file.flush();
28
1/2
✓ Branch 14 → 15 taken 2 times.
✗ Branch 14 → 26 not taken.
2 file.close();
29 2 }
30
31 /**
32 * Retrieve the contents of a file as a string
33 *
34 * @param filePath File path
35 * @return File contents as a string
36 */
37 611 std::string FileUtil::getFileContent(const std::filesystem::path &filePath) {
38
1/2
✓ Branch 2 → 3 taken 611 times.
✗ Branch 2 → 35 not taken.
611 std::ifstream file(filePath);
39
3/4
✓ Branch 3 → 4 taken 611 times.
✗ Branch 3 → 33 not taken.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 12 taken 610 times.
611 if (!file)
40
3/6
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 27 not taken.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 25 not taken.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 22 not taken.
1 throw CompilerError(IO_ERROR, "Failed to open file: " + filePath.string());
41
1/2
✓ Branch 12 → 13 taken 610 times.
✗ Branch 12 → 33 not taken.
610 std::stringstream stringStream;
42
1/2
✓ Branch 14 → 15 taken 610 times.
✗ Branch 14 → 31 not taken.
610 stringStream << file.rdbuf();
43
1/2
✓ Branch 15 → 16 taken 610 times.
✗ Branch 15 → 31 not taken.
610 file.close();
44
1/2
✓ Branch 16 → 17 taken 610 times.
✗ Branch 16 → 31 not taken.
1220 return stringStream.str();
45 611 }
46
47 /**
48 * Retrieve the number of lines in a file
49 *
50 * @param filePath File path
51 * @return Number of lines
52 */
53 2 size_t FileUtil::getLineCount(const std::filesystem::path &filePath) {
54
1/2
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 36 not taken.
2 std::ifstream file(filePath);
55
3/4
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 34 not taken.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 12 taken 1 time.
2 if (!file)
56
3/6
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 28 not taken.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 26 not taken.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 23 not taken.
1 throw CompilerError(IO_ERROR, "Failed to open file: " + filePath.string());
57 1 size_t lineCount = 0;
58 1 std::string line;
59
4/6
✓ Branch 15 → 16 taken 6 times.
✗ Branch 15 → 32 not taken.
✓ Branch 16 → 17 taken 6 times.
✗ Branch 16 → 32 not taken.
✓ Branch 17 → 14 taken 5 times.
✓ Branch 17 → 18 taken 1 time.
6 while (std::getline(file, line))
60 5 lineCount++;
61
1/2
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 32 not taken.
1 file.close();
62 1 return lineCount;
63 2 }
64
65 /**
66 * Execute external command. Used to execute compiled binaries
67 *
68 * @param command Command to execute
69 * @param redirectStdErrToStdOut Redirect StdErr to StdOut
70 * @return Result struct
71 */
72 394 ExecResult FileUtil::exec(const std::string &command, bool redirectStdErrToStdOut) {
73 #if OS_UNIX
74
1/2
✓ Branch 2 → 3 taken 394 times.
✗ Branch 2 → 41 not taken.
394 std::string redirectedCommand = command;
75
2/2
✓ Branch 3 → 4 taken 197 times.
✓ Branch 3 → 5 taken 197 times.
394 if (redirectStdErrToStdOut)
76
1/2
✓ Branch 4 → 5 taken 197 times.
✗ Branch 4 → 39 not taken.
197 redirectedCommand += " 2>&1"; // Redirect stderr to stdout
77
1/2
✓ Branch 6 → 7 taken 394 times.
✗ Branch 6 → 39 not taken.
394 FILE *pipe = popen(redirectedCommand.c_str(), "r");
78 #elif OS_WINDOWS
79 std::string redirectedCommand = command;
80 if (redirectStdErrToStdOut)
81 redirectedCommand = "\"" + command + " 2>&1\""; // Redirect stderr to stdout
82 FILE *pipe = _popen(redirectedCommand.c_str(), "r");
83 #else
84 #error "Unsupported platform"
85 #endif
86
87 if (!pipe) // GCOV_EXCL_LINE
88 throw CompilerError(IO_ERROR, "Failed to execute command: " + command); // GCOV_EXCL_LINE
89
90 394 std::array<char, 128> buffer{};
91
1/2
✓ Branch 13 → 14 taken 394 times.
✗ Branch 13 → 39 not taken.
394 std::stringstream result;
92
3/4
✓ Branch 22 → 23 taken 6818 times.
✗ Branch 22 → 37 not taken.
✓ Branch 23 → 15 taken 6424 times.
✓ Branch 23 → 24 taken 394 times.
14030 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
93
1/2
✓ Branch 17 → 18 taken 6424 times.
✗ Branch 17 → 37 not taken.
6424 result << buffer.data();
94
95 #if OS_UNIX
96
1/2
✓ Branch 24 → 25 taken 394 times.
✗ Branch 24 → 37 not taken.
394 const int exitCode = pclose(pipe) / 256;
97 #elif OS_WINDOWS
98 const int exitCode = _pclose(pipe);
99 #else
100 #error "Unsupported platform"
101 #endif
102 394 return {result.str(), exitCode};
103
1/2
✓ Branch 25 → 26 taken 394 times.
✗ Branch 25 → 37 not taken.
394 }
104
105 /**
106 * Checks if a certain command is available on the computer
107 *
108 * @param cmd Command to search for
109 * @return Present or not
110 */
111 3 bool FileUtil::isCommandAvailable(const std::string &cmd) {
112 #if OS_UNIX
113
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";
114 #elif OS_WINDOWS
115 const std::string checkCmd = "where " + cmd + " > nul 2>&1";
116 #else
117 #error "Unsupported platform"
118 #endif
119
1/2
✓ Branch 6 → 7 taken 3 times.
✗ Branch 6 → 14 not taken.
6 return std::system(checkCmd.c_str()) == 0;
120 3 }
121
122 /**
123 * Checks if Graphviz is installed on the system
124 *
125 * @return Present or not
126 */
127
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 FileUtil::isGraphvizInstalled() { return isCommandAvailable("dot"); }
128
129 /**
130 * Search for a supported linker invoker on the system and return the executable name or path.
131 * This function may throw a LinkerError if no linker invoker is found.
132 *
133 * @return Name and path to the linker invoker executable
134 */
135 197 ExternalBinaryFinderResult FileUtil::findLinkerInvoker() {
136 #if OS_UNIX
137
1/2
✓ Branch 26 → 4 taken 197 times.
✗ Branch 26 → 27 not taken.
197 for (const char *linkerInvokerName : {"clang", "gcc"})
138
2/4
✓ Branch 8 → 9 taken 197 times.
✗ Branch 8 → 36 not taken.
✓ Branch 23 → 6 taken 197 times.
✗ Branch 23 → 24 not taken.
394 for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"})
139
4/8
✓ Branch 10 → 11 taken 197 times.
✗ Branch 10 → 43 not taken.
✓ Branch 11 → 12 taken 197 times.
✗ Branch 11 → 41 not taken.
✓ Branch 12 → 13 taken 197 times.
✗ Branch 12 → 39 not taken.
✓ Branch 15 → 16 taken 197 times.
✗ Branch 15 → 18 not taken.
197 if (std::filesystem::exists(path + linkerInvokerName))
140
2/4
✓ Branch 16 → 17 taken 197 times.
✗ Branch 16 → 45 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 25 taken 197 times.
394 return ExternalBinaryFinderResult{linkerInvokerName, path + linkerInvokerName};
141 #elif OS_WINDOWS
142 for (const char *linkerInvokerName : {"clang", "gcc"})
143 if (isCommandAvailable(std::string(linkerInvokerName) + " -v"))
144 return ExternalBinaryFinderResult{linkerInvokerName, linkerInvokerName};
145 #else
146 #error "Unsupported platform"
147 #endif
148 const auto msg = "No supported linker invoker was found on the system. Supported are: clang and gcc"; // LCOV_EXCL_LINE
149 throw LinkerError(LINKER_NOT_FOUND, msg); // LCOV_EXCL_LINE
150 }
151
152 /**
153 * Search for a supported linker on the system and return the executable name or path.
154 * This function may throw a LinkerError if no linker is found.
155 *
156 * @return Name and path to the linker executable
157 */
158 197 ExternalBinaryFinderResult FileUtil::findLinker([[maybe_unused]] const CliOptions &cliOptions) {
159 #if OS_UNIX
160 197 std::vector<const char *> linkerList;
161
1/2
✓ Branch 2 → 3 taken 197 times.
✗ Branch 2 → 75 not taken.
197 linkerList.reserve(5);
162 // mold does only support linking for unix and darwin
163
1/2
✓ Branch 4 → 5 taken 197 times.
✗ Branch 4 → 7 not taken.
197 if (!cliOptions.targetTriple.isOSWindows())
164
1/2
✓ Branch 5 → 6 taken 197 times.
✗ Branch 5 → 48 not taken.
197 linkerList.push_back("mold");
165
1/2
✓ Branch 7 → 8 taken 197 times.
✗ Branch 7 → 49 not taken.
197 linkerList.push_back("ld.lld");
166
1/2
✓ Branch 8 → 9 taken 197 times.
✗ Branch 8 → 50 not taken.
197 linkerList.push_back("ld64.ddl");
167
1/2
✓ Branch 9 → 10 taken 197 times.
✗ Branch 9 → 51 not taken.
197 linkerList.push_back("gold");
168
1/2
✓ Branch 10 → 11 taken 197 times.
✗ Branch 10 → 52 not taken.
197 linkerList.push_back("ld");
169
170
1/2
✓ Branch 38 → 13 taken 197 times.
✗ Branch 38 → 39 not taken.
197 for (const char *linkerName : linkerList)
171
2/4
✓ Branch 18 → 19 taken 394 times.
✗ Branch 18 → 53 not taken.
✓ Branch 33 → 16 taken 394 times.
✗ Branch 33 → 34 not taken.
788 for (const std::string path : {"/usr/bin/", "/usr/local/bin/", "/bin/"})
172
5/8
✓ Branch 20 → 21 taken 394 times.
✗ Branch 20 → 60 not taken.
✓ Branch 21 → 22 taken 394 times.
✗ Branch 21 → 58 not taken.
✓ Branch 22 → 23 taken 394 times.
✗ Branch 22 → 56 not taken.
✓ Branch 25 → 26 taken 197 times.
✓ Branch 25 → 28 taken 197 times.
394 if (std::filesystem::exists(path + linkerName))
173
3/4
✓ Branch 26 → 27 taken 197 times.
✗ Branch 26 → 62 not taken.
✓ Branch 30 → 31 taken 197 times.
✓ Branch 30 → 35 taken 197 times.
591 return ExternalBinaryFinderResult{linkerName, path + linkerName};
174 #elif OS_WINDOWS
175 for (const char *linkerName : {"lld", "ld"})
176 if (isCommandAvailable(std::string(linkerName) + " -v"))
177 return ExternalBinaryFinderResult{linkerName, linkerName};
178 #else
179 #error "Unsupported platform"
180 #endif
181 const auto msg = "No supported linker was found on the system. Supported are: mold, lld, gold and ld"; // LCOV_EXCL_LINE
182 throw LinkerError(LINKER_NOT_FOUND, msg); // LCOV_EXCL_LINE
183 197 }
184
185 /**
186 * Retrieve the dir, where the standard library lives.
187 * Returns an empty string if the std was not found.
188 *
189 * @return Std directory
190 */
191 936 std::filesystem::path FileUtil::getStdDir() {
192 #if OS_UNIX
193
3/6
✓ Branch 2 → 3 taken 936 times.
✗ Branch 2 → 32 not taken.
✓ Branch 3 → 4 taken 936 times.
✗ Branch 3 → 30 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 936 times.
936 if (exists(std::filesystem::path("/usr/lib/spice/std/")))
194 return "/usr/lib/spice/std/";
195 #endif
196
1/2
✓ Branch 8 → 9 taken 936 times.
✗ Branch 8 → 21 not taken.
936 if (std::getenv("SPICE_STD_DIR"))
197
3/6
✓ Branch 10 → 11 taken 936 times.
✗ Branch 10 → 33 not taken.
✓ Branch 11 → 12 taken 936 times.
✗ Branch 11 → 34 not taken.
✓ Branch 12 → 13 taken 936 times.
✗ Branch 12 → 15 not taken.
936 if (const std::filesystem::path stdPath(std::getenv("SPICE_STD_DIR")); exists(stdPath))
198
2/4
✓ Branch 13 → 14 taken 936 times.
✗ Branch 13 → 34 not taken.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 20 taken 936 times.
936 return stdPath;
199 const auto errMsg = "Standard library could not be found. Check if the env var SPICE_STD_DIR exists"; // GCOV_EXCL_LINE
200 throw CompilerError(STD_NOT_FOUND, errMsg); // GCOV_EXCL_LINE
201 }
202
203 /**
204 * Retrieve the dir, where the bootstrap compiler lives.
205 * Returns an empty string if the bootstrap compiler was not found.
206 *
207 * @return
208 */
209 18 std::filesystem::path FileUtil::getBootstrapDir() {
210
1/2
✓ Branch 3 → 4 taken 18 times.
✗ Branch 3 → 13 not taken.
18 if (std::getenv("SPICE_BOOTSTRAP_DIR")) {
211
3/8
✓ Branch 5 → 6 taken 18 times.
✗ Branch 5 → 22 not taken.
✓ Branch 6 → 7 taken 18 times.
✗ Branch 6 → 23 not taken.
✓ Branch 7 → 8 taken 18 times.
✗ Branch 7 → 9 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
18 if (const std::filesystem::path stdPath(std::getenv("SPICE_BOOTSTRAP_DIR")); exists(stdPath))
212 36 return stdPath;
213 }
214 const auto errMsg = "Bootstrap compiler could not be found. Check if the env var SPICE_BOOTSTRAP_DIR exists"; // GCOV_EXCL_LINE
215 throw CompilerError(BOOTSTRAP_NOT_FOUND, errMsg); // GCOV_EXCL_LINE
216 }
217
218 /**
219 * Retrieve the dir, where output binaries should go when installing them
220 *
221 * @return Installation directory
222 */
223 2 std::filesystem::path FileUtil::getSpiceBinDir() {
224 #if OS_UNIX
225 2 return "/usr/local/bin/";
226 #elif OS_WINDOWS
227 const char *userProfile = std::getenv("USERPROFILE");
228 assert(userProfile != nullptr && strlen(userProfile) > 0);
229 return std::filesystem::path(userProfile) / "spice" / "bin";
230 #else
231 #error "Unsupported platform"
232 #endif
233 }
234
235 } // namespace spice::compiler
236