llvm-project

Форк
0
/
ClangTidyDiagnosticConsumer.cpp 
815 строк · 30.5 Кб
1
//===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== //
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
///  \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext
10
///  and ClangTidyError classes.
11
///
12
///  This tool uses the Clang Tooling infrastructure, see
13
///    http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
14
///  for details on setting it up with LLVM source tree.
15
///
16
//===----------------------------------------------------------------------===//
17

18
#include "ClangTidyDiagnosticConsumer.h"
19
#include "ClangTidyOptions.h"
20
#include "GlobList.h"
21
#include "NoLintDirectiveHandler.h"
22
#include "clang/AST/ASTContext.h"
23
#include "clang/AST/ASTDiagnostic.h"
24
#include "clang/AST/Attr.h"
25
#include "clang/Basic/CharInfo.h"
26
#include "clang/Basic/Diagnostic.h"
27
#include "clang/Basic/DiagnosticOptions.h"
28
#include "clang/Basic/FileManager.h"
29
#include "clang/Basic/SourceManager.h"
30
#include "clang/Frontend/DiagnosticRenderer.h"
31
#include "clang/Lex/Lexer.h"
32
#include "clang/Tooling/Core/Diagnostic.h"
33
#include "clang/Tooling/Core/Replacement.h"
34
#include "llvm/ADT/BitVector.h"
35
#include "llvm/ADT/STLExtras.h"
36
#include "llvm/ADT/SmallString.h"
37
#include "llvm/ADT/StringMap.h"
38
#include "llvm/Support/FormatVariadic.h"
39
#include "llvm/Support/Regex.h"
40
#include <optional>
41
#include <tuple>
42
#include <utility>
43
#include <vector>
44
using namespace clang;
45
using namespace tidy;
46

47
namespace {
48
class ClangTidyDiagnosticRenderer : public DiagnosticRenderer {
49
public:
50
  ClangTidyDiagnosticRenderer(const LangOptions &LangOpts,
51
                              DiagnosticOptions *DiagOpts,
52
                              ClangTidyError &Error)
53
      : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
54

55
protected:
56
  void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc,
57
                             DiagnosticsEngine::Level Level, StringRef Message,
58
                             ArrayRef<CharSourceRange> Ranges,
59
                             DiagOrStoredDiag Info) override {
60
    // Remove check name from the message.
61
    // FIXME: Remove this once there's a better way to pass check names than
62
    // appending the check name to the message in ClangTidyContext::diag and
63
    // using getCustomDiagID.
64
    std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]";
65
    Message.consume_back(CheckNameInMessage);
66

67
    auto TidyMessage =
68
        Loc.isValid()
69
            ? tooling::DiagnosticMessage(Message, Loc.getManager(), Loc)
70
            : tooling::DiagnosticMessage(Message);
71

72
    // Make sure that if a TokenRange is received from the check it is unfurled
73
    // into a real CharRange for the diagnostic printer later.
74
    // Whatever we store here gets decoupled from the current SourceManager, so
75
    // we **have to** know the exact position and length of the highlight.
76
    auto ToCharRange = [this, &Loc](const CharSourceRange &SourceRange) {
77
      if (SourceRange.isCharRange())
78
        return SourceRange;
79
      assert(SourceRange.isTokenRange());
80
      SourceLocation End = Lexer::getLocForEndOfToken(
81
          SourceRange.getEnd(), 0, Loc.getManager(), LangOpts);
82
      return CharSourceRange::getCharRange(SourceRange.getBegin(), End);
83
    };
84

85
    // We are only interested in valid ranges.
86
    auto ValidRanges =
87
        llvm::make_filter_range(Ranges, [](const CharSourceRange &R) {
88
          return R.getAsRange().isValid();
89
        });
90

91
    if (Level == DiagnosticsEngine::Note) {
92
      Error.Notes.push_back(TidyMessage);
93
      for (const CharSourceRange &SourceRange : ValidRanges)
94
        Error.Notes.back().Ranges.emplace_back(Loc.getManager(),
95
                                               ToCharRange(SourceRange));
96
      return;
97
    }
98
    assert(Error.Message.Message.empty() && "Overwriting a diagnostic message");
99
    Error.Message = TidyMessage;
100
    for (const CharSourceRange &SourceRange : ValidRanges)
101
      Error.Message.Ranges.emplace_back(Loc.getManager(),
102
                                        ToCharRange(SourceRange));
103
  }
