1 //===--- Diagnostics.cpp -----------------------------------------*- C++-*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "Diagnostics.h" 10 #include "../clang-tidy/ClangTidyDiagnosticConsumer.h" 11 #include "Compiler.h" 12 #include "Protocol.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" 40 #include <algorithm> 41 #include <cassert> 42 #include <optional> 43 #include <set> 44 #include <string> 45 #include <tuple> 46 #include <utility> 47 #include <vector> 48 49 namespace clang { 50 namespace clangd { 51 namespace { 52 53 const char *getDiagnosticCode(unsigned ID) { 54 switch (ID) { 55 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \ 56 SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY) \ 57 case clang::diag::ENUM: \ 58 return #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" 70 #undef DIAG 71 default: 72 return nullptr; 73 } 74 } 75 76 bool mentionsMainFile(const Diag &D) { 77 if (D.InsideMainFile) 78 return true; 79 // Fixes are always in the main file. 80 if (!D.Fixes.empty()) 81 return true; 82 for (auto &N : D.Notes) { 83 if (N.InsideMainFile) 84 return true; 85 } 86 return false; 87 } 88 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) 93 return true; 94 return false; 95 } 96 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)) 104 return false; 105 return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd()); 106 } 107 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)); 116 return R; 117 }; 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)); 123 } 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)); 129 } 130 // Source locations from stale preambles might become OOB. 131 // FIXME: These diagnostics might point to wrong locations even when they're 132 // not OOB. 133 auto [FID, Offset] = M.getDecomposedLoc(Loc); 134 if (Offset > M.getBufferData(FID).size()) 135 return std::nullopt; 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); 139 Token Tok; 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)); 143 } 144 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) { 152 switch (N.ID) { 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: 183 R = N.Range; 184 return "in template"; 185 default: 186 break; 187 } 188 } 189 } 190 // Look for where the file with the error was #included. 191 auto GetIncludeLoc = [&SM](SourceLocation SLoc) { 192 return SM.getIncludeLoc(SM.getFileID(SLoc)); 193 }; 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( 200 SM, 201 Lexer::getLocForEndOfToken(IncludeLocation, 0, SM, LangOptions())); 202 return "in included file"; 203 } 204 } 205 return nullptr; 206 } 207 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(); 215 Range R; 216 const char *Prefix = getMainFileRange(D, SM, DiagLoc, R); 217 if (!Prefix) 218 return false; 219 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"; 227 N.Range = D.Range; 228 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); 235 return true; 236 } 237 238 bool isNote(DiagnosticsEngine::Level L) { 239 return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark; 240 } 241 242 llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) { 243 switch (Lvl) { 244 case DiagnosticsEngine::Ignored: 245 return "ignored"; 246 case DiagnosticsEngine::Note: 247 return "note"; 248 case DiagnosticsEngine::Remark: 249 return "remark"; 250 case DiagnosticsEngine::Warning: 251 return "warning"; 252 case DiagnosticsEngine::Error: 253 return "error"; 254 case DiagnosticsEngine::Fatal: 255 return "fatal error"; 256 } 257 llvm_unreachable("unhandled DiagnosticsEngine::Level"); 258 } 259 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: 262 /// 263 /// main.cpp:12:23: error: undeclared identifier 264 /// 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 267 /// in the editors: 268 /// 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) << ":"; 277 } else { 278 OS << D.File << ":"; 279 } 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) 287 OS << " "; 288 else 289 OS << "\n"; 290 OS << diagLeveltoString(D.Severity) << ": " << D.Message; 291 } 292 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]); 297 return Message; 298 } 299 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. 302 /// Example output: 303 /// 304 /// no matching function for call to 'foo' 305 /// 306 /// main.cpp:3:5: note: candidate function not viable: requires 2 arguments 307 /// 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) { 311 std::string Result; 312 llvm::raw_string_ostream OS(Result); 313 OS << D.Message; 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) { 319 OS << "\n\n"; 320 printDiag(OS, Note); 321 } 322 return capitalize(std::move(Result)); 323 } 324 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) { 328 std::string Result; 329 llvm::raw_string_ostream OS(Result); 330 OS << Note.Message; 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) { 334 OS << "\n\n"; 335 printDiag(OS, Main); 336 } 337 return capitalize(std::move(Result)); 338 } 339 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, 365 }; 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, 384 }; 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); 389 } 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); 395 } 396 } 397 } // namespace 398 399 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) { 400 OS << "["; 401 if (!D.InsideMainFile) 402 OS << D.File << ":"; 403 OS << D.Range.start << "-" << D.Range.end << "] "; 404 405 return OS << D.Message; 406 } 407 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) { 412 OS << Sep << Edit; 413 Sep = ", "; 414 } 415 return OS << "}"; 416 } 417 418 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D) { 419 OS << static_cast<const DiagBase &>(D); 420 if (!D.Notes.empty()) { 421 OS << ", notes: {"; 422 const char *Sep = ""; 423 for (auto &Note : D.Notes) { 424 OS << Sep << Note; 425 Sep = ", "; 426 } 427 OS << "}"; 428 } 429 if (!D.Fixes.empty()) { 430 OS << ", fixes: {"; 431 const char *Sep = ""; 432 for (auto &Fix : D.Fixes) { 433 OS << Sep << Fix; 434 Sep = ", "; 435 } 436 OS << "}"; 437 } 438 return OS; 439 } 440 441 Diag toDiag(const llvm::SMDiagnostic &D, Diag::DiagSource Source) { 442 Diag Result; 443 Result.Message = D.getMessage().str(); 444 switch (D.getKind()) { 445 case llvm::SourceMgr::DK_Error: 446 Result.Severity = DiagnosticsEngine::Error; 447 break; 448 case llvm::SourceMgr::DK_Warning: 449 Result.Severity = DiagnosticsEngine::Warning; 450 break; 451 default: 452 break; 453 } 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()}}; 461 else 462 Result.Range = {{D.getLineNo() - 1, (int)D.getRanges().front().first}, 463 {D.getLineNo() - 1, (int)D.getRanges().front().second}}; 464 return Result; 465 } 466 467 void toLSPDiags( 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); 479 } 480 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; 487 } else { 488 auto It = 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; 493 } 494 495 Main.code = D.Name; 496 if (auto URI = getDiagnosticDocURI(D.Source, D.ID, D.Name)) { 497 Main.codeDescription.emplace(); 498 Main.codeDescription->href = std::move(*URI); 499 } 500 switch (D.Source) { 501 case Diag::Clang: 502 Main.source = "clang"; 503 break; 504 case Diag::ClangTidy: 505 Main.source = "clang-tidy"; 506 break; 507 case Diag::Clangd: 508 Main.source = "clangd"; 509 break; 510 case Diag::ClangdConfig: 511 Main.source = "clangd-config"; 512 break; 513 case Diag::Unknown: 514 break; 515 } 516 if (Opts.SendDiagnosticCategory && !D.Category.empty()) 517 Main.category = D.Category; 518 519 Main.message = mainMessage(D, Opts); 520 if (Opts.EmitRelatedLocations) { 521 Main.relatedInformation.emplace(); 522 for (auto &Note : D.Notes) { 523 if (!Note.AbsFile) { 524 vlog("Dropping note from unknown file: {0}", Note); 525 continue; 526 } 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)); 533 } 534 } 535 Main.tags = D.Tags; 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); 540 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) 546 continue; 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>()); 552 } 553 } 554 555 int getSeverity(DiagnosticsEngine::Level L) { 556 switch (L) { 557 case DiagnosticsEngine::Remark: 558 return 4; 559 case DiagnosticsEngine::Note: 560 return 3; 561 case DiagnosticsEngine::Warning: 562 return 2; 563 case DiagnosticsEngine::Fatal: 564 case DiagnosticsEngine::Error: 565 return 1; 566 case DiagnosticsEngine::Ignored: 567 return 0; 568 } 569 llvm_unreachable("Unknown diagnostic level!"); 570 } 571 572 std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) { 573 // Do not forget to emit a pending diagnostic if there is one. 574 flushLastDiag(); 575 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 = [&] { 581 if (OrigSrcMgr) { 582 return OrigSrcMgr->getDiagnostics() 583 .getDiagnosticIDs() 584 ->getWarningOptionForDiag(Diag.ID); 585 } 586 if (!DiagnosticIDs::IsCustomDiag(Diag.ID)) 587 return DiagnosticIDs{}.getWarningOptionForDiag(Diag.ID); 588 return StringRef{}; 589 }(); 590 591 if (!Warning.empty()) { 592 Diag.Name = ("-W" + Warning).str(); 593 } else { 594 StringRef Name(ClangDiag); 595 // Almost always an error, with a name like err_enum_class_reference. 596 // Drop the err_ prefix for brevity. 597 Name.consume_front("err_"); 598 Diag.Name = std::string(Name); 599 } 600 Diag.Source = Diag::Clang; 601 } else if (Tidy != nullptr) { 602 std::string TidyDiag = Tidy->getCheckName(Diag.ID); 603 if (!TidyDiag.empty()) { 604 Diag.Name = std::move(TidyDiag); 605 Diag.Source = Diag::ClangTidy; 606 // clang-tidy bakes the name into diagnostic messages. Strip it out. 607 // It would be much nicer to make clang-tidy not do this. 608 auto CleanMessage = [&](std::string &Msg) { 609 StringRef Rest(Msg); 610 if (Rest.consume_back("]") && Rest.consume_back(Diag.Name) && 611 Rest.consume_back(" [")) 612 Msg.resize(Rest.size()); 613 }; 614 CleanMessage(Diag.Message); 615 for (auto &Note : Diag.Notes) 616 CleanMessage(Note.Message); 617 for (auto &Fix : Diag.Fixes) 618 CleanMessage(Fix.Message); 619 } 620 } 621 setTags(Diag); 622 } 623 // Deduplicate clang-tidy diagnostics -- some clang-tidy checks may emit 624 // duplicated messages due to various reasons (e.g. the check doesn't handle 625 // template instantiations well; clang-tidy alias checks). 626 std::set<std::pair<Range, std::string>> SeenDiags; 627 llvm::erase_if(Output, [&](const Diag &D) { 628 return !SeenDiags.emplace(D.Range, D.Message).second; 629 }); 630 return std::move(Output); 631 } 632 633 void StoreDiags::BeginSourceFile(const LangOptions &Opts, 634 const Preprocessor *PP) { 635 LangOpts = Opts; 636 if (PP) { 637 OrigSrcMgr = &PP->getSourceManager(); 638 } 639 } 640 641 void StoreDiags::EndSourceFile() { 642 flushLastDiag(); 643 LangOpts = std::nullopt; 644 OrigSrcMgr = nullptr; 645 } 646 647 /// Sanitizes a piece for presenting it in a synthesized fix message. Ensures 648 /// the result is not too large and does not contain newlines. 649 static void writeCodeToFixMessage(llvm::raw_ostream &OS, llvm::StringRef Code) { 650 constexpr unsigned MaxLen = 50; 651 if (Code == "\n") { 652 OS << "\\n"; 653 return; 654 } 655 // Only show the first line if there are many. 656 llvm::StringRef R = Code.split('\n').first; 657 // Shorten the message if it's too long. 658 R = R.take_front(MaxLen); 659 660 OS << R; 661 if (R.size() != Code.size()) 662 OS << "…"; 663 } 664 665 /// Fills \p D with all information, except the location-related bits. 666 /// Also note that ID and Name are not part of clangd::DiagBase and should be 667 /// set elsewhere. 668 static void fillNonLocationData(DiagnosticsEngine::Level DiagLevel, 669 const clang::Diagnostic &Info, 670 clangd::DiagBase &D) { 671 llvm::SmallString<64> Message; 672 Info.FormatDiagnostic(Message); 673 674 D.Message = std::string(Message); 675 D.Severity = DiagLevel; 676 D.Category = DiagnosticIDs::getCategoryNameFromID( 677 DiagnosticIDs::getCategoryNumberForDiag(Info.getID())) 678 .str(); 679 } 680 681 void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 682 const clang::Diagnostic &Info) { 683 // If the diagnostic was generated for a different SourceManager, skip it. 684 // This happens when a module is imported and needs to be implicitly built. 685 // The compilation of that module will use the same StoreDiags, but different 686 // SourceManager. 687 if (OrigSrcMgr && Info.hasSourceManager() && 688 OrigSrcMgr != &Info.getSourceManager()) { 689 IgnoreDiagnostics::log(DiagLevel, Info); 690 return; 691 } 692 693 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); 694 bool OriginallyError = 695 Info.getDiags()->getDiagnosticIDs()->isDefaultMappingAsError( 696 Info.getID()); 697 698 if (Info.getLocation().isInvalid()) { 699 // Handle diagnostics coming from command-line arguments. The source manager 700 // is *not* available at this point, so we cannot use it. 701 if (!OriginallyError) { 702 IgnoreDiagnostics::log(DiagLevel, Info); 703 return; // non-errors add too much noise, do not show them. 704 } 705 706 flushLastDiag(); 707 708 LastDiag = Diag(); 709 LastDiagLoc.reset(); 710 LastDiagOriginallyError = OriginallyError; 711 LastDiag->ID = Info.getID(); 712 fillNonLocationData(DiagLevel, Info, *LastDiag); 713 LastDiag->InsideMainFile = true; 714 // Put it at the start of the main file, for a lack of a better place. 715 LastDiag->Range.start = Position{0, 0}; 716 LastDiag->Range.end = Position{0, 0}; 717 return; 718 } 719 720 if (!LangOpts || !Info.hasSourceManager()) { 721 IgnoreDiagnostics::log(DiagLevel, Info); 722 return; 723 } 724 725 SourceManager &SM = Info.getSourceManager(); 726 727 auto FillDiagBase = [&](DiagBase &D) { 728 fillNonLocationData(DiagLevel, Info, D); 729 730 SourceLocation PatchLoc = 731 translatePreamblePatchLocation(Info.getLocation(), SM); 732 D.InsideMainFile = isInsideMainFile(PatchLoc, SM); 733 if (auto DRange = diagnosticRange(Info, *LangOpts)) 734 D.Range = *DRange; 735 else 736 D.Severity = DiagnosticsEngine::Ignored; 737 auto FID = SM.getFileID(Info.getLocation()); 738 if (const auto FE = SM.getFileEntryRefForID(FID)) { 739 D.File = FE->getName().str(); 740 D.AbsFile = getCanonicalPath(*FE, SM.getFileManager()); 741 } 742 D.ID = Info.getID(); 743 return D; 744 }; 745 746 auto AddFix = [&](bool SyntheticMessage) -> bool { 747 assert(!Info.getFixItHints().empty() && 748 "diagnostic does not have attached fix-its"); 749 // No point in generating fixes, if the diagnostic is for a different file. 750 if (!LastDiag->InsideMainFile) 751 return false; 752 // Copy as we may modify the ranges. 753 auto FixIts = Info.getFixItHints().vec(); 754 llvm::SmallVector<TextEdit, 1> Edits; 755 for (auto &FixIt : FixIts) { 756 // Allow fixits within a single macro-arg expansion to be applied. 757 // This can be incorrect if the argument is expanded multiple times in 758 // different contexts. Hopefully this is rare! 759 if (FixIt.RemoveRange.getBegin().isMacroID() && 760 FixIt.RemoveRange.getEnd().isMacroID() && 761 SM.getFileID(FixIt.RemoveRange.getBegin()) == 762 SM.getFileID(FixIt.RemoveRange.getEnd())) { 763 FixIt.RemoveRange = CharSourceRange( 764 {SM.getTopMacroCallerLoc(FixIt.RemoveRange.getBegin()), 765 SM.getTopMacroCallerLoc(FixIt.RemoveRange.getEnd())}, 766 FixIt.RemoveRange.isTokenRange()); 767 } 768 // Otherwise, follow clang's behavior: no fixits in macros. 769 if (FixIt.RemoveRange.getBegin().isMacroID() || 770 FixIt.RemoveRange.getEnd().isMacroID()) 771 return false; 772 if (!isInsideMainFile(FixIt.RemoveRange.getBegin(), SM)) 773 return false; 774 Edits.push_back(toTextEdit(FixIt, SM, *LangOpts)); 775 } 776 777 llvm::SmallString<64> Message; 778 // If requested and possible, create a message like "change 'foo' to 'bar'". 779 if (SyntheticMessage && FixIts.size() == 1) { 780 const auto &FixIt = FixIts.front(); 781 bool Invalid = false; 782 llvm::StringRef Remove = 783 Lexer::getSourceText(FixIt.RemoveRange, SM, *LangOpts, &Invalid); 784 llvm::StringRef Insert = FixIt.CodeToInsert; 785 if (!Invalid) { 786 llvm::raw_svector_ostream M(Message); 787 if (!Remove.empty() && !Insert.empty()) { 788 M << "change '"; 789 writeCodeToFixMessage(M, Remove); 790 M << "' to '"; 791 writeCodeToFixMessage(M, Insert); 792 M << "'"; 793 } else if (!Remove.empty()) { 794 M << "remove '"; 795 writeCodeToFixMessage(M, Remove); 796 M << "'"; 797 } else if (!Insert.empty()) { 798 M << "insert '"; 799 writeCodeToFixMessage(M, Insert); 800 M << "'"; 801 } 802 // Don't allow source code to inject newlines into diagnostics. 803 std::replace(Message.begin(), Message.end(), '\n', ' '); 804 } 805 } 806 if (Message.empty()) // either !SyntheticMessage, or we failed to make one. 807 Info.FormatDiagnostic(Message); 808 LastDiag->Fixes.push_back( 809 Fix{std::string(Message), std::move(Edits), {}}); 810 return true; 811 }; 812 813 if (!isNote(DiagLevel)) { 814 // Handle the new main diagnostic. 815 flushLastDiag(); 816 817 LastDiag = Diag(); 818 // FIXME: Merge with feature modules. 819 if (Adjuster) 820 DiagLevel = Adjuster(DiagLevel, Info); 821 822 FillDiagBase(*LastDiag); 823 if (isExcluded(LastDiag->ID)) 824 LastDiag->Severity = DiagnosticsEngine::Ignored; 825 if (DiagCB) 826 DiagCB(Info, *LastDiag); 827 // Don't bother filling in the rest if diag is going to be dropped. 828 if (LastDiag->Severity == DiagnosticsEngine::Ignored) 829 return; 830 831 LastDiagLoc.emplace(Info.getLocation(), Info.getSourceManager()); 832 LastDiagOriginallyError = OriginallyError; 833 if (!Info.getFixItHints().empty()) 834 AddFix(true /* try to invent a message instead of repeating the diag */); 835 if (Fixer) { 836 auto ExtraFixes = Fixer(LastDiag->Severity, Info); 837 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(), 838 ExtraFixes.end()); 839 } 840 } else { 841 // Handle a note to an existing diagnostic. 842 if (!LastDiag) { 843 assert(false && "Adding a note without main diagnostic"); 844 IgnoreDiagnostics::log(DiagLevel, Info); 845 return; 846 } 847 848 // If a diagnostic was suppressed due to the suppression filter, 849 // also suppress notes associated with it. 850 if (LastDiag->Severity == DiagnosticsEngine::Ignored) 851 return; 852 853 // Give include-fixer a chance to replace a note with a fix. 854 if (Fixer) { 855 auto ReplacementFixes = Fixer(LastDiag->Severity, Info); 856 if (!ReplacementFixes.empty()) { 857 assert(Info.getNumFixItHints() == 0 && 858 "Include-fixer replaced a note with clang fix-its attached!"); 859 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ReplacementFixes.begin(), 860 ReplacementFixes.end()); 861 return; 862 } 863 } 864 865 if (!Info.getFixItHints().empty()) { 866 // A clang note with fix-it is not a separate diagnostic in clangd. We 867 // attach it as a Fix to the main diagnostic instead. 868 if (!AddFix(false /* use the note as the message */)) 869 IgnoreDiagnostics::log(DiagLevel, Info); 870 } else { 871 // A clang note without fix-its corresponds to clangd::Note. 872 Note N; 873 FillDiagBase(N); 874 875 LastDiag->Notes.push_back(std::move(N)); 876 } 877 } 878 } 879 880 void StoreDiags::flushLastDiag() { 881 if (!LastDiag) 882 return; 883 auto Finish = llvm::make_scope_exit([&, NDiags(Output.size())] { 884 if (Output.size() == NDiags) // No new diag emitted. 885 vlog("Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message); 886 LastDiag.reset(); 887 }); 888 889 if (LastDiag->Severity == DiagnosticsEngine::Ignored) 890 return; 891 // Move errors that occur from headers into main file. 892 if (!LastDiag->InsideMainFile && LastDiagLoc && LastDiagOriginallyError) { 893 if (tryMoveToMainFile(*LastDiag, *LastDiagLoc)) { 894 // Suppress multiple errors from the same inclusion. 895 if (!IncludedErrorLocations 896 .insert({LastDiag->Range.start.line, 897 LastDiag->Range.start.character}) 898 .second) 899 return; 900 } 901 } 902 if (!mentionsMainFile(*LastDiag)) 903 return; 904 Output.push_back(std::move(*LastDiag)); 905 } 906 907 bool isDiagnosticSuppressed(const clang::Diagnostic &Diag, 908 const llvm::StringSet<> &Suppress, 909 const LangOptions &LangOpts) { 910 // Don't complain about header-only stuff in mainfiles if it's a header. 911 // FIXME: would be cleaner to suppress in clang, once we decide whether the 912 // behavior should be to silently-ignore or respect the pragma. 913 if (Diag.getID() == diag::pp_pragma_sysheader_in_main_file && 914 LangOpts.IsHeaderFile) 915 return true; 916 917 if (const char *CodePtr = getDiagnosticCode(Diag.getID())) { 918 if (Suppress.contains(normalizeSuppressedCode(CodePtr))) 919 return true; 920 } 921 StringRef Warning = 922 Diag.getDiags()->getDiagnosticIDs()->getWarningOptionForDiag( 923 Diag.getID()); 924 if (!Warning.empty() && Suppress.contains(Warning)) 925 return true; 926 return false; 927 } 928 929 llvm::StringRef normalizeSuppressedCode(llvm::StringRef Code) { 930 Code.consume_front("err_"); 931 Code.consume_front("-W"); 932 return Code; 933 } 934 935 std::optional<std::string> getDiagnosticDocURI(Diag::DiagSource Source, 936 unsigned ID, 937 llvm::StringRef Name) { 938 switch (Source) { 939 case Diag::Unknown: 940 break; 941 case Diag::Clang: 942 // There is a page listing many warning flags, but it provides too little 943 // information to be worth linking. 944 // https://clang.llvm.org/docs/DiagnosticsReference.html 945 break; 946 case Diag::ClangTidy: { 947 StringRef Module, Check; 948 // This won't correctly get the module for clang-analyzer checks, but as we 949 // don't link in the analyzer that shouldn't be an issue. 950 // This would also need updating if anyone decides to create a module with a 951 // '-' in the name. 952 std::tie(Module, Check) = Name.split('-'); 953 if (Module.empty() || Check.empty()) 954 return std::nullopt; 955 return ("https://clang.llvm.org/extra/clang-tidy/checks/" + Module + "/" + 956 Check + ".html") 957 .str(); 958 } 959 case Diag::Clangd: 960 if (Name == "unused-includes" || Name == "missing-includes") 961 return {"https://clangd.llvm.org/guides/include-cleaner"}; 962 break; 963 case Diag::ClangdConfig: 964 // FIXME: we should link to https://clangd.llvm.org/config 965 // However we have no diagnostic codes, which the link should describe! 966 break; 967 } 968 return std::nullopt; 969 } 970 971 } // namespace clangd 972 } // namespace clang 973