1//===- SSAFLinker.cpp - SSAF Linker ---------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the SSAF entity linker tool that performs entity
10// linking across multiple TU summaries using the EntityLinker framework.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/EntityLinker.h"
15#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/TUSummaryEncoding.h"
16#include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h"
17#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormatRegistry.h"
18#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
19#include "clang/ScalableStaticAnalysisFramework/SSAFForceLinker.h" // IWYU pragma: keep
20#include "llvm/ADT/STLExtras.h"
21#include "llvm/ADT/SmallVector.h"
22#include "llvm/Support/CommandLine.h"
23#include "llvm/Support/FileSystem.h"
24#include "llvm/Support/FormatVariadic.h"
25#include "llvm/Support/InitLLVM.h"
26#include "llvm/Support/Path.h"
27#include "llvm/Support/Process.h"
28#include "llvm/Support/Timer.h"
29#include "llvm/Support/WithColor.h"
30#include "llvm/Support/raw_ostream.h"
31#include <memory>
32#include <string>
33#include <system_error>
34
35using namespace llvm;
36using namespace clang::ssaf;
37
38namespace fs = llvm::sys::fs;
39namespace path = llvm::sys::path;
40
41namespace {
42
43//===----------------------------------------------------------------------===//
44// Command-Line Options
45//===----------------------------------------------------------------------===//
46
47cl::OptionCategory SsafLinkerCategory("clang-ssaf-linker options");
48
49cl::list<std::string> InputPaths(cl::Positional, cl::desc("<input files>"),
50 cl::OneOrMore, cl::cat(SsafLinkerCategory));
51
52cl::opt<std::string> OutputPath("o", cl::desc("Output summary path"),
53 cl::value_desc("path"), cl::Required,
54 cl::cat(SsafLinkerCategory));
55
56cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"),
57 cl::init(Val: false), cl::cat(SsafLinkerCategory));
58
59cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(Val: false),
60 cl::cat(SsafLinkerCategory));
61
62//===----------------------------------------------------------------------===//
63// Error Messages
64//===----------------------------------------------------------------------===//
65
66namespace ErrorMessages {
67
68constexpr const char *CannotValidateSummary =
69 "failed to validate summary '{0}': {1}";
70
71constexpr const char *OutputDirectoryMissing =
72 "Parent directory does not exist";
73
74constexpr const char *OutputDirectoryNotWritable =
75 "Parent directory is not writable";
76
77constexpr const char *ExtensionNotSupplied = "Extension not supplied";
78
79constexpr const char *NoFormatForExtension =
80 "Format not registered for extension '{0}'";
81
82constexpr const char *LinkingSummary = "Linking summary '{0}'";
83
84} // namespace ErrorMessages
85
86//===----------------------------------------------------------------------===//
87// Diagnostic Utilities
88//===----------------------------------------------------------------------===//
89
90constexpr unsigned IndentationWidth = 2;
91
92llvm::StringRef ToolName;
93
94template <typename... Ts> [[noreturn]] void fail(const char *Msg) {
95 llvm::WithColor::error(OS&: llvm::errs(), Prefix: ToolName) << Msg << "\n";
96 llvm::sys::Process::Exit(RetCode: 1);
97}
98
99template <typename... Ts>
100[[noreturn]] void fail(const char *Fmt, Ts &&...Args) {
101 std::string Message = llvm::formatv(Fmt, std::forward<Ts>(Args)...);
102 fail(Msg: Message.data());
103}
104
105template <typename... Ts> [[noreturn]] void fail(llvm::Error Err) {
106 fail(Msg: toString(E: std::move(Err)).data());
107}
108
109template <typename... Ts>
110void info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
111 if (Verbose) {
112 llvm::WithColor::note()
113 << std::string(IndentationLevel * IndentationWidth, ' ') << "- "
114 << llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
115 }
116}
117
118//===----------------------------------------------------------------------===//
119// Format Registry
120//===----------------------------------------------------------------------===//
121
122SerializationFormat *getFormatForExtension(llvm::StringRef Extension) {
123 static llvm::SmallVector<
124 std::pair<std::string, std::unique_ptr<SerializationFormat>>, 4>
125 ExtensionFormatList;
126
127 // Most recently used format is most likely to be reused again.
128 auto ReversedList = llvm::reverse(C&: ExtensionFormatList);
129 auto It = llvm::find_if(Range&: ReversedList, P: [&](const auto &Entry) {
130 return Entry.first == Extension;
131 });
132 if (It != ReversedList.end()) {
133 return It->second.get();
134 }
135
136 if (!isFormatRegistered(FormatName: Extension)) {
137 return nullptr;
138 }
139
140 auto Format = makeFormat(FormatName: Extension);
141 SerializationFormat *Result = Format.get();
142 assert(Result);
143
144 ExtensionFormatList.emplace_back(Args&: Extension, Args: std::move(Format));
145
146 return Result;
147}
148
149//===----------------------------------------------------------------------===//
150// Data Structures
151//===----------------------------------------------------------------------===//
152
153struct SummaryFile {
154 std::string Path;
155 SerializationFormat *Format = nullptr;
156
157 static SummaryFile fromPath(llvm::StringRef Path) {
158 llvm::StringRef Extension = path::extension(path: Path);
159 if (Extension.empty()) {
160 fail(Fmt: ErrorMessages::CannotValidateSummary, Args&: Path,
161 Args: ErrorMessages::ExtensionNotSupplied);
162 }
163 Extension = Extension.drop_front();
164 SerializationFormat *Format = getFormatForExtension(Extension);
165 if (!Format) {
166 std::string BadExtension =
167 llvm::formatv(Fmt: ErrorMessages::NoFormatForExtension, Vals&: Extension);
168 fail(Fmt: ErrorMessages::CannotValidateSummary, Args&: Path, Args&: BadExtension);
169 }
170 return {.Path: Path.str(), .Format: Format};
171 }
172};
173
174struct LinkerInput {
175 std::vector<SummaryFile> InputFiles;
176 SummaryFile OutputFile;
177 std::string LinkUnitName;
178};
179
180static void printVersion(llvm::raw_ostream &OS) { OS << ToolName << " 0.1\n"; }
181
182//===----------------------------------------------------------------------===//
183// Pipeline
184//===----------------------------------------------------------------------===//
185
186LinkerInput validate(llvm::TimerGroup &TG) {
187 llvm::Timer TValidate("validate", "Validate Input", TG);
188 LinkerInput LI;
189
190 {
191 llvm::TimeRegion _(Time ? &TValidate : nullptr);
192 llvm::StringRef ParentDir = path::parent_path(path: OutputPath);
193 llvm::StringRef DirToCheck = ParentDir.empty() ? "." : ParentDir;
194
195 if (!fs::exists(Path: DirToCheck)) {
196 fail(Fmt: ErrorMessages::CannotValidateSummary, Args&: OutputPath,
197 Args: ErrorMessages::OutputDirectoryMissing);
198 }
199
200 if (fs::access(Path: DirToCheck, Mode: fs::AccessMode::Write)) {
201 fail(Fmt: ErrorMessages::CannotValidateSummary, Args&: OutputPath,
202 Args: ErrorMessages::OutputDirectoryNotWritable);
203 }
204
205 LI.OutputFile = SummaryFile::fromPath(Path: OutputPath);
206 LI.LinkUnitName = path::stem(path: LI.OutputFile.Path).str();
207 }
208
209 info(IndentationLevel: 2, Fmt: "Validated output summary path '{0}'.", Args&: LI.OutputFile.Path);
210
211 {
212 llvm::TimeRegion _(Time ? &TValidate : nullptr);
213 for (const auto &InputPath : InputPaths) {
214 llvm::SmallString<256> RealPath;
215 std::error_code EC = fs::real_path(path: InputPath, output&: RealPath, expand_tilde: true);
216 if (EC) {
217 fail(Fmt: ErrorMessages::CannotValidateSummary, Args: InputPath, Args: EC.message());
218 }
219 LI.InputFiles.push_back(x: SummaryFile::fromPath(Path: RealPath));
220 }
221 }
222
223 info(IndentationLevel: 2, Fmt: "Validated {0} input summary paths.", Args: LI.InputFiles.size());
224
225 return LI;
226}
227
228void link(const LinkerInput &LI, llvm::TimerGroup &TG) {
229 info(IndentationLevel: 2, Fmt: "Constructing linker.");
230
231 EntityLinker EL(NestedBuildNamespace(
232 BuildNamespace(BuildNamespaceKind::LinkUnit, LI.LinkUnitName)));
233
234 llvm::Timer TRead("read", "Read Summaries", TG);
235 llvm::Timer TLink("link", "Link Summaries", TG);
236 llvm::Timer TWrite("write", "Write Summary", TG);
237
238 info(IndentationLevel: 2, Fmt: "Linking summaries.");
239
240 for (auto [Index, InputFile] : llvm::enumerate(First: LI.InputFiles)) {
241 std::unique_ptr<TUSummaryEncoding> Summary;
242
243 {
244 info(IndentationLevel: 3, Fmt: "[{0}/{1}] Reading '{2}'.", Args: (Index + 1), Args: LI.InputFiles.size(),
245 Args: InputFile.Path);
246
247 llvm::TimeRegion _(Time ? &TRead : nullptr);
248
249 auto ExpectedSummaryEncoding =
250 InputFile.Format->readTUSummaryEncoding(Path: InputFile.Path);
251 if (!ExpectedSummaryEncoding) {
252 fail(Err: ExpectedSummaryEncoding.takeError());
253 }
254
255 Summary = std::make_unique<TUSummaryEncoding>(
256 args: std::move(*ExpectedSummaryEncoding));
257 }
258
259 {
260 info(IndentationLevel: 3, Fmt: "[{0}/{1}] Linking '{2}'.", Args: (Index + 1), Args: LI.InputFiles.size(),
261 Args: InputFile.Path);
262
263 llvm::TimeRegion _(Time ? &TLink : nullptr);
264
265 if (auto Err = EL.link(Summary: std::move(Summary))) {
266 fail(Err: ErrorBuilder::wrap(E: std::move(Err))
267 .context(Fmt: ErrorMessages::LinkingSummary, ArgVals: InputFile.Path)
268 .build());
269 }
270 }
271 }
272
273 {
274 info(IndentationLevel: 2, Fmt: "Writing output summary to '{0}'.", Args: LI.OutputFile.Path);
275
276 llvm::TimeRegion _(Time ? &TWrite : nullptr);
277
278 auto Output = std::move(EL).getOutput();
279 if (auto Err = LI.OutputFile.Format->writeLUSummaryEncoding(
280 SummaryEncoding: Output, Path: LI.OutputFile.Path)) {
281 fail(Err: std::move(Err));
282 }
283 }
284}
285
286} // namespace
287
288//===----------------------------------------------------------------------===//
289// Driver
290//===----------------------------------------------------------------------===//
291
292int main(int argc, const char **argv) {
293 InitLLVM X(argc, argv);
294 // path::stem strips the .exe extension on Windows so ToolName is consistent.
295 ToolName = llvm::sys::path::stem(path: argv[0]);
296
297 // Hide options unrelated to clang-ssaf-linker from --help output.
298 cl::HideUnrelatedOptions(Category&: SsafLinkerCategory);
299 // Register a custom version printer for the --version flag.
300 cl::SetVersionPrinter(printVersion);
301 // Parse command-line arguments and exit with an error if they are invalid.
302 cl::ParseCommandLineOptions(argc, argv, Overview: "SSAF Linker\n");
303
304 llvm::TimerGroup LinkerTimers(ToolName, "SSAF Linker");
305 LinkerInput LI;
306
307 {
308 info(IndentationLevel: 0, Fmt: "Linking started.");
309
310 {
311 info(IndentationLevel: 1, Fmt: "Validating input.");
312 LI = validate(TG&: LinkerTimers);
313 }
314
315 {
316 info(IndentationLevel: 1, Fmt: "Linking input.");
317 link(LI, TG&: LinkerTimers);
318 }
319
320 info(IndentationLevel: 0, Fmt: "Linking finished.");
321 }
322
323 return 0;
324}
325