brintos

brintos / llvm-project-archived public Read only

0
0
Text · 34.2 KiB · bc605fd Raw
973 lines · cpp
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-exception6//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: keep16#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 49namespace clang {50namespace clangd {51namespace {52 53const 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 DIAG71  default:72    return nullptr;73  }74}75 76bool 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 89bool 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!99bool 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.110std::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're132  // 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 location138  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.147const 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 location210//   - for errors in template instantiation, use the instantiation location211// In both cases, add the original header location as a note.212bool 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 238bool isNote(DiagnosticsEngine::Level L) {239  return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark;240}241 242llvm::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 includes261/// location, severity and error message. An example of the output message is:262///263///     main.cpp:12:23: error: undeclared identifier264///265/// For main file we only print the basename and for all other files we print266/// the filename on a separate line to provide a slightly more readable output267/// in the editors:268///269///     dir1/dir2/dir3/../../dir4/header.h:12:23270///     error: undeclared identifier271void 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 they274    // 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 when281  // 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 separate285  // line improves readability.286  if (D.InsideMainFile)287    OS << " ";288  else289    OS << "\n";290  OS << diagLeveltoString(D.Severity) << ": " << D.Message;291}292 293/// Capitalizes the first word in the diagnostic's message.294std::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 arguments307///308///     dir1/dir2/dir3/../../dir4/header.h:12:23309///     note: candidate function not viable: requires 3 arguments310std::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.326std::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 the332  // 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 340void 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} // namespace398 399llvm::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 408llvm::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 418llvm::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 441Diag 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  else462    Result.Range = {{D.getLineNo() - 1, (int)D.getRanges().front().first},463                    {D.getLineNo() - 1, (int)D.getRanges().front().second}};464  return Result;465}466 467void 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 deprecated473  // declartions. These already have visible decorations inside the editor and474  // most users find the extra clutter in the UI (gutter, minimap, diagnostics475  // 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 a482  // diagnostic made it so for, it means either itself or one of its notes is483  // 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 diagnostics542  // 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 555int 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 572std::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 emit624  // duplicated messages due to various reasons (e.g. the check doesn't handle625  // 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 633void StoreDiags::BeginSourceFile(const LangOptions &Opts,634                                 const Preprocessor *PP) {635  LangOpts = Opts;636  if (PP) {637    OrigSrcMgr = &PP->getSourceManager();638  }639}640 641void 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. Ensures648/// the result is not too large and does not contain newlines.649static 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 be667/// set elsewhere.668static 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 681void 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 different686  // 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 manager700    // 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    else736      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 in758      // 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        llvm::replace(Message, '\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. We867      // 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 880void 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 (!IncludedErrorLocations896               .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 907bool 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 the912  //        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 929llvm::StringRef normalizeSuppressedCode(llvm::StringRef Code) {930  Code.consume_front("err_");931  Code.consume_front("-W");932  return Code;933}934 935std::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 little943    // information to be worth linking.944    // https://clang.llvm.org/docs/DiagnosticsReference.html945    break;946  case Diag::ClangTidy: {947    StringRef Module, Check;948    // This won't correctly get the module for clang-analyzer checks, but as we949    // 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 a951    // '-' 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/config965    // However we have no diagnostic codes, which the link should describe!966    break;967  }968  return std::nullopt;969}970 971} // namespace clangd972} // namespace clang973