Skip to content

Commit 8e29d19

Browse files
Revert "[clang-tidy] Cache the locations of NOLINTBEGIN/END blocks"
Build warning here: https://lab.llvm.org/buildbot/#/builders/57/builds/14322
1 parent c5907f8 commit 8e29d19

25 files changed

+334
-752
lines changed

clang-tools-extra/clang-tidy/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ add_clang_library(clangTidy
1717
ClangTidyProfiling.cpp
1818
ExpandModularHeadersPPCallbacks.cpp
1919
GlobList.cpp
20-
NoLintDirectiveHandler.cpp
2120

2221
DEPENDS
2322
ClangSACheckers

clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp

Lines changed: 213 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include "ClangTidyDiagnosticConsumer.h"
1919
#include "ClangTidyOptions.h"
2020
#include "GlobList.h"
21-
#include "NoLintDirectiveHandler.h"
2221
#include "clang/AST/ASTContext.h"
2322
#include "clang/AST/ASTDiagnostic.h"
2423
#include "clang/AST/Attr.h"
@@ -189,7 +188,7 @@ DiagnosticBuilder ClangTidyContext::diag(
189188
return DiagEngine->Report(ID);
190189
}
191190

192-
DiagnosticBuilder ClangTidyContext::diag(const tooling::Diagnostic &Error) {
191+
DiagnosticBuilder ClangTidyContext::diag(const ClangTidyError &Error) {
193192
SourceManager &SM = DiagEngine->getSourceManager();
194193
llvm::ErrorOr<const FileEntry *> File =
195194
SM.getFileManager().getFile(Error.Message.FilePath);
@@ -207,15 +206,6 @@ DiagnosticBuilder ClangTidyContext::configurationDiag(
207206
return diag("clang-tidy-config", Message, Level);
208207
}
209208

210-
bool ClangTidyContext::shouldSuppressDiagnostic(
211-
DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info,
212-
SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO,
213-
bool EnableNoLintBlocks) {
214-
std::string CheckName = getCheckName(Info.getID());
215-
return NoLintHandler.shouldSuppress(DiagLevel, Info, CheckName, NoLintErrors,
216-
AllowIO, EnableNoLintBlocks);
217-
}
218-
219209
void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {
220210
DiagEngine->setSourceManager(SourceMgr);
221211
}
@@ -317,9 +307,218 @@ void ClangTidyDiagnosticConsumer::finalizeLastError() {
317307
LastErrorPassesLineFilter = false;
318308
}
319309

310+
static bool isNOLINTFound(StringRef NolintDirectiveText, StringRef CheckName,
311+
StringRef Line, size_t *FoundNolintIndex = nullptr,
312+
StringRef *FoundNolintChecksStr = nullptr) {
313+
if (FoundNolintIndex)
314+
*FoundNolintIndex = StringRef::npos;
315+
if (FoundNolintChecksStr)
316+
*FoundNolintChecksStr = StringRef();
317+
318+
size_t NolintIndex = Line.find(NolintDirectiveText);
319+
if (NolintIndex == StringRef::npos)
320+
return false;
321+
322+
size_t BracketIndex = NolintIndex + NolintDirectiveText.size();
323+
if (BracketIndex < Line.size() && isalnum(Line[BracketIndex])) {
324+
// Reject this search result, otherwise it will cause false positives when
325+
// NOLINT is found as a substring of NOLINT(NEXTLINE/BEGIN/END).
326+
return false;
327+
}
328+
329+
// Check if specific checks are specified in brackets.
330+
if (BracketIndex < Line.size() && Line[BracketIndex] == '(') {
331+
++BracketIndex;
332+
const size_t BracketEndIndex = Line.find(')', BracketIndex);
333+
if (BracketEndIndex != StringRef::npos) {
334+
StringRef ChecksStr =
335+
Line.substr(BracketIndex, BracketEndIndex - BracketIndex);
336+
if (FoundNolintChecksStr)
337+
*FoundNolintChecksStr = ChecksStr;
338+
// Allow specifying a few checks with a glob expression, ignoring
339+
// negative globs (which would effectively disable the suppression).
340+
GlobList Globs(ChecksStr, /*KeepNegativeGlobs=*/false);
341+
if (!Globs.contains(CheckName))
342+
return false;
343+
}
344+
}
345+
346+
if (FoundNolintIndex)
347+
*FoundNolintIndex = NolintIndex;
348+
349+
return true;
350+
}
351+
352+
static llvm::Optional<StringRef> getBuffer(const SourceManager &SM, FileID File,
353+
bool AllowIO) {
354+
return AllowIO ? SM.getBufferDataOrNone(File)
355+
: SM.getBufferDataIfLoaded(File);
356+
}
357+
358+
static ClangTidyError createNolintError(const ClangTidyContext &Context,
359+
const SourceManager &SM,
360+
SourceLocation Loc,
361+
bool IsNolintBegin) {
362+
ClangTidyError Error("clang-tidy-nolint", ClangTidyError::Error,
363+
Context.getCurrentBuildDirectory(), false);
364+
StringRef Message =
365+
IsNolintBegin
366+
? ("unmatched 'NOLINTBEGIN' comment without a subsequent 'NOLINT"
367+
"END' comment")
368+
: ("unmatched 'NOLINTEND' comment without a previous 'NOLINT"
369+
"BEGIN' comment");
370+
Error.Message = tooling::DiagnosticMessage(Message, SM, Loc);
371+
return Error;
372+
}
373+
374+
static Optional<ClangTidyError> tallyNolintBegins(
375+
const ClangTidyContext &Context, const SourceManager &SM,
376+
StringRef CheckName, SmallVector<StringRef> Lines, SourceLocation LinesLoc,
377+
SmallVector<std::pair<SourceLocation, StringRef>> &NolintBegins) {
378+
// Keep a running total of how many NOLINT(BEGIN...END) blocks are active, as
379+
// well as the bracket expression (if any) that was used in the NOLINT
380+
// expression.
381+
size_t NolintIndex;
382+
StringRef NolintChecksStr;
383+
for (const auto &Line : Lines) {
384+
if (isNOLINTFound("NOLINTBEGIN", CheckName, Line, &NolintIndex,
385+
&NolintChecksStr)) {
386+
// Check if a new block is being started.
387+
NolintBegins.emplace_back(std::make_pair(
388+
LinesLoc.getLocWithOffset(NolintIndex), NolintChecksStr));
389+
} else if (isNOLINTFound("NOLINTEND", CheckName, Line, &NolintIndex,
390+
&NolintChecksStr)) {
391+
// Check if the previous block is being closed.
392+
if (!NolintBegins.empty() &&
393+
NolintBegins.back().second == NolintChecksStr) {
394+
NolintBegins.pop_back();
395+
} else {
396+
// Trying to close a nonexistent block. Return a diagnostic about this
397+
// misuse that can be displayed along with the original clang-tidy check
398+
// that the user was attempting to suppress.
399+
return createNolintError(Context, SM,
400+
LinesLoc.getLocWithOffset(NolintIndex), false);
401+
}
402+
}
403+
// Advance source location to the next line.
404+
LinesLoc = LinesLoc.getLocWithOffset(Line.size() + sizeof('\n'));
405+
}
406+
return None; // All NOLINT(BEGIN/END) use has been consistent so far.
407+
}
408+
409+
static bool
410+
lineIsWithinNolintBegin(const ClangTidyContext &Context,
411+
SmallVectorImpl<ClangTidyError> &SuppressionErrors,
412+
const SourceManager &SM, SourceLocation Loc,
413+
StringRef CheckName, StringRef TextBeforeDiag,
414+
StringRef TextAfterDiag) {
415+
Loc = SM.getExpansionRange(Loc).getBegin();
416+
SourceLocation FileStartLoc = SM.getLocForStartOfFile(SM.getFileID(Loc));
417+
SmallVector<std::pair<SourceLocation, StringRef>> NolintBegins;
418+
419+
// Check if there's an open NOLINT(BEGIN...END) block on the previous lines.
420+
SmallVector<StringRef> PrevLines;
421+
TextBeforeDiag.split(PrevLines, '\n');
422+
auto Error = tallyNolintBegins(Context, SM, CheckName, PrevLines,
423+
FileStartLoc, NolintBegins);
424+
if (Error) {
425+
SuppressionErrors.emplace_back(Error.getValue());
426+
}
427+
bool WithinNolintBegin = !NolintBegins.empty();
428+
429+
// Check that every block is terminated correctly on the following lines.
430+
SmallVector<StringRef> FollowingLines;
431+
TextAfterDiag.split(FollowingLines, '\n');
432+
Error = tallyNolintBegins(Context, SM, CheckName, FollowingLines, Loc,
433+
NolintBegins);
434+
if (Error) {
435+
SuppressionErrors.emplace_back(Error.getValue());
436+
}
437+
438+
// The following blocks were never closed. Return diagnostics for each
439+
// instance that can be displayed along with the original clang-tidy check
440+
// that the user was attempting to suppress.
441+
for (const auto &NolintBegin : NolintBegins) {
442+
SuppressionErrors.emplace_back(
443+
createNolintError(Context, SM, NolintBegin.first, true));
444+
}
445+
446+
return WithinNolintBegin && SuppressionErrors.empty();
447+
}
448+
449+
static bool
450+
lineIsMarkedWithNOLINT(const ClangTidyContext &Context,
451+
SmallVectorImpl<ClangTidyError> &SuppressionErrors,
452+
bool AllowIO, const SourceManager &SM,
453+
SourceLocation Loc, StringRef CheckName,
454+
bool EnableNolintBlocks) {
455+
// Get source code for this location.
456+
FileID File;
457+
unsigned Offset;
458+
std::tie(File, Offset) = SM.getDecomposedSpellingLoc(Loc);
459+
Optional<StringRef> Buffer = getBuffer(SM, File, AllowIO);
460+
if (!Buffer)
461+
return false;
462+
463+
// Check if there's a NOLINT on this line.
464+
StringRef TextAfterDiag = Buffer->substr(Offset);
465+
StringRef RestOfThisLine, FollowingLines;
466+
std::tie(RestOfThisLine, FollowingLines) = TextAfterDiag.split('\n');
467+
if (isNOLINTFound("NOLINT", CheckName, RestOfThisLine))
468+
return true;
469+
470+
// Check if there's a NOLINTNEXTLINE on the previous line.
471+
StringRef TextBeforeDiag = Buffer->substr(0, Offset);
472+
size_t LastNewLinePos = TextBeforeDiag.rfind('\n');
473+
StringRef PrevLines = (LastNewLinePos == StringRef::npos)
474+
? StringRef()
475+
: TextBeforeDiag.slice(0, LastNewLinePos);
476+
LastNewLinePos = PrevLines.rfind('\n');
477+
StringRef PrevLine = (LastNewLinePos == StringRef::npos)
478+
? PrevLines
479+
: PrevLines.substr(LastNewLinePos + 1);
480+
if (isNOLINTFound("NOLINTNEXTLINE", CheckName, PrevLine))
481+
return true;
482+
483+
// Check if this line is within a NOLINT(BEGIN...END) block.
484+
return EnableNolintBlocks &&
485+
lineIsWithinNolintBegin(Context, SuppressionErrors, SM, Loc, CheckName,
486+
TextBeforeDiag, TextAfterDiag);
487+
}
488+
489+
static bool lineIsMarkedWithNOLINTinMacro(
490+
const Diagnostic &Info, const ClangTidyContext &Context,
491+
SmallVectorImpl<ClangTidyError> &SuppressionErrors, bool AllowIO,
492+
bool EnableNolintBlocks) {
493+
const SourceManager &SM = Info.getSourceManager();
494+
SourceLocation Loc = Info.getLocation();
495+
std::string CheckName = Context.getCheckName(Info.getID());
496+
while (true) {
497+
if (lineIsMarkedWithNOLINT(Context, SuppressionErrors, AllowIO, SM, Loc,
498+
CheckName, EnableNolintBlocks))
499+
return true;
500+
if (!Loc.isMacroID())
501+
return false;
502+
Loc = SM.getImmediateExpansionRange(Loc).getBegin();
503+
}
504+
return false;
505+
}
506+
320507
namespace clang {
321508
namespace tidy {
322509

510+
bool shouldSuppressDiagnostic(
511+
DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info,
512+
ClangTidyContext &Context,
513+
SmallVectorImpl<ClangTidyError> &SuppressionErrors, bool AllowIO,
514+
bool EnableNolintBlocks) {
515+
return Info.getLocation().isValid() &&
516+
DiagLevel != DiagnosticsEngine::Error &&
517+
DiagLevel != DiagnosticsEngine::Fatal &&
518+
lineIsMarkedWithNOLINTinMacro(Info, Context, SuppressionErrors,
519+
AllowIO, EnableNolintBlocks);
520+
}
521+
323522
const llvm::StringMap<tooling::Replacements> *
324523
getFixIt(const tooling::Diagnostic &Diagnostic, bool GetFixFromNotes) {
325524
if (!Diagnostic.Message.Fix.empty())
@@ -346,15 +545,12 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
346545
if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
347546
return;
348547

349-
SmallVector<tooling::Diagnostic, 1> SuppressionErrors;
350-
if (Context.shouldSuppressDiagnostic(DiagLevel, Info, SuppressionErrors,
351-
EnableNolintBlocks)) {
548+
SmallVector<ClangTidyError, 1> SuppressionErrors;
549+
if (shouldSuppressDiagnostic(DiagLevel, Info, Context, SuppressionErrors,
550+
EnableNolintBlocks)) {
352551
++Context.Stats.ErrorsIgnoredNOLINT;
353552
// Ignored a warning, should ignore related notes as well
354553
LastErrorWasIgnored = true;
355-
Context.DiagEngine->Clear();
356-
for (const auto &Error : SuppressionErrors)
357-
Context.diag(Error);
358554
return;
359555
}
360556

clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
#include "ClangTidyOptions.h"
1313
#include "ClangTidyProfiling.h"
14-
#include "NoLintDirectiveHandler.h"
1514
#include "clang/Basic/Diagnostic.h"
1615
#include "clang/Tooling/Core/Diagnostic.h"
1716
#include "llvm/ADT/DenseMap.h"
@@ -96,33 +95,13 @@ class ClangTidyContext {
9695
DiagnosticBuilder diag(StringRef CheckName, StringRef Message,
9796
DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
9897

99-
DiagnosticBuilder diag(const tooling::Diagnostic &Error);
98+
DiagnosticBuilder diag(const ClangTidyError &Error);
10099

101100
/// Report any errors to do with reading the configuration using this method.
102101
DiagnosticBuilder
103102
configurationDiag(StringRef Message,
104103
DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
105104

106-
/// Check whether a given diagnostic should be suppressed due to the presence
107-
/// of a "NOLINT" suppression comment.
108-
/// This is exposed so that other tools that present clang-tidy diagnostics
109-
/// (such as clangd) can respect the same suppression rules as clang-tidy.
110-
/// This does not handle suppression of notes following a suppressed
111-
/// diagnostic; that is left to the caller as it requires maintaining state in
112-
/// between calls to this function.
113-
/// If any NOLINT is malformed, e.g. a BEGIN without a subsequent END, an
114-
/// error about it will be returned in output \param NoLintErrors.
115-
/// If \param AllowIO is false, the function does not attempt to read source
116-
/// files from disk which are not already mapped into memory; such files are
117-
/// treated as not containing a suppression comment.
118-
/// \param EnableNoLintBlocks controls whether to honor NOLINTBEGIN/NOLINTEND
119-
/// blocks; if false, only considers line-level disabling.
120-
bool
121-
shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
122-
const Diagnostic &Info,
123-
SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
124-
bool AllowIO = true, bool EnableNoLintBlocks = true);
125-
126105
/// Sets the \c SourceManager of the used \c DiagnosticsEngine.
127106
///
128107
/// This is called from the \c ClangTidyCheck base class.
@@ -229,10 +208,30 @@ class ClangTidyContext {
229208
std::string ProfilePrefix;
230209

231210
bool AllowEnablingAnalyzerAlphaCheckers;
232-
233-
NoLintDirectiveHandler NoLintHandler;
234211
};
235212

213+
/// Check whether a given diagnostic should be suppressed due to the presence
214+
/// of a "NOLINT" suppression comment.
215+
/// This is exposed so that other tools that present clang-tidy diagnostics
216+
/// (such as clangd) can respect the same suppression rules as clang-tidy.
217+
/// This does not handle suppression of notes following a suppressed diagnostic;
218+
/// that is left to the caller as it requires maintaining state in between calls
219+
/// to this function.
220+
/// If `AllowIO` is false, the function does not attempt to read source files
221+
/// from disk which are not already mapped into memory; such files are treated
222+
/// as not containing a suppression comment.
223+
/// \param EnableNolintBlocks controls whether to honor NOLINTBEGIN/NOLINTEND
224+
/// blocks; if false, only considers line-level disabling.
225+
/// If suppression is not possible due to improper use of "NOLINT" comments -
226+
/// for example, the use of a "NOLINTBEGIN" comment that is not followed by a
227+
/// "NOLINTEND" comment - a diagnostic regarding the improper use is returned
228+
/// via the output argument `SuppressionErrors`.
229+
bool shouldSuppressDiagnostic(
230+
DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info,
231+
ClangTidyContext &Context,
232+
SmallVectorImpl<ClangTidyError> &SuppressionErrors, bool AllowIO = true,
233+
bool EnableNolintBlocks = true);
234+
236235
/// Gets the Fix attached to \p Diagnostic.
237236
/// If there isn't a Fix attached to the diagnostic and \p AnyFix is true, Check
238237
/// to see if exactly one note has a Fix and return it. Otherwise return

0 commit comments

Comments
 (0)