GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 65.6% 80 / 23 / 145
Functions: 90.0% 9 / 0 / 10
Branches: 36.3% 77 / 100 / 312

src/linker/ExternalLinkerInterface.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "ExternalLinkerInterface.h"
4
5 #include <algorithm>
6 #include <iostream>
7 #include <vector>
8
9 #include <driver/Driver.h>
10 #include <exception/CompilerError.h>
11 #include <exception/LinkerError.h>
12 #include <util/GlobalDefinitions.h>
13 #include <util/SystemUtil.h>
14 #include <util/Timer.h>
15
16 namespace spice::compiler {
17
18 1328 ExternalLinkerInterface::ExternalLinkerInterface(const CliOptions &cliOptions)
19 1328 : outputPath(cliOptions.outputPath), cliOptions(cliOptions) {}
20
21 766 void ExternalLinkerInterface::prepare() {
22 // Static linking
23
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 10 taken 766 times.
766 if (cliOptions.outputContainer == OutputContainer::SHARED_LIBRARY) {
24 addLinkerFlag("-shared");
25
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 18 taken 766 times.
766 } else if (cliOptions.staticLinking) {
26 addLinkerFlag("-static");
27 }
28
29 // The following flags only make sense if we want to emit an executable
30
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 766 times.
766 if (cliOptions.outputContainer != OutputContainer::EXECUTABLE)
31 return;
32
33 // Stripping symbols, on request only: the symbol table is what a run-time symbolizer such as the stack trace
34 // runtime resolves addresses against, so a stripped executable prints nothing but addresses. Darwin is excluded
35 // because its linker has no equivalent of '-Wl,-s'.
36
2/6
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 24 taken 766 times.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 24 not taken.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 33 taken 766 times.
766 if (cliOptions.stripSymbols && !cliOptions.targetTriple.isOSDarwin())
37 addLinkerFlag("-Wl,-s");
38
39 // Sanitizers
40
3/6
✓ Branch 33 → 34 taken 758 times.
✓ Branch 33 → 35 taken 6 times.
✓ Branch 33 → 42 taken 2 times.
✗ Branch 33 → 49 not taken.
✗ Branch 33 → 57 not taken.
✗ Branch 33 → 64 not taken.
766 switch (cliOptions.instrumentation.sanitizer) {
41 758 case Sanitizer::NONE:
42 758 break;
43 6 case Sanitizer::ADDRESS:
44
2/4
✓ Branch 37 → 38 taken 6 times.
✗ Branch 37 → 114 not taken.
✓ Branch 38 → 39 taken 6 times.
✗ Branch 38 → 112 not taken.
6 addLinkerFlag("-fsanitize=address");
45 6 break;
46 2 case Sanitizer::THREAD:
47
2/4
✓ Branch 44 → 45 taken 2 times.
✗ Branch 44 → 120 not taken.
✓ Branch 45 → 46 taken 2 times.
✗ Branch 45 → 118 not taken.
2 addLinkerFlag("-fsanitize=thread");
48 2 break;
49 case Sanitizer::MEMORY:
50 addLinkerFlag("-fsanitize=memory");
51 requestLibMathLinkage();
52 break;
53 case Sanitizer::TYPE:
54 addLinkerFlag("-fsanitize=type");
55 break;
56 }
57
58 // Code coverage
59
2/2
✓ Branch 64 → 65 taken 378 times.
✓ Branch 64 → 72 taken 388 times.
766 if (cliOptions.instrumentation.codeCoverage)
60
2/4
✓ Branch 67 → 68 taken 378 times.
✗ Branch 67 → 138 not taken.
✓ Branch 68 → 69 taken 378 times.
✗ Branch 68 → 136 not taken.
756 addLinkerFlag("--coverage");
61
62 // Web Assembly
63
1/2
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 93 taken 766 times.
766 if (cliOptions.targetTriple.isWasm()) {
64 addLinkerFlag("-nostdlib");
65 addLinkerFlag("-Wl,--no-entry");
66 addLinkerFlag("-Wl,--export-all");
67 }
68 }
69
70 766 void ExternalLinkerInterface::run() const {
71
1/4
✓ Branch 2 → 3 taken 766 times.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 6 not taken.
✗ Branch 2 → 7 not taken.
766 switch (cliOptions.outputContainer) {
72 766 case OutputContainer::EXECUTABLE:
73 case OutputContainer::SHARED_LIBRARY:
74 766 link();
75 766 break;
76 case OutputContainer::STATIC_LIBRARY:
77 archive();
78 break;
79 case OutputContainer::OBJECT_FILE:
80 // No linking necessary
81 break;
82 default:
83 assert_fail("Unknown output container");
84 }
85 766 }
86
87 /**
88 * Cleanup intermediary object files
89 */
90 766 void ExternalLinkerInterface::cleanup() const {
91 // Cleanup intermediary object files
92
1/2
✓ Branch 2 → 3 taken 766 times.
✗ Branch 2 → 33 not taken.
766 const char *objFileExt = SystemUtil::getOutputFileExtension(cliOptions, cliOptions.outputContainer);
93
2/4
✓ Branch 3 → 4 taken 766 times.
✗ Branch 3 → 27 not taken.
✓ Branch 4 → 5 taken 766 times.
✗ Branch 4 → 27 not taken.
766 if (cliOptions.outputContainer != OutputContainer::OBJECT_FILE && !cliOptions.dump.dumpToFiles)
94
2/2
✓ Branch 25 → 7 taken 5422 times.
✓ Branch 25 → 26 taken 766 times.
6954 for (const std::filesystem::path &path : linkedFiles)
95
3/6
✓ Branch 9 → 10 taken 5422 times.
✗ Branch 9 → 31 not taken.
✓ Branch 10 → 11 taken 5422 times.
✗ Branch 10 → 28 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 5422 times.
5422 if (path.extension() == objFileExt)
96 std::filesystem::remove(path);
97 766 }
98
99 /**
100 * Link the object files to an executable
101 */
102 766 void ExternalLinkerInterface::link() const {
103
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 766 times.
766 assert(!outputPath.empty());
104
105 // Find the linker invoker and linker
106
1/2
✓ Branch 5 → 6 taken 766 times.
✗ Branch 5 → 212 not taken.
766 const auto [linkerInvokerName, linkerInvokerPath] = SystemUtil::findLinkerInvoker();
107
1/2
✓ Branch 6 → 7 taken 766 times.
✗ Branch 6 → 210 not taken.
766 const auto [linkerName, linkerPath] = SystemUtil::findLinker(cliOptions);
108 766 const bool isGccInvoker = std::string_view(linkerInvokerName) == LINKER_INVOKER_NAME_GCC;
109 766 const bool isClangInvoker = std::string_view(linkerInvokerName) == LINKER_INVOKER_NAME_CLANG;
110 // GCC does not implement MemorySanitizer or TypeSanitizer
111 766 const Sanitizer sanitizer = cliOptions.instrumentation.sanitizer;
112
1/6
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 23 taken 766 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 23 not taken.
766 if (!isClangInvoker && (sanitizer == Sanitizer::MEMORY || sanitizer == Sanitizer::TYPE)) {
113 const std::string msg = "Memory and type sanitizers require clang as the linker invoker, but 'gcc' was selected";
114 throw LinkerError(SANITIZER_NOT_SUPPORTED_BY_LINKER_INVOKER, msg);
115 }
116
117 // Build the linker argument vector. Each entry is passed verbatim to the linker invoker (no shell), so file paths
118 // can never be re-interpreted as shell syntax - this is what prevents command injection via attacker-controlled
119 // object-file or output paths.
120 766 std::vector<std::string> args;
121 // GCC 16 dropped '-fuse-ld=ld'; skip when using GCC with the default BFD linker
122
2/6
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 28 taken 766 times.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 29 not taken.
✓ Branch 30 → 31 taken 766 times.
✗ Branch 30 → 35 not taken.
766 if (!isGccInvoker || std::string_view(linkerName) != LINKER_NAME_LD)
123
2/4
✓ Branch 31 → 32 taken 766 times.
✗ Branch 31 → 166 not taken.
✓ Branch 32 → 33 taken 766 times.
✗ Branch 32 → 164 not taken.
766 args.push_back("-fuse-ld=" + linkerPath);
124 // '--target=' is clang-only; GCC uses target-specific toolchain prefixes instead
125
1/2
✓ Branch 35 → 36 taken 766 times.
✗ Branch 35 → 41 not taken.
766 if (!isGccInvoker)
126
2/4
✓ Branch 37 → 38 taken 766 times.
✗ Branch 37 → 169 not taken.
✓ Branch 38 → 39 taken 766 times.
✗ Branch 38 → 167 not taken.
766 args.push_back("--target=" + cliOptions.targetTriple.str());
127 // Append output path
128
1/2
✓ Branch 41 → 42 taken 766 times.
✗ Branch 41 → 206 not taken.
766 args.emplace_back("-o");
129
2/4
✓ Branch 42 → 43 taken 766 times.
✗ Branch 42 → 172 not taken.
✓ Branch 43 → 44 taken 766 times.
✗ Branch 43 → 170 not taken.
766 args.push_back(outputPath.string());
130 // Append object files
131
2/2
✓ Branch 61 → 47 taken 5422 times.
✓ Branch 61 → 62 taken 766 times.
6954 for (const std::filesystem::path &objectFilePath : linkedFiles)
132
2/4
✓ Branch 49 → 50 taken 5422 times.
✗ Branch 49 → 175 not taken.
✓ Branch 50 → 51 taken 5422 times.
✗ Branch 50 → 173 not taken.
5422 args.push_back(objectFilePath.string());
133 // Append the std's own library search path, so the runtime modules can link against the static support libraries
134 // that ship with the std (currently std/runtime/lib/libbacktrace.a, which std/runtime/impl/stack_trace_native.spice
135 // pulls in with '-lbacktrace'). It goes ahead of the linker flags below because search directories are tried in the
136 // order given, so the std links against its own archive rather than a same-named one in a directory that a
137 // binding's '-L' flag happens to add. Passed verbatim rather than through addLinkerFlag(), so that a std path
138 // containing '$' or a backtick is not mistaken for a variable reference or a command substitution.
139 // Only for a native build: those archives are built for the host the std was installed on, so offering them while
140 // linking for another target can only ever produce a mismatch - lld rejects every member outright
141 // ("is incompatible with aarch64linux"). Cross-compiling a program that takes a stack trace needs a libbacktrace
142 // built for the target, which has to come from the toolchain's own search path.
143
1/2
✓ Branch 62 → 63 taken 766 times.
✗ Branch 62 → 74 not taken.
766 if (cliOptions.isNativeTarget)
144
2/4
✓ Branch 63 → 64 taken 766 times.
✗ Branch 63 → 185 not taken.
✓ Branch 65 → 66 taken 766 times.
✗ Branch 65 → 72 not taken.
766 if (const std::filesystem::path stdRuntimeLibDir = SystemUtil::getStdRuntimeLibDir(); !stdRuntimeLibDir.empty())
145
3/6
✓ Branch 66 → 67 taken 766 times.
✗ Branch 66 → 181 not taken.
✓ Branch 67 → 68 taken 766 times.
✗ Branch 67 → 179 not taken.
✓ Branch 68 → 69 taken 766 times.
✗ Branch 68 → 177 not taken.
766 args.push_back("-L" + stdRuntimeLibDir.string());
146 // Append linker flags, expanding any environment-variable references or backtick command substitutions they may
147 // contain (e.g. std bindings using "-L$LLVM_LIB_DIR" or "`pkg-config --cflags --libs libcurl`"); a single flag can
148 // expand into several argv entries. They go behind the object files, because a '-l' naming a static archive is only
149 // searched for symbols that are still undefined at the point it appears - ahead of them it would resolve nothing.
150
2/2
✓ Branch 106 → 76 taken 3378 times.
✓ Branch 106 → 107 taken 766 times.
4910 for (const std::string &linkerFlag : linkerFlags)
151
3/4
✓ Branch 78 → 79 taken 3378 times.
✗ Branch 78 → 188 not taken.
✓ Branch 95 → 81 taken 3412 times.
✓ Branch 95 → 96 taken 3378 times.
13580 for (std::string &expandedFlag : SystemUtil::expandLinkerFlag(linkerFlag))
152
1/2
✓ Branch 85 → 86 taken 3412 times.
✗ Branch 85 → 186 not taken.
3412 args.push_back(std::move(expandedFlag));
153
2/2
✓ Branch 108 → 109 taken 2 times.
✓ Branch 108 → 110 taken 764 times.
766 if (linkLibMath)
154
1/2
✓ Branch 109 → 110 taken 2 times.
✗ Branch 109 → 206 not taken.
2 args.emplace_back("-lm");
155
156 // Print status message
157
1/2
✗ Branch 110 → 111 not taken.
✓ Branch 110 → 126 taken 766 times.
766 if (cliOptions.printDebugOutput) {
158 const std::string command = SystemUtil::renderCommandForDisplay(linkerInvokerPath, args);
159 std::cout << "\nLinking with: " << linkerInvokerName << " (invoker) / " << linkerName << " (linker)"; // GCOV_EXCL_LINE
160 std::cout << "\nLinker command: " << command; // GCOV_EXCL_LINE
161 std::cout << "\nEmitting executable to path: " << outputPath.string() << "\n"; // GCOV_EXCL_LINE
162 }
163
164 // Call the linker
165
1/2
✓ Branch 126 → 127 taken 766 times.
✗ Branch 126 → 206 not taken.
766 Timer timer;
166
1/2
✓ Branch 127 → 128 taken 766 times.
✗ Branch 127 → 206 not taken.
766 timer.start();
167
1/2
✓ Branch 128 → 129 taken 766 times.
✗ Branch 128 → 206 not taken.
766 const auto [output, exitCode] = SystemUtil::exec(linkerInvokerPath, args);
168
1/2
✓ Branch 129 → 130 taken 766 times.
✗ Branch 129 → 204 not taken.
766 timer.stop();
169
170 // Check for linker error
171 if (exitCode != 0) { // GCOV_EXCL_LINE
172 const std::string command = SystemUtil::renderCommandForDisplay(linkerInvokerPath, args); // GCOV_EXCL_LINE
173 const std::string errorMessage = "Linker exited with non-zero exit code\nLinker command: " + command; // GCOV_EXCL_LINE
174 throw LinkerError(LINKER_ERROR, errorMessage); // GCOV_EXCL_LINE
175 } // GCOV_EXCL_LINE
176
177 // Print linker result if appropriate
178 if (cliOptions.printDebugOutput && !output.empty()) // GCOV_EXCL_LINE
179 std::cout << "Linking result: " << output << "\n\n"; // GCOV_EXCL_LINE
180
181 // Print link time
182 if (cliOptions.printDebugOutput) // GCOV_EXCL_LINE
183 std::cout << "Total link time: " << timer.getDurationMilliseconds() << " ms\n\n"; // GCOV_EXCL_LINE
184 766 }
185
186 /**
187 * Archive the object files to a static library
188 */
189 void ExternalLinkerInterface::archive() const {
190 assert(!outputPath.empty());
191
192 // Find the archiver
193 const auto [archiverName, archiverPath] = SystemUtil::findArchiver();
194
195 // Build the archiver argument vector (passed verbatim, no shell involved)
196 std::vector<std::string> args;
197 args.emplace_back("rcs"); // r = insert files into archive; c = create archive if not existing, s = create archive index
198 args.push_back(outputPath.string());
199 for (const std::filesystem::path &path : linkedFiles)
200 args.push_back(path.string());
201
202 // Print status message
203 if (cliOptions.printDebugOutput) {
204 std::cout << "\nArchiving with: " << archiverName; // GCOV_EXCL_LINE
205 std::cout << "\nArchiver command: " << SystemUtil::renderCommandForDisplay(archiverPath, args); // GCOV_EXCL_LINE
206 std::cout << "\nEmitting static library to path: " << outputPath.string() << "\n"; // GCOV_EXCL_LINE
207 }
208
209 // Call the archiver
210 Timer timer;
211 timer.start();
212 const auto [output, exitCode] = SystemUtil::exec(archiverPath, args);
213 timer.stop();
214
215 // Check for linker error
216 if (exitCode != 0) { // GCOV_EXCL_LINE
217 const std::string command = SystemUtil::renderCommandForDisplay(archiverPath, args); // GCOV_EXCL_LINE
218 const std::string errorMessage = "Archiver exited with non-zero exit code\nArchiver command: " + command; // GCOV_EXCL_LINE
219 throw LinkerError(LINKER_ERROR, errorMessage); // GCOV_EXCL_LINE
220 }
221
222 // Print linker result if appropriate
223 if (cliOptions.printDebugOutput && !output.empty()) // GCOV_EXCL_LINE
224 std::cout << "Archiving result: " << output << "\n\n"; // GCOV_EXCL_LINE
225
226 // Print link time
227 if (cliOptions.printDebugOutput) // GCOV_EXCL_LINE
228 std::cout << "Total archive time: " << timer.getDurationMilliseconds() << " ms\n\n"; // GCOV_EXCL_LINE
229 }
230
231 /**
232 * Add another object file to be linked when calling 'link()'
233 *
234 * @param path Path to the object file
235 */
236 5630 void ExternalLinkerInterface::addFileToLinkage(const std::filesystem::path &path) {
237
3/4
✓ Branch 3 → 4 taken 5630 times.
✗ Branch 3 → 13 not taken.
✓ Branch 10 → 11 taken 5626 times.
✓ Branch 10 → 12 taken 4 times.
11260 if (std::ranges::find(linkedFiles, path) == linkedFiles.end())
238 5626 linkedFiles.push_back(path);
239 5630 }
240
241 /**
242 * Add another linker flag for the call to the linker executable
243 *
244 * @param flag Linker flag
245 */
246 3388 void ExternalLinkerInterface::addLinkerFlag(const std::string &flag) {
247
3/4
✓ Branch 3 → 4 taken 3388 times.
✗ Branch 3 → 13 not taken.
✓ Branch 10 → 11 taken 3384 times.
✓ Branch 10 → 12 taken 4 times.
6776 if (std::ranges::find(linkerFlags, flag) == linkerFlags.end())
248 3384 linkerFlags.push_back(flag);
249 3388 }
250
251 /**
252 * Add another source file to compile and link in (C or C++)
253 *
254 * @param additionalSource Additional source file
255 */
256 24 void ExternalLinkerInterface::addAdditionalSourcePath(std::filesystem::path additionalSource) {
257 // Check if the file exists
258
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 12 taken 22 times.
24 if (!exists(additionalSource)) {
259
3/6
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 22 not taken.
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 20 not taken.
✓ Branch 6 → 7 taken 2 times.
✗ Branch 6 → 18 not taken.
2 const std::string msg = "The additional source file '" + additionalSource.string() + "' does not exist";
260
1/2
✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 24 not taken.
2 throw CompilerError(IO_ERROR, msg);
261 2 }
262
263 // Add the file to the linker
264
1/2
✓ Branch 12 → 13 taken 22 times.
✗ Branch 12 → 30 not taken.
22 additionalSource = canonical(additionalSource);
265 22 additionalSource.make_preferred();
266 22 addFileToLinkage(additionalSource);
267 22 }
268
269 /**
270 * Link against libmath a.k.a. -lm
271 */
272 4 void ExternalLinkerInterface::requestLibMathLinkage() { linkLibMath = true; }
273
274 } // namespace spice::compiler
275