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