Skip to content

Commit 5da7c04

Browse files
Re-land "Cache the locations of NOLINTBEGIN/END blocks" with fix for build bot
1 parent 3d8fa00 commit 5da7c04

25 files changed

+752
-334
lines changed

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

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

2122
DEPENDS
2223
ClangSACheckers

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

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

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

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+
209219
void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {
210220
DiagEngine->setSourceManager(SourceMgr);
211221
}
@@ -307,218 +317,9 @@ void ClangTidyDiagnosticConsumer::finalizeLastError() {
307317
LastErrorPassesLineFilter = false;
308318
}
309319

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-
507320
namespace clang {
508321
namespace tidy {
509322

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-
522323
const llvm::StringMap<tooling::Replacements> *
523324
getFixIt(const tooling::Diagnostic &Diagnostic, bool GetFixFromNotes) {
524325
if (!Diagnostic.Message.Fix.empty())
@@ -545,12 +346,15 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
545346
if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
546347
return;
547348

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

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

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

1212
#include "ClangTidyOptions.h"
1313
#include "ClangTidyProfiling.h"
14+
#include "NoLintDirectiveHandler.h"
1415
#include "clang/Basic/Diagnostic.h"
1516
#include "clang/Tooling/Core/Diagnostic.h"
1617
#include "llvm/ADT/DenseMap.h"
@@ -95,13 +96,33 @@ class ClangTidyContext {
9596
DiagnosticBuilder diag(StringRef CheckName, StringRef Message,
9697
DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
9798

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

100101
/// Report any errors to do with reading the configuration using this method.
101102
DiagnosticBuilder
102103
configurationDiag(StringRef Message,
103104
DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
104105

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+
105126
/// Sets the \c SourceManager of the used \c DiagnosticsEngine.
106127
///
107128
/// This is called from the \c ClangTidyCheck base class.
@@ -208,29 +229,9 @@ class ClangTidyContext {
208229
std::string ProfilePrefix;
209230

210231
bool AllowEnablingAnalyzerAlphaCheckers;
211-
};
212232

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);
233+
NoLintDirectiveHandler NoLintHandler;
234+
};
234235

235236
/// Gets the Fix attached to \p Diagnostic.
236237
/// If there isn't a Fix attached to the diagnostic and \p AnyFix is true, Check

0 commit comments

Comments
 (0)