1 //===--- Diagnostics.cpp -----------------------------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "Diagnostics.h"
10 #include "../clang-tidy/ClangTidyDiagnosticConsumer.h"
13 #include "SourceCode.h"
14 #include "support/Logger.h"
15 #include "clang/Basic/AllDiagnostics.h" // IWYU pragma: keep
16 #include "clang/Basic/Diagnostic.h"
17 #include "clang/Basic/DiagnosticIDs.h"
18 #include "clang/Basic/LLVM.h"
19 #include "clang/Basic/SourceLocation.h"
20 #include "clang/Basic/SourceManager.h"
21 #include "clang/Basic/TokenKinds.h"
22 #include "clang/Lex/Lexer.h"
23 #include "clang/Lex/Token.h"
24 #include "llvm/ADT/ArrayRef.h"
25 #include "llvm/ADT/DenseSet.h"
26 #include "llvm/ADT/STLExtras.h"
27 #include "llvm/ADT/STLFunctionalExtras.h"
28 #include "llvm/ADT/ScopeExit.h"
29 #include "llvm/ADT/SmallString.h"
30 #include "llvm/ADT/SmallVector.h"
31 #include "llvm/ADT/StringExtras.h"
32 #include "llvm/ADT/StringRef.h"
33 #include "llvm/ADT/StringSet.h"
34 #include "llvm/ADT/Twine.h"
35 #include "llvm/Support/ErrorHandling.h"
36 #include "llvm/Support/FormatVariadic.h"
37 #include "llvm/Support/Path.h"
38 #include "llvm/Support/SourceMgr.h"
39 #include "llvm/Support/raw_ostream.h"
53 const char *getDiagnosticCode(unsigned ID
) {
55 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \
56 SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY) \
57 case clang::diag::ENUM: \
59 #include "clang/Basic/DiagnosticASTKinds.inc"
60 #include "clang/Basic/DiagnosticAnalysisKinds.inc"
61 #include "clang/Basic/DiagnosticCommentKinds.inc"
62 #include "clang/Basic/DiagnosticCommonKinds.inc"
63 #include "clang/Basic/DiagnosticDriverKinds.inc"
64 #include "clang/Basic/DiagnosticFrontendKinds.inc"
65 #include "clang/Basic/DiagnosticLexKinds.inc"
66 #include "clang/Basic/DiagnosticParseKinds.inc"
67 #include "clang/Basic/DiagnosticRefactoringKinds.inc"
68 #include "clang/Basic/DiagnosticSemaKinds.inc"
69 #include "clang/Basic/DiagnosticSerializationKinds.inc"
76 bool mentionsMainFile(const Diag
&D
) {
79 // Fixes are always in the main file.
82 for (auto &N
: D
.Notes
) {
89 bool isExcluded(unsigned DiagID
) {
90 // clang will always fail parsing MS ASM, we don't link in desc + asm parser.
91 if (DiagID
== clang::diag::err_msasm_unable_to_create_target
||
92 DiagID
== clang::diag::err_msasm_unsupported_arch
)
97 // Checks whether a location is within a half-open range.
98 // Note that clang also uses closed source ranges, which this can't handle!
99 bool locationInRange(SourceLocation L
, CharSourceRange R
,
100 const SourceManager
&M
) {
101 assert(R
.isCharRange());
102 if (!R
.isValid() || M
.getFileID(R
.getBegin()) != M
.getFileID(R
.getEnd()) ||
103 M
.getFileID(R
.getBegin()) != M
.getFileID(L
))
105 return L
!= R
.getEnd() && M
.isPointWithin(L
, R
.getBegin(), R
.getEnd());
108 // Clang diags have a location (shown as ^) and 0 or more ranges (~~~~).
109 // LSP needs a single range.
110 std::optional
<Range
> diagnosticRange(const clang::Diagnostic
&D
,
111 const LangOptions
&L
) {
112 auto &M
= D
.getSourceManager();
113 auto PatchedRange
= [&M
](CharSourceRange
&R
) {
114 R
.setBegin(translatePreamblePatchLocation(R
.getBegin(), M
));
115 R
.setEnd(translatePreamblePatchLocation(R
.getEnd(), M
));
118 auto Loc
= M
.getFileLoc(D
.getLocation());
119 for (const auto &CR
: D
.getRanges()) {
120 auto R
= Lexer::makeFileCharRange(CR
, M
, L
);
121 if (locationInRange(Loc
, R
, M
))
122 return halfOpenToRange(M
, PatchedRange(R
));
124 // The range may be given as a fixit hint instead.
125 for (const auto &F
: D
.getFixItHints()) {
126 auto R
= Lexer::makeFileCharRange(F
.RemoveRange
, M
, L
);
127 if (locationInRange(Loc
, R
, M
))
128 return halfOpenToRange(M
, PatchedRange(R
));
130 // Source locations from stale preambles might become OOB.
131 // FIXME: These diagnostics might point to wrong locations even when they're
133 auto [FID
, Offset
] = M
.getDecomposedLoc(Loc
);
134 if (Offset
> M
.getBufferData(FID
).size())
136 // If the token at the location is not a comment, we use the token.
137 // If we can't get the token at the location, fall back to using the location
138 auto R
= CharSourceRange::getCharRange(Loc
);
140 if (!Lexer::getRawToken(Loc
, Tok
, M
, L
, true) && Tok
.isNot(tok::comment
))
141 R
= CharSourceRange::getTokenRange(Tok
.getLocation(), Tok
.getEndLoc());
142 return halfOpenToRange(M
, PatchedRange(R
));
145 // Try to find a location in the main-file to report the diagnostic D.
146 // Returns a description like "in included file", or nullptr on failure.
147 const char *getMainFileRange(const Diag
&D
, const SourceManager
&SM
,
148 SourceLocation DiagLoc
, Range
&R
) {
149 // Look for a note in the main file indicating template instantiation.
150 for (const auto &N
: D
.Notes
) {
151 if (N
.InsideMainFile
) {
153 case diag::note_template_class_instantiation_was_here
:
154 case diag::note_template_class_explicit_specialization_was_here
:
155 case diag::note_template_class_instantiation_here
:
156 case diag::note_template_member_class_here
:
157 case diag::note_template_member_function_here
:
158 case diag::note_function_template_spec_here
:
159 case diag::note_template_static_data_member_def_here
:
160 case diag::note_template_variable_def_here
:
161 case diag::note_template_enum_def_here
:
162 case diag::note_template_nsdmi_here
:
163 case diag::note_template_type_alias_instantiation_here
:
164 case diag::note_template_exception_spec_instantiation_here
:
165 case diag::note_template_requirement_instantiation_here
:
166 case diag::note_evaluating_exception_spec_here
:
167 case diag::note_default_arg_instantiation_here
:
168 case diag::note_default_function_arg_instantiation_here
:
169 case diag::note_explicit_template_arg_substitution_here
:
170 case diag::note_function_template_deduction_instantiation_here
:
171 case diag::note_deduced_template_arg_substitution_here
:
172 case diag::note_prior_template_arg_substitution
:
173 case diag::note_template_default_arg_checking
:
174 case diag::note_concept_specialization_here
:
175 case diag::note_nested_requirement_here
:
176 case diag::note_checking_constraints_for_template_id_here
:
177 case diag::note_checking_constraints_for_var_spec_id_here
:
178 case diag::note_checking_constraints_for_class_spec_id_here
:
179 case diag::note_checking_constraints_for_function_here
:
180 case diag::note_constraint_substitution_here
:
181 case diag::note_constraint_normalization_here
:
182 case diag::note_parameter_mapping_substitution_here
:
184 return "in template";
190 // Look for where the file with the error was #included.
191 auto GetIncludeLoc
= [&SM
](SourceLocation SLoc
) {
192 return SM
.getIncludeLoc(SM
.getFileID(SLoc
));
194 for (auto IncludeLocation
= GetIncludeLoc(SM
.getExpansionLoc(DiagLoc
));
195 IncludeLocation
.isValid();
196 IncludeLocation
= GetIncludeLoc(IncludeLocation
)) {
197 if (clangd::isInsideMainFile(IncludeLocation
, SM
)) {
198 R
.start
= sourceLocToPosition(SM
, IncludeLocation
);
199 R
.end
= sourceLocToPosition(
201 Lexer::getLocForEndOfToken(IncludeLocation
, 0, SM
, LangOptions()));
202 return "in included file";
208 // Place the diagnostic the main file, rather than the header, if possible:
209 // - for errors in included files, use the #include location
210 // - for errors in template instantiation, use the instantiation location
211 // In both cases, add the original header location as a note.
212 bool tryMoveToMainFile(Diag
&D
, FullSourceLoc DiagLoc
) {
213 const SourceManager
&SM
= DiagLoc
.getManager();
214 DiagLoc
= DiagLoc
.getExpansionLoc();
216 const char *Prefix
= getMainFileRange(D
, SM
, DiagLoc
, R
);
220 // Add a note that will point to real diagnostic.
221 auto FE
= *SM
.getFileEntryRefForID(SM
.getFileID(DiagLoc
));
222 D
.Notes
.emplace(D
.Notes
.begin());
223 Note
&N
= D
.Notes
.front();
224 N
.AbsFile
= std::string(FE
.getFileEntry().tryGetRealPathName());
225 N
.File
= std::string(FE
.getName());
226 N
.Message
= "error occurred here";
229 // Update diag to point at include inside main file.
230 D
.File
= SM
.getFileEntryRefForID(SM
.getMainFileID())->getName().str();
231 D
.Range
= std::move(R
);
232 D
.InsideMainFile
= true;
233 // Update message to mention original file.
234 D
.Message
= llvm::formatv("{0}: {1}", Prefix
, D
.Message
);
238 bool isNote(DiagnosticsEngine::Level L
) {
239 return L
== DiagnosticsEngine::Note
|| L
== DiagnosticsEngine::Remark
;
242 llvm::StringRef
diagLeveltoString(DiagnosticsEngine::Level Lvl
) {
244 case DiagnosticsEngine::Ignored
:
246 case DiagnosticsEngine::Note
:
248 case DiagnosticsEngine::Remark
:
250 case DiagnosticsEngine::Warning
:
252 case DiagnosticsEngine::Error
:
254 case DiagnosticsEngine::Fatal
:
255 return "fatal error";
257 llvm_unreachable("unhandled DiagnosticsEngine::Level");
260 /// Prints a single diagnostic in a clang-like manner, the output includes
261 /// location, severity and error message. An example of the output message is:
263 /// main.cpp:12:23: error: undeclared identifier
265 /// For main file we only print the basename and for all other files we print
266 /// the filename on a separate line to provide a slightly more readable output
269 /// dir1/dir2/dir3/../../dir4/header.h:12:23
270 /// error: undeclared identifier
271 void printDiag(llvm::raw_string_ostream
&OS
, const DiagBase
&D
) {
272 if (D
.InsideMainFile
) {
273 // Paths to main files are often taken from compile_command.json, where they
274 // are typically absolute. To reduce noise we print only basename for them,
275 // it should not be confusing and saves space.
276 OS
<< llvm::sys::path::filename(D
.File
) << ":";
280 // Note +1 to line and character. clangd::Range is zero-based, but when
281 // printing for users we want one-based indexes.
282 auto Pos
= D
.Range
.start
;
283 OS
<< (Pos
.line
+ 1) << ":" << (Pos
.character
+ 1) << ":";
284 // The non-main-file paths are often too long, putting them on a separate
285 // line improves readability.
286 if (D
.InsideMainFile
)
290 OS
<< diagLeveltoString(D
.Severity
) << ": " << D
.Message
;
293 /// Capitalizes the first word in the diagnostic's message.
294 std::string
capitalize(std::string Message
) {
295 if (!Message
.empty())
296 Message
[0] = llvm::toUpper(Message
[0]);
300 /// Returns a message sent to LSP for the main diagnostic in \p D.
301 /// This message may include notes, if they're not emitted in some other way.
304 /// no matching function for call to 'foo'
306 /// main.cpp:3:5: note: candidate function not viable: requires 2 arguments
308 /// dir1/dir2/dir3/../../dir4/header.h:12:23
309 /// note: candidate function not viable: requires 3 arguments
310 std::string
mainMessage(const Diag
&D
, const ClangdDiagnosticOptions
&Opts
) {
312 llvm::raw_string_ostream
OS(Result
);
314 if (Opts
.DisplayFixesCount
&& !D
.Fixes
.empty())
315 OS
<< " (" << (D
.Fixes
.size() > 1 ? "fixes" : "fix") << " available)";
316 // If notes aren't emitted as structured info, add them to the message.
317 if (!Opts
.EmitRelatedLocations
)
318 for (auto &Note
: D
.Notes
) {
322 return capitalize(std::move(Result
));
325 /// Returns a message sent to LSP for the note of the main diagnostic.
326 std::string
noteMessage(const Diag
&Main
, const DiagBase
&Note
,
327 const ClangdDiagnosticOptions
&Opts
) {
329 llvm::raw_string_ostream
OS(Result
);
331 // If the client doesn't support structured links between the note and the
332 // original diagnostic, then emit the main diagnostic to give context.
333 if (!Opts
.EmitRelatedLocations
) {
337 return capitalize(std::move(Result
));
340 void setTags(clangd::Diag
&D
) {
341 static const auto *DeprecatedDiags
= new llvm::DenseSet
<unsigned>{
342 diag::warn_access_decl_deprecated
,
343 diag::warn_atl_uuid_deprecated
,
344 diag::warn_deprecated
,
345 diag::warn_deprecated_altivec_src_compat
,
346 diag::warn_deprecated_comma_subscript
,
347 diag::warn_deprecated_copy
,
348 diag::warn_deprecated_copy_with_dtor
,
349 diag::warn_deprecated_copy_with_user_provided_copy
,
350 diag::warn_deprecated_copy_with_user_provided_dtor
,
351 diag::warn_deprecated_def
,
352 diag::warn_deprecated_increment_decrement_volatile
,
353 diag::warn_deprecated_message
,
354 diag::warn_deprecated_redundant_constexpr_static_def
,
355 diag::warn_deprecated_register
,
356 diag::warn_deprecated_simple_assign_volatile
,
357 diag::warn_deprecated_string_literal_conversion
,
358 diag::warn_deprecated_this_capture
,
359 diag::warn_deprecated_volatile_param
,
360 diag::warn_deprecated_volatile_return
,
361 diag::warn_deprecated_volatile_structured_binding
,
362 diag::warn_opencl_attr_deprecated_ignored
,
363 diag::warn_property_method_deprecated
,
364 diag::warn_vector_mode_deprecated
,
366 static const auto *UnusedDiags
= new llvm::DenseSet
<unsigned>{
367 diag::warn_opencl_attr_deprecated_ignored
,
368 diag::warn_pragma_attribute_unused
,
369 diag::warn_unused_but_set_parameter
,
370 diag::warn_unused_but_set_variable
,
371 diag::warn_unused_comparison
,
372 diag::warn_unused_const_variable
,
373 diag::warn_unused_exception_param
,
374 diag::warn_unused_function
,
375 diag::warn_unused_label
,
376 diag::warn_unused_lambda_capture
,
377 diag::warn_unused_local_typedef
,
378 diag::warn_unused_member_function
,
379 diag::warn_unused_parameter
,
380 diag::warn_unused_private_field
,
381 diag::warn_unused_property_backing_ivar
,
382 diag::warn_unused_template
,
383 diag::warn_unused_variable
,
385 if (DeprecatedDiags
->contains(D
.ID
)) {
386 D
.Tags
.push_back(DiagnosticTag::Deprecated
);
387 } else if (UnusedDiags
->contains(D
.ID
)) {
388 D
.Tags
.push_back(DiagnosticTag::Unnecessary
);
390 if (D
.Source
== Diag::ClangTidy
) {
391 if (llvm::StringRef(D
.Name
).starts_with("misc-unused-"))
392 D
.Tags
.push_back(DiagnosticTag::Unnecessary
);
393 if (llvm::StringRef(D
.Name
).starts_with("modernize-"))
394 D
.Tags
.push_back(DiagnosticTag::Deprecated
);
399 llvm::raw_ostream
&operator<<(llvm::raw_ostream
&OS
, const DiagBase
&D
) {
401 if (!D
.InsideMainFile
)
403 OS
<< D
.Range
.start
<< "-" << D
.Range
.end
<< "] ";
405 return OS
<< D
.Message
;
408 llvm::raw_ostream
&operator<<(llvm::raw_ostream
&OS
, const Fix
&F
) {
409 OS
<< F
.Message
<< " {";
410 const char *Sep
= "";
411 for (const auto &Edit
: F
.Edits
) {
418 llvm::raw_ostream
&operator<<(llvm::raw_ostream
&OS
, const Diag
&D
) {
419 OS
<< static_cast<const DiagBase
&>(D
);
420 if (!D
.Notes
.empty()) {
422 const char *Sep
= "";
423 for (auto &Note
: D
.Notes
) {
429 if (!D
.Fixes
.empty()) {
431 const char *Sep
= "";
432 for (auto &Fix
: D
.Fixes
) {
441 Diag
toDiag(const llvm::SMDiagnostic
&D
, Diag::DiagSource Source
) {
443 Result
.Message
= D
.getMessage().str();
444 switch (D
.getKind()) {
445 case llvm::SourceMgr::DK_Error
:
446 Result
.Severity
= DiagnosticsEngine::Error
;
448 case llvm::SourceMgr::DK_Warning
:
449 Result
.Severity
= DiagnosticsEngine::Warning
;
454 Result
.Source
= Source
;
455 Result
.AbsFile
= D
.getFilename().str();
456 Result
.InsideMainFile
= D
.getSourceMgr()->FindBufferContainingLoc(
457 D
.getLoc()) == D
.getSourceMgr()->getMainFileID();
458 if (D
.getRanges().empty())
459 Result
.Range
= {{D
.getLineNo() - 1, D
.getColumnNo()},
460 {D
.getLineNo() - 1, D
.getColumnNo()}};
462 Result
.Range
= {{D
.getLineNo() - 1, (int)D
.getRanges().front().first
},
463 {D
.getLineNo() - 1, (int)D
.getRanges().front().second
}};
468 const Diag
&D
, const URIForFile
&File
, const ClangdDiagnosticOptions
&Opts
,
469 llvm::function_ref
<void(clangd::Diagnostic
, llvm::ArrayRef
<Fix
>)> OutFn
) {
470 clangd::Diagnostic Main
;
471 Main
.severity
= getSeverity(D
.Severity
);
472 // We downgrade severity for certain noisy warnings, like deprecated
473 // declartions. These already have visible decorations inside the editor and
474 // most users find the extra clutter in the UI (gutter, minimap, diagnostics
475 // views) overwhelming.
476 if (D
.Severity
== DiagnosticsEngine::Warning
) {
477 if (llvm::is_contained(D
.Tags
, DiagnosticTag::Deprecated
))
478 Main
.severity
= getSeverity(DiagnosticsEngine::Remark
);
481 // Main diagnostic should always refer to a range inside main file. If a
482 // diagnostic made it so for, it means either itself or one of its notes is
483 // inside main file. It's also possible that there's a fix in the main file,
484 // but we preserve fixes iff primary diagnostic is in the main file.
485 if (D
.InsideMainFile
) {
486 Main
.range
= D
.Range
;
489 llvm::find_if(D
.Notes
, [](const Note
&N
) { return N
.InsideMainFile
; });
490 assert(It
!= D
.Notes
.end() &&
491 "neither the main diagnostic nor notes are inside main file");
492 Main
.range
= It
->Range
;
496 if (auto URI
= getDiagnosticDocURI(D
.Source
, D
.ID
, D
.Name
)) {
497 Main
.codeDescription
.emplace();
498 Main
.codeDescription
->href
= std::move(*URI
);
502 Main
.source
= "clang";
504 case Diag::ClangTidy
:
505 Main
.source
= "clang-tidy";
508 Main
.source
= "clangd";
510 case Diag::ClangdConfig
:
511 Main
.source
= "clangd-config";
516 if (Opts
.SendDiagnosticCategory
&& !D
.Category
.empty())
517 Main
.category
= D
.Category
;
519 Main
.message
= mainMessage(D
, Opts
);
520 if (Opts
.EmitRelatedLocations
) {
521 Main
.relatedInformation
.emplace();
522 for (auto &Note
: D
.Notes
) {
524 vlog("Dropping note from unknown file: {0}", Note
);
527 DiagnosticRelatedInformation RelInfo
;
528 RelInfo
.location
.range
= Note
.Range
;
529 RelInfo
.location
.uri
=
530 URIForFile::canonicalize(*Note
.AbsFile
, File
.file());
531 RelInfo
.message
= noteMessage(D
, Note
, Opts
);
532 Main
.relatedInformation
->push_back(std::move(RelInfo
));
536 // FIXME: Get rid of the copies here by taking in a mutable clangd::Diag.
537 for (auto &Entry
: D
.OpaqueData
)
538 Main
.data
.insert({Entry
.first
, Entry
.second
});
539 OutFn(std::move(Main
), D
.Fixes
);
541 // If we didn't emit the notes as relatedLocations, emit separate diagnostics
542 // so the user can find the locations easily.
543 if (!Opts
.EmitRelatedLocations
)
544 for (auto &Note
: D
.Notes
) {
545 if (!Note
.InsideMainFile
)
547 clangd::Diagnostic Res
;
548 Res
.severity
= getSeverity(Note
.Severity
);
549 Res
.range
= Note
.Range
;
550 Res
.message
= noteMessage(D
, Note
, Opts
);
551 OutFn(std::move(Res
), llvm::ArrayRef
<Fix
>());
555 int getSeverity(DiagnosticsEngine::Level L
) {
557 case DiagnosticsEngine::Remark
:
559 case DiagnosticsEngine::Note
:
561 case DiagnosticsEngine::Warning
:
563 case DiagnosticsEngine::Fatal
:
564 case DiagnosticsEngine::Error
:
566 case DiagnosticsEngine::Ignored
:
569 llvm_unreachable("Unknown diagnostic level!");
572 std::vector
<Diag
> StoreDiags::take(const clang::tidy::ClangTidyContext
*Tidy
) {
573 // Do not forget to emit a pending diagnostic if there is one.
576 // Fill in name/source now that we have all the context needed to map them.
577 for (auto &Diag
: Output
) {
578 if (const char *ClangDiag
= getDiagnosticCode(Diag
.ID
)) {
579 // Warnings controlled by -Wfoo are better recognized by that name.
580 StringRef Warning
= DiagnosticIDs::getWarningOptionForDiag(Diag
.ID
);
581 if (!Warning
.empty()) {
582 Diag
.Name
= ("-W" + Warning
).str();
584 StringRef
Name(ClangDiag
);
585 // Almost always an error, with a name like err_enum_class_reference.
586 // Drop the err_ prefix for brevity.
587 Name
.consume_front("err_");
588 Diag
.Name
= std::string(Name
);
590 Diag
.Source
= Diag::Clang
;
591 } else if (Tidy
!= nullptr) {
592 std::string TidyDiag
= Tidy
->getCheckName(Diag
.ID
);
593 if (!TidyDiag
.empty()) {
594 Diag
.Name
= std::move(TidyDiag
);
595 Diag
.Source
= Diag::ClangTidy
;
596 // clang-tidy bakes the name into diagnostic messages. Strip it out.
597 // It would be much nicer to make clang-tidy not do this.
598 auto CleanMessage
= [&](std::string
&Msg
) {
600 if (Rest
.consume_back("]") && Rest
.consume_back(Diag
.Name
) &&
601 Rest
.consume_back(" ["))
602 Msg
.resize(Rest
.size());
604 CleanMessage(Diag
.Message
);
605 for (auto &Note
: Diag
.Notes
)
606 CleanMessage(Note
.Message
);
607 for (auto &Fix
: Diag
.Fixes
)
608 CleanMessage(Fix
.Message
);
613 // Deduplicate clang-tidy diagnostics -- some clang-tidy checks may emit
614 // duplicated messages due to various reasons (e.g. the check doesn't handle
615 // template instantiations well; clang-tidy alias checks).
616 std::set
<std::pair
<Range
, std::string
>> SeenDiags
;
617 llvm::erase_if(Output
, [&](const Diag
&D
) {
618 return !SeenDiags
.emplace(D
.Range
, D
.Message
).second
;
620 return std::move(Output
);
623 void StoreDiags::BeginSourceFile(const LangOptions
&Opts
,
624 const Preprocessor
*PP
) {
627 OrigSrcMgr
= &PP
->getSourceManager();
631 void StoreDiags::EndSourceFile() {
633 LangOpts
= std::nullopt
;
634 OrigSrcMgr
= nullptr;
637 /// Sanitizes a piece for presenting it in a synthesized fix message. Ensures
638 /// the result is not too large and does not contain newlines.
639 static void writeCodeToFixMessage(llvm::raw_ostream
&OS
, llvm::StringRef Code
) {
640 constexpr unsigned MaxLen
= 50;
645 // Only show the first line if there are many.
646 llvm::StringRef R
= Code
.split('\n').first
;
647 // Shorten the message if it's too long.
648 R
= R
.take_front(MaxLen
);
651 if (R
.size() != Code
.size())
655 /// Fills \p D with all information, except the location-related bits.
656 /// Also note that ID and Name are not part of clangd::DiagBase and should be
658 static void fillNonLocationData(DiagnosticsEngine::Level DiagLevel
,
659 const clang::Diagnostic
&Info
,
660 clangd::DiagBase
&D
) {
661 llvm::SmallString
<64> Message
;
662 Info
.FormatDiagnostic(Message
);
664 D
.Message
= std::string(Message
);
665 D
.Severity
= DiagLevel
;
666 D
.Category
= DiagnosticIDs::getCategoryNameFromID(
667 DiagnosticIDs::getCategoryNumberForDiag(Info
.getID()))
671 void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel
,
672 const clang::Diagnostic
&Info
) {
673 // If the diagnostic was generated for a different SourceManager, skip it.
674 // This happens when a module is imported and needs to be implicitly built.
675 // The compilation of that module will use the same StoreDiags, but different
677 if (OrigSrcMgr
&& Info
.hasSourceManager() &&
678 OrigSrcMgr
!= &Info
.getSourceManager()) {
679 IgnoreDiagnostics::log(DiagLevel
, Info
);
683 DiagnosticConsumer::HandleDiagnostic(DiagLevel
, Info
);
684 bool OriginallyError
=
685 Info
.getDiags()->getDiagnosticIDs()->isDefaultMappingAsError(
688 if (Info
.getLocation().isInvalid()) {
689 // Handle diagnostics coming from command-line arguments. The source manager
690 // is *not* available at this point, so we cannot use it.
691 if (!OriginallyError
) {
692 IgnoreDiagnostics::log(DiagLevel
, Info
);
693 return; // non-errors add too much noise, do not show them.
700 LastDiagOriginallyError
= OriginallyError
;
701 LastDiag
->ID
= Info
.getID();
702 fillNonLocationData(DiagLevel
, Info
, *LastDiag
);
703 LastDiag
->InsideMainFile
= true;
704 // Put it at the start of the main file, for a lack of a better place.
705 LastDiag
->Range
.start
= Position
{0, 0};
706 LastDiag
->Range
.end
= Position
{0, 0};
710 if (!LangOpts
|| !Info
.hasSourceManager()) {
711 IgnoreDiagnostics::log(DiagLevel
, Info
);
715 SourceManager
&SM
= Info
.getSourceManager();
717 auto FillDiagBase
= [&](DiagBase
&D
) {
718 fillNonLocationData(DiagLevel
, Info
, D
);
720 SourceLocation PatchLoc
=
721 translatePreamblePatchLocation(Info
.getLocation(), SM
);
722 D
.InsideMainFile
= isInsideMainFile(PatchLoc
, SM
);
723 if (auto DRange
= diagnosticRange(Info
, *LangOpts
))
726 D
.Severity
= DiagnosticsEngine::Ignored
;
727 auto FID
= SM
.getFileID(Info
.getLocation());
728 if (const auto FE
= SM
.getFileEntryRefForID(FID
)) {
729 D
.File
= FE
->getName().str();
730 D
.AbsFile
= getCanonicalPath(*FE
, SM
.getFileManager());
736 auto AddFix
= [&](bool SyntheticMessage
) -> bool {
737 assert(!Info
.getFixItHints().empty() &&
738 "diagnostic does not have attached fix-its");
739 // No point in generating fixes, if the diagnostic is for a different file.
740 if (!LastDiag
->InsideMainFile
)
742 // Copy as we may modify the ranges.
743 auto FixIts
= Info
.getFixItHints().vec();
744 llvm::SmallVector
<TextEdit
, 1> Edits
;
745 for (auto &FixIt
: FixIts
) {
746 // Allow fixits within a single macro-arg expansion to be applied.
747 // This can be incorrect if the argument is expanded multiple times in
748 // different contexts. Hopefully this is rare!
749 if (FixIt
.RemoveRange
.getBegin().isMacroID() &&
750 FixIt
.RemoveRange
.getEnd().isMacroID() &&
751 SM
.getFileID(FixIt
.RemoveRange
.getBegin()) ==
752 SM
.getFileID(FixIt
.RemoveRange
.getEnd())) {
753 FixIt
.RemoveRange
= CharSourceRange(
754 {SM
.getTopMacroCallerLoc(FixIt
.RemoveRange
.getBegin()),
755 SM
.getTopMacroCallerLoc(FixIt
.RemoveRange
.getEnd())},
756 FixIt
.RemoveRange
.isTokenRange());
758 // Otherwise, follow clang's behavior: no fixits in macros.
759 if (FixIt
.RemoveRange
.getBegin().isMacroID() ||
760 FixIt
.RemoveRange
.getEnd().isMacroID())
762 if (!isInsideMainFile(FixIt
.RemoveRange
.getBegin(), SM
))
764 Edits
.push_back(toTextEdit(FixIt
, SM
, *LangOpts
));
767 llvm::SmallString
<64> Message
;
768 // If requested and possible, create a message like "change 'foo' to 'bar'".
769 if (SyntheticMessage
&& FixIts
.size() == 1) {
770 const auto &FixIt
= FixIts
.front();
771 bool Invalid
= false;
772 llvm::StringRef Remove
=
773 Lexer::getSourceText(FixIt
.RemoveRange
, SM
, *LangOpts
, &Invalid
);
774 llvm::StringRef Insert
= FixIt
.CodeToInsert
;
776 llvm::raw_svector_ostream
M(Message
);
777 if (!Remove
.empty() && !Insert
.empty()) {
779 writeCodeToFixMessage(M
, Remove
);
781 writeCodeToFixMessage(M
, Insert
);
783 } else if (!Remove
.empty()) {
785 writeCodeToFixMessage(M
, Remove
);
787 } else if (!Insert
.empty()) {
789 writeCodeToFixMessage(M
, Insert
);
792 // Don't allow source code to inject newlines into diagnostics.
793 std::replace(Message
.begin(), Message
.end(), '\n', ' ');
796 if (Message
.empty()) // either !SyntheticMessage, or we failed to make one.
797 Info
.FormatDiagnostic(Message
);
798 LastDiag
->Fixes
.push_back(
799 Fix
{std::string(Message
), std::move(Edits
), {}});
803 if (!isNote(DiagLevel
)) {
804 // Handle the new main diagnostic.
808 // FIXME: Merge with feature modules.
810 DiagLevel
= Adjuster(DiagLevel
, Info
);
812 FillDiagBase(*LastDiag
);
813 if (isExcluded(LastDiag
->ID
))
814 LastDiag
->Severity
= DiagnosticsEngine::Ignored
;
816 DiagCB(Info
, *LastDiag
);
817 // Don't bother filling in the rest if diag is going to be dropped.
818 if (LastDiag
->Severity
== DiagnosticsEngine::Ignored
)
821 LastDiagLoc
.emplace(Info
.getLocation(), Info
.getSourceManager());
822 LastDiagOriginallyError
= OriginallyError
;
823 if (!Info
.getFixItHints().empty())
824 AddFix(true /* try to invent a message instead of repeating the diag */);
826 auto ExtraFixes
= Fixer(LastDiag
->Severity
, Info
);
827 LastDiag
->Fixes
.insert(LastDiag
->Fixes
.end(), ExtraFixes
.begin(),
831 // Handle a note to an existing diagnostic.
833 assert(false && "Adding a note without main diagnostic");
834 IgnoreDiagnostics::log(DiagLevel
, Info
);
838 // If a diagnostic was suppressed due to the suppression filter,
839 // also suppress notes associated with it.
840 if (LastDiag
->Severity
== DiagnosticsEngine::Ignored
)
843 // Give include-fixer a chance to replace a note with a fix.
845 auto ReplacementFixes
= Fixer(LastDiag
->Severity
, Info
);
846 if (!ReplacementFixes
.empty()) {
847 assert(Info
.getNumFixItHints() == 0 &&
848 "Include-fixer replaced a note with clang fix-its attached!");
849 LastDiag
->Fixes
.insert(LastDiag
->Fixes
.end(), ReplacementFixes
.begin(),
850 ReplacementFixes
.end());
855 if (!Info
.getFixItHints().empty()) {
856 // A clang note with fix-it is not a separate diagnostic in clangd. We
857 // attach it as a Fix to the main diagnostic instead.
858 if (!AddFix(false /* use the note as the message */))
859 IgnoreDiagnostics::log(DiagLevel
, Info
);
861 // A clang note without fix-its corresponds to clangd::Note.
865 LastDiag
->Notes
.push_back(std::move(N
));
870 void StoreDiags::flushLastDiag() {
873 auto Finish
= llvm::make_scope_exit([&, NDiags(Output
.size())] {
874 if (Output
.size() == NDiags
) // No new diag emitted.
875 vlog("Dropped diagnostic: {0}: {1}", LastDiag
->File
, LastDiag
->Message
);
879 if (LastDiag
->Severity
== DiagnosticsEngine::Ignored
)
881 // Move errors that occur from headers into main file.
882 if (!LastDiag
->InsideMainFile
&& LastDiagLoc
&& LastDiagOriginallyError
) {
883 if (tryMoveToMainFile(*LastDiag
, *LastDiagLoc
)) {
884 // Suppress multiple errors from the same inclusion.
885 if (!IncludedErrorLocations
886 .insert({LastDiag
->Range
.start
.line
,
887 LastDiag
->Range
.start
.character
})
892 if (!mentionsMainFile(*LastDiag
))
894 Output
.push_back(std::move(*LastDiag
));
897 bool isBuiltinDiagnosticSuppressed(unsigned ID
,
898 const llvm::StringSet
<> &Suppress
,
899 const LangOptions
&LangOpts
) {
900 // Don't complain about header-only stuff in mainfiles if it's a header.
901 // FIXME: would be cleaner to suppress in clang, once we decide whether the
902 // behavior should be to silently-ignore or respect the pragma.
903 if (ID
== diag::pp_pragma_sysheader_in_main_file
&& LangOpts
.IsHeaderFile
)
906 if (const char *CodePtr
= getDiagnosticCode(ID
)) {
907 if (Suppress
.contains(normalizeSuppressedCode(CodePtr
)))
910 StringRef Warning
= DiagnosticIDs::getWarningOptionForDiag(ID
);
911 if (!Warning
.empty() && Suppress
.contains(Warning
))
916 llvm::StringRef
normalizeSuppressedCode(llvm::StringRef Code
) {
917 Code
.consume_front("err_");
918 Code
.consume_front("-W");
922 std::optional
<std::string
> getDiagnosticDocURI(Diag::DiagSource Source
,
924 llvm::StringRef Name
) {
929 // There is a page listing many warning flags, but it provides too little
930 // information to be worth linking.
931 // https://clang.llvm.org/docs/DiagnosticsReference.html
933 case Diag::ClangTidy
: {
934 StringRef Module
, Check
;
935 // This won't correctly get the module for clang-analyzer checks, but as we
936 // don't link in the analyzer that shouldn't be an issue.
937 // This would also need updating if anyone decides to create a module with a
939 std::tie(Module
, Check
) = Name
.split('-');
940 if (Module
.empty() || Check
.empty())
942 return ("https://clang.llvm.org/extra/clang-tidy/checks/" + Module
+ "/" +
947 if (Name
== "unused-includes" || Name
== "missing-includes")
948 return {"https://clangd.llvm.org/guides/include-cleaner"};
950 case Diag::ClangdConfig
:
951 // FIXME: we should link to https://clangd.llvm.org/config
952 // However we have no diagnostic codes, which the link should describe!
958 } // namespace clangd