| 1 | //===- SSAFFormat.cpp - SSAF Format Tool ----------------------------------===// |
| 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 format tool that validates and converts |
| 10 | // TU and LU summaries between registered serialization formats. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h" |
| 15 | #include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h" |
| 16 | #include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h" |
| 17 | #include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h" |
| 18 | #include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h" |
| 19 | #include "clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h" |
| 20 | #include "clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormatRegistry.h" |
| 21 | #include "clang/ScalableStaticAnalysis/SSAFForceLinker.h" // IWYU pragma: keep |
| 22 | #include "clang/ScalableStaticAnalysis/Tool/Utils.h" |
| 23 | #include "llvm/ADT/STLExtras.h" |
| 24 | #include "llvm/ADT/SmallVector.h" |
| 25 | #include "llvm/Support/CommandLine.h" |
| 26 | #include "llvm/Support/ErrorHandling.h" |
| 27 | #include "llvm/Support/Format.h" |
| 28 | #include "llvm/Support/FormatVariadic.h" |
| 29 | #include "llvm/Support/InitLLVM.h" |
| 30 | #include "llvm/Support/Path.h" |
| 31 | #include "llvm/Support/raw_ostream.h" |
| 32 | #include <memory> |
| 33 | #include <optional> |
| 34 | #include <string> |
| 35 | |
| 36 | using namespace llvm; |
| 37 | using namespace clang::ssaf; |
| 38 | |
| 39 | namespace { |
| 40 | |
| 41 | //===----------------------------------------------------------------------===// |
| 42 | // Summary Type |
| 43 | //===----------------------------------------------------------------------===// |
| 44 | |
| 45 | enum class SummaryType { |
| 46 | Auto, |
| 47 | TU, |
| 48 | LU, |
| 49 | StaticLibrary, |
| 50 | MultiArchStaticLibrary, |
| 51 | MultiArchSharedLibrary, |
| 52 | WPA |
| 53 | }; |
| 54 | |
| 55 | //===----------------------------------------------------------------------===// |
| 56 | // Command-Line Options |
| 57 | //===----------------------------------------------------------------------===// |
| 58 | |
| 59 | cl::OptionCategory SsafFormatCategory("clang-ssaf-format options" ); |
| 60 | |
| 61 | cl::list<std::string> LoadPlugins("load" , |
| 62 | cl::desc("Load a plugin shared library" ), |
| 63 | cl::value_desc("path" ), |
| 64 | cl::cat(SsafFormatCategory)); |
| 65 | |
| 66 | // Defaults to 'auto', which inspects the file's self-describing 'type' |
| 67 | // field and dispatches to the matching reader/writer. Explicit values |
| 68 | // force the use of the corresponding kind-specific reader/writer. |
| 69 | cl::opt<SummaryType> Type( |
| 70 | "type" , |
| 71 | cl::desc("Summary type (defaults to 'auto', which uses the file's " |
| 72 | "self-describing 'type' field)" ), |
| 73 | cl::values(clEnumValN(SummaryType::Auto, "auto" , |
| 74 | "Detect type from the file's 'type' field" ), |
| 75 | clEnumValN(SummaryType::TU, "tu" , "Translation unit summary" ), |
| 76 | clEnumValN(SummaryType::LU, "lu" , "Link unit summary" ), |
| 77 | clEnumValN(SummaryType::StaticLibrary, "static-library" , |
| 78 | "Static library of translation unit summaries" ), |
| 79 | clEnumValN(SummaryType::MultiArchStaticLibrary, |
| 80 | "multi-arch-static-library" , |
| 81 | "Multi-architecture static library" ), |
| 82 | clEnumValN(SummaryType::MultiArchSharedLibrary, |
| 83 | "multi-arch-shared-library" , |
| 84 | "Multi-architecture shared library" ), |
| 85 | clEnumValN(SummaryType::WPA, "wpa" , |
| 86 | "Whole-program analysis suite" )), |
| 87 | cl::init(Val: SummaryType::Auto), cl::cat(SsafFormatCategory)); |
| 88 | |
| 89 | cl::opt<std::string> InputPath(cl::Positional, cl::desc("<input file>" ), |
| 90 | cl::cat(SsafFormatCategory)); |
| 91 | |
| 92 | cl::opt<std::string> OutputPath("o" , cl::desc("Output file path" ), |
| 93 | cl::value_desc("path" ), |
| 94 | cl::cat(SsafFormatCategory)); |
| 95 | |
| 96 | cl::opt<bool> UseEncoding("encoding" , |
| 97 | cl::desc("Read and write summary encodings rather " |
| 98 | "than decoded summaries" ), |
| 99 | cl::cat(SsafFormatCategory)); |
| 100 | |
| 101 | cl::opt<bool> ListFormats("list" , |
| 102 | cl::desc("List registered serialization formats and " |
| 103 | "analyses, then exit" ), |
| 104 | cl::init(Val: false), cl::cat(SsafFormatCategory)); |
| 105 | |
| 106 | //===----------------------------------------------------------------------===// |
| 107 | // Format Listing |
| 108 | //===----------------------------------------------------------------------===// |
| 109 | |
| 110 | constexpr size_t FormatIndent = 4; |
| 111 | constexpr size_t AnalysisIndent = 4; |
| 112 | |
| 113 | struct AnalysisData { |
| 114 | std::string Name; |
| 115 | std::string Desc; |
| 116 | }; |
| 117 | |
| 118 | struct FormatData { |
| 119 | std::string Name; |
| 120 | std::string Desc; |
| 121 | llvm::SmallVector<AnalysisData> Analyses; |
| 122 | }; |
| 123 | |
| 124 | struct PrintLayout { |
| 125 | size_t FormatNumWidth; |
| 126 | size_t MaxFormatNameWidth; |
| 127 | size_t FormatNameCol; |
| 128 | size_t AnalysisCol; |
| 129 | size_t AnalysisNumWidth; |
| 130 | size_t MaxAnalysisNameWidth; |
| 131 | }; |
| 132 | |
| 133 | llvm::SmallVector<FormatData> collectFormats() { |
| 134 | llvm::SmallVector<FormatData> Formats; |
| 135 | for (const auto &Entry : SerializationFormatRegistry::entries()) { |
| 136 | FormatData FD; |
| 137 | FD.Name = Entry.getName().str(); |
| 138 | FD.Desc = Entry.getDesc().str(); |
| 139 | auto Format = Entry.instantiate(); |
| 140 | Format->forEachRegisteredAnalysis( |
| 141 | Callback: [&](llvm::StringRef Name, llvm::StringRef Desc) { |
| 142 | FD.Analyses.push_back(Elt: {.Name: Name.str(), .Desc: Desc.str()}); |
| 143 | }); |
| 144 | Formats.push_back(Elt: std::move(FD)); |
| 145 | } |
| 146 | return Formats; |
| 147 | } |
| 148 | |
| 149 | void printAnalysis(const AnalysisData &AD, size_t AnalysisIndex, |
| 150 | size_t FormatIndex, const PrintLayout &Layout) { |
| 151 | std::string AnalysisNum = std::to_string(val: FormatIndex + 1) + "." + |
| 152 | std::to_string(val: AnalysisIndex + 1) + "." ; |
| 153 | llvm::outs().indent(NumSpaces: Layout.AnalysisCol) |
| 154 | << llvm::right_justify(Str: AnalysisNum, Width: Layout.AnalysisNumWidth) << " " |
| 155 | << llvm::left_justify(Str: AD.Name, Width: Layout.MaxAnalysisNameWidth) << " - " |
| 156 | << AD.Desc << "\n" ; |
| 157 | } |
| 158 | |
| 159 | void printAnalyses(const llvm::SmallVector<AnalysisData> &Analyses, |
| 160 | size_t FormatIndex, const PrintLayout &Layout) { |
| 161 | if (Analyses.empty()) { |
| 162 | llvm::outs().indent(NumSpaces: Layout.FormatNameCol) << "Analyses: (none)\n" ; |
| 163 | return; |
| 164 | } |
| 165 | |
| 166 | llvm::outs().indent(NumSpaces: Layout.FormatNameCol) << "Analyses:\n" ; |
| 167 | |
| 168 | for (size_t AnalysisIndex = 0; AnalysisIndex < Analyses.size(); |
| 169 | ++AnalysisIndex) { |
| 170 | printAnalysis(AD: Analyses[AnalysisIndex], AnalysisIndex, FormatIndex, Layout); |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | void printFormat(const FormatData &FD, size_t FormatIndex, |
| 175 | const PrintLayout &Layout) { |
| 176 | // Blank line before each format entry for readability. |
| 177 | llvm::outs() << "\n" ; |
| 178 | |
| 179 | std::string FormatNum = std::to_string(val: FormatIndex + 1) + "." ; |
| 180 | llvm::outs().indent(NumSpaces: FormatIndent) |
| 181 | << llvm::right_justify(Str: FormatNum, Width: Layout.FormatNumWidth) << " " |
| 182 | << llvm::left_justify(Str: FD.Name, Width: Layout.MaxFormatNameWidth) << " - " |
| 183 | << FD.Desc << "\n" ; |
| 184 | |
| 185 | printAnalyses(Analyses: FD.Analyses, FormatIndex, Layout); |
| 186 | } |
| 187 | |
| 188 | void printFormats(const llvm::SmallVector<FormatData> &Formats, |
| 189 | const PrintLayout &Layout) { |
| 190 | llvm::outs() << "Registered serialization formats:\n" ; |
| 191 | for (size_t FormatIndex = 0; FormatIndex < Formats.size(); ++FormatIndex) { |
| 192 | printFormat(FD: Formats[FormatIndex], FormatIndex, Layout); |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | PrintLayout computePrintLayout(const llvm::SmallVector<FormatData> &Formats) { |
| 197 | size_t MaxFormatNameWidth = 0; |
| 198 | size_t MaxAnalysisCount = 0; |
| 199 | size_t MaxAnalysisNameWidth = 0; |
| 200 | for (const auto &FD : Formats) { |
| 201 | MaxFormatNameWidth = std::max(a: MaxFormatNameWidth, b: FD.Name.size()); |
| 202 | MaxAnalysisCount = std::max(a: MaxAnalysisCount, b: FD.Analyses.size()); |
| 203 | for (const auto &AD : FD.Analyses) { |
| 204 | MaxAnalysisNameWidth = std::max(a: MaxAnalysisNameWidth, b: AD.Name.size()); |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | // Width of the widest format number string, e.g. "10." -> 3. |
| 209 | size_t FormatNumWidth = |
| 210 | std::to_string(val: Formats.size()).size() + 1; // +1 for '.' |
| 211 | // Width of the widest analysis number string, e.g. "10.10." -> 6. |
| 212 | size_t AnalysisNumWidth = std::to_string(val: Formats.size()).size() + 1 + |
| 213 | std::to_string(val: MaxAnalysisCount).size() + 1; |
| 214 | |
| 215 | // Where the format name starts (also where "Analyses:" is indented to). |
| 216 | size_t FormatNameCol = FormatIndent + FormatNumWidth + 1; |
| 217 | // Where the analysis number starts. |
| 218 | size_t AnalysisCol = FormatNameCol + AnalysisIndent; |
| 219 | |
| 220 | return { |
| 221 | .FormatNumWidth: FormatNumWidth, .MaxFormatNameWidth: MaxFormatNameWidth, .FormatNameCol: FormatNameCol, |
| 222 | .AnalysisCol: AnalysisCol, .AnalysisNumWidth: AnalysisNumWidth, .MaxAnalysisNameWidth: MaxAnalysisNameWidth, |
| 223 | }; |
| 224 | } |
| 225 | |
| 226 | void listFormats() { |
| 227 | llvm::SmallVector<FormatData> Formats = collectFormats(); |
| 228 | if (Formats.empty()) { |
| 229 | llvm::outs() << "No serialization formats registered.\n" ; |
| 230 | return; |
| 231 | } |
| 232 | printFormats(Formats, Layout: computePrintLayout(Formats)); |
| 233 | } |
| 234 | |
| 235 | //===----------------------------------------------------------------------===// |
| 236 | // Input Validation |
| 237 | //===----------------------------------------------------------------------===// |
| 238 | |
| 239 | struct FormatInput { |
| 240 | FormatFile InputFile; |
| 241 | std::optional<FormatFile> OutputFile; |
| 242 | }; |
| 243 | |
| 244 | FormatInput validateInput() { |
| 245 | assert(!ListFormats); |
| 246 | |
| 247 | FormatInput FI; |
| 248 | |
| 249 | // Validate the input path. |
| 250 | { |
| 251 | if (InputPath.empty()) { |
| 252 | fail(Msg: "no input file specified" ); |
| 253 | } |
| 254 | |
| 255 | FI.InputFile = FormatFile::fromInputPath(Path: InputPath); |
| 256 | } |
| 257 | |
| 258 | // Validate the output path. |
| 259 | if (!OutputPath.empty()) { |
| 260 | FI.OutputFile = FormatFile::fromOutputPath(Path: OutputPath); |
| 261 | } |
| 262 | |
| 263 | return FI; |
| 264 | } |
| 265 | |
| 266 | //===----------------------------------------------------------------------===// |
| 267 | // Format Conversion |
| 268 | //===----------------------------------------------------------------------===// |
| 269 | |
| 270 | template <typename ReadFn, typename WriteFn> |
| 271 | void run(const FormatInput &FI, ReadFn Read, WriteFn Write) { |
| 272 | auto ExpectedResult = (FI.InputFile.Format->*Read)(FI.InputFile.Path); |
| 273 | if (!ExpectedResult) { |
| 274 | fail(ExpectedResult.takeError()); |
| 275 | } |
| 276 | |
| 277 | if (!FI.OutputFile) { |
| 278 | return; |
| 279 | } |
| 280 | |
| 281 | auto Err = |
| 282 | (FI.OutputFile->Format->*Write)(*ExpectedResult, FI.OutputFile->Path); |
| 283 | if (Err) { |
| 284 | fail(std::move(Err)); |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | void convert(const FormatInput &FI) { |
| 289 | switch (Type) { |
| 290 | case SummaryType::Auto: |
| 291 | if (UseEncoding) { |
| 292 | run(FI, Read: &SerializationFormat::readArtifactEncoding, |
| 293 | Write: &SerializationFormat::writeArtifactEncoding); |
| 294 | } else { |
| 295 | run(FI, Read: &SerializationFormat::readArtifact, |
| 296 | Write: &SerializationFormat::writeArtifact); |
| 297 | } |
| 298 | return; |
| 299 | case SummaryType::TU: |
| 300 | if (UseEncoding) { |
| 301 | run(FI, Read: &SerializationFormat::readTUSummaryEncoding, |
| 302 | Write: &SerializationFormat::writeTUSummaryEncoding); |
| 303 | } else { |
| 304 | run(FI, Read: &SerializationFormat::readTUSummary, |
| 305 | Write: &SerializationFormat::writeTUSummary); |
| 306 | } |
| 307 | return; |
| 308 | case SummaryType::LU: |
| 309 | if (UseEncoding) { |
| 310 | run(FI, Read: &SerializationFormat::readLUSummaryEncoding, |
| 311 | Write: &SerializationFormat::writeLUSummaryEncoding); |
| 312 | } else { |
| 313 | run(FI, Read: &SerializationFormat::readLUSummary, |
| 314 | Write: &SerializationFormat::writeLUSummary); |
| 315 | } |
| 316 | return; |
| 317 | case SummaryType::StaticLibrary: |
| 318 | // StaticLibrary has only an encoded representation, so --encoding is a |
| 319 | // no-op here: both paths route to readStaticLibrary / writeStaticLibrary. |
| 320 | run(FI, Read: &SerializationFormat::readStaticLibrary, |
| 321 | Write: &SerializationFormat::writeStaticLibrary); |
| 322 | return; |
| 323 | case SummaryType::MultiArchStaticLibrary: |
| 324 | // MultiArchStaticLibrary has only an encoded representation, so |
| 325 | // --encoding is a no-op here: both paths route to |
| 326 | // readMultiArchStaticLibrary / writeMultiArchStaticLibrary. |
| 327 | run(FI, Read: &SerializationFormat::readMultiArchStaticLibrary, |
| 328 | Write: &SerializationFormat::writeMultiArchStaticLibrary); |
| 329 | return; |
| 330 | case SummaryType::MultiArchSharedLibrary: |
| 331 | // MultiArchSharedLibrary has only an encoded representation, so |
| 332 | // --encoding is a no-op here: both paths route to |
| 333 | // readMultiArchSharedLibrary / writeMultiArchSharedLibrary. |
| 334 | run(FI, Read: &SerializationFormat::readMultiArchSharedLibrary, |
| 335 | Write: &SerializationFormat::writeMultiArchSharedLibrary); |
| 336 | return; |
| 337 | case SummaryType::WPA: |
| 338 | run(FI, Read: &SerializationFormat::readWPASuite, |
| 339 | Write: &SerializationFormat::writeWPASuite); |
| 340 | return; |
| 341 | } |
| 342 | |
| 343 | llvm_unreachable("Unhandled SummaryType variant" ); |
| 344 | } |
| 345 | |
| 346 | } // namespace |
| 347 | |
| 348 | //===----------------------------------------------------------------------===// |
| 349 | // Driver |
| 350 | //===----------------------------------------------------------------------===// |
| 351 | |
| 352 | int main(int argc, const char **argv) { |
| 353 | llvm::StringRef ToolHeading = "SSAF Format" ; |
| 354 | |
| 355 | InitLLVM X(argc, argv); |
| 356 | initTool(argc, argv, Version: "0.1" , Category&: SsafFormatCategory, ToolHeading); |
| 357 | |
| 358 | loadPlugins(Paths: LoadPlugins); |
| 359 | |
| 360 | if (ListFormats) { |
| 361 | listFormats(); |
| 362 | } else { |
| 363 | FormatInput FI = validateInput(); |
| 364 | convert(FI); |
| 365 | } |
| 366 | |
| 367 | return 0; |
| 368 | } |
| 369 | |