clang-tools  7.0.0
Diagnostics.cpp
Go to the documentation of this file.
1 //===--- Diagnostics.cpp ----------------------------------------*- C++-*-===//
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 "Diagnostics.h"
11 #include "Compiler.h"
12 #include "Logger.h"
13 #include "SourceCode.h"
14 #include "clang/Basic/SourceManager.h"
15 #include "clang/Lex/Lexer.h"
16 #include "llvm/Support/Capacity.h"
17 #include "llvm/Support/Path.h"
18 #include <algorithm>
19 
20 namespace clang {
21 namespace clangd {
22 
23 namespace {
24 
25 bool mentionsMainFile(const Diag &D) {
26  if (D.InsideMainFile)
27  return true;
28  // Fixes are always in the main file.
29  if (!D.Fixes.empty())
30  return true;
31  for (auto &N : D.Notes) {
32  if (N.InsideMainFile)
33  return true;
34  }
35  return false;
36 }
37 
38 // Checks whether a location is within a half-open range.
39 // Note that clang also uses closed source ranges, which this can't handle!
40 bool locationInRange(SourceLocation L, CharSourceRange R,
41  const SourceManager &M) {
42  assert(R.isCharRange());
43  if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) ||
44  M.getFileID(R.getBegin()) != M.getFileID(L))
45  return false;
46  return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd());
47 }
48 
49 // Clang diags have a location (shown as ^) and 0 or more ranges (~~~~).
50 // LSP needs a single range.
51 Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) {
52  auto &M = D.getSourceManager();
53  auto Loc = M.getFileLoc(D.getLocation());
54  // Accept the first range that contains the location.
55  for (const auto &CR : D.getRanges()) {
56  auto R = Lexer::makeFileCharRange(CR, M, L);
57  if (locationInRange(Loc, R, M))
58  return halfOpenToRange(M, R);
59  }
60  // The range may be given as a fixit hint instead.
61  for (const auto &F : D.getFixItHints()) {
62  auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L);
63  if (locationInRange(Loc, R, M))
64  return halfOpenToRange(M, R);
65  }
66  // If no suitable range is found, just use the token at the location.
67  auto R = Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Loc), M, L);
68  if (!R.isValid()) // Fall back to location only, let the editor deal with it.
69  R = CharSourceRange::getCharRange(Loc);
70  return halfOpenToRange(M, R);
71 }
72 
73 TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M,
74  const LangOptions &L) {
75  TextEdit Result;
76  Result.range =
77  halfOpenToRange(M, Lexer::makeFileCharRange(FixIt.RemoveRange, M, L));
78  Result.newText = FixIt.CodeToInsert;
79  return Result;
80 }
81 
82 bool isInsideMainFile(const SourceLocation Loc, const SourceManager &M) {
83  return Loc.isValid() && M.isInMainFile(Loc);
84 }
85 
86 bool isInsideMainFile(const clang::Diagnostic &D) {
87  if (!D.hasSourceManager())
88  return false;
89 
90  return isInsideMainFile(D.getLocation(), D.getSourceManager());
91 }
92 
93 bool isNote(DiagnosticsEngine::Level L) {
94  return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark;
95 }
96 
97 llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
98  switch (Lvl) {
99  case DiagnosticsEngine::Ignored:
100  return "ignored";
101  case DiagnosticsEngine::Note:
102  return "note";
103  case DiagnosticsEngine::Remark:
104  return "remark";
105  case DiagnosticsEngine::Warning:
106  return "warning";
107  case DiagnosticsEngine::Error:
108  return "error";
109  case DiagnosticsEngine::Fatal:
110  return "fatal error";
111  }
112  llvm_unreachable("unhandled DiagnosticsEngine::Level");
113 }
114 
115 /// Prints a single diagnostic in a clang-like manner, the output includes
116 /// location, severity and error message. An example of the output message is:
117 ///
118 /// main.cpp:12:23: error: undeclared identifier
119 ///
120 /// For main file we only print the basename and for all other files we print
121 /// the filename on a separate line to provide a slightly more readable output
122 /// in the editors:
123 ///
124 /// dir1/dir2/dir3/../../dir4/header.h:12:23
125 /// error: undeclared identifier
126 void printDiag(llvm::raw_string_ostream &OS, const DiagBase &D) {
127  if (D.InsideMainFile) {
128  // Paths to main files are often taken from compile_command.json, where they
129  // are typically absolute. To reduce noise we print only basename for them,
130  // it should not be confusing and saves space.
131  OS << llvm::sys::path::filename(D.File) << ":";
132  } else {
133  OS << D.File << ":";
134  }
135  // Note +1 to line and character. clangd::Range is zero-based, but when
136  // printing for users we want one-based indexes.
137  auto Pos = D.Range.start;
138  OS << (Pos.line + 1) << ":" << (Pos.character + 1) << ":";
139  // The non-main-file paths are often too long, putting them on a separate
140  // line improves readability.
141  if (D.InsideMainFile)
142  OS << " ";
143  else
144  OS << "\n";
145  OS << diagLeveltoString(D.Severity) << ": " << D.Message;
146 }
147 
148 /// Returns a message sent to LSP for the main diagnostic in \p D.
149 /// The message includes all the notes with their corresponding locations.
150 /// However, notes with fix-its are excluded as those usually only contain a
151 /// fix-it message and just add noise if included in the message for diagnostic.
152 /// Example output:
153 ///
154 /// no matching function for call to 'foo'
155 ///
156 /// main.cpp:3:5: note: candidate function not viable: requires 2 arguments
157 ///
158 /// dir1/dir2/dir3/../../dir4/header.h:12:23
159 /// note: candidate function not viable: requires 3 arguments
160 std::string mainMessage(const Diag &D) {
161  std::string Result;
162  llvm::raw_string_ostream OS(Result);
163  OS << D.Message;
164  for (auto &Note : D.Notes) {
165  OS << "\n\n";
166  printDiag(OS, Note);
167  }
168  OS.flush();
169  return Result;
170 }
171 
172 /// Returns a message sent to LSP for the note of the main diagnostic.
173 /// The message includes the main diagnostic to provide the necessary context
174 /// for the user to understand the note.
175 std::string noteMessage(const Diag &Main, const DiagBase &Note) {
176  std::string Result;
177  llvm::raw_string_ostream OS(Result);
178  OS << Note.Message;
179  OS << "\n\n";
180  printDiag(OS, Main);
181  OS.flush();
182  return Result;
183 }
184 } // namespace
185 
186 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) {
187  if (!D.InsideMainFile)
188  OS << "[in " << D.File << "] ";
189  return OS << D.Message;
190 }
191 
192 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F) {
193  OS << F.Message << " {";
194  const char *Sep = "";
195  for (const auto &Edit : F.Edits) {
196  OS << Sep << Edit;
197  Sep = ", ";
198  }
199  return OS << "}";
200 }
201 
202 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D) {
203  OS << static_cast<const DiagBase &>(D);
204  if (!D.Notes.empty()) {
205  OS << ", notes: {";
206  const char *Sep = "";
207  for (auto &Note : D.Notes) {
208  OS << Sep << Note;
209  Sep = ", ";
210  }
211  OS << "}";
212  }
213  if (!D.Fixes.empty()) {
214  OS << ", fixes: {";
215  const char *Sep = "";
216  for (auto &Fix : D.Fixes) {
217  OS << Sep << Fix;
218  Sep = ", ";
219  }
220  }
221  return OS;
222 }
223 
225  const Diag &D,
226  llvm::function_ref<void(clangd::Diagnostic, llvm::ArrayRef<Fix>)> OutFn) {
227  auto FillBasicFields = [](const DiagBase &D) -> clangd::Diagnostic {
228  clangd::Diagnostic Res;
229  Res.range = D.Range;
230  Res.severity = getSeverity(D.Severity);
231  return Res;
232  };
233 
234  {
235  clangd::Diagnostic Main = FillBasicFields(D);
236  Main.message = mainMessage(D);
237  OutFn(std::move(Main), D.Fixes);
238  }
239 
240  for (auto &Note : D.Notes) {
241  if (!Note.InsideMainFile)
242  continue;
243  clangd::Diagnostic Res = FillBasicFields(Note);
244  Res.message = noteMessage(D, Note);
245  OutFn(std::move(Res), llvm::ArrayRef<Fix>());
246  }
247 }
248 
249 int getSeverity(DiagnosticsEngine::Level L) {
250  switch (L) {
251  case DiagnosticsEngine::Remark:
252  return 4;
253  case DiagnosticsEngine::Note:
254  return 3;
255  case DiagnosticsEngine::Warning:
256  return 2;
257  case DiagnosticsEngine::Fatal:
258  case DiagnosticsEngine::Error:
259  return 1;
260  case DiagnosticsEngine::Ignored:
261  return 0;
262  }
263  llvm_unreachable("Unknown diagnostic level!");
264 }
265 
266 std::vector<Diag> StoreDiags::take() { return std::move(Output); }
267 
268 void StoreDiags::BeginSourceFile(const LangOptions &Opts,
269  const Preprocessor *) {
270  LangOpts = Opts;
271 }
272 
274  flushLastDiag();
275  LangOpts = llvm::None;
276 }
277 
278 void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
279  const clang::Diagnostic &Info) {
280  DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
281 
282  if (!LangOpts || !Info.hasSourceManager()) {
283  IgnoreDiagnostics::log(DiagLevel, Info);
284  return;
285  }
286 
287  bool InsideMainFile = isInsideMainFile(Info);
288 
289  auto FillDiagBase = [&](DiagBase &D) {
290  D.Range = diagnosticRange(Info, *LangOpts);
291  llvm::SmallString<64> Message;
292  Info.FormatDiagnostic(Message);
293  D.Message = Message.str();
294  D.InsideMainFile = InsideMainFile;
295  D.File = Info.getSourceManager().getFilename(Info.getLocation());
296  D.Severity = DiagLevel;
297  return D;
298  };
299 
300  auto AddFix = [&](bool SyntheticMessage) -> bool {
301  assert(!Info.getFixItHints().empty() &&
302  "diagnostic does not have attached fix-its");
303  if (!InsideMainFile)
304  return false;
305 
306  llvm::SmallVector<TextEdit, 1> Edits;
307  for (auto &FixIt : Info.getFixItHints()) {
308  if (!isInsideMainFile(FixIt.RemoveRange.getBegin(),
309  Info.getSourceManager()))
310  return false;
311  Edits.push_back(toTextEdit(FixIt, Info.getSourceManager(), *LangOpts));
312  }
313 
314  llvm::SmallString<64> Message;
315  // If requested and possible, create a message like "change 'foo' to 'bar'".
316  if (SyntheticMessage && Info.getNumFixItHints() == 1) {
317  const auto &FixIt = Info.getFixItHint(0);
318  bool Invalid = false;
319  StringRef Remove = Lexer::getSourceText(
320  FixIt.RemoveRange, Info.getSourceManager(), *LangOpts, &Invalid);
321  StringRef Insert = FixIt.CodeToInsert;
322  if (!Invalid) {
323  llvm::raw_svector_ostream M(Message);
324  if (!Remove.empty() && !Insert.empty())
325  M << "change '" << Remove << "' to '" << Insert << "'";
326  else if (!Remove.empty())
327  M << "remove '" << Remove << "'";
328  else if (!Insert.empty())
329  M << "insert '" << Insert << "'";
330  // Don't allow source code to inject newlines into diagnostics.
331  std::replace(Message.begin(), Message.end(), '\n', ' ');
332  }
333  }
334  if (Message.empty()) // either !SytheticMessage, or we failed to make one.
335  Info.FormatDiagnostic(Message);
336  LastDiag->Fixes.push_back(Fix{Message.str(), std::move(Edits)});
337  return true;
338  };
339 
340  if (!isNote(DiagLevel)) {
341  // Handle the new main diagnostic.
342  flushLastDiag();
343 
344  LastDiag = Diag();
345  FillDiagBase(*LastDiag);
346 
347  if (!Info.getFixItHints().empty())
348  AddFix(true /* try to invent a message instead of repeating the diag */);
349  } else {
350  // Handle a note to an existing diagnostic.
351  if (!LastDiag) {
352  assert(false && "Adding a note without main diagnostic");
353  IgnoreDiagnostics::log(DiagLevel, Info);
354  return;
355  }
356 
357  if (!Info.getFixItHints().empty()) {
358  // A clang note with fix-it is not a separate diagnostic in clangd. We
359  // attach it as a Fix to the main diagnostic instead.
360  if (!AddFix(false /* use the note as the message */))
361  IgnoreDiagnostics::log(DiagLevel, Info);
362  } else {
363  // A clang note without fix-its corresponds to clangd::Note.
364  Note N;
365  FillDiagBase(N);
366 
367  LastDiag->Notes.push_back(std::move(N));
368  }
369  }
370 }
371 
372 void StoreDiags::flushLastDiag() {
373  if (!LastDiag)
374  return;
375  if (mentionsMainFile(*LastDiag))
376  Output.push_back(std::move(*LastDiag));
377  else
378  log("Dropped diagnostic outside main file: {0}: {1}", LastDiag->File,
379  LastDiag->Message);
380  LastDiag.reset();
381 }
382 
383 } // namespace clangd
384 } // namespace clang
SourceLocation Loc
&#39;#&#39; location in the include directive
static void log(DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &Info)
Definition: Compiler.cpp:20
std::vector< Diag > take()
Contains basic information about a diagnostic.
Definition: Diagnostics.h:27
void toLSPDiags(const Diag &D, llvm::function_ref< void(clangd::Diagnostic, llvm::ArrayRef< Fix >)> OutFn)
Conversion to LSP diagnostics.
Documents should not be synced at all.
void BeginSourceFile(const LangOptions &Opts, const Preprocessor *) override
A top-level diagnostic that may have Notes and Fixes.
Definition: Diagnostics.h:54
std::string Message
Message for the fix-it.
Definition: Diagnostics.h:43
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
Definition: Diagnostics.h:58
llvm::SmallVector< TextEdit, 1 > Edits
TextEdits from clang&#39;s fix-its. Must be non-empty.
Definition: Diagnostics.h:45
int getSeverity(DiagnosticsEngine::Level L)
Convert from clang diagnostic level to LSP severity.
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &Info) override
void log(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:62
clangd::Range Range
Definition: Diagnostics.h:32
DiagnosticsEngine::Level Severity
Definition: Diagnostics.h:33
Represents a single fix-it that editor can apply to fix the error.
Definition: Diagnostics.h:41
Position Pos
FunctionInfo Info
int line
Line position in a document (zero-based).
Definition: Protocol.h:91
int character
Character offset on a line in a document (zero-based).
Definition: Protocol.h:96
std::vector< Note > Notes
Elaborate on the problem, usually pointing to a related piece of code.
Definition: Diagnostics.h:56
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
std::string message
The diagnostic&#39;s code.
Definition: Protocol.h:513
CharSourceRange Range
SourceRange for the file name.
int severity
The diagnostic&#39;s severity.
Definition: Protocol.h:501
void EndSourceFile() override
Represents a note for the diagnostic.
Definition: Diagnostics.h:51
Range range
The range at which the message applies.
Definition: Protocol.h:497
static cl::opt< bool > Fix("fix", cl::desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
raw_ostream & operator<<(raw_ostream &OS, const CodeCompletion &C)
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R)
Definition: SourceCode.cpp:147