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/SourceLocation.h" 19 #include "clang/Basic/SourceManager.h" 20 #include "clang/Lex/Lexer.h" 21 #include "clang/Lex/Token.h" 22 #include "llvm/ADT/ArrayRef.h" 23 #include "llvm/ADT/DenseSet.h" 24 #include "llvm/ADT/Optional.h" 25 #include "llvm/ADT/STLExtras.h" 26 #include "llvm/ADT/ScopeExit.h" 27 #include "llvm/ADT/SmallString.h" 28 #include "llvm/ADT/SmallVector.h" 29 #include "llvm/ADT/StringRef.h" 30 #include "llvm/ADT/Twine.h" 31 #include "llvm/Support/Path.h" 32 #include "llvm/Support/raw_ostream.h" 33 #include <algorithm> 34 #include <cassert> 35 #include <cstddef> 36 #include <vector> 37 38 namespace clang { 39 namespace clangd { 40 namespace { 41 42 const char *getDiagnosticCode(unsigned ID) { 43 switch (ID) { 44 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR, \ 45 SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY) \ 46 case clang::diag::ENUM: \ 47 return #ENUM; 48 #include "clang/Basic/DiagnosticASTKinds.inc" 49 #include "clang/Basic/DiagnosticAnalysisKinds.inc" 50 #include "clang/Basic/DiagnosticCommentKinds.inc" 51 #include "clang/Basic/DiagnosticCommonKinds.inc" 52 #include "clang/Basic/DiagnosticDriverKinds.inc" 53 #include "clang/Basic/DiagnosticFrontendKinds.inc" 54 #include "clang/Basic/DiagnosticLexKinds.inc" 55 #include "clang/Basic/DiagnosticParseKinds.inc" 56 #include "clang/Basic/DiagnosticRefactoringKinds.inc" 57 #include "clang/Basic/DiagnosticSemaKinds.inc" 58 #include "clang/Basic/DiagnosticSerializationKinds.inc" 59 #undef DIAG 60 default: 61 return nullptr; 62 } 63 } 64 65 bool mentionsMainFile(const Diag &D) { 66 if (D.InsideMainFile) 67 return true; 68 // Fixes are always in the main file. 69 if (!D.Fixes.empty()) 70 return true; 71 for (auto &N : D.Notes) { 72 if (N.InsideMainFile) 73 return true; 74 } 75 return false; 76 } 77 78 bool isExcluded(unsigned DiagID) { 79 // clang will always fail parsing MS ASM, we don't link in desc + asm parser. 80 if (DiagID == clang::diag::err_msasm_unable_to_create_target || 81 DiagID == clang::diag::err_msasm_unsupported_arch) 82 return true; 83 return false; 84 } 85 86 // Checks whether a location is within a half-open range. 87 // Note that clang also uses closed source ranges, which this can't handle! 88 bool locationInRange(SourceLocation L, CharSourceRange R, 89 const SourceManager &M) { 90 assert(R.isCharRange()); 91 if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) || 92 M.getFileID(R.getBegin()) != M.getFileID(L)) 93 return false; 94 return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd()); 95 } 96 97 // Clang diags have a location (shown as ^) and 0 or more ranges (~~~~). 98 // LSP needs a single range. 99 Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) { 100 auto &M = D.getSourceManager(); 101 auto Loc = M.getFileLoc(D.getLocation()); 102 for (const auto &CR : D.getRanges()) { 103 auto R = Lexer::makeFileCharRange(CR, M, L); 104 if (locationInRange(Loc, R, M)) 105 return halfOpenToRange(M, R); 106 } 107 // The range may be given as a fixit hint instead. 108 for (const auto &F : D.getFixItHints()) { 109 auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L); 110 if (locationInRange(Loc, R, M)) 111 return halfOpenToRange(M, R); 112 } 113 // If the token at the location is not a comment, we use the token. 114 // If we can't get the token at the location, fall back to using the location 115 auto R = CharSourceRange::getCharRange(Loc); 116 Token Tok; 117 if (!Lexer::getRawToken(Loc, Tok, M, L, true) && Tok.isNot(tok::comment)) { 118 R = CharSourceRange::getTokenRange(Tok.getLocation(), Tok.getEndLoc()); 119 } 120 return halfOpenToRange(M, R); 121 } 122 123 // Try to find a location in the main-file to report the diagnostic D. 124 // Returns a description like "in included file", or nullptr on failure. 125 const char *getMainFileRange(const Diag &D, const SourceManager &SM, 126 SourceLocation DiagLoc, Range &R) { 127 // Look for a note in the main file indicating template instantiation. 128 for (const auto &N : D.Notes) { 129 if (N.InsideMainFile) { 130 switch (N.ID) { 131 case diag::note_template_class_instantiation_was_here: 132 case diag::note_template_class_explicit_specialization_was_here: 133 case diag::note_template_class_instantiation_here: 134 case diag::note_template_member_class_here: 135 case diag::note_template_member_function_here: 136 case diag::note_function_template_spec_here: 137 case diag::note_template_static_data_member_def_here: 138 case diag::note_template_variable_def_here: 139 case diag::note_template_enum_def_here: 140 case diag::note_template_nsdmi_here: 141 case diag::note_template_type_alias_instantiation_here: 142 case diag::note_template_exception_spec_instantiation_here: 143 case diag::note_template_requirement_instantiation_here: 144 case diag::note_evaluating_exception_spec_here: 145 case diag::note_default_arg_instantiation_here: 146 case diag::note_default_function_arg_instantiation_here: 147 case diag::note_explicit_template_arg_substitution_here: 148 case diag::note_function_template_deduction_instantiation_here: 149 case diag::note_deduced_template_arg_substitution_here: 150 case diag::note_prior_template_arg_substitution: 151 case diag::note_template_default_arg_checking: 152 case diag::note_concept_specialization_here: 153 case diag::note_nested_requirement_here: 154 case diag::note_checking_constraints_for_template_id_here: 155 case diag::note_checking_constraints_for_var_spec_id_here: 156 case diag::note_checking_constraints_for_class_spec_id_here: 157 case diag::note_checking_constraints_for_function_here: 158 case diag::note_constraint_substitution_here: 159 case diag::note_constraint_normalization_here: 160 case diag::note_parameter_mapping_substitution_here: 161 R = N.Range; 162 return "in template"; 163 default: 164 break; 165 } 166 } 167 } 168 // Look for where the file with the error was #included. 169 auto GetIncludeLoc = [&SM](SourceLocation SLoc) { 170 return SM.getIncludeLoc(SM.getFileID(SLoc)); 171 }; 172 for (auto IncludeLocation = GetIncludeLoc(SM.getExpansionLoc(DiagLoc)); 173 IncludeLocation.isValid(); 174 IncludeLocation = GetIncludeLoc(IncludeLocation)) { 175 if (clangd::isInsideMainFile(IncludeLocation, SM)) { 176 R.start = sourceLocToPosition(SM, IncludeLocation); 177 R.end = sourceLocToPosition( 178 SM, 179 Lexer::getLocForEndOfToken(IncludeLocation, 0, SM, LangOptions())); 180 return "in included file"; 181 } 182 } 183 return nullptr; 184 } 185 186 // Place the diagnostic the main file, rather than the header, if possible: 187 // - for errors in included files, use the #include location 188 // - for errors in template instantiation, use the instantiation location 189 // In both cases, add the original header location as a note. 190 bool tryMoveToMainFile(Diag &D, FullSourceLoc DiagLoc) { 191 const SourceManager &SM = DiagLoc.getManager(); 192 DiagLoc = DiagLoc.getExpansionLoc(); 193 Range R; 194 const char *Prefix = getMainFileRange(D, SM, DiagLoc, R); 195 if (!Prefix) 196 return false; 197 198 // Add a note that will point to real diagnostic. 199 auto FE = *SM.getFileEntryRefForID(SM.getFileID(DiagLoc)); 200 D.Notes.emplace(D.Notes.begin()); 201 Note &N = D.Notes.front(); 202 N.AbsFile = std::string(FE.getFileEntry().tryGetRealPathName()); 203 N.File = std::string(FE.getName()); 204 N.Message = "error occurred here"; 205 N.Range = D.Range; 206 207 // Update diag to point at include inside main file. 208 D.File = SM.getFileEntryRefForID(SM.getMainFileID())->getName().str(); 209 D.Range = std::move(R); 210 D.InsideMainFile = true; 211 // Update message to mention original file. 212 D.Message = llvm::formatv("{0}: {1}", Prefix, D.Message); 213 return true; 214 } 215 216 bool isInsideMainFile(const clang::Diagnostic &D) { 217 if (!D.hasSourceManager()) 218 return false; 219 220 return clangd::isInsideMainFile(D.getLocation(), D.getSourceManager()); 221 } 222 223 bool isNote(DiagnosticsEngine::Level L) { 224 return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark; 225 } 226 227 llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) { 228 switch (Lvl) { 229 case DiagnosticsEngine::Ignored: 230 return "ignored"; 231 case DiagnosticsEngine::Note: 232 return "note"; 233 case DiagnosticsEngine::Remark: 234 return "remark"; 235 case DiagnosticsEngine::Warning: 236 return "warning"; 237 case DiagnosticsEngine::Error: 238 return "error"; 239 case DiagnosticsEngine::Fatal: 240 return "fatal error"; 241 } 242 llvm_unreachable("unhandled DiagnosticsEngine::Level"); 243 } 244 245 /// Prints a single diagnostic in a clang-like manner, the output includes 246 /// location, severity and error message. An example of the output message is: 247 /// 248 /// main.cpp:12:23: error: undeclared identifier 249 /// 250 /// For main file we only print the basename and for all other files we print 251 /// the filename on a separate line to provide a slightly more readable output 252 /// in the editors: 253 /// 254 /// dir1/dir2/dir3/../../dir4/header.h:12:23 255 /// error: undeclared identifier 256 void printDiag(llvm::raw_string_ostream &OS, const DiagBase &D) { 257 if (D.InsideMainFile) { 258 // Paths to main files are often taken from compile_command.json, where they 259 // are typically absolute. To reduce noise we print only basename for them, 260 // it should not be confusing and saves space. 261 OS << llvm::sys::path::filename(D.File) << ":"; 262 } else { 263 OS << D.File << ":"; 264 } 265 // Note +1 to line and character. clangd::Range is zero-based, but when 266 // printing for users we want one-based indexes. 267 auto Pos = D.Range.start; 268 OS << (Pos.line + 1) << ":" << (Pos.character + 1) << ":"; 269 // The non-main-file paths are often too long, putting them on a separate 270 // line improves readability. 271 if (D.InsideMainFile) 272 OS << " "; 273 else 274 OS << "\n"; 275 OS << diagLeveltoString(D.Severity) << ": " << D.Message; 276 } 277 278 /// Capitalizes the first word in the diagnostic's message. 279 std::string capitalize(std::string Message) { 280 if (!Message.empty()) 281 Message[0] = llvm::toUpper(Message[0]); 282 return Message; 283 } 284 285 /// Returns a message sent to LSP for the main diagnostic in \p D. 286 /// This message may include notes, if they're not emitted in some other way. 287 /// Example output: 288 /// 289 /// no matching function for call to 'foo' 290 /// 291 /// main.cpp:3:5: note: candidate function not viable: requires 2 arguments 292 /// 293 /// dir1/dir2/dir3/../../dir4/header.h:12:23 294 /// note: candidate function not viable: requires 3 arguments 295 std::string mainMessage(const Diag &D, const ClangdDiagnosticOptions &Opts) { 296 std::string Result; 297 llvm::raw_string_ostream OS(Result); 298 OS << D.Message; 299 if (Opts.DisplayFixesCount && !D.Fixes.empty()) 300 OS << " (" << (D.Fixes.size() > 1 ? "fixes" : "fix") << " available)"; 301 // If notes aren't emitted as structured info, add them to the message. 302 if (!Opts.EmitRelatedLocations) 303 for (auto &Note : D.Notes) { 304 OS << "\n\n"; 305 printDiag(OS, Note); 306 } 307 OS.flush(); 308 return capitalize(std::move(Result)); 309 } 310 311 /// Returns a message sent to LSP for the note of the main diagnostic. 312 std::string noteMessage(const Diag &Main, const DiagBase &Note, 313 const ClangdDiagnosticOptions &Opts) { 314 std::string Result; 315 llvm::raw_string_ostream OS(Result); 316 OS << Note.Message; 317 // If the client doesn't support structured links between the note and the 318 // original diagnostic, then emit the main diagnostic to give context. 319 if (!Opts.EmitRelatedLocations) { 320 OS << "\n\n"; 321 printDiag(OS, Main); 322 } 323 OS.flush(); 324 return capitalize(std::move(Result)); 325 } 326 327 void setTags(clangd::Diag &D) { 328 static const auto *DeprecatedDiags = new llvm::DenseSet<unsigned>{ 329 diag::warn_access_decl_deprecated, 330 diag::warn_atl_uuid_deprecated, 331 diag::warn_deprecated, 332 diag::warn_deprecated_altivec_src_compat, 333 diag::warn_deprecated_comma_subscript, 334 diag::warn_deprecated_compound_assign_volatile, 335 diag::warn_deprecated_copy, 336 diag::warn_deprecated_copy_with_dtor, 337 diag::warn_deprecated_copy_with_user_provided_copy, 338 diag::warn_deprecated_copy_with_user_provided_dtor, 339 diag::warn_deprecated_def, 340 diag::warn_deprecated_increment_decrement_volatile, 341 diag::warn_deprecated_message, 342 diag::warn_deprecated_redundant_constexpr_static_def, 343 diag::warn_deprecated_register, 344 diag::warn_deprecated_simple_assign_volatile, 345 diag::warn_deprecated_string_literal_conversion, 346 diag::warn_deprecated_this_capture, 347 diag::warn_deprecated_volatile_param, 348 diag::warn_deprecated_volatile_return, 349 diag::warn_deprecated_volatile_structured_binding, 350 diag::warn_opencl_attr_deprecated_ignored, 351 diag::warn_property_method_deprecated, 352 diag::warn_vector_mode_deprecated, 353 }; 354 static const auto *UnusedDiags = new llvm::DenseSet<unsigned>{ 355 diag::warn_opencl_attr_deprecated_ignored, 356 diag::warn_pragma_attribute_unused, 357 diag::warn_unused_but_set_parameter, 358 diag::warn_unused_but_set_variable, 359 diag::warn_unused_comparison, 360 diag::warn_unused_const_variable, 361 diag::warn_unused_exception_param, 362 diag::warn_unused_function, 363 diag::warn_unused_label, 364 diag::warn_unused_lambda_capture, 365 diag::warn_unused_local_typedef, 366 diag::warn_unused_member_function, 367 diag::warn_unused_parameter, 368 diag::warn_unused_private_field, 369 diag::warn_unused_property_backing_ivar, 370 diag::warn_unused_template, 371 diag::warn_unused_variable, 372 }; 373 if (DeprecatedDiags->contains(D.ID)) { 374 D.Tags.push_back(DiagnosticTag::Deprecated); 375 } else if (UnusedDiags->contains(D.ID)) { 376 D.Tags.push_back(DiagnosticTag::Unnecessary); 377 } 378 // FIXME: Set tags for tidy-based diagnostics too. 379 } 380 } // namespace 381 382 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) { 383 OS << "["; 384 if (!D.InsideMainFile) 385 OS << D.File << ":"; 386 OS << D.Range.start << "-" << D.Range.end << "] "; 387 388 return OS << D.Message; 389 } 390 391 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F) { 392 OS << F.Message << " {"; 393 const char *Sep = ""; 394 for (const auto &Edit : F.Edits) { 395 OS << Sep << Edit; 396 Sep = ", "; 397 } 398 return OS << "}"; 399 } 400 401 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D) { 402 OS << static_cast<const DiagBase &>(D); 403 if (!D.Notes.empty()) { 404 OS << ", notes: {"; 405 const char *Sep = ""; 406 for (auto &Note : D.Notes) { 407 OS << Sep << Note; 408 Sep = ", "; 409 } 410 OS << "}"; 411 } 412 if (!D.Fixes.empty()) { 413 OS << ", fixes: {"; 414 const char *Sep = ""; 415 for (auto &Fix : D.Fixes) { 416 OS << Sep << Fix; 417 Sep = ", "; 418 } 419 OS << "}"; 420 } 421 return OS; 422 } 423 424 CodeAction toCodeAction(const Fix &F, const URIForFile &File) { 425 CodeAction Action; 426 Action.title = F.Message; 427 Action.kind = std::string(CodeAction::QUICKFIX_KIND); 428 Action.edit.emplace(); 429 Action.edit->changes[File.uri()] = {F.Edits.begin(), F.Edits.end()}; 430 return Action; 431 } 432 433 Diag toDiag(const llvm::SMDiagnostic &D, Diag::DiagSource Source) { 434 Diag Result; 435 Result.Message = D.getMessage().str(); 436 switch (D.getKind()) { 437 case llvm::SourceMgr::DK_Error: 438 Result.Severity = DiagnosticsEngine::Error; 439 break; 440 case llvm::SourceMgr::DK_Warning: 441 Result.Severity = DiagnosticsEngine::Warning; 442 break; 443 default: 444 break; 445 } 446 Result.Source = Source; 447 Result.AbsFile = D.getFilename().str(); 448 Result.InsideMainFile = D.getSourceMgr()->FindBufferContainingLoc( 449 D.getLoc()) == D.getSourceMgr()->getMainFileID(); 450 if (D.getRanges().empty()) 451 Result.Range = {{D.getLineNo() - 1, D.getColumnNo()}, 452 {D.getLineNo() - 1, D.getColumnNo()}}; 453 else 454 Result.Range = {{D.getLineNo() - 1, (int)D.getRanges().front().first}, 455 {D.getLineNo() - 1, (int)D.getRanges().front().second}}; 456 return Result; 457 } 458 459 void toLSPDiags( 460 const Diag &D, const URIForFile &File, const ClangdDiagnosticOptions &Opts, 461 llvm::function_ref<void(clangd::Diagnostic, llvm::ArrayRef<Fix>)> OutFn) { 462 clangd::Diagnostic Main; 463 Main.severity = getSeverity(D.Severity); 464 465 // Main diagnostic should always refer to a range inside main file. If a 466 // diagnostic made it so for, it means either itself or one of its notes is 467 // inside main file. It's also possible that there's a fix in the main file, 468 // but we preserve fixes iff primary diagnostic is in the main file. 469 if (D.InsideMainFile) { 470 Main.range = D.Range; 471 } else { 472 auto It = 473 llvm::find_if(D.Notes, [](const Note &N) { return N.InsideMainFile; }); 474 assert(It != D.Notes.end() && 475 "neither the main diagnostic nor notes are inside main file"); 476 Main.range = It->Range; 477 } 478 479 Main.code = D.Name; 480 if (auto URI = getDiagnosticDocURI(D.Source, D.ID, D.Name)) { 481 Main.codeDescription.emplace(); 482 Main.codeDescription->href = std::move(*URI); 483 } 484 switch (D.Source) { 485 case Diag::Clang: 486 Main.source = "clang"; 487 break; 488 case Diag::ClangTidy: 489 Main.source = "clang-tidy"; 490 break; 491 case Diag::Clangd: 492 Main.source = "clangd"; 493 break; 494 case Diag::ClangdConfig: 495 Main.source = "clangd-config"; 496 break; 497 case Diag::Unknown: 498 break; 499 } 500 if (Opts.EmbedFixesInDiagnostics) { 501 Main.codeActions.emplace(); 502 for (const auto &Fix : D.Fixes) 503 Main.codeActions->push_back(toCodeAction(Fix, File)); 504 if (Main.codeActions->size() == 1) 505 Main.codeActions->front().isPreferred = true; 506 } 507 if (Opts.SendDiagnosticCategory && !D.Category.empty()) 508 Main.category = D.Category; 509 510 Main.message = mainMessage(D, Opts); 511 if (Opts.EmitRelatedLocations) { 512 Main.relatedInformation.emplace(); 513 for (auto &Note : D.Notes) { 514 if (!Note.AbsFile) { 515 vlog("Dropping note from unknown file: {0}", Note); 516 continue; 517 } 518 DiagnosticRelatedInformation RelInfo; 519 RelInfo.location.range = Note.Range; 520 RelInfo.location.uri = 521 URIForFile::canonicalize(*Note.AbsFile, File.file()); 522 RelInfo.message = noteMessage(D, Note, Opts); 523 Main.relatedInformation->push_back(std::move(RelInfo)); 524 } 525 } 526 Main.tags = D.Tags; 527 OutFn(std::move(Main), D.Fixes); 528 529 // If we didn't emit the notes as relatedLocations, emit separate diagnostics 530 // so the user can find the locations easily. 531 if (!Opts.EmitRelatedLocations) 532 for (auto &Note : D.Notes) { 533 if (!Note.InsideMainFile) 534 continue; 535 clangd::Diagnostic Res; 536 Res.severity = getSeverity(Note.Severity); 537 Res.range = Note.Range; 538 Res.message = noteMessage(D, Note, Opts); 539 OutFn(std::move(Res), llvm::ArrayRef<Fix>()); 540 } 541 542 // FIXME: Get rid of the copies here by taking in a mutable clangd::Diag. 543 for (auto &Entry : D.OpaqueData) 544 Main.data.insert({Entry.first, Entry.second}); 545 } 546 547 int getSeverity(DiagnosticsEngine::Level L) { 548 switch (L) { 549 case DiagnosticsEngine::Remark: 550 return 4; 551 case DiagnosticsEngine::Note: 552 return 3; 553 case DiagnosticsEngine::Warning: 554 return 2; 555 case DiagnosticsEngine::Fatal: 556 case DiagnosticsEngine::Error: 557 return 1; 558 case DiagnosticsEngine::Ignored: 559 return 0; 560 } 561 llvm_unreachable("Unknown diagnostic level!"); 562 } 563 564 std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) { 565 // Do not forget to emit a pending diagnostic if there is one. 566 flushLastDiag(); 567 568 // Fill in name/source now that we have all the context needed to map them. 569 for (auto &Diag : Output) { 570 setTags(Diag); 571 if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) { 572 // Warnings controlled by -Wfoo are better recognized by that name. 573 StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(Diag.ID); 574 if (!Warning.empty()) { 575 Diag.Name = ("-W" + Warning).str(); 576 } else { 577 StringRef Name(ClangDiag); 578 // Almost always an error, with a name like err_enum_class_reference. 579 // Drop the err_ prefix for brevity. 580 Name.consume_front("err_"); 581 Diag.Name = std::string(Name); 582 } 583 Diag.Source = Diag::Clang; 584 continue; 585 } 586 if (Tidy != nullptr) { 587 std::string TidyDiag = Tidy->getCheckName(Diag.ID); 588 if (!TidyDiag.empty()) { 589 Diag.Name = std::move(TidyDiag); 590 Diag.Source = Diag::ClangTidy; 591 // clang-tidy bakes the name into diagnostic messages. Strip it out. 592 // It would be much nicer to make clang-tidy not do this. 593 auto CleanMessage = [&](std::string &Msg) { 594 StringRef Rest(Msg); 595 if (Rest.consume_back("]") && Rest.consume_back(Diag.Name) && 596 Rest.consume_back(" [")) 597 Msg.resize(Rest.size()); 598 }; 599 CleanMessage(Diag.Message); 600 for (auto &Note : Diag.Notes) 601 CleanMessage(Note.Message); 602 for (auto &Fix : Diag.Fixes) 603 CleanMessage(Fix.Message); 604 continue; 605 } 606 } 607 } 608 // Deduplicate clang-tidy diagnostics -- some clang-tidy checks may emit 609 // duplicated messages due to various reasons (e.g. the check doesn't handle 610 // template instantiations well; clang-tidy alias checks). 611 std::set<std::pair<Range, std::string>> SeenDiags; 612 llvm::erase_if(Output, [&](const Diag &D) { 613 return !SeenDiags.emplace(D.Range, D.Message).second; 614 }); 615 return std::move(Output); 616 } 617 618 void StoreDiags::BeginSourceFile(const LangOptions &Opts, 619 const Preprocessor *PP) { 620 LangOpts = Opts; 621 if (PP) { 622 OrigSrcMgr = &PP->getSourceManager(); 623 } 624 } 625 626 void StoreDiags::EndSourceFile() { 627 flushLastDiag(); 628 LangOpts = None; 629 OrigSrcMgr = nullptr; 630 } 631 632 /// Sanitizes a piece for presenting it in a synthesized fix message. Ensures 633 /// the result is not too large and does not contain newlines. 634 static void writeCodeToFixMessage(llvm::raw_ostream &OS, llvm::StringRef Code) { 635 constexpr unsigned MaxLen = 50; 636 if (Code == "\n") { 637 OS << "\\n"; 638 return; 639 } 640 // Only show the first line if there are many. 641 llvm::StringRef R = Code.split('\n').first; 642 // Shorten the message if it's too long. 643 R = R.take_front(MaxLen); 644 645 OS << R; 646 if (R.size() != Code.size()) 647 OS << "…"; 648 } 649 650 /// Fills \p D with all information, except the location-related bits. 651 /// Also note that ID and Name are not part of clangd::DiagBase and should be 652 /// set elsewhere. 653 static void fillNonLocationData(DiagnosticsEngine::Level DiagLevel, 654 const clang::Diagnostic &Info, 655 clangd::DiagBase &D) { 656 llvm::SmallString<64> Message; 657 Info.FormatDiagnostic(Message); 658 659 D.Message = std::string(Message.str()); 660 D.Severity = DiagLevel; 661 D.Category = DiagnosticIDs::getCategoryNameFromID( 662 DiagnosticIDs::getCategoryNumberForDiag(Info.getID())) 663 .str(); 664 } 665 666 void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 667 const clang::Diagnostic &Info) { 668 // If the diagnostic was generated for a different SourceManager, skip it. 669 // This happens when a module is imported and needs to be implicitly built. 670 // The compilation of that module will use the same StoreDiags, but different 671 // SourceManager. 672 if (OrigSrcMgr && Info.hasSourceManager() && 673 OrigSrcMgr != &Info.getSourceManager()) { 674 IgnoreDiagnostics::log(DiagLevel, Info); 675 return; 676 } 677 678 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); 679 bool OriginallyError = 680 Info.getDiags()->getDiagnosticIDs()->isDefaultMappingAsError( 681 Info.getID()); 682 683 if (Info.getLocation().isInvalid()) { 684 // Handle diagnostics coming from command-line arguments. The source manager 685 // is *not* available at this point, so we cannot use it. 686 if (!OriginallyError) { 687 IgnoreDiagnostics::log(DiagLevel, Info); 688 return; // non-errors add too much noise, do not show them. 689 } 690 691 flushLastDiag(); 692 693 LastDiag = Diag(); 694 LastDiagLoc.reset(); 695 LastDiagOriginallyError = OriginallyError; 696 LastDiag->ID = Info.getID(); 697 fillNonLocationData(DiagLevel, Info, *LastDiag); 698 LastDiag->InsideMainFile = true; 699 // Put it at the start of the main file, for a lack of a better place. 700 LastDiag->Range.start = Position{0, 0}; 701 LastDiag->Range.end = Position{0, 0}; 702 return; 703 } 704 705 if (!LangOpts || !Info.hasSourceManager()) { 706 IgnoreDiagnostics::log(DiagLevel, Info); 707 return; 708 } 709 710 SourceManager &SM = Info.getSourceManager(); 711 712 auto FillDiagBase = [&](DiagBase &D) { 713 fillNonLocationData(DiagLevel, Info, D); 714 715 D.InsideMainFile = isInsideMainFile(Info); 716 D.Range = diagnosticRange(Info, *LangOpts); 717 D.File = std::string(SM.getFilename(Info.getLocation())); 718 D.AbsFile = getCanonicalPath( 719 SM.getFileEntryForID(SM.getFileID(Info.getLocation())), SM); 720 D.ID = Info.getID(); 721 return D; 722 }; 723 724 auto AddFix = [&](bool SyntheticMessage) -> bool { 725 assert(!Info.getFixItHints().empty() && 726 "diagnostic does not have attached fix-its"); 727 // No point in generating fixes, if the diagnostic is for a different file. 728 if (!LastDiag->InsideMainFile) 729 return false; 730 // Copy as we may modify the ranges. 731 auto FixIts = Info.getFixItHints().vec(); 732 llvm::SmallVector<TextEdit, 1> Edits; 733 for (auto &FixIt : FixIts) { 734 // Allow fixits within a single macro-arg expansion to be applied. 735 // This can be incorrect if the argument is expanded multiple times in 736 // different contexts. Hopefully this is rare! 737 if (FixIt.RemoveRange.getBegin().isMacroID() && 738 FixIt.RemoveRange.getEnd().isMacroID() && 739 SM.getFileID(FixIt.RemoveRange.getBegin()) == 740 SM.getFileID(FixIt.RemoveRange.getEnd())) { 741 FixIt.RemoveRange = CharSourceRange( 742 {SM.getTopMacroCallerLoc(FixIt.RemoveRange.getBegin()), 743 SM.getTopMacroCallerLoc(FixIt.RemoveRange.getEnd())}, 744 FixIt.RemoveRange.isTokenRange()); 745 } 746 // Otherwise, follow clang's behavior: no fixits in macros. 747 if (FixIt.RemoveRange.getBegin().isMacroID() || 748 FixIt.RemoveRange.getEnd().isMacroID()) 749 return false; 750 if (!isInsideMainFile(FixIt.RemoveRange.getBegin(), SM)) 751 return false; 752 Edits.push_back(toTextEdit(FixIt, SM, *LangOpts)); 753 } 754 755 llvm::SmallString<64> Message; 756 // If requested and possible, create a message like "change 'foo' to 'bar'". 757 if (SyntheticMessage && FixIts.size() == 1) { 758 const auto &FixIt = FixIts.front(); 759 bool Invalid = false; 760 llvm::StringRef Remove = 761 Lexer::getSourceText(FixIt.RemoveRange, SM, *LangOpts, &Invalid); 762 llvm::StringRef Insert = FixIt.CodeToInsert; 763 if (!Invalid) { 764 llvm::raw_svector_ostream M(Message); 765 if (!Remove.empty() && !Insert.empty()) { 766 M << "change '"; 767 writeCodeToFixMessage(M, Remove); 768 M << "' to '"; 769 writeCodeToFixMessage(M, Insert); 770 M << "'"; 771 } else if (!Remove.empty()) { 772 M << "remove '"; 773 writeCodeToFixMessage(M, Remove); 774 M << "'"; 775 } else if (!Insert.empty()) { 776 M << "insert '"; 777 writeCodeToFixMessage(M, Insert); 778 M << "'"; 779 } 780 // Don't allow source code to inject newlines into diagnostics. 781 std::replace(Message.begin(), Message.end(), '\n', ' '); 782 } 783 } 784 if (Message.empty()) // either !SyntheticMessage, or we failed to make one. 785 Info.FormatDiagnostic(Message); 786 LastDiag->Fixes.push_back( 787 Fix{std::string(Message.str()), std::move(Edits)}); 788 return true; 789 }; 790 791 if (!isNote(DiagLevel)) { 792 // Handle the new main diagnostic. 793 flushLastDiag(); 794 795 LastDiag = Diag(); 796 // FIXME: Merge with feature modules. 797 if (Adjuster) 798 DiagLevel = Adjuster(DiagLevel, Info); 799 800 FillDiagBase(*LastDiag); 801 if (isExcluded(LastDiag->ID)) 802 LastDiag->Severity = DiagnosticsEngine::Ignored; 803 if (DiagCB) 804 DiagCB(Info, *LastDiag); 805 // Don't bother filling in the rest if diag is going to be dropped. 806 if (LastDiag->Severity == DiagnosticsEngine::Ignored) 807 return; 808 809 LastDiagLoc.emplace(Info.getLocation(), Info.getSourceManager()); 810 LastDiagOriginallyError = OriginallyError; 811 if (!Info.getFixItHints().empty()) 812 AddFix(true /* try to invent a message instead of repeating the diag */); 813 if (Fixer) { 814 auto ExtraFixes = Fixer(LastDiag->Severity, Info); 815 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ExtraFixes.begin(), 816 ExtraFixes.end()); 817 } 818 } else { 819 // Handle a note to an existing diagnostic. 820 if (!LastDiag) { 821 assert(false && "Adding a note without main diagnostic"); 822 IgnoreDiagnostics::log(DiagLevel, Info); 823 return; 824 } 825 826 // If a diagnostic was suppressed due to the suppression filter, 827 // also suppress notes associated with it. 828 if (LastDiag->Severity == DiagnosticsEngine::Ignored) 829 return; 830 831 // Give include-fixer a chance to replace a note with a fix. 832 if (Fixer) { 833 auto ReplacementFixes = Fixer(LastDiag->Severity, Info); 834 if (!ReplacementFixes.empty()) { 835 assert(Info.getNumFixItHints() == 0 && 836 "Include-fixer replaced a note with clang fix-its attached!"); 837 LastDiag->Fixes.insert(LastDiag->Fixes.end(), ReplacementFixes.begin(), 838 ReplacementFixes.end()); 839 return; 840 } 841 } 842 843 if (!Info.getFixItHints().empty()) { 844 // A clang note with fix-it is not a separate diagnostic in clangd. We 845 // attach it as a Fix to the main diagnostic instead. 846 if (!AddFix(false /* use the note as the message */)) 847 IgnoreDiagnostics::log(DiagLevel, Info); 848 } else { 849 // A clang note without fix-its corresponds to clangd::Note. 850 Note N; 851 FillDiagBase(N); 852 853 LastDiag->Notes.push_back(std::move(N)); 854 } 855 } 856 } 857 858 void StoreDiags::flushLastDiag() { 859 if (!LastDiag) 860 return; 861 auto Finish = llvm::make_scope_exit([&, NDiags(Output.size())] { 862 if (Output.size() == NDiags) // No new diag emitted. 863 vlog("Dropped diagnostic: {0}: {1}", LastDiag->File, LastDiag->Message); 864 LastDiag.reset(); 865 }); 866 867 if (LastDiag->Severity == DiagnosticsEngine::Ignored) 868 return; 869 // Move errors that occur from headers into main file. 870 if (!LastDiag->InsideMainFile && LastDiagLoc && LastDiagOriginallyError) { 871 if (tryMoveToMainFile(*LastDiag, *LastDiagLoc)) { 872 // Suppress multiple errors from the same inclusion. 873 if (!IncludedErrorLocations 874 .insert({LastDiag->Range.start.line, 875 LastDiag->Range.start.character}) 876 .second) 877 return; 878 } 879 } 880 if (!mentionsMainFile(*LastDiag)) 881 return; 882 Output.push_back(std::move(*LastDiag)); 883 } 884 885 bool isBuiltinDiagnosticSuppressed(unsigned ID, 886 const llvm::StringSet<> &Suppress, 887 const LangOptions &LangOpts) { 888 // Don't complain about header-only stuff in mainfiles if it's a header. 889 // FIXME: would be cleaner to suppress in clang, once we decide whether the 890 // behavior should be to silently-ignore or respect the pragma. 891 if (ID == diag::pp_pragma_sysheader_in_main_file && LangOpts.IsHeaderFile) 892 return true; 893 894 if (const char *CodePtr = getDiagnosticCode(ID)) { 895 if (Suppress.contains(normalizeSuppressedCode(CodePtr))) 896 return true; 897 } 898 StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(ID); 899 if (!Warning.empty() && Suppress.contains(Warning)) 900 return true; 901 return false; 902 } 903 904 llvm::StringRef normalizeSuppressedCode(llvm::StringRef Code) { 905 Code.consume_front("err_"); 906 Code.consume_front("-W"); 907 return Code; 908 } 909 910 llvm::Optional<std::string> getDiagnosticDocURI(Diag::DiagSource Source, 911 unsigned ID, 912 llvm::StringRef Name) { 913 switch (Source) { 914 case Diag::Unknown: 915 break; 916 case Diag::Clang: 917 // There is a page listing many warning flags, but it provides too little 918 // information to be worth linking. 919 // https://clang.llvm.org/docs/DiagnosticsReference.html 920 break; 921 case Diag::ClangTidy: 922 return {("https://clang.llvm.org/extra/clang-tidy/checks/" + Name + ".html") 923 .str()}; 924 case Diag::Clangd: 925 if (Name == "unused-includes") 926 return {"https://clangd.llvm.org/guides/include-cleaner"}; 927 break; 928 case Diag::ClangdConfig: 929 // FIXME: we should link to https://clangd.llvm.org/config 930 // However we have no diagnostic codes, which the link should describe! 931 break; 932 } 933 return llvm::None; 934 } 935 936 } // namespace clangd 937 } // namespace clang 938