blob: 0670de087e2f347d707d1d735ceab877adb5a94a [file] [log] [blame]
Kirill Bobyrev8e35f1e2018-08-14 16:03:32 +00001//===--- Diagnostics.cpp -----------------------------------------*- C++-*-===//
Ilya Biryukov71028b82018-03-12 15:28:22 +00002//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Ilya Biryukov71028b82018-03-12 15:28:22 +00006//
Kirill Bobyrev8e35f1e2018-08-14 16:03:32 +00007//===----------------------------------------------------------------------===//
Ilya Biryukov71028b82018-03-12 15:28:22 +00008
9#include "Diagnostics.h"
Sam McCall641caa52019-04-17 12:35:16 +000010#include "../clang-tidy/ClangTidyDiagnosticConsumer.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000011#include "Compiler.h"
12#include "Logger.h"
Sam McCallc9e4ee92019-04-18 15:17:07 +000013#include "Protocol.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000014#include "SourceCode.h"
Sam McCall641caa52019-04-17 12:35:16 +000015#include "clang/Basic/AllDiagnostics.h"
Kadir Cetinkaya01efe642019-04-29 10:25:44 +000016#include "clang/Basic/Diagnostic.h"
Sam McCall641caa52019-04-17 12:35:16 +000017#include "clang/Basic/DiagnosticIDs.h"
Kadir Cetinkaya01efe642019-04-29 10:25:44 +000018#include "clang/Basic/FileManager.h"
Ilya Biryukovd73ac962019-08-28 09:24:55 +000019#include "clang/Basic/SourceLocation.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000020#include "clang/Basic/SourceManager.h"
21#include "clang/Lex/Lexer.h"
Kadir Cetinkaya88e636d2019-06-13 12:31:36 +000022#include "clang/Lex/Token.h"
Kadir Cetinkaya01efe642019-04-29 10:25:44 +000023#include "llvm/ADT/ArrayRef.h"
Kadir Cetinkaya38496d52019-07-30 10:26:51 +000024#include "llvm/ADT/DenseSet.h"
Ilya Biryukovd73ac962019-08-28 09:24:55 +000025#include "llvm/ADT/SmallString.h"
Kadir Cetinkaya01efe642019-04-29 10:25:44 +000026#include "llvm/ADT/StringRef.h"
27#include "llvm/ADT/Twine.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000028#include "llvm/Support/Capacity.h"
29#include "llvm/Support/Path.h"
Kadir Cetinkaya01efe642019-04-29 10:25:44 +000030#include "llvm/Support/ScopedPrinter.h"
31#include "llvm/Support/Signals.h"
Ilya Biryukov0f748e62019-05-24 10:26:23 +000032#include "llvm/Support/raw_ostream.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000033#include <algorithm>
Ilya Biryukov0f748e62019-05-24 10:26:23 +000034#include <cstddef>
Ilya Biryukov71028b82018-03-12 15:28:22 +000035
36namespace clang {
37namespace clangd {
Ilya Biryukov71028b82018-03-12 15:28:22 +000038namespace {
39
Sam McCall641caa52019-04-17 12:35:16 +000040const char *getDiagnosticCode(unsigned ID) {
41 switch (ID) {
42#define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \
43 SHOWINSYSHEADER, CATEGORY) \
44 case clang::diag::ENUM: \
45 return #ENUM;
46#include "clang/Basic/DiagnosticASTKinds.inc"
47#include "clang/Basic/DiagnosticAnalysisKinds.inc"
48#include "clang/Basic/DiagnosticCommentKinds.inc"
49#include "clang/Basic/DiagnosticCommonKinds.inc"
50#include "clang/Basic/DiagnosticDriverKinds.inc"
51#include "clang/Basic/DiagnosticFrontendKinds.inc"
52#include "clang/Basic/DiagnosticLexKinds.inc"
53#include "clang/Basic/DiagnosticParseKinds.inc"
54#include "clang/Basic/DiagnosticRefactoringKinds.inc"
55#include "clang/Basic/DiagnosticSemaKinds.inc"
56#include "clang/Basic/DiagnosticSerializationKinds.inc"
57#undef DIAG
58 default:
59 return nullptr;
60 }
61}
62
Ilya Biryukov71028b82018-03-12 15:28:22 +000063bool mentionsMainFile(const Diag &D) {
64 if (D.InsideMainFile)
65 return true;
66 // Fixes are always in the main file.
67 if (!D.Fixes.empty())
68 return true;
69 for (auto &N : D.Notes) {
70 if (N.InsideMainFile)
71 return true;
72 }
73 return false;
74}
75
Sam McCall94603ec2019-12-09 11:57:23 +010076bool isBlacklisted(const Diag &D) {
Sam McCalld0ccd552019-12-09 19:21:58 +010077 // clang will always fail parsing MS ASM, we don't link in desc + asm parser.
78 if (D.ID == clang::diag::err_msasm_unable_to_create_target ||
79 D.ID == clang::diag::err_msasm_unsupported_arch)
Sam McCall94603ec2019-12-09 11:57:23 +010080 return true;
81 return false;
82}
83
Ilya Biryukov71028b82018-03-12 15:28:22 +000084// Checks whether a location is within a half-open range.
85// Note that clang also uses closed source ranges, which this can't handle!
86bool locationInRange(SourceLocation L, CharSourceRange R,
87 const SourceManager &M) {
88 assert(R.isCharRange());
89 if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) ||
90 M.getFileID(R.getBegin()) != M.getFileID(L))
91 return false;
92 return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd());
93}
94
95// Clang diags have a location (shown as ^) and 0 or more ranges (~~~~).
96// LSP needs a single range.
97Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) {
98 auto &M = D.getSourceManager();
99 auto Loc = M.getFileLoc(D.getLocation());
Ilya Biryukov71028b82018-03-12 15:28:22 +0000100 for (const auto &CR : D.getRanges()) {
101 auto R = Lexer::makeFileCharRange(CR, M, L);
102 if (locationInRange(Loc, R, M))
103 return halfOpenToRange(M, R);
104 }
105 // The range may be given as a fixit hint instead.
106 for (const auto &F : D.getFixItHints()) {
107 auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L);
108 if (locationInRange(Loc, R, M))
109 return halfOpenToRange(M, R);
110 }
Kadir Cetinkaya88e636d2019-06-13 12:31:36 +0000111 // If the token at the location is not a comment, we use the token.
112 // If we can't get the token at the location, fall back to using the location
113 auto R = CharSourceRange::getCharRange(Loc);
114 Token Tok;
115 if (!Lexer::getRawToken(Loc, Tok, M, L, true) && Tok.isNot(tok::comment)) {
116 R = CharSourceRange::getTokenRange(Tok.getLocation(), Tok.getEndLoc());
117 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000118 return halfOpenToRange(M, R);
119}
120
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000121// Returns whether the \p D is modified.
122bool adjustDiagFromHeader(Diag &D, const clang::Diagnostic &Info,
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000123 const LangOptions &LangOpts) {
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000124 // We only report diagnostics with at least error severity from headers.
125 if (D.Severity < DiagnosticsEngine::Level::Error)
126 return false;
127
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000128 const SourceManager &SM = Info.getSourceManager();
Kadir Cetinkayae8410292019-11-20 16:17:03 +0100129 const SourceLocation &DiagLoc = SM.getExpansionLoc(Info.getLocation());
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000130 SourceLocation IncludeInMainFile;
131 auto GetIncludeLoc = [&SM](SourceLocation SLoc) {
132 return SM.getIncludeLoc(SM.getFileID(SLoc));
133 };
134 for (auto IncludeLocation = GetIncludeLoc(DiagLoc); IncludeLocation.isValid();
Haojian Wud614a652019-08-12 09:35:04 +0000135 IncludeLocation = GetIncludeLoc(IncludeLocation)) {
136 if (clangd::isInsideMainFile(IncludeLocation, SM)) {
137 IncludeInMainFile = IncludeLocation;
138 break;
139 }
140 }
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000141 if (IncludeInMainFile.isInvalid())
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000142 return false;
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000143
144 // Update diag to point at include inside main file.
145 D.File = SM.getFileEntryForID(SM.getMainFileID())->getName().str();
146 D.Range.start = sourceLocToPosition(SM, IncludeInMainFile);
147 D.Range.end = sourceLocToPosition(
148 SM, Lexer::getLocForEndOfToken(IncludeInMainFile, 0, SM, LangOpts));
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000149 D.InsideMainFile = true;
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000150
151 // Add a note that will point to real diagnostic.
152 const auto *FE = SM.getFileEntryForID(SM.getFileID(DiagLoc));
153 D.Notes.emplace_back();
154 Note &N = D.Notes.back();
155 N.AbsFile = FE->tryGetRealPathName();
156 N.File = FE->getName();
157 N.Message = "error occurred here";
158 N.Range = diagnosticRange(Info, LangOpts);
159
160 // Update message to mention original file.
161 D.Message = llvm::Twine("in included file: ", D.Message).str();
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000162 return true;
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000163}
164
Ilya Biryukov71028b82018-03-12 15:28:22 +0000165bool isInsideMainFile(const clang::Diagnostic &D) {
166 if (!D.hasSourceManager())
167 return false;
168
Haojian Wu6ae86ea2019-07-19 08:33:39 +0000169 return clangd::isInsideMainFile(D.getLocation(), D.getSourceManager());
Ilya Biryukov71028b82018-03-12 15:28:22 +0000170}
171
172bool isNote(DiagnosticsEngine::Level L) {
173 return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark;
174}
175
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000176llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000177 switch (Lvl) {
178 case DiagnosticsEngine::Ignored:
179 return "ignored";
180 case DiagnosticsEngine::Note:
181 return "note";
182 case DiagnosticsEngine::Remark:
183 return "remark";
184 case DiagnosticsEngine::Warning:
185 return "warning";
186 case DiagnosticsEngine::Error:
187 return "error";
188 case DiagnosticsEngine::Fatal:
189 return "fatal error";
190 }
191 llvm_unreachable("unhandled DiagnosticsEngine::Level");
192}
193
194/// Prints a single diagnostic in a clang-like manner, the output includes
195/// location, severity and error message. An example of the output message is:
196///
197/// main.cpp:12:23: error: undeclared identifier
198///
199/// For main file we only print the basename and for all other files we print
200/// the filename on a separate line to provide a slightly more readable output
201/// in the editors:
202///
203/// dir1/dir2/dir3/../../dir4/header.h:12:23
204/// error: undeclared identifier
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000205void printDiag(llvm::raw_string_ostream &OS, const DiagBase &D) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000206 if (D.InsideMainFile) {
207 // Paths to main files are often taken from compile_command.json, where they
208 // are typically absolute. To reduce noise we print only basename for them,
209 // it should not be confusing and saves space.
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000210 OS << llvm::sys::path::filename(D.File) << ":";
Ilya Biryukov71028b82018-03-12 15:28:22 +0000211 } else {
212 OS << D.File << ":";
213 }
214 // Note +1 to line and character. clangd::Range is zero-based, but when
215 // printing for users we want one-based indexes.
216 auto Pos = D.Range.start;
217 OS << (Pos.line + 1) << ":" << (Pos.character + 1) << ":";
218 // The non-main-file paths are often too long, putting them on a separate
219 // line improves readability.
220 if (D.InsideMainFile)
221 OS << " ";
222 else
223 OS << "\n";
224 OS << diagLeveltoString(D.Severity) << ": " << D.Message;
225}
226
Alex Lorenzb411cf32018-08-03 20:43:28 +0000227/// Capitalizes the first word in the diagnostic's message.
228std::string capitalize(std::string Message) {
229 if (!Message.empty())
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000230 Message[0] = llvm::toUpper(Message[0]);
Alex Lorenzb411cf32018-08-03 20:43:28 +0000231 return Message;
232}
233
Ilya Biryukov71028b82018-03-12 15:28:22 +0000234/// Returns a message sent to LSP for the main diagnostic in \p D.
Sam McCallc9e4ee92019-04-18 15:17:07 +0000235/// This message may include notes, if they're not emited in some other way.
Ilya Biryukov71028b82018-03-12 15:28:22 +0000236/// Example output:
237///
238/// no matching function for call to 'foo'
239///
240/// main.cpp:3:5: note: candidate function not viable: requires 2 arguments
241///
242/// dir1/dir2/dir3/../../dir4/header.h:12:23
243/// note: candidate function not viable: requires 3 arguments
Sam McCallc9e4ee92019-04-18 15:17:07 +0000244std::string mainMessage(const Diag &D, const ClangdDiagnosticOptions &Opts) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000245 std::string Result;
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000246 llvm::raw_string_ostream OS(Result);
Ilya Biryukov71028b82018-03-12 15:28:22 +0000247 OS << D.Message;
Sam McCallc9e4ee92019-04-18 15:17:07 +0000248 if (Opts.DisplayFixesCount && !D.Fixes.empty())
Eric Liu00eaf672019-01-31 16:09:25 +0000249 OS << " (" << (D.Fixes.size() > 1 ? "fixes" : "fix") << " available)";
Sam McCallc9e4ee92019-04-18 15:17:07 +0000250 // If notes aren't emitted as structured info, add them to the message.
251 if (!Opts.EmitRelatedLocations)
252 for (auto &Note : D.Notes) {
253 OS << "\n\n";
254 printDiag(OS, Note);
255 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000256 OS.flush();
Alex Lorenzb411cf32018-08-03 20:43:28 +0000257 return capitalize(std::move(Result));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000258}
259
260/// Returns a message sent to LSP for the note of the main diagnostic.
Sam McCallc9e4ee92019-04-18 15:17:07 +0000261std::string noteMessage(const Diag &Main, const DiagBase &Note,
262 const ClangdDiagnosticOptions &Opts) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000263 std::string Result;
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000264 llvm::raw_string_ostream OS(Result);
Ilya Biryukov71028b82018-03-12 15:28:22 +0000265 OS << Note.Message;
Sam McCallc9e4ee92019-04-18 15:17:07 +0000266 // If the client doesn't support structured links between the note and the
267 // original diagnostic, then emit the main diagnostic to give context.
268 if (!Opts.EmitRelatedLocations) {
269 OS << "\n\n";
270 printDiag(OS, Main);
271 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000272 OS.flush();
Alex Lorenzb411cf32018-08-03 20:43:28 +0000273 return capitalize(std::move(Result));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000274}
275} // namespace
276
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000277llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) {
Sam McCall991e3162018-11-20 10:58:48 +0000278 OS << "[";
Ilya Biryukov71028b82018-03-12 15:28:22 +0000279 if (!D.InsideMainFile)
Sam McCall991e3162018-11-20 10:58:48 +0000280 OS << D.File << ":";
281 OS << D.Range.start << "-" << D.Range.end << "] ";
282
Ilya Biryukov71028b82018-03-12 15:28:22 +0000283 return OS << D.Message;
284}
285
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000286llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000287 OS << F.Message << " {";
288 const char *Sep = "";
289 for (const auto &Edit : F.Edits) {
290 OS << Sep << Edit;
291 Sep = ", ";
292 }
293 return OS << "}";
294}
295
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000296llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000297 OS << static_cast<const DiagBase &>(D);
298 if (!D.Notes.empty()) {
299 OS << ", notes: {";
300 const char *Sep = "";
301 for (auto &Note : D.Notes) {
302 OS << Sep << Note;
303 Sep = ", ";
304 }
305 OS << "}";
306 }
307 if (!D.Fixes.empty()) {
308 OS << ", fixes: {";
309 const char *Sep = "";
310 for (auto &Fix : D.Fixes) {
311 OS << Sep << Fix;
312 Sep = ", ";
313 }
314 }
315 return OS;
316}
317
Sam McCall16e70702018-10-24 07:59:38 +0000318CodeAction toCodeAction(const Fix &F, const URIForFile &File) {
319 CodeAction Action;
320 Action.title = F.Message;
321 Action.kind = CodeAction::QUICKFIX_KIND;
322 Action.edit.emplace();
323 Action.edit->changes.emplace();
324 (*Action.edit->changes)[File.uri()] = {F.Edits.begin(), F.Edits.end()};
325 return Action;
326}
327
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000328void toLSPDiags(
329 const Diag &D, const URIForFile &File, const ClangdDiagnosticOptions &Opts,
330 llvm::function_ref<void(clangd::Diagnostic, llvm::ArrayRef<Fix>)> OutFn) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000331 auto FillBasicFields = [](const DiagBase &D) -> clangd::Diagnostic {
332 clangd::Diagnostic Res;
333 Res.range = D.Range;
334 Res.severity = getSeverity(D.Severity);
335 return Res;
336 };
337
Sam McCallc9e4ee92019-04-18 15:17:07 +0000338 clangd::Diagnostic Main = FillBasicFields(D);
339 Main.code = D.Name;
340 switch (D.Source) {
341 case Diag::Clang:
342 Main.source = "clang";
343 break;
344 case Diag::ClangTidy:
345 Main.source = "clang-tidy";
346 break;
347 case Diag::Unknown:
348 break;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000349 }
Sam McCallc9e4ee92019-04-18 15:17:07 +0000350 if (Opts.EmbedFixesInDiagnostics) {
351 Main.codeActions.emplace();
352 for (const auto &Fix : D.Fixes)
353 Main.codeActions->push_back(toCodeAction(Fix, File));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000354 }
Sam McCallc9e4ee92019-04-18 15:17:07 +0000355 if (Opts.SendDiagnosticCategory && !D.Category.empty())
356 Main.category = D.Category;
357
358 Main.message = mainMessage(D, Opts);
359 if (Opts.EmitRelatedLocations) {
360 Main.relatedInformation.emplace();
361 for (auto &Note : D.Notes) {
362 if (!Note.AbsFile) {
363 vlog("Dropping note from unknown file: {0}", Note);
364 continue;
365 }
366 DiagnosticRelatedInformation RelInfo;
367 RelInfo.location.range = Note.Range;
368 RelInfo.location.uri =
369 URIForFile::canonicalize(*Note.AbsFile, File.file());
370 RelInfo.message = noteMessage(D, Note, Opts);
371 Main.relatedInformation->push_back(std::move(RelInfo));
372 }
373 }
374 OutFn(std::move(Main), D.Fixes);
375
376 // If we didn't emit the notes as relatedLocations, emit separate diagnostics
377 // so the user can find the locations easily.
378 if (!Opts.EmitRelatedLocations)
379 for (auto &Note : D.Notes) {
380 if (!Note.InsideMainFile)
381 continue;
382 clangd::Diagnostic Res = FillBasicFields(Note);
383 Res.message = noteMessage(D, Note, Opts);
384 OutFn(std::move(Res), llvm::ArrayRef<Fix>());
385 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000386}
387
388int getSeverity(DiagnosticsEngine::Level L) {
389 switch (L) {
390 case DiagnosticsEngine::Remark:
391 return 4;
392 case DiagnosticsEngine::Note:
393 return 3;
394 case DiagnosticsEngine::Warning:
395 return 2;
396 case DiagnosticsEngine::Fatal:
397 case DiagnosticsEngine::Error:
398 return 1;
399 case DiagnosticsEngine::Ignored:
400 return 0;
401 }
402 llvm_unreachable("Unknown diagnostic level!");
403}
404
Sam McCall641caa52019-04-17 12:35:16 +0000405std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) {
Ilya Biryukovd73ac962019-08-28 09:24:55 +0000406 // Do not forget to emit a pending diagnostic if there is one.
407 flushLastDiag();
408
Sam McCall641caa52019-04-17 12:35:16 +0000409 // Fill in name/source now that we have all the context needed to map them.
410 for (auto &Diag : Output) {
411 if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) {
Sam McCalld98170c2019-04-17 20:12:03 +0000412 // Warnings controlled by -Wfoo are better recognized by that name.
413 StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(Diag.ID);
414 if (!Warning.empty()) {
415 Diag.Name = ("-W" + Warning).str();
416 } else {
417 StringRef Name(ClangDiag);
418 // Almost always an error, with a name like err_enum_class_reference.
419 // Drop the err_ prefix for brevity.
420 Name.consume_front("err_");
421 Diag.Name = Name;
422 }
Sam McCall641caa52019-04-17 12:35:16 +0000423 Diag.Source = Diag::Clang;
424 continue;
425 }
426 if (Tidy != nullptr) {
427 std::string TidyDiag = Tidy->getCheckName(Diag.ID);
428 if (!TidyDiag.empty()) {
429 Diag.Name = std::move(TidyDiag);
430 Diag.Source = Diag::ClangTidy;
Sam McCallaa4eb102019-04-17 20:15:08 +0000431 // clang-tidy bakes the name into diagnostic messages. Strip it out.
432 // It would be much nicer to make clang-tidy not do this.
433 auto CleanMessage = [&](std::string &Msg) {
434 StringRef Rest(Msg);
435 if (Rest.consume_back("]") && Rest.consume_back(Diag.Name) &&
436 Rest.consume_back(" ["))
437 Msg.resize(Rest.size());
438 };
439 CleanMessage(Diag.Message);
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000440 for (auto &Note : Diag.Notes)
Sam McCallaa4eb102019-04-17 20:15:08 +0000441 CleanMessage(Note.Message);
Haojian Wub739b912019-07-01 08:05:53 +0000442 for (auto &Fix : Diag.Fixes)
443 CleanMessage(Fix.Message);
Sam McCall641caa52019-04-17 12:35:16 +0000444 continue;
445 }
446 }
447 }
Haojian Wuee080362019-07-05 12:57:56 +0000448 // Deduplicate clang-tidy diagnostics -- some clang-tidy checks may emit
449 // duplicated messages due to various reasons (e.g. the check doesn't handle
450 // template instantiations well; clang-tidy alias checks).
451 std::set<std::pair<Range, std::string>> SeenDiags;
452 llvm::erase_if(Output, [&](const Diag& D) {
453 return !SeenDiags.emplace(D.Range, D.Message).second;
454 });
Sam McCall641caa52019-04-17 12:35:16 +0000455 return std::move(Output);
456}
Ilya Biryukov71028b82018-03-12 15:28:22 +0000457
458void StoreDiags::BeginSourceFile(const LangOptions &Opts,
459 const Preprocessor *) {
460 LangOpts = Opts;
461}
462
463void StoreDiags::EndSourceFile() {
Sam McCallc008af62018-10-20 15:30:37 +0000464 LangOpts = None;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000465}
466
Ilya Biryukov0f748e62019-05-24 10:26:23 +0000467/// Sanitizes a piece for presenting it in a synthesized fix message. Ensures
468/// the result is not too large and does not contain newlines.
469static void writeCodeToFixMessage(llvm::raw_ostream &OS, llvm::StringRef Code) {
470 constexpr unsigned MaxLen = 50;
471
472 // Only show the first line if there are many.
473 llvm::StringRef R = Code.split('\n').first;
474 // Shorten the message if it's too long.
475 R = R.take_front(MaxLen);
476
477 OS << R;
478 if (R.size() != Code.size())
479 OS << "…";
480}
481
Ilya Biryukovd73ac962019-08-28 09:24:55 +0000482/// Fills \p D with all information, except the location-related bits.
483/// Also note that ID and Name are not part of clangd::DiagBase and should be
484/// set elsewhere.
485static void fillNonLocationData(DiagnosticsEngine::Level DiagLevel,
486 const clang::Diagnostic &Info,
487 clangd::DiagBase &D) {
488 llvm::SmallString<64> Message;
489 Info.FormatDiagnostic(Message);
490
491 D.Message = Message.str();
492 D.Severity = DiagLevel;
493 D.Category = DiagnosticIDs::getCategoryNameFromID(
494 DiagnosticIDs::getCategoryNumberForDiag(Info.getID()))
495 .str();
496}
497
Ilya Biryukov71028b82018-03-12 15:28:22 +0000498void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
499 const clang::Diagnostic &Info) {
500 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
501
Ilya Biryukovd73ac962019-08-28 09:24:55 +0000502 if (Info.getLocation().isInvalid()) {
503 // Handle diagnostics coming from command-line arguments. The source manager
504 // is *not* available at this point, so we cannot use it.
505 if (DiagLevel < DiagnosticsEngine::Level::Error) {
506 IgnoreDiagnostics::log(DiagLevel, Info);
507 return; // non-errors add too much noise, do not show them.
508 }
509
510 flushLastDiag();
511
512 LastDiag = Diag();
513 LastDiag->ID = Info.getID();
514 fillNonLocationData(DiagLevel, Info, *LastDiag);
515 LastDiag->InsideMainFile = true;
516 // Put it at the start of the main file, for a lack of a better place.
517 LastDiag->Range.start = Position{0, 0};
518 LastDiag->Range.end = Position{0, 0};
519 return;
520 }
521
Ilya Biryukov71028b82018-03-12 15:28:22 +0000522 if (!LangOpts || !Info.hasSourceManager()) {
523 IgnoreDiagnostics::log(DiagLevel, Info);
524 return;
525 }
526
527 bool InsideMainFile = isInsideMainFile(Info);
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000528 SourceManager &SM = Info.getSourceManager();
Ilya Biryukov71028b82018-03-12 15:28:22 +0000529
530 auto FillDiagBase = [&](DiagBase &D) {
Ilya Biryukovd73ac962019-08-28 09:24:55 +0000531 fillNonLocationData(DiagLevel, Info, D);
532
Ilya Biryukov71028b82018-03-12 15:28:22 +0000533 D.InsideMainFile = InsideMainFile;
Ilya Biryukovd73ac962019-08-28 09:24:55 +0000534 D.Range = diagnosticRange(Info, *LangOpts);
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000535 D.File = SM.getFilename(Info.getLocation());
Sam McCallc9e4ee92019-04-18 15:17:07 +0000536 D.AbsFile = getCanonicalPath(
537 SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM);
Ilya Biryukov71028b82018-03-12 15:28:22 +0000538 return D;
539 };
540
Sam McCallba3c02e2018-04-03 17:35:57 +0000541 auto AddFix = [&](bool SyntheticMessage) -> bool {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000542 assert(!Info.getFixItHints().empty() &&
543 "diagnostic does not have attached fix-its");
544 if (!InsideMainFile)
545 return false;
546
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000547 llvm::SmallVector<TextEdit, 1> Edits;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000548 for (auto &FixIt : Info.getFixItHints()) {
Haojian Wuc8f74962019-02-22 09:43:56 +0000549 // Follow clang's behavior, don't apply FixIt to the code in macros,
550 // we are less certain it is the right fix.
551 if (FixIt.RemoveRange.getBegin().isMacroID() ||
552 FixIt.RemoveRange.getEnd().isMacroID())
553 return false;
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000554 if (!isInsideMainFile(FixIt.RemoveRange.getBegin(), SM))
Ilya Biryukov71028b82018-03-12 15:28:22 +0000555 return false;
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000556 Edits.push_back(toTextEdit(FixIt, SM, *LangOpts));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000557 }
558
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000559 llvm::SmallString<64> Message;
Sam McCallba3c02e2018-04-03 17:35:57 +0000560 // If requested and possible, create a message like "change 'foo' to 'bar'".
561 if (SyntheticMessage && Info.getNumFixItHints() == 1) {
562 const auto &FixIt = Info.getFixItHint(0);
563 bool Invalid = false;
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000564 llvm::StringRef Remove =
565 Lexer::getSourceText(FixIt.RemoveRange, SM, *LangOpts, &Invalid);
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000566 llvm::StringRef Insert = FixIt.CodeToInsert;
Sam McCallba3c02e2018-04-03 17:35:57 +0000567 if (!Invalid) {
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000568 llvm::raw_svector_ostream M(Message);
Ilya Biryukov0f748e62019-05-24 10:26:23 +0000569 if (!Remove.empty() && !Insert.empty()) {
570 M << "change '";
571 writeCodeToFixMessage(M, Remove);
572 M << "' to '";
573 writeCodeToFixMessage(M, Insert);
574 M << "'";
575 } else if (!Remove.empty()) {
576 M << "remove '";
577 writeCodeToFixMessage(M, Remove);
578 M << "'";
579 } else if (!Insert.empty()) {
580 M << "insert '";
581 writeCodeToFixMessage(M, Insert);
582 M << "'";
583 }
Sam McCallba3c02e2018-04-03 17:35:57 +0000584 // Don't allow source code to inject newlines into diagnostics.
585 std::replace(Message.begin(), Message.end(), '\n', ' ');
586 }
587 }
588 if (Message.empty()) // either !SytheticMessage, or we failed to make one.
589 Info.FormatDiagnostic(Message);
Ilya Biryukov71028b82018-03-12 15:28:22 +0000590 LastDiag->Fixes.push_back(Fix{Message.str(), std::move(Edits)});
591 return true;
592 };
593
594 if (!isNote(DiagLevel)) {
595 // Handle the new main diagnostic.
596 flushLastDiag();
597
Fangrui Songcb4b3e52019-05-19 04:19:14 +0000598 if (Adjuster) {
599 DiagLevel = Adjuster(DiagLevel, Info);
600 if (DiagLevel == DiagnosticsEngine::Ignored) {
601 LastPrimaryDiagnosticWasSuppressed = true;
602 return;
603 }
Fangrui Songc2aded52019-05-19 04:06:52 +0000604 }
605 LastPrimaryDiagnosticWasSuppressed = false;
606
Ilya Biryukov71028b82018-03-12 15:28:22 +0000607 LastDiag = Diag();
Haojian Wu24a8f1c2019-03-06 10:51:38 +0000608 LastDiag->ID = Info.getID();
Ilya Biryukov71028b82018-03-12 15:28:22 +0000609 FillDiagBase(*LastDiag);
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000610 if (!InsideMainFile)
611 LastDiagWasAdjusted = adjustDiagFromHeader(*LastDiag, Info, *LangOpts);
Ilya Biryukov71028b82018-03-12 15:28:22 +0000612
613 if (!Info.getFixItHints().empty())
Sam McCallba3c02e2018-04-03 17:35:57 +0000614 AddFix(true /* try to invent a message instead of repeating the diag */);
Eric Liudd662772019-01-28 14:01:55 +0000615 if (Fixer) {
616 auto ExtraFixes = Fixer(DiagLevel, Info);
617 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(),
618 ExtraFixes.end());
619 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000620 } else {
621 // Handle a note to an existing diagnostic.
Fangrui Songc2aded52019-05-19 04:06:52 +0000622
623 // If a diagnostic was suppressed due to the suppression filter,
624 // also suppress notes associated with it.
625 if (LastPrimaryDiagnosticWasSuppressed) {
626 return;
627 }
628
Ilya Biryukov71028b82018-03-12 15:28:22 +0000629 if (!LastDiag) {
630 assert(false && "Adding a note without main diagnostic");
631 IgnoreDiagnostics::log(DiagLevel, Info);
632 return;
633 }
634
635 if (!Info.getFixItHints().empty()) {
636 // A clang note with fix-it is not a separate diagnostic in clangd. We
637 // attach it as a Fix to the main diagnostic instead.
Sam McCallba3c02e2018-04-03 17:35:57 +0000638 if (!AddFix(false /* use the note as the message */))
Ilya Biryukov71028b82018-03-12 15:28:22 +0000639 IgnoreDiagnostics::log(DiagLevel, Info);
640 } else {
641 // A clang note without fix-its corresponds to clangd::Note.
642 Note N;
643 FillDiagBase(N);
644
645 LastDiag->Notes.push_back(std::move(N));
646 }
647 }
648}
649
650void StoreDiags::flushLastDiag() {
651 if (!LastDiag)
652 return;
Sam McCall94603ec2019-12-09 11:57:23 +0100653 if (!isBlacklisted(*LastDiag) && mentionsMainFile(*LastDiag) &&
Kadir Cetinkaya38496d52019-07-30 10:26:51 +0000654 (!LastDiagWasAdjusted ||
655 // Only report the first diagnostic coming from each particular header.
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000656 IncludeLinesWithErrors.insert(LastDiag->Range.start.line).second)) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000657 Output.push_back(std::move(*LastDiag));
Kadir Cetinkaya01efe642019-04-29 10:25:44 +0000658 } else {
659 vlog("Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message);
660 }
Ilya Biryukov71028b82018-03-12 15:28:22 +0000661 LastDiag.reset();
Ilya Biryukovd73ac962019-08-28 09:24:55 +0000662 LastDiagWasAdjusted = false;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000663}
664
665} // namespace clangd
666} // namespace clang