GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 65.5% 78 / 23 / 142
Functions: 90.0% 9 / 0 / 10
Branches: 37.5% 75 / 100 / 300

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 1298 ExternalLinkerInterface::ExternalLinkerInterface(const CliOptions &cliOptions)
19 1298 : outputPath(cliOptions.outputPath), cliOptions(cliOptions) {}
20
21 750 void ExternalLinkerInterface::prepare() {
22 // Static linking
23
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 10 taken 750 times.
750 if (cliOptions.outputContainer == OutputContainer::SHARED_LIBRARY) {
24 addLinkerFlag("-shared");
25
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 18 taken 750 times.
750 } 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 750 times.
750 if (cliOptions.outputContainer != OutputContainer::EXECUTABLE)
31 return;
32
33 // Stripping symbols
34
5/6
✓ Branch 20 → 21 taken 374 times.
✓ Branch 20 → 24 taken 376 times.
✓ Branch 22 → 23 taken 374 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 374 times.
✓ Branch 25 → 33 taken 376 times.
750 if (!cliOptions.instrumentation.generateDebugInfo && !cliOptions.targetTriple.isOSDarwin())
35
2/4
✓ Branch 28 → 29 taken 374 times.
✗ Branch 28 → 108 not taken.
✓ Branch 29 → 30 taken 374 times.
✗ Branch 29 → 106 not taken.
748 addLinkerFlag("-Wl,-s");
36
37 // Sanitizers
38
3/6
✓ Branch 33 → 34 taken 742 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.
750 switch (cliOptions.instrumentation.sanitizer) {
39 742 case Sanitizer::NONE:
40 742 break;
41 6 case Sanitizer::ADDRESS:
42
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");
43 6 break;
44 2 case Sanitizer::THREAD:
45
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");
46 2 break;
47 case Sanitizer::MEMORY:
48 addLinkerFlag("-fsanitize=memory");
49 requestLibMathLinkage();
50 break;
51 case Sanitizer::TYPE:
52 addLinkerFlag("-fsanitize=type");
53 break;
54 }
55
56 // Code coverage
57
2/2
✓ Branch 64 → 65 taken 370 times.
✓ Branch 64 → 72 taken 380 times.
750 if (cliOptions.instrumentation.codeCoverage)
58
2/4
✓ Branch 67 → 68 taken 370 times.
✗ Branch 67 → 138 not taken.
✓ Branch 68 → 69 taken 370 times.
✗ Branch 68 → 136 not taken.
740 addLinkerFlag("--coverage");
59
60 // Web Assembly
61
1/2
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 93 taken 750 times.
750 if (cliOptions.targetTriple.isWasm()) {
62 addLinkerFlag("-nostdlib");
63 addLinkerFlag("-Wl,--no-entry");
64 addLinkerFlag("-Wl,--export-all");
65 }
66 }
67
68 750 void ExternalLinkerInterface::run() const {
69
1/4
✓ Branch 2 → 3 taken 750 times.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 6 not taken.
✗ Branch 2 → 7 not taken.
750 switch (cliOptions.outputContainer) {
70 750 case OutputContainer::EXECUTABLE:
71 case OutputContainer::SHARED_LIBRARY:
72 750 link();
73 750 break;
74 case OutputContainer::STATIC_LIBRARY:
75 archive();
76 break;
77 case OutputContainer::OBJECT_FILE:
78 // No linking necessary
79 break;
80 default:
81 assert_fail("Unknown output container");
82 }
83 750 }
84
85 /**
86 * Cleanup intermediary object files
87 */
88 750 void ExternalLinkerInterface::cleanup() const {
89 // Cleanup intermediary object files
90
1/2
✓ Branch 2 → 3 taken 750 times.
✗ Branch 2 → 33 not taken.
750 const char *objFileExt = SystemUtil::getOutputFileExtension(cliOptions, cliOptions.outputContainer);
91
2/4
✓ Branch 3 → 4 taken 750 times.
✗ Branch 3 → 27 not taken.
✓ Branch 4 → 5 taken 750 times.
✗ Branch 4 → 27 not taken.
750 if (cliOptions.outputContainer != OutputContainer::OBJECT_FILE && !cliOptions.dump.dumpToFiles)
92
2/2
✓ Branch 25 → 7 taken 5252 times.
✓ Branch 25 → 26 taken 750 times.
6752 for (const std::filesystem::path &path : linkedFiles)
93
3/6
✓ Branch 9 → 10 taken 5252 times.
✗ Branch 9 → 31 not taken.
✓ Branch 10 → 11 taken 5252 times.
✗ Branch 10 → 28 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 5252 times.
5252 if (path.extension() == objFileExt)
94 std::filesystem::remove(path);
95 750 }
96
97 /**
98 * Link the object files to an executable
99 */
100 750 void ExternalLinkerInterface::link() const {
101
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 750 times.
750 assert(!outputPath.empty());
102
103 // Find the linker invoker and linker
104
1/2
✓ Branch 5 → 6 taken 750 times.
✗ Branch 5 → 191 not taken.
750 const auto [linkerInvokerName, linkerInvokerPath] = SystemUtil::findLinkerInvoker();
105
1/2
✓ Branch 6 → 7 taken 750 times.
✗ Branch 6 → 189 not taken.
750 const auto [linkerName, linkerPath] = SystemUtil::findLinker(cliOptions);
106 750 const bool isGccInvoker = std::string_view(linkerInvokerName) == LINKER_INVOKER_NAME_GCC;
107 750 const bool isClangInvoker = std::string_view(linkerInvokerName) == LINKER_INVOKER_NAME_CLANG;
108 // GCC does not implement MemorySanitizer or TypeSanitizer
109 750 const Sanitizer sanitizer = cliOptions.instrumentation.sanitizer;
110
1/6
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 23 taken 750 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 23 not taken.
750 if (!isClangInvoker && (sanitizer == Sanitizer::MEMORY || sanitizer == Sanitizer::TYPE)) {
111 const std::string msg = "Memory and type sanitizers require clang as the linker invoker, but 'gcc' was selected";
112 throw LinkerError(SANITIZER_NOT_SUPPORTED_BY_LINKER_INVOKER, msg);
113 }
114
115 // Build the linker argument vector. Each entry is passed verbatim to the linker invoker (no shell), so file paths
116 // can never be re-interpreted as shell syntax - this is what prevents command injection via attacker-controlled
117 // object-file or output paths.
118 750 std::vector<std::string> args;
119 // GCC 16 dropped '-fuse-ld=ld'; skip when using GCC with the default BFD linker
120
2/6
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 28 taken 750 times.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 29 not taken.
✓ Branch 30 → 31 taken 750 times.
✗ Branch 30 → 35 not taken.
750 if (!isGccInvoker || std::string_view(linkerName) != LINKER_NAME_LD)
121
2/4
✓ Branch 31 → 32 taken 750 times.
✗ Branch 31 → 154 not taken.
✓ Branch 32 → 33 taken 750 times.
✗ Branch 32 → 152 not taken.
750 args.push_back("-fuse-ld=" + linkerPath);
122 // '--target=' is clang-only; GCC uses target-specific toolchain prefixes instead
123
1/2
✓ Branch 35 → 36 taken 750 times.
✗ Branch 35 → 41 not taken.
750 if (!isGccInvoker)
124
2/4
✓ Branch 37 → 38 taken 750 times.
✗ Branch 37 → 157 not taken.
✓ Branch 38 → 39 taken 750 times.
✗ Branch 38 → 155 not taken.
750 args.push_back("--target=" + cliOptions.targetTriple.str());
125 // Append linker flags, expanding any environment-variable references or backtick command substitutions they may
126 // contain (e.g. std bindings using "-L$LLVM_LIB_DIR" or "`pkg-config --cflags --libs libcurl`"); a single flag can
127 // expand into several argv entries.
128
2/2
✓ Branch 73 → 43 taken 3734 times.
✓ Branch 73 → 74 taken 750 times.
5234 for (const std::string &linkerFlag : linkerFlags)
129
3/4
✓ Branch 45 → 46 taken 3734 times.
✗ Branch 45 → 160 not taken.
✓ Branch 62 → 48 taken 3768 times.
✓ Branch 62 → 63 taken 3734 times.
15004 for (std::string &expandedFlag : SystemUtil::expandLinkerFlag(linkerFlag))
130
1/2
✓ Branch 52 → 53 taken 3768 times.
✗ Branch 52 → 158 not taken.
3768 args.push_back(std::move(expandedFlag));
131
2/2
✓ Branch 75 → 76 taken 2 times.
✓ Branch 75 → 77 taken 748 times.
750 if (linkLibMath)
132
1/2
✓ Branch 76 → 77 taken 2 times.
✗ Branch 76 → 185 not taken.
2 args.emplace_back("-lm");
133 // Append output path
134
1/2
✓ Branch 77 → 78 taken 750 times.
✗ Branch 77 → 185 not taken.
750 args.emplace_back("-o");
135
2/4
✓ Branch 78 → 79 taken 750 times.
✗ Branch 78 → 164 not taken.
✓ Branch 79 → 80 taken 750 times.
✗ Branch 79 → 162 not taken.
750 args.push_back(outputPath.string());
136 // Append object files
137
2/2
✓ Branch 97 → 83 taken 5252 times.
✓ Branch 97 → 98 taken 750 times.
6752 for (const std::filesystem::path &objectFilePath : linkedFiles)
138
2/4
✓ Branch 85 → 86 taken 5252 times.
✗ Branch 85 → 167 not taken.
✓ Branch 86 → 87 taken 5252 times.
✗ Branch 86 → 165 not taken.
5252 args.push_back(objectFilePath.string());
139
140 // Print status message
141
1/2
✗ Branch 98 → 99 not taken.
✓ Branch 98 → 114 taken 750 times.
750 if (cliOptions.printDebugOutput) {
142 const std::string command = SystemUtil::renderCommandForDisplay(linkerInvokerPath, args);
143 std::cout << "\nLinking with: " << linkerInvokerName << " (invoker) / " << linkerName << " (linker)"; // GCOV_EXCL_LINE
144 std::cout << "\nLinker command: " << command; // GCOV_EXCL_LINE
145 std::cout << "\nEmitting executable to path: " << outputPath.string() << "\n"; // GCOV_EXCL_LINE
146 }
147
148 // Call the linker
149
1/2
✓ Branch 114 → 115 taken 750 times.
✗ Branch 114 → 185 not taken.
750 Timer timer;
150
1/2
✓ Branch 115 → 116 taken 750 times.
✗ Branch 115 → 185 not taken.
750 timer.start();
151
1/2
✓ Branch 116 → 117 taken 750 times.
✗ Branch 116 → 185 not taken.
750 const auto [output, exitCode] = SystemUtil::exec(linkerInvokerPath, args);
152
1/2
✓ Branch 117 → 118 taken 750 times.
✗ Branch 117 → 183 not taken.
750 timer.stop();
153
154 // Check for linker error
155 if (exitCode != 0) { // GCOV_EXCL_LINE
156 const std::string command = SystemUtil::renderCommandForDisplay(linkerInvokerPath, args); // GCOV_EXCL_LINE
157 const std::string errorMessage = "Linker exited with non-zero exit code\nLinker command: " + command; // GCOV_EXCL_LINE
158 throw LinkerError(LINKER_ERROR, errorMessage); // GCOV_EXCL_LINE
159 } // GCOV_EXCL_LINE
160
161 // Print linker result if appropriate
162 if (cliOptions.printDebugOutput && !output.empty()) // GCOV_EXCL_LINE
163 std::cout << "Linking result: " << output << "\n\n"; // GCOV_EXCL_LINE
164
165 // Print link time
166 if (cliOptions.printDebugOutput) // GCOV_EXCL_LINE
167 std::cout << "Total link time: " << timer.getDurationMilliseconds() << " ms\n\n"; // GCOV_EXCL_LINE
168 750 }
169
170 /**
171 * Archive the object files to a static library
172 */
173 void ExternalLinkerInterface::archive() const {
174 assert(!outputPath.empty());
175
176 // Find the archiver
177 const auto [archiverName, archiverPath] = SystemUtil::findArchiver();
178
179 // Build the archiver argument vector (passed verbatim, no shell involved)
180 std::vector<std::string> args;
181 args.emplace_back("rcs"); // r = insert files into archive; c = create archive if not existing, s = create archive index
182 args.push_back(outputPath.string());
183 for (const std::filesystem::path &path : linkedFiles)
184 args.push_back(path.string());
185
186 // Print status message
187 if (cliOptions.printDebugOutput) {
188 std::cout << "\nArchiving with: " << archiverName; // GCOV_EXCL_LINE
189 std::cout << "\nArchiver command: " << SystemUtil::renderCommandForDisplay(archiverPath, args); // GCOV_EXCL_LINE
190 std::cout << "\nEmitting static library to path: " << outputPath.string() << "\n"; // GCOV_EXCL_LINE
191 }
192
193 // Call the archiver
194 Timer timer;
195 timer.start();
196 const auto [output, exitCode] = SystemUtil::exec(archiverPath, args);
197 timer.stop();
198
199 // Check for linker error
200 if (exitCode != 0) { // GCOV_EXCL_LINE
201 const std::string command = SystemUtil::renderCommandForDisplay(archiverPath, args); // GCOV_EXCL_LINE
202 const std::string errorMessage = "Archiver exited with non-zero exit code\nArchiver command: " + command; // GCOV_EXCL_LINE
203 throw LinkerError(LINKER_ERROR, errorMessage); // GCOV_EXCL_LINE
204 }
205
206 // Print linker result if appropriate
207 if (cliOptions.printDebugOutput && !output.empty()) // GCOV_EXCL_LINE
208 std::cout << "Archiving result: " << output << "\n\n"; // GCOV_EXCL_LINE
209
210 // Print link time
211 if (cliOptions.printDebugOutput) // GCOV_EXCL_LINE
212 std::cout << "Total archive time: " << timer.getDurationMilliseconds() << " ms\n\n"; // GCOV_EXCL_LINE
213 }
214
215 /**
216 * Add another object file to be linked when calling 'link()'
217 *
218 * @param path Path to the object file
219 */
220 5440 void ExternalLinkerInterface::addFileToLinkage(const std::filesystem::path &path) {
221
2/4
✓ Branch 3 → 4 taken 5440 times.
✗ Branch 3 → 13 not taken.
✓ Branch 10 → 11 taken 5440 times.
✗ Branch 10 → 12 not taken.
10880 if (std::ranges::find(linkedFiles, path) == linkedFiles.end())
222 5440 linkedFiles.push_back(path);
223 5440 }
224
225 /**
226 * Add another linker flag for the call to the linker executable
227 *
228 * @param flag Linker flag
229 */
230 3744 void ExternalLinkerInterface::addLinkerFlag(const std::string &flag) {
231
3/4
✓ Branch 3 → 4 taken 3744 times.
✗ Branch 3 → 13 not taken.
✓ Branch 10 → 11 taken 3740 times.
✓ Branch 10 → 12 taken 4 times.
7488 if (std::ranges::find(linkerFlags, flag) == linkerFlags.end())
232 3740 linkerFlags.push_back(flag);
233 3744 }
234
235 /**
236 * Add another source file to compile and link in (C or C++)
237 *
238 * @param additionalSource Additional source file
239 */
240 24 void ExternalLinkerInterface::addAdditionalSourcePath(std::filesystem::path additionalSource) {
241 // Check if the file exists
242
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 12 taken 22 times.
24 if (!exists(additionalSource)) {
243
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";
244
1/2
✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 24 not taken.
2 throw CompilerError(IO_ERROR, msg);
245 2 }
246
247 // Add the file to the linker
248
1/2
✓ Branch 12 → 13 taken 22 times.
✗ Branch 12 → 30 not taken.
22 additionalSource = canonical(additionalSource);
249 22 additionalSource.make_preferred();
250 22 addFileToLinkage(additionalSource);
251 22 }
252
253 /**
254 * Link against libmath a.k.a. -lm
255 */
256 4 void ExternalLinkerInterface::requestLibMathLinkage() { linkLibMath = true; }
257
258 } // namespace spice::compiler
259