104

105
  void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
106
                         DiagnosticsEngine::Level Level,
107
                         ArrayRef<CharSourceRange> Ranges) override {}
108

109
  void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level,
110
                       SmallVectorImpl<CharSourceRange> &Ranges,
111
                       ArrayRef<FixItHint> Hints) override {
112
    assert(Loc.isValid());
113
    tooling::DiagnosticMessage *DiagWithFix =
114
        Level == DiagnosticsEngine::Note ? &Error.Notes.back() : &Error.Message;
115

116
    for (const auto &FixIt : Hints) {
117
      CharSourceRange Range = FixIt.RemoveRange;
118
      assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&
119
             "Invalid range in the fix-it hint.");
120
      assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&
121
             "Only file locations supported in fix-it hints.");
122

123
      tooling::Replacement Replacement(Loc.getManager(), Range,
124
                                       FixIt.CodeToInsert);
125
      llvm::Error Err =
126
          DiagWithFix->Fix[Replacement.getFilePath()].add(Replacement);
127
      // FIXME: better error handling (at least, don't let other replacements be
128
      // applied).
129
      if (Err) {
130
        llvm::errs() << "Fix conflicts with existing fix! "
131
                     << llvm::toString(std::move(Err)) << "\n";
132
        assert(false && "Fix conflicts with existing fix!");
133
      }
134
    }
135
  }
136

137
  void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) override {}
138

139
  void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
140
                          StringRef ModuleName) override {}
141

142
  void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc,
143
                                  StringRef ModuleName) override {}
144

145
  void endDiagnostic(DiagOrStoredDiag D,
146
                     DiagnosticsEngine::Level Level) override {
147
    assert(!Error.Message.Message.empty() && "Message has not been set");
148
  }
149

150
private:
151
  ClangTidyError &Error;
152
};
153
} // end anonymous namespace
154

155
ClangTidyError::ClangTidyError(StringRef CheckName,
156
                               ClangTidyError::Level DiagLevel,
157
                               StringRef BuildDirectory, bool IsWarningAsError)
158
    : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
159
      IsWarningAsError(IsWarningAsError) {}
160

161
ClangTidyContext::ClangTidyContext(
162
    std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
163
    bool AllowEnablingAnalyzerAlphaCheckers, bool EnableModuleHeadersParsing)
164
    : OptionsProvider(std::move(OptionsProvider)),
165

166
      AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers),
167
      EnableModuleHeadersParsing(EnableModuleHeadersParsing) {
168
  // Before the first translation unit we can get errors related to command-line
169
  // parsing, use empty string for the file name in this case.
170
  setCurrentFile("");
171
}
172

173
ClangTidyContext::~ClangTidyContext() = default;
174

175
DiagnosticBuilder ClangTidyContext::diag(
176
    StringRef CheckName, SourceLocation Loc, StringRef Description,
177
    DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
178
  assert(Loc.isValid());
179
  unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
180
      Level, (Description + " [" + CheckName + "]").str());
181
  CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
182
  return DiagEngine->Report(Loc, ID);
183
}
184

185
DiagnosticBuilder ClangTidyContext::diag(
186
    StringRef CheckName, StringRef Description,
187
    DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
188
  unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
189
      Level, (Description + " [" + CheckName + "]").str());
190
  CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
191
  return DiagEngine->Report(ID);
192
}
193

194
DiagnosticBuilder ClangTidyContext::diag(const tooling::Diagnostic &Error) {
195
  SourceManager &SM = DiagEngine->getSourceManager();
196
  FileManager &FM = SM.getFileManager();
197
  FileEntryRef File = llvm::cantFail(FM.getFileRef(Error.Message.FilePath));
198
  FileID ID = SM.getOrCreateFileID(File, SrcMgr::C_User);
199
  SourceLocation FileStartLoc = SM.getLocForStartOfFile(ID);
200
  SourceLocation Loc = FileStartLoc.getLocWithOffset(
201
      static_cast<SourceLocation::IntTy>(Error.Message.FileOffset));
202
  return diag(Error.DiagnosticName, Loc, Error.Message.Message,
203
              static_cast<DiagnosticIDs::Level>(Error.DiagLevel));
204
}
205

