1 //===-- ClangIncludeFixer.cpp - Standalone include fixer ------------------===// 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 #include "FuzzySymbolIndex.h" 10 #include "InMemorySymbolIndex.h" 11 #include "IncludeFixer.h" 12 #include "IncludeFixerContext.h" 13 #include "SymbolIndexManager.h" 14 #include "YamlSymbolIndex.h" 15 #include "clang/Format/Format.h" 16 #include "clang/Frontend/TextDiagnosticPrinter.h" 17 #include "clang/Rewrite/Core/Rewriter.h" 18 #include "clang/Tooling/CommonOptionsParser.h" 19 #include "clang/Tooling/Core/Replacement.h" 20 #include "clang/Tooling/Tooling.h" 21 #include "llvm/Support/CommandLine.h" 22 #include "llvm/Support/Path.h" 23 #include "llvm/Support/YAMLTraits.h" 24 25 using namespace clang; 26 using namespace llvm; 27 using clang::include_fixer::IncludeFixerContext; 28 29 LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(IncludeFixerContext) 30 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::HeaderInfo) 31 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::QuerySymbolInfo) 32 33 namespace llvm { 34 namespace yaml { 35 36 template <> struct MappingTraits<tooling::Range> { 37 struct NormalizedRange { 38 NormalizedRange(const IO &) : Offset(0), Length(0) {} 39 40 NormalizedRange(const IO &, const tooling::Range &R) 41 : Offset(R.getOffset()), Length(R.getLength()) {} 42 43 tooling::Range denormalize(const IO &) { 44 return tooling::Range(Offset, Length); 45 } 46 47 unsigned Offset; 48 unsigned Length; 49 }; 50 static void mapping(IO &IO, tooling::Range &Info) { 51 MappingNormalization<NormalizedRange, tooling::Range> Keys(IO, Info); 52 IO.mapRequired("Offset", Keys->Offset); 53 IO.mapRequired("Length", Keys->Length); 54 } 55 }; 56 57 template <> struct MappingTraits<IncludeFixerContext::HeaderInfo> { 58 static void mapping(IO &io, IncludeFixerContext::HeaderInfo &Info) { 59 io.mapRequired("Header", Info.Header); 60 io.mapRequired("QualifiedName", Info.QualifiedName); 61 } 62 }; 63 64 template <> struct MappingTraits<IncludeFixerContext::QuerySymbolInfo> { 65 static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info) { 66 io.mapRequired("RawIdentifier", Info.RawIdentifier); 67 io.mapRequired("Range", Info.Range); 68 } 69 }; 70 71 template <> struct MappingTraits<IncludeFixerContext> { 72 static void mapping(IO &IO, IncludeFixerContext &Context) { 73 IO.mapRequired("QuerySymbolInfos", Context.QuerySymbolInfos); 74 IO.mapRequired("HeaderInfos", Context.HeaderInfos); 75 IO.mapRequired("FilePath", Context.FilePath); 76 } 77 }; 78 } // namespace yaml 79 } // namespace llvm 80 81 namespace { 82 cl::OptionCategory IncludeFixerCategory("Tool options"); 83 84 enum DatabaseFormatTy { 85 fixed, ///< Hard-coded mapping. 86 yaml, ///< Yaml database created by find-all-symbols. 87 fuzzyYaml, ///< Yaml database with fuzzy-matched identifiers. 88 }; 89 90 cl::opt<DatabaseFormatTy> DatabaseFormat( 91 "db", cl::desc("Specify input format"), 92 cl::values(clEnumVal(fixed, "Hard-coded mapping"), 93 clEnumVal(yaml, "Yaml database created by find-all-symbols"), 94 clEnumVal(fuzzyYaml, "Yaml database, with fuzzy-matched names")), 95 cl::init(yaml), cl::cat(IncludeFixerCategory)); 96 97 cl::opt<std::string> Input("input", 98 cl::desc("String to initialize the database"), 99 cl::cat(IncludeFixerCategory)); 100 101 cl::opt<std::string> 102 QuerySymbol("query-symbol", 103 cl::desc("Query a given symbol (e.g. \"a::b::foo\") in\n" 104 "database directly without parsing the file."), 105 cl::cat(IncludeFixerCategory)); 106 107 cl::opt<bool> 108 MinimizeIncludePaths("minimize-paths", 109 cl::desc("Whether to minimize added include paths"), 110 cl::init(true), cl::cat(IncludeFixerCategory)); 111 112 cl::opt<bool> Quiet("q", cl::desc("Reduce terminal output"), cl::init(false), 113 cl::cat(IncludeFixerCategory)); 114 115 cl::opt<bool> 116 STDINMode("stdin", 117 cl::desc("Override source file's content (in the overlaying\n" 118 "virtual file system) with input from <stdin> and run\n" 119 "the tool on the new content with the compilation\n" 120 "options of the source file. This mode is currently\n" 121 "used for editor integration."), 122 cl::init(false), cl::cat(IncludeFixerCategory)); 123 124 cl::opt<bool> OutputHeaders( 125 "output-headers", 126 cl::desc("Print the symbol being queried and all its relevant headers in\n" 127 "JSON format to stdout:\n" 128 " {\n" 129 " \"FilePath\": \"/path/to/foo.cc\",\n" 130 " \"QuerySymbolInfos\": [\n" 131 " {\"RawIdentifier\": \"foo\",\n" 132 " \"Range\": {\"Offset\": 0, \"Length\": 3}}\n" 133 " ],\n" 134 " \"HeaderInfos\": [ {\"Header\": \"\\\"foo_a.h\\\"\",\n" 135 " \"QualifiedName\": \"a::foo\"} ]\n" 136 " }"), 137 cl::init(false), cl::cat(IncludeFixerCategory)); 138 139 cl::opt<std::string> InsertHeader( 140 "insert-header", 141 cl::desc("Insert a specific header. This should run with STDIN mode.\n" 142 "The result is written to stdout. It is currently used for\n" 143 "editor integration. Support YAML/JSON format:\n" 144 " -insert-header=\"{\n" 145 " FilePath: \"/path/to/foo.cc\",\n" 146 " QuerySymbolInfos: [\n" 147 " {RawIdentifier: foo,\n" 148 " Range: {Offset: 0, Length: 3}}\n" 149 " ],\n" 150 " HeaderInfos: [ {Headers: \"\\\"foo_a.h\\\"\",\n" 151 " QualifiedName: \"a::foo\"} ]}\""), 152 cl::init(""), cl::cat(IncludeFixerCategory)); 153 154 cl::opt<std::string> 155 Style("style", 156 cl::desc("Fallback style for reformatting after inserting new\n" 157 "headers if there is no clang-format config file found."), 158 cl::init("llvm"), cl::cat(IncludeFixerCategory)); 159 160 std::unique_ptr<include_fixer::SymbolIndexManager> 161 createSymbolIndexManager(StringRef FilePath) { 162 using find_all_symbols::SymbolInfo; 163 164 auto SymbolIndexMgr = llvm::make_unique<include_fixer::SymbolIndexManager>(); 165 switch (DatabaseFormat) { 166 case fixed: { 167 // Parse input and fill the database with it. 168 // <symbol>=<header><, header...> 169 // Multiple symbols can be given, separated by semicolons. 170 std::map<std::string, std::vector<std::string>> SymbolsMap; 171 SmallVector<StringRef, 4> SemicolonSplits; 172 StringRef(Input).split(SemicolonSplits, ";"); 173 std::vector<find_all_symbols::SymbolAndSignals> Symbols; 174 for (StringRef Pair : SemicolonSplits) { 175 auto Split = Pair.split('='); 176 std::vector<std::string> Headers; 177 SmallVector<StringRef, 4> CommaSplits; 178 Split.second.split(CommaSplits, ","); 179 for (size_t I = 0, E = CommaSplits.size(); I != E; ++I) 180 Symbols.push_back( 181 {SymbolInfo(Split.first.trim(), SymbolInfo::SymbolKind::Unknown, 182 CommaSplits[I].trim(), {}), 183 // Use fake "seen" signal for tests, so first header wins. 184 SymbolInfo::Signals(/*Seen=*/static_cast<unsigned>(E - I), 185 /*Used=*/0)}); 186 } 187 SymbolIndexMgr->addSymbolIndex([=]() { 188 return llvm::make_unique<include_fixer::InMemorySymbolIndex>(Symbols); 189 }); 190 break; 191 } 192 case yaml: { 193 auto CreateYamlIdx = [=]() -> std::unique_ptr<include_fixer::SymbolIndex> { 194 llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> DB( 195 nullptr); 196 if (!Input.empty()) { 197 DB = include_fixer::YamlSymbolIndex::createFromFile(Input); 198 } else { 199 // If we don't have any input file, look in the directory of the 200 // first 201 // file and its parents. 202 SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath)); 203 StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); 204 DB = include_fixer::YamlSymbolIndex::createFromDirectory( 205 Directory, "find_all_symbols_db.yaml"); 206 } 207 208 if (!DB) { 209 llvm::errs() << "Couldn't find YAML db: " << DB.getError().message() 210 << '\n'; 211 return nullptr; 212 } 213 return std::move(*DB); 214 }; 215 216 SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx)); 217 break; 218 } 219 case fuzzyYaml: { 220 // This mode is not very useful, because we don't correct the identifier. 221 // It's main purpose is to expose FuzzySymbolIndex to tests. 222 SymbolIndexMgr->addSymbolIndex( 223 []() -> std::unique_ptr<include_fixer::SymbolIndex> { 224 auto DB = include_fixer::FuzzySymbolIndex::createFromYAML(Input); 225 if (!DB) { 226 llvm::errs() << "Couldn't load fuzzy YAML db: " 227 << llvm::toString(DB.takeError()) << '\n'; 228 return nullptr; 229 } 230 return std::move(*DB); 231 }); 232 break; 233 } 234 } 235 return SymbolIndexMgr; 236 } 237 238 void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) { 239 OS << "{\n" 240 << " \"FilePath\": \"" 241 << llvm::yaml::escape(Context.getFilePath()) << "\",\n" 242 << " \"QuerySymbolInfos\": [\n"; 243 for (const auto &Info : Context.getQuerySymbolInfos()) { 244 OS << " {\"RawIdentifier\": \"" << Info.RawIdentifier << "\",\n"; 245 OS << " \"Range\":{"; 246 OS << "\"Offset\":" << Info.Range.getOffset() << ","; 247 OS << "\"Length\":" << Info.Range.getLength() << "}}"; 248 if (&Info != &Context.getQuerySymbolInfos().back()) 249 OS << ",\n"; 250 } 251 OS << "\n ],\n"; 252 OS << " \"HeaderInfos\": [\n"; 253 const auto &HeaderInfos = Context.getHeaderInfos(); 254 for (const auto &Info : HeaderInfos) { 255 OS << " {\"Header\": \"" << llvm::yaml::escape(Info.Header) << "\",\n" 256 << " \"QualifiedName\": \"" << Info.QualifiedName << "\"}"; 257 if (&Info != &HeaderInfos.back()) 258 OS << ",\n"; 259 } 260 OS << "\n"; 261 OS << " ]\n"; 262 OS << "}\n"; 263 } 264 265 int includeFixerMain(int argc, const char **argv) { 266 tooling::CommonOptionsParser options(argc, argv, IncludeFixerCategory); 267 tooling::ClangTool tool(options.getCompilations(), 268 options.getSourcePathList()); 269 270 llvm::StringRef SourceFilePath = options.getSourcePathList().front(); 271 // In STDINMode, we override the file content with the <stdin> input. 272 // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of 273 // the if-block so that `Code` is not released after the if-block. 274 std::unique_ptr<llvm::MemoryBuffer> Code; 275 if (STDINMode) { 276 assert(options.getSourcePathList().size() == 1 && 277 "Expect exactly one file path in STDINMode."); 278 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> CodeOrErr = 279 MemoryBuffer::getSTDIN(); 280 if (std::error_code EC = CodeOrErr.getError()) { 281 errs() << EC.message() << "\n"; 282 return 1; 283 } 284 Code = std::move(CodeOrErr.get()); 285 if (Code->getBufferSize() == 0) 286 return 0; // Skip empty files. 287 288 tool.mapVirtualFile(SourceFilePath, Code->getBuffer()); 289 } 290 291 if (!InsertHeader.empty()) { 292 if (!STDINMode) { 293 errs() << "Should be running in STDIN mode\n"; 294 return 1; 295 } 296 297 llvm::yaml::Input yin(InsertHeader); 298 IncludeFixerContext Context; 299 yin >> Context; 300 301 const auto &HeaderInfos = Context.getHeaderInfos(); 302 assert(!HeaderInfos.empty()); 303 // We only accept one unique header. 304 // Check all elements in HeaderInfos have the same header. 305 bool IsUniqueHeader = std::equal( 306 HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(), 307 [](const IncludeFixerContext::HeaderInfo &LHS, 308 const IncludeFixerContext::HeaderInfo &RHS) { 309 return LHS.Header == RHS.Header; 310 }); 311 if (!IsUniqueHeader) { 312 errs() << "Expect exactly one unique header.\n"; 313 return 1; 314 } 315 316 // If a header has multiple symbols, we won't add the missing namespace 317 // qualifiers because we don't know which one is exactly used. 318 // 319 // Check whether all elements in HeaderInfos have the same qualified name. 320 bool IsUniqueQualifiedName = std::equal( 321 HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(), 322 [](const IncludeFixerContext::HeaderInfo &LHS, 323 const IncludeFixerContext::HeaderInfo &RHS) { 324 return LHS.QualifiedName == RHS.QualifiedName; 325 }); 326 auto InsertStyle = format::getStyle(format::DefaultFormatStyle, 327 Context.getFilePath(), Style); 328 if (!InsertStyle) { 329 llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n"; 330 return 1; 331 } 332 auto Replacements = clang::include_fixer::createIncludeFixerReplacements( 333 Code->getBuffer(), Context, *InsertStyle, 334 /*AddQualifiers=*/IsUniqueQualifiedName); 335 if (!Replacements) { 336 errs() << "Failed to create replacements: " 337 << llvm::toString(Replacements.takeError()) << "\n"; 338 return 1; 339 } 340 341 auto ChangedCode = 342 tooling::applyAllReplacements(Code->getBuffer(), *Replacements); 343 if (!ChangedCode) { 344 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; 345 return 1; 346 } 347 llvm::outs() << *ChangedCode; 348 return 0; 349 } 350 351 // Set up data source. 352 std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr = 353 createSymbolIndexManager(SourceFilePath); 354 if (!SymbolIndexMgr) 355 return 1; 356 357 // Query symbol mode. 358 if (!QuerySymbol.empty()) { 359 auto MatchedSymbols = SymbolIndexMgr->search( 360 QuerySymbol, /*IsNestedSearch=*/true, SourceFilePath); 361 for (auto &Symbol : MatchedSymbols) { 362 std::string HeaderPath = Symbol.getFilePath().str(); 363 Symbol.SetFilePath(((HeaderPath[0] == '"' || HeaderPath[0] == '<') 364 ? HeaderPath 365 : "\"" + HeaderPath + "\"")); 366 } 367 368 // We leave an empty symbol range as we don't know the range of the symbol 369 // being queried in this mode. clang-include-fixer won't add namespace 370 // qualifiers if the symbol range is empty, which also fits this case. 371 IncludeFixerContext::QuerySymbolInfo Symbol; 372 Symbol.RawIdentifier = QuerySymbol; 373 auto Context = 374 IncludeFixerContext(SourceFilePath, {Symbol}, MatchedSymbols); 375 writeToJson(llvm::outs(), Context); 376 return 0; 377 } 378 379 // Now run our tool. 380 std::vector<include_fixer::IncludeFixerContext> Contexts; 381 include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts, 382 Style, MinimizeIncludePaths); 383 384 if (tool.run(&Factory) != 0) { 385 // We suppress all Clang diagnostics (because they would be wrong, 386 // clang-include-fixer does custom recovery) but still want to give some 387 // feedback in case there was a compiler error we couldn't recover from. 388 // The most common case for this is a #include in the file that couldn't be 389 // found. 390 llvm::errs() << "Fatal compiler error occurred while parsing file!" 391 " (incorrect include paths?)\n"; 392 return 1; 393 } 394 395 assert(!Contexts.empty()); 396 397 if (OutputHeaders) { 398 // FIXME: Print contexts of all processing files instead of the first one. 399 writeToJson(llvm::outs(), Contexts.front()); 400 return 0; 401 } 402 403 std::vector<tooling::Replacements> FixerReplacements; 404 for (const auto &Context : Contexts) { 405 StringRef FilePath = Context.getFilePath(); 406 auto InsertStyle = 407 format::getStyle(format::DefaultFormatStyle, FilePath, Style); 408 if (!InsertStyle) { 409 llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n"; 410 return 1; 411 } 412 auto Buffer = llvm::MemoryBuffer::getFile(FilePath); 413 if (!Buffer) { 414 errs() << "Couldn't open file: " + FilePath.str() + ": " 415 << Buffer.getError().message() + "\n"; 416 return 1; 417 } 418 419 auto Replacements = clang::include_fixer::createIncludeFixerReplacements( 420 Buffer.get()->getBuffer(), Context, *InsertStyle); 421 if (!Replacements) { 422 errs() << "Failed to create replacement: " 423 << llvm::toString(Replacements.takeError()) << "\n"; 424 return 1; 425 } 426 FixerReplacements.push_back(*Replacements); 427 } 428 429 if (!Quiet) { 430 for (const auto &Context : Contexts) { 431 if (!Context.getHeaderInfos().empty()) { 432 llvm::errs() << "Added #include " 433 << Context.getHeaderInfos().front().Header << " for " 434 << Context.getFilePath() << "\n"; 435 } 436 } 437 } 438 439 if (STDINMode) { 440 assert(FixerReplacements.size() == 1); 441 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), 442 FixerReplacements.front()); 443 if (!ChangedCode) { 444 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; 445 return 1; 446 } 447 llvm::outs() << *ChangedCode; 448 return 0; 449 } 450 451 // Set up a new source manager for applying the resulting replacements. 452 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions); 453 DiagnosticsEngine Diagnostics(new DiagnosticIDs, &*DiagOpts); 454 TextDiagnosticPrinter DiagnosticPrinter(outs(), &*DiagOpts); 455 SourceManager SM(Diagnostics, tool.getFiles()); 456 Diagnostics.setClient(&DiagnosticPrinter, false); 457 458 // Write replacements to disk. 459 Rewriter Rewrites(SM, LangOptions()); 460 for (const auto &Replacement : FixerReplacements) { 461 if (!tooling::applyAllReplacements(Replacement, Rewrites)) { 462 llvm::errs() << "Failed to apply replacements.\n"; 463 return 1; 464 } 465 } 466 return Rewrites.overwriteChangedFiles(); 467 } 468 469 } // namespace 470 471 int main(int argc, const char **argv) { 472 return includeFixerMain(argc, argv); 473 } 474