brintos

brintos / llvm-project-archived public Read only

0
0
Text · 17.5 KiB · 568cb2b Raw
478 lines · cpp
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-exception6//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 25using namespace clang;26using namespace llvm;27using clang::include_fixer::IncludeFixerContext;28 29LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(IncludeFixerContext)30LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::HeaderInfo)31LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::QuerySymbolInfo)32 33namespace llvm {34namespace yaml {35 36template <> 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 57template <> 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 64template <> 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 71template <> 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 yaml79} // namespace llvm80 81namespace {82cl::OptionCategory IncludeFixerCategory("Tool options");83 84enum 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 90cl::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 97cl::opt<std::string> Input("input",98                           cl::desc("String to initialize the database"),99                           cl::cat(IncludeFixerCategory));100 101cl::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 107cl::opt<bool>108    MinimizeIncludePaths("minimize-paths",109                         cl::desc("Whether to minimize added include paths"),110                         cl::init(true), cl::cat(IncludeFixerCategory));111 112cl::opt<bool> Quiet("q", cl::desc("Reduce terminal output"), cl::init(false),113                    cl::cat(IncludeFixerCategory));114 115cl::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 124cl::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 139cl::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 154cl::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 160std::unique_ptr<include_fixer::SymbolIndexManager>161createSymbolIndexManager(StringRef FilePath) {162  using find_all_symbols::SymbolInfo;163 164  auto SymbolIndexMgr = std::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    SmallVector<StringRef, 4> SemicolonSplits;171    StringRef(Input).split(SemicolonSplits, ";");172    std::vector<find_all_symbols::SymbolAndSignals> Symbols;173    for (StringRef Pair : SemicolonSplits) {174      auto Split = Pair.split('=');175      SmallVector<StringRef, 4> CommaSplits;176      Split.second.split(CommaSplits, ",");177      for (size_t I = 0, E = CommaSplits.size(); I != E; ++I)178        Symbols.push_back(179            {SymbolInfo(Split.first.trim(), SymbolInfo::SymbolKind::Unknown,180                        CommaSplits[I].trim(), {}),181             // Use fake "seen" signal for tests, so first header wins.182             SymbolInfo::Signals(/*Seen=*/static_cast<unsigned>(E - I),183                                 /*Used=*/0)});184    }185    SymbolIndexMgr->addSymbolIndex([=]() {186      return std::make_unique<include_fixer::InMemorySymbolIndex>(Symbols);187    });188    break;189  }190  case yaml: {191    auto CreateYamlIdx = [=]() -> std::unique_ptr<include_fixer::SymbolIndex> {192      llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> DB(193          nullptr);194      if (!Input.empty()) {195        DB = include_fixer::YamlSymbolIndex::createFromFile(Input);196      } else {197        // If we don't have any input file, look in the directory of the198        // first199        // file and its parents.200        SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath));201        StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);202        DB = include_fixer::YamlSymbolIndex::createFromDirectory(203            Directory, "find_all_symbols_db.yaml");204      }205 206      if (!DB) {207        llvm::errs() << "Couldn't find YAML db: " << DB.getError().message()208                     << '\n';209        return nullptr;210      }211      return std::move(*DB);212    };213 214    SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx));215    break;216  }217  case fuzzyYaml: {218    // This mode is not very useful, because we don't correct the identifier.219    // It's main purpose is to expose FuzzySymbolIndex to tests.220    SymbolIndexMgr->addSymbolIndex(221        []() -> std::unique_ptr<include_fixer::SymbolIndex> {222          auto DB = include_fixer::FuzzySymbolIndex::createFromYAML(Input);223          if (!DB) {224            llvm::errs() << "Couldn't load fuzzy YAML db: "225                         << llvm::toString(DB.takeError()) << '\n';226            return nullptr;227          }228          return std::move(*DB);229        });230    break;231  }232  }233  return SymbolIndexMgr;234}235 236void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) {237  OS << "{\n"238     << "  \"FilePath\": \""239     << llvm::yaml::escape(Context.getFilePath()) << "\",\n"240     << "  \"QuerySymbolInfos\": [\n";241  for (const auto &Info : Context.getQuerySymbolInfos()) {242    OS << "     {\"RawIdentifier\": \"" << Info.RawIdentifier << "\",\n";243    OS << "      \"Range\":{";244    OS << "\"Offset\":" << Info.Range.getOffset() << ",";245    OS << "\"Length\":" << Info.Range.getLength() << "}}";246    if (&Info != &Context.getQuerySymbolInfos().back())247      OS << ",\n";248  }249  OS << "\n  ],\n";250  OS << "  \"HeaderInfos\": [\n";251  const auto &HeaderInfos = Context.getHeaderInfos();252  for (const auto &Info : HeaderInfos) {253    OS << "     {\"Header\": \"" << llvm::yaml::escape(Info.Header) << "\",\n"254       << "      \"QualifiedName\": \"" << Info.QualifiedName << "\"}";255    if (&Info != &HeaderInfos.back())256      OS << ",\n";257  }258  OS << "\n";259  OS << "  ]\n";260  OS << "}\n";261}262 263int includeFixerMain(int argc, const char **argv) {264  auto ExpectedParser =265      tooling::CommonOptionsParser::create(argc, argv, IncludeFixerCategory);266  if (!ExpectedParser) {267    llvm::errs() << ExpectedParser.takeError();268    return 1;269  }270  tooling::CommonOptionsParser &options = ExpectedParser.get();271  tooling::ClangTool tool(options.getCompilations(),272                          options.getSourcePathList());273 274  llvm::StringRef SourceFilePath = options.getSourcePathList().front();275  // In STDINMode, we override the file content with the <stdin> input.276  // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of277  // the if-block so that `Code` is not released after the if-block.278  std::unique_ptr<llvm::MemoryBuffer> Code;279  if (STDINMode) {280    assert(options.getSourcePathList().size() == 1 &&281           "Expect exactly one file path in STDINMode.");282    llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> CodeOrErr =283        MemoryBuffer::getSTDIN();284    if (std::error_code EC = CodeOrErr.getError()) {285      errs() << EC.message() << "\n";286      return 1;287    }288    Code = std::move(CodeOrErr.get());289    if (Code->getBufferSize() == 0)290      return 0;  // Skip empty files.291 292    tool.mapVirtualFile(SourceFilePath, Code->getBuffer());293  }294 295  if (!InsertHeader.empty()) {296    if (!STDINMode) {297      errs() << "Should be running in STDIN mode\n";298      return 1;299    }300 301    llvm::yaml::Input yin(InsertHeader);302    IncludeFixerContext Context;303    yin >> Context;304 305    const auto &HeaderInfos = Context.getHeaderInfos();306    assert(!HeaderInfos.empty());307    // We only accept one unique header.308    // Check all elements in HeaderInfos have the same header.309    bool IsUniqueHeader = std::equal(310        HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(),311        [](const IncludeFixerContext::HeaderInfo &LHS,312           const IncludeFixerContext::HeaderInfo &RHS) {313          return LHS.Header == RHS.Header;314        });315    if (!IsUniqueHeader) {316      errs() << "Expect exactly one unique header.\n";317      return 1;318    }319 320    // If a header has multiple symbols, we won't add the missing namespace321    // qualifiers because we don't know which one is exactly used.322    //323    // Check whether all elements in HeaderInfos have the same qualified name.324    bool IsUniqueQualifiedName = std::equal(325        HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(),326        [](const IncludeFixerContext::HeaderInfo &LHS,327           const IncludeFixerContext::HeaderInfo &RHS) {328          return LHS.QualifiedName == RHS.QualifiedName;329        });330    auto InsertStyle = format::getStyle(format::DefaultFormatStyle,331                                        Context.getFilePath(), Style);332    if (!InsertStyle) {333      llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n";334      return 1;335    }336    auto Replacements = clang::include_fixer::createIncludeFixerReplacements(337        Code->getBuffer(), Context, *InsertStyle,338        /*AddQualifiers=*/IsUniqueQualifiedName);339    if (!Replacements) {340      errs() << "Failed to create replacements: "341             << llvm::toString(Replacements.takeError()) << "\n";342      return 1;343    }344 345    auto ChangedCode =346        tooling::applyAllReplacements(Code->getBuffer(), *Replacements);347    if (!ChangedCode) {348      llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";349      return 1;350    }351    llvm::outs() << *ChangedCode;352    return 0;353  }354 355  // Set up data source.356  std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr =357      createSymbolIndexManager(SourceFilePath);358  if (!SymbolIndexMgr)359    return 1;360 361  // Query symbol mode.362  if (!QuerySymbol.empty()) {363    auto MatchedSymbols = SymbolIndexMgr->search(364        QuerySymbol, /*IsNestedSearch=*/true, SourceFilePath);365    for (auto &Symbol : MatchedSymbols) {366      std::string HeaderPath = Symbol.getFilePath().str();367      Symbol.SetFilePath(((HeaderPath[0] == '"' || HeaderPath[0] == '<')368                              ? HeaderPath369                              : "\"" + HeaderPath + "\""));370    }371 372    // We leave an empty symbol range as we don't know the range of the symbol373    // being queried in this mode. clang-include-fixer won't add namespace374    // qualifiers if the symbol range is empty, which also fits this case.375    IncludeFixerContext::QuerySymbolInfo Symbol;376    Symbol.RawIdentifier = QuerySymbol;377    auto Context =378        IncludeFixerContext(SourceFilePath, {Symbol}, MatchedSymbols);379    writeToJson(llvm::outs(), Context);380    return 0;381  }382 383  // Now run our tool.384  std::vector<include_fixer::IncludeFixerContext> Contexts;385  include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts,386                                                   Style, MinimizeIncludePaths);387 388  if (tool.run(&Factory) != 0) {389    // We suppress all Clang diagnostics (because they would be wrong,390    // clang-include-fixer does custom recovery) but still want to give some391    // feedback in case there was a compiler error we couldn't recover from.392    // The most common case for this is a #include in the file that couldn't be393    // found.394    llvm::errs() << "Fatal compiler error occurred while parsing file!"395                    " (incorrect include paths?)\n";396    return 1;397  }398 399  assert(!Contexts.empty());400 401  if (OutputHeaders) {402    // FIXME: Print contexts of all processing files instead of the first one.403    writeToJson(llvm::outs(), Contexts.front());404    return 0;405  }406 407  std::vector<tooling::Replacements> FixerReplacements;408  for (const auto &Context : Contexts) {409    StringRef FilePath = Context.getFilePath();410    auto InsertStyle =411        format::getStyle(format::DefaultFormatStyle, FilePath, Style);412    if (!InsertStyle) {413      llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n";414      return 1;415    }416    auto Buffer = llvm::MemoryBuffer::getFile(FilePath, /*IsText=*/true);417    if (!Buffer) {418      errs() << "Couldn't open file: " + FilePath.str() + ": "419             << Buffer.getError().message() + "\n";420      return 1;421    }422 423    auto Replacements = clang::include_fixer::createIncludeFixerReplacements(424        Buffer.get()->getBuffer(), Context, *InsertStyle);425    if (!Replacements) {426      errs() << "Failed to create replacement: "427             << llvm::toString(Replacements.takeError()) << "\n";428      return 1;429    }430    FixerReplacements.push_back(*Replacements);431  }432 433  if (!Quiet) {434    for (const auto &Context : Contexts) {435      if (!Context.getHeaderInfos().empty()) {436        llvm::errs() << "Added #include "437                     << Context.getHeaderInfos().front().Header << " for "438                     << Context.getFilePath() << "\n";439      }440    }441  }442 443  if (STDINMode) {444    assert(FixerReplacements.size() == 1);445    auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(),446                                                     FixerReplacements.front());447    if (!ChangedCode) {448      llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";449      return 1;450    }451    llvm::outs() << *ChangedCode;452    return 0;453  }454 455  // Set up a new source manager for applying the resulting replacements.456  DiagnosticOptions DiagOpts;457  DiagnosticsEngine Diagnostics(DiagnosticIDs::create(), DiagOpts);458  TextDiagnosticPrinter DiagnosticPrinter(outs(), DiagOpts);459  SourceManager SM(Diagnostics, tool.getFiles());460  Diagnostics.setClient(&DiagnosticPrinter, false);461 462  // Write replacements to disk.463  Rewriter Rewrites(SM, LangOptions());464  for (const auto &Replacement : FixerReplacements) {465    if (!tooling::applyAllReplacements(Replacement, Rewrites)) {466      llvm::errs() << "Failed to apply replacements.\n";467      return 1;468    }469  }470  return Rewrites.overwriteChangedFiles();471}472 473} // namespace474 475int main(int argc, const char **argv) {476  return includeFixerMain(argc, argv);477}478