206
DiagnosticBuilder ClangTidyContext::configurationDiag(
207
    StringRef Message,
208
    DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
209
  return diag("clang-tidy-config", Message, Level);
210
}
211

212
bool ClangTidyContext::shouldSuppressDiagnostic(
213
    DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info,
214
    SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO,
215
    bool EnableNoLintBlocks) {
216
  std::string CheckName = getCheckName(Info.getID());
217
  return NoLintHandler.shouldSuppress(DiagLevel, Info, CheckName, NoLintErrors,
218
                                      AllowIO, EnableNoLintBlocks);
219
}
220

221
void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {
222
  DiagEngine->setSourceManager(SourceMgr);
223
}
224

225
static bool parseFileExtensions(llvm::ArrayRef<std::string> AllFileExtensions,
226
                                FileExtensionsSet &FileExtensions) {
227
  FileExtensions.clear();
228
  for (StringRef Suffix : AllFileExtensions) {
229
    StringRef Extension = Suffix.trim();
230
    if (!llvm::all_of(Extension, isAlphanumeric))
231
      return false;
232
    FileExtensions.insert(Extension);
233
  }
234
  return true;
235
}
236

237
void ClangTidyContext::setCurrentFile(StringRef File) {
238
  CurrentFile = std::string(File);
239
  CurrentOptions = getOptionsForFile(CurrentFile);
240
  CheckFilter = std::make_unique<CachedGlobList>(*getOptions().Checks);
241
  WarningAsErrorFilter =
242
      std::make_unique<CachedGlobList>(*getOptions().WarningsAsErrors);
243
  if (!parseFileExtensions(*getOptions().HeaderFileExtensions,
244
                           HeaderFileExtensions))
245
    this->configurationDiag("Invalid header file extensions");
246
  if (!parseFileExtensions(*getOptions().ImplementationFileExtensions,
247
                           ImplementationFileExtensions))
248
    this->configurationDiag("Invalid implementation file extensions");
249
}
250

251
void ClangTidyContext::setASTContext(ASTContext *Context) {
252
  DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
253
  LangOpts = Context->getLangOpts();
254
}
255

256
const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const {
257
  return OptionsProvider->getGlobalOptions();
258
}
259

260
const ClangTidyOptions &ClangTidyContext::getOptions() const {
261
  return CurrentOptions;
262
}
263

264
ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const {
265
  // Merge options on top of getDefaults() as a safeguard against options with
266
  // unset values.
267
  return ClangTidyOptions::getDefaults().merge(
268
      OptionsProvider->getOptions(File), 0);
269
}
270

271
void ClangTidyContext::setEnableProfiling(bool P) { Profile = P; }
272

273
void ClangTidyContext::setProfileStoragePrefix(StringRef Prefix) {
274
  ProfilePrefix = std::string(Prefix);
275
}
276

277
std::optional<ClangTidyProfiling::StorageParams>
278
ClangTidyContext::getProfileStorageParams() const {
279
  if (ProfilePrefix.empty())
280
    return std::nullopt;
281

282
  return ClangTidyProfiling::StorageParams(ProfilePrefix, CurrentFile);
283
}
284

285
bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const {
286
  assert(CheckFilter != nullptr);
287
  return CheckFilter->contains(CheckName);
288
}
289

290
bool ClangTidyContext::treatAsError(StringRef CheckName) const {
291
  assert(WarningAsErrorFilter != nullptr);
292
  return WarningAsErrorFilter->contains(CheckName);
293
}
294

295
std::string ClangTidyContext::getCheckName(unsigned DiagnosticID) const {
296
  std::string ClangWarningOption = std::string(
297
      DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(DiagnosticID));
298
  if (!ClangWarningOption.empty())
299
    return "clang-diagnostic-" + ClangWarningOption;
300
  llvm::DenseMap<unsigned, std::string>::const_iterator I =
301
      CheckNamesByDiagnosticID.find(DiagnosticID);
302
  if (I != CheckNamesByDiagnosticID.end())
303
    return I->second;
304
  return "";
305
}
306

307
ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(
308
    ClangTidyContext &Ctx, DiagnosticsEngine *ExternalDiagEngine,
309
    bool RemoveIncompatibleErrors, bool GetFixesFromNotes,
310
    bool EnableNolintBlocks)
311
    : Context(Ctx), ExternalDiagEngine(ExternalDiagEngine),
312
      RemoveIncompatibleErrors(RemoveIncompatibleErrors),
313
      GetFixesFromNotes(GetFixesFromNotes),
314
      EnableNolintBlocks(EnableNolintBlocks) {
315

316
  if (Context.getOptions().HeaderFilterRegex &&
317
      !Context.getOptions().HeaderFilterRegex->empty())
318
    HeaderFilter =
319
        std::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);
320

321
  if (Context.getOptions().ExcludeHeaderFilterRegex &&
322
      !Context.getOptions().ExcludeHeaderFilterRegex->empty())
323
    ExcludeHeaderFilter = std::make_unique<llvm::Regex>(
324
        *Context.getOptions().ExcludeHeaderFilterRegex);
325
}
326

327
void ClangTidyDiagnosticConsumer::finalizeLastError() {
328
  if (!Errors.empty()) {
329
    ClangTidyError &Error = Errors.back();
330
    if (Error.DiagnosticName == "clang-tidy-config") {
331
      // Never ignore these.
332
    } else if (!Context.isCheckEnabled(Error.DiagnosticName) &&
333
               Error.DiagLevel != ClangTidyError::Error) {
334
      ++Context.Stats.ErrorsIgnoredCheckFilter;
335
      Errors.pop_back();
336
    } else if (!LastErrorRelatesToUserCode) {
337
      ++Context.Stats.ErrorsIgnoredNonUserCode;
338
      Errors.pop_back();
339
    } else if (!LastErrorPassesLineFilter) {
340
      ++Context.Stats.ErrorsIgnoredLineFilter;
341
      Errors.pop_back();
342
    } else {
343
      ++Context.Stats.ErrorsDisplayed;
344
    }
345
  }
346
  LastErrorRelatesToUserCode = false;
347
  LastErrorPassesLineFilter = false;
348
}
349

350
namespace clang::tidy {
351

352
const llvm::StringMap<tooling::Replacements> *
353
getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix) {
354
  if (!Diagnostic.Message.Fix.empty())
355
    return &Diagnostic.Message.Fix;
356
  if (!AnyFix)
357
    return nullptr;
358
  const llvm::StringMap<tooling::Replacements> *Result = nullptr;
359
  for (const auto &Note : Diagnostic.Notes) {
360
    if (!Note.Fix.empty()) {
361
      if (Result)
362
        // We have 2 different fixes in notes, bail out.
363
        return nullptr;
364
      Result = &Note.Fix;
365
    }
366
  }
367
  return Result;
368
}
369

370
} // namespace clang::tidy
371

372
void ClangTidyDiagnosticConsumer::HandleDiagnostic(
373
    DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {
374
  if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
375
    return;
376

377
  SmallVector<tooling::Diagnostic, 1> SuppressionErrors;
378
  if (Context.shouldSuppressDiagnostic(DiagLevel, Info, SuppressionErrors,
379
                                       EnableNolintBlocks)) {
380
    ++Context.Stats.ErrorsIgnoredNOLINT;
381
    // Ignored a warning, should ignore related notes as well
382
    LastErrorWasIgnored = true;
383
    Context.DiagEngine->Clear();
384
    for (const auto &Error : SuppressionErrors)
385
      Context.diag(Error);
386
    return;
387
  }
388

389
  LastErrorWasIgnored = false;
390
  // Count warnings/errors.
391
  DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
392

393
  if (DiagLevel == DiagnosticsEngine::Note) {
394
    assert(!Errors.empty() &&
395
           "A diagnostic note can only be appended to a message.");
396
  } else {
397
    finalizeLastError();
398
    std::string CheckName = Context.getCheckName(Info.getID());
399
    if (CheckName.empty()) {
400
      // This is a compiler diagnostic without a warning option. Assign check
401
      // name based on its level.
402
      switch (DiagLevel) {
403
      case DiagnosticsEngine::Error:
404
      case DiagnosticsEngine::Fatal:
405
        CheckName = "clang-diagnostic-error";
406
        break;
407
      case DiagnosticsEngine::Warning:
408
        CheckName = "clang-diagnostic-warning";
409
        break;
410
      case DiagnosticsEngine::Remark:
411
        CheckName = "clang-diagnostic-remark";
412
        break;
413
      default:
414
        CheckName = "clang-diagnostic-unknown";
415
        break;
416
      }
417
    }
418

419
    ClangTidyError::Level Level = ClangTidyError::Warning;
420
    if (DiagLevel == DiagnosticsEngine::Error ||
421
        DiagLevel == DiagnosticsEngine::Fatal) {
422
      // Force reporting of Clang errors regardless of filters and non-user
423
      // code.
424
      Level = ClangTidyError::Error;
425
      LastErrorRelatesToUserCode = true;
426
      LastErrorPassesLineFilter = true;
427
    } else if (DiagLevel == DiagnosticsEngine::Remark) {
428
      Level = ClangTidyError::Remark;
429
    }
430

431
    bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&
432
                            Context.treatAsError(CheckName);
433
    Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(),
434
                        IsWarningAsError);
435
  }
436

437
  if (ExternalDiagEngine) {
438
    // If there is an external diagnostics engine, like in the
439
    // ClangTidyPluginAction case, forward the diagnostics to it.
440
    forwardDiagnostic(Info);
441
  } else {
442
    ClangTidyDiagnosticRenderer Converter(
443
        Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
444
        Errors.back());
445
    SmallString<100> Message;
446
    Info.FormatDiagnostic(Message);
447
    FullSourceLoc Loc;
448
    if (Info.hasSourceManager())
449
      Loc = FullSourceLoc(Info.getLocation(), Info.getSourceManager());
450
    else if (Context.DiagEngine->hasSourceManager())
451
      Loc = FullSourceLoc(Info.getLocation(),
452
                          Context.DiagEngine->getSourceManager());
453
    Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
454
                             Info.getFixItHints());
455
  }
456

457
  if (Info.hasSourceManager())
458
    checkFilters(Info.getLocation(), Info.getSourceManager());
459

460
  Context.DiagEngine->Clear();
461
  for (const auto &Error : SuppressionErrors)
462
    Context.diag(Error);
463
}
464

465
bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
466
                                                   unsigned LineNumber) const {
467
  if (Context.getGlobalOptions().LineFilter.empty())
468
    return true;
469
  for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {
470
    if (FileName.ends_with(Filter.Name)) {
471
      if (Filter.LineRanges.empty())
472
        return true;
473
      for (const FileFilter::LineRange &Range : Filter.LineRanges) {
474
        if (Range.first <= LineNumber && LineNumber <= Range.second)
475
          return true;
476
      }
477
      return false;
478
    }
479
  }
480
  return false;
481
}
482

483
void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) {
484
  // Acquire a diagnostic ID also in the external diagnostics engine.
485
  auto DiagLevelAndFormatString =
486
      Context.getDiagLevelAndFormatString(Info.getID(), Info.getLocation());
487
  unsigned ExternalID = ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID(
488
      DiagLevelAndFormatString.first, DiagLevelAndFormatString.second);
489

490
  // Forward the details.
491
  auto Builder = ExternalDiagEngine->Report(Info.getLocation(), ExternalID);
492
  for (const FixItHint &Hint : Info.getFixItHints())
493
    Builder << Hint;
494
  for (auto Range : Info.getRanges())
495
    Builder << Range;
496
  for (unsigned Index = 0; Index < Info.getNumArgs(); ++Index) {
497
    DiagnosticsEngine::ArgumentKind Kind = Info.getArgKind(Index);
498
    switch (Kind) {
499
    case clang::DiagnosticsEngine::ak_std_string:
500
      Builder << Info.getArgStdStr(Index);
501
      break;
502
    case clang::DiagnosticsEngine::ak_c_string:
503
      Builder << Info.getArgCStr(Index);
504
      break;
505
    case clang::DiagnosticsEngine::ak_sint:
506
      Builder << Info.getArgSInt(Index);
507
      break;
508
    case clang::DiagnosticsEngine::ak_uint:
509
      Builder << Info.getArgUInt(Index);
510
      break;
511
    case clang::DiagnosticsEngine::ak_tokenkind:
512
      Builder << static_cast<tok::TokenKind>(Info.getRawArg(Index));
513
      break;
514
    case clang::DiagnosticsEngine::ak_identifierinfo:
515
      Builder << Info.getArgIdentifier(Index);
516
      break;
517
    case clang::DiagnosticsEngine::ak_qual:
518
      Builder << Qualifiers::fromOpaqueValue(Info.getRawArg(Index));
519
      break;
520
    case clang::DiagnosticsEngine::ak_qualtype:
521
      Builder << QualType::getFromOpaquePtr((void *)Info.getRawArg(Index));
522
      break;
523
    case clang::DiagnosticsEngine::ak_declarationname:
524
      Builder << DeclarationName::getFromOpaqueInteger(Info.getRawArg(Index));
525
      break;
526
    case clang::DiagnosticsEngine::ak_nameddecl:
527
      Builder << reinterpret_cast<const NamedDecl *>(Info.getRawArg(Index));
528
      break;
529
    case clang::DiagnosticsEngine::ak_nestednamespec:
530
      Builder << reinterpret_cast<NestedNameSpecifier *>(Info.getRawArg(Index));
531
      break;
532
    case clang::DiagnosticsEngine::ak_declcontext:
533
      Builder << reinterpret_cast<DeclContext *>(Info.getRawArg(Index));
534
      break;
535
    case clang::DiagnosticsEngine::ak_qualtype_pair:
536
      assert(false); // This one is not passed around.
537
      break;
538
    case clang::DiagnosticsEngine::ak_attr:
539
      Builder << reinterpret_cast<Attr *>(Info.getRawArg(Index));
540
      break;
541
    case clang::DiagnosticsEngine::ak_addrspace:
542
      Builder << static_cast<LangAS>(Info.getRawArg(Index));
543
      break;
544
    }
545
  }
546
}
547

548
void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
549
                                               const SourceManager &Sources) {
550
  // Invalid location may mean a diagnostic in a command line, don't skip these.
551
  if (!Location.isValid()) {
552
    LastErrorRelatesToUserCode = true;
553
    LastErrorPassesLineFilter = true;
554
    return;
555
  }
556

557
  if (!*Context.getOptions().SystemHeaders &&
558
      (Sources.isInSystemHeader(Location) || Sources.isInSystemMacro(Location)))
559
    return;
560

561
  // FIXME: We start with a conservative approach here, but the actual type of
562
  // location needed depends on the check (in particular, where this check wants
563
  // to apply fixes).
564
  FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
565
  OptionalFileEntryRef File = Sources.getFileEntryRefForID(FID);
566

567
  // -DMACRO definitions on the command line have locations in a virtual buffer
568
  // that doesn't have a FileEntry. Don't skip these as well.
569
  if (!File) {
570
    LastErrorRelatesToUserCode = true;
571
    LastErrorPassesLineFilter = true;
572
    return;
573
  }
574

575
  StringRef FileName(File->getName());
576
  LastErrorRelatesToUserCode =
577
      LastErrorRelatesToUserCode || Sources.isInMainFile(Location) ||
578
      (HeaderFilter &&
579
       (HeaderFilter->match(FileName) &&
580
        !(ExcludeHeaderFilter && ExcludeHeaderFilter->match(FileName))));
581

582
  unsigned LineNumber = Sources.getExpansionLineNumber(Location);
583
  LastErrorPassesLineFilter =
584
      LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
585
}
586

587
void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
588
  // Each error is modelled as the set of intervals in which it applies
589
  // replacements. To detect overlapping replacements, we use a sweep line
590
  // algorithm over these sets of intervals.
591
  // An event here consists of the opening or closing of an interval. During the
592
  // process, we maintain a counter with the amount of open intervals. If we
593
  // find an endpoint of an interval and this counter is different from 0, it
594
  // means that this interval overlaps with another one, so we set it as
595
  // inapplicable.
596
  struct Event {
597
    // An event can be either the begin or the end of an interval.
598
    enum EventType {
599
      ET_Begin = 1,
600
      ET_Insert = 0,
601
      ET_End = -1,
602
    };
603

604
    Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,
605
          unsigned ErrorSize)
606
        : Type(Type), ErrorId(ErrorId) {
607
      // The events are going to be sorted by their position. In case of draw:
608
      //
609
      // * If an interval ends at the same position at which other interval
610
      //   begins, this is not an overlapping, so we want to remove the ending
611
      //   interval before adding the starting one: end events have higher
612
      //   priority than begin events.
613
      //
614
      // * If we have several begin points at the same position, we will mark as
615
      //   inapplicable the ones that we process later, so the first one has to
616
      //   be the one with the latest end point, because this one will contain
617
      //   all the other intervals. For the same reason, if we have several end
618
      //   points in the same position, the last one has to be the one with the
619
      //   earliest begin point. In both cases, we sort non-increasingly by the
620
      //   position of the complementary.
621
      //
622
      // * In case of two equal intervals, the one whose error is bigger can
623
      //   potentially contain the other one, so we want to process its begin
624
      //   points before and its end points later.
625
      //
626
      // * Finally, if we have two equal intervals whose errors have the same
627
      //   size, none of them will be strictly contained inside the other.
628
      //   Sorting by ErrorId will guarantee that the begin point of the first
629
      //   one will be processed before, disallowing the second one, and the
630
      //   end point of the first one will also be processed before,
631
      //   disallowing the first one.
632
      switch (Type) {
633
      case ET_Begin:
634
        Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
635
        break;
636
      case ET_Insert:
637
        Priority = std::make_tuple(Begin, Type, -End, ErrorSize, ErrorId);
638
        break;
639
      case ET_End:
640
        Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
641
        break;
642
      }
643
    }
644

645
    bool operator<(const Event &Other) const {
646
      return Priority < Other.Priority;
647
    }
648

649
    // Determines if this event is the begin or the end of an interval.
650
    EventType Type;
651
    // The index of the error to which the interval that generated this event
652
    // belongs.
653
    unsigned ErrorId;
654
    // The events will be sorted based on this field.
655
    std::tuple<unsigned, EventType, int, int, unsigned> Priority;
656
  };
657

658
  removeDuplicatedDiagnosticsOfAliasCheckers();
659

660
  // Compute error sizes.
661
  std::vector<int> Sizes;
662
  std::vector<
663
      std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>>
664
      ErrorFixes;
665
  for (auto &Error : Errors) {
666
    if (const auto *Fix = getFixIt(Error, GetFixesFromNotes))
667
      ErrorFixes.emplace_back(
668
          &Error, const_cast<llvm::StringMap<tooling::Replacements> *>(Fix));
669
  }
670
  for (const auto &ErrorAndFix : ErrorFixes) {
671
    int Size = 0;
672
    for (const auto &FileAndReplaces : *ErrorAndFix.second) {
673
      for (const auto &Replace : FileAndReplaces.second)
674
        Size += Replace.getLength();
675
    }
676
    Sizes.push_back(Size);
677
  }
678

679
  // Build events from error intervals.
680
  llvm::StringMap<std::vector<Event>> FileEvents;
681
  for (unsigned I = 0; I < ErrorFixes.size(); ++I) {
682
    for (const auto &FileAndReplace : *ErrorFixes[I].second) {
683
      for (const auto &Replace : FileAndReplace.second) {
684
        unsigned Begin = Replace.getOffset();
685
        unsigned End = Begin + Replace.getLength();
686
        auto &Events = FileEvents[Replace.getFilePath()];
687
        if (Begin == End) {
688
          Events.emplace_back(Begin, End, Event::ET_Insert, I, Sizes[I]);
689
        } else {
690
          Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
691
          Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
692
        }
693
      }
694
    }
695
  }
696

697
  llvm::BitVector Apply(ErrorFixes.size(), true);
698
  for (auto &FileAndEvents : FileEvents) {
699
    std::vector<Event> &Events = FileAndEvents.second;
700
    // Sweep.
701
    llvm::sort(Events);
702
    int OpenIntervals = 0;
703
    for (const auto &Event : Events) {
704
      switch (Event.Type) {
705
      case Event::ET_Begin:
706
        if (OpenIntervals++ != 0)
707
          Apply[Event.ErrorId] = false;
708
        break;
709
      case Event::ET_Insert:
710
        if (OpenIntervals != 0)
711
          Apply[Event.ErrorId] = false;
712
        break;
713
      case Event::ET_End:
714
        if (--OpenIntervals != 0)
715
          Apply[Event.ErrorId] = false;
716
        break;
717
      }
718
    }
719
    assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");
720
  }
721

722
  for (unsigned I = 0; I < ErrorFixes.size(); ++I) {
723
    if (!Apply[I]) {
724
      ErrorFixes[I].second->clear();
725
      ErrorFixes[I].first->Notes.emplace_back(
726
          "this fix will not be applied because it overlaps with another fix");
727
    }
728
  }
729
}
730

731
namespace {
732
struct LessClangTidyError {
733
  bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
734
    const tooling::DiagnosticMessage &M1 = LHS.Message;
735
    const tooling::DiagnosticMessage &M2 = RHS.Message;
736

737
    return std::tie(M1.FilePath, M1.FileOffset, LHS.DiagnosticName,
738
                    M1.Message) <
739
           std::tie(M2.FilePath, M2.FileOffset, RHS.DiagnosticName, M2.Message);
740
  }
741
};
742
struct EqualClangTidyError {
743
  bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
744
    LessClangTidyError Less;
745
    return !Less(LHS, RHS) && !Less(RHS, LHS);
746
  }
747
};
748
} // end anonymous namespace
749

750
std::vector<ClangTidyError> ClangTidyDiagnosticConsumer::take() {
751
  finalizeLastError();
752

753
  llvm::stable_sort(Errors, LessClangTidyError());
754
  Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
755
               Errors.end());
756
  if (RemoveIncompatibleErrors)
757
    removeIncompatibleErrors();
758
  return std::move(Errors);
759
}
760

761
namespace {
762
struct LessClangTidyErrorWithoutDiagnosticName {
763
  bool operator()(const ClangTidyError *LHS, const ClangTidyError *RHS) const {
764
    const tooling::DiagnosticMessage &M1 = LHS->Message;
765
    const tooling::DiagnosticMessage &M2 = RHS->Message;
766

767
    return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
768
           std::tie(M2.FilePath, M2.FileOffset, M2.Message);
769
  }
770
};
771
} // end anonymous namespace
772

773
void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() {
774
  using UniqueErrorSet =
775
      std::set<ClangTidyError *, LessClangTidyErrorWithoutDiagnosticName>;
776
  UniqueErrorSet UniqueErrors;
777

778
  auto IT = Errors.begin();
779
  while (IT != Errors.end()) {
780
    ClangTidyError &Error = *IT;
781
    std::pair<UniqueErrorSet::iterator, bool> Inserted =
782
        UniqueErrors.insert(&Error);
783

784
    // Unique error, we keep it and move along.
785
    if (Inserted.second) {
786
      ++IT;
787
    } else {
788
      ClangTidyError &ExistingError = **Inserted.first;
789
      const llvm::StringMap<tooling::Replacements> &CandidateFix =
790
          Error.Message.Fix;
791
      const llvm::StringMap<tooling::Replacements> &ExistingFix =
792
          (*Inserted.first)->Message.Fix;
793

794
      if (CandidateFix != ExistingFix) {
795

796
        // In case of a conflict, don't suggest any fix-it.
797
        ExistingError.Message.Fix.clear();
798
        ExistingError.Notes.emplace_back(
799
            llvm::formatv("cannot apply fix-it because an alias checker has "
800
                          "suggested a different fix-it; please remove one of "
801
                          "the checkers ('{0}', '{1}') or "
802
                          "ensure they are both configured the same",
803
                          ExistingError.DiagnosticName, Error.DiagnosticName)
804
                .str());
805
      }
806

807
      if (Error.IsWarningAsError)
808
        ExistingError.IsWarningAsError = true;
809

810
      // Since it is the same error, we should take it as alias and remove it.
811
      ExistingError.EnabledDiagnosticAliases.emplace_back(Error.DiagnosticName);
812
      IT = Errors.erase(IT);
813
    }
814
  }
815
}
816

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.