[clang-tools-extra] [clang-tidy][NFC] Do less unnecessary work in `NoLintDirectiveHandler` (PR #147553)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 8 08:40:54 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tidy
Author: Victor Chernyakin (localspook)
<details>
<summary>Changes</summary>
I plan to do more work in this area, but I feel this is a good first set of changes.
Summary:
- `NoLintBlockToken` is too big: it stores a whole `NoLintToken` inside itself, when all it needs from that `NoLintToken` is its `Pos` and `ChecksGlob`.
- `formNoLintBlocks` builds up a vector of unmatched tokens, which are later transformed into errors. We can skip the middle step and make `formNoLintBlocks` create errors directly.
- In `generateCache`, the line `Cache[FileName] = ...;` default-constructs a cache entry only to immediately overwrite it. We can avoid that by using `Cache.try_emplace(FileName, ...);` instead.
- `NoLintToken`'s constructor takes `const std::optional<std::string>&` when all it needs is `const std::optional<StringRef>&`. This forces its caller, `getNoLints`, to create temporary strings.
- `NoLintToken::checks` returns `std::optional<std::string>` by value, creating unnecessary copies in `formNoLintBlocks`.
---
Full diff: https://github.com/llvm/llvm-project/pull/147553.diff
2 Files Affected:
- (modified) clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp (+55-60)
- (modified) clang-tools-extra/clang-tidy/NoLintDirectiveHandler.h (-2)
``````````diff
diff --git a/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp b/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp
index ed03b456f4954..bbae2c171f790 100644
--- a/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp
+++ b/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp
@@ -25,10 +25,9 @@
#include "llvm/ADT/StringSwitch.h"
#include <cassert>
#include <cstddef>
-#include <iterator>
+#include <memory>
#include <optional>
#include <string>
-#include <tuple>
#include <utility>
namespace clang::tidy {
@@ -79,7 +78,7 @@ class NoLintToken {
// - An empty string means nothing is suppressed - equivalent to NOLINT().
// - Negative globs ignored (which would effectively disable the suppression).
NoLintToken(NoLintType Type, size_t Pos,
- const std::optional<std::string> &Checks)
+ const std::optional<StringRef> &Checks)
: Type(Type), Pos(Pos), ChecksGlob(std::make_unique<CachedGlobList>(
Checks.value_or("*"),
/*KeepNegativeGlobs=*/false)) {
@@ -93,15 +92,17 @@ class NoLintToken {
// The location of the first character, "N", in "NOLINT".
size_t Pos;
+ // A glob of the checks this NOLINT token disables.
+ std::unique_ptr<CachedGlobList> ChecksGlob;
+
// If this NOLINT specifies checks, return the checks.
- std::optional<std::string> checks() const { return Checks; }
+ const std::optional<std::string> &checks() const { return Checks; }
// Whether this NOLINT applies to the provided check.
bool suppresses(StringRef Check) const { return ChecksGlob->contains(Check); }
private:
std::optional<std::string> Checks;
- std::unique_ptr<CachedGlobList> ChecksGlob;
};
} // namespace
@@ -131,11 +132,11 @@ static SmallVector<NoLintToken> getNoLints(StringRef Buffer) {
continue;
// Get checks, if specified.
- std::optional<std::string> Checks;
+ std::optional<StringRef> Checks;
if (Pos < Buffer.size() && Buffer[Pos] == '(') {
size_t ClosingBracket = Buffer.find_first_of("\n)", ++Pos);
if (ClosingBracket != StringRef::npos && Buffer[ClosingBracket] == ')') {
- Checks = Buffer.slice(Pos, ClosingBracket).str();
+ Checks = Buffer.slice(Pos, ClosingBracket);
Pos = ClosingBracket + 1;
}
}
@@ -155,34 +156,51 @@ namespace {
// Represents a source range within a pair of NOLINT(BEGIN/END) comments.
class NoLintBlockToken {
public:
- NoLintBlockToken(NoLintToken Begin, const NoLintToken &End)
- : Begin(std::move(Begin)), EndPos(End.Pos) {
- assert(this->Begin.Type == NoLintType::NoLintBegin);
- assert(End.Type == NoLintType::NoLintEnd);
- assert(this->Begin.Pos < End.Pos);
- assert(this->Begin.checks() == End.checks());
- }
+ NoLintBlockToken(size_t BeginPos, size_t EndPos,
+ std::unique_ptr<CachedGlobList> ChecksGlob)
+ : BeginPos(BeginPos), EndPos(EndPos), ChecksGlob(std::move(ChecksGlob)) {}
// Whether the provided diagnostic is within and is suppressible by this block
// of NOLINT(BEGIN/END) comments.
bool suppresses(size_t DiagPos, StringRef DiagName) const {
- return (Begin.Pos < DiagPos) && (DiagPos < EndPos) &&
- Begin.suppresses(DiagName);
+ return (BeginPos < DiagPos) && (DiagPos < EndPos) &&
+ ChecksGlob->contains(DiagName);
}
private:
- NoLintToken Begin;
+ size_t BeginPos;
size_t EndPos;
+ std::unique_ptr<CachedGlobList> ChecksGlob;
};
} // namespace
+// Construct a [clang-tidy-nolint] diagnostic to do with the unmatched
+// NOLINT(BEGIN/END) pair.
+static tooling::Diagnostic makeNoLintError(const SourceManager &SrcMgr,
+ FileID File,
+ const NoLintToken &NoLint) {
+ tooling::Diagnostic Error;
+ Error.DiagLevel = tooling::Diagnostic::Error;
+ Error.DiagnosticName = "clang-tidy-nolint";
+ StringRef Message =
+ (NoLint.Type == NoLintType::NoLintBegin)
+ ? ("unmatched 'NOLINTBEGIN' comment without a subsequent 'NOLINT"
+ "END' comment")
+ : ("unmatched 'NOLINTEND' comment without a previous 'NOLINT"
+ "BEGIN' comment");
+ SourceLocation Loc = SrcMgr.getComposedLoc(File, NoLint.Pos);
+ Error.Message = tooling::DiagnosticMessage(Message, SrcMgr, Loc);
+ return Error;
+}
+
// Match NOLINTBEGINs with their corresponding NOLINTENDs and move them into
-// `NoLintBlockToken`s. If any BEGINs or ENDs are left over, they are moved to
-// `UnmatchedTokens`.
+// `NoLintBlockToken`s. If any BEGINs or ENDs are left over, a diagnostic is
+// written to `NoLintErrors`.
static SmallVector<NoLintBlockToken>
-formNoLintBlocks(SmallVector<NoLintToken> NoLints,
- SmallVectorImpl<NoLintToken> &UnmatchedTokens) {
+formNoLintBlocks(SmallVector<NoLintToken> NoLints, const SourceManager &SrcMgr,
+ FileID File,
+ SmallVectorImpl<tooling::Diagnostic> &NoLintErrors) {
SmallVector<NoLintBlockToken> CompletedBlocks;
SmallVector<NoLintToken> Stack;
@@ -196,16 +214,20 @@ formNoLintBlocks(SmallVector<NoLintToken> NoLints,
// A new block is being started. Add it to the stack.
Stack.emplace_back(std::move(NoLint));
else if (NoLint.Type == NoLintType::NoLintEnd) {
- if (!Stack.empty() && Stack.back().checks() == NoLint.checks())
+ if (!Stack.empty() && Stack.back().checks() == NoLint.checks()) {
// The previous block is being closed. Pop one element off the stack.
- CompletedBlocks.emplace_back(Stack.pop_back_val(), NoLint);
- else
+ CompletedBlocks.emplace_back(Stack.back().Pos, NoLint.Pos,
+ std::move(Stack.back().ChecksGlob));
+ Stack.pop_back();
+ } else
// Trying to close the wrong block.
- UnmatchedTokens.emplace_back(std::move(NoLint));
+ NoLintErrors.emplace_back(makeNoLintError(SrcMgr, File, NoLint));
}
}
- llvm::move(Stack, std::back_inserter(UnmatchedTokens));
+ for (const NoLintToken &NoLint : Stack)
+ NoLintErrors.emplace_back(makeNoLintError(SrcMgr, File, NoLint));
+
return CompletedBlocks;
}
@@ -274,7 +296,7 @@ static std::pair<size_t, size_t> getLineStartAndEnd(StringRef Buffer,
size_t From) {
size_t StartPos = Buffer.find_last_of('\n', From) + 1;
size_t EndPos = std::min(Buffer.find('\n', From), Buffer.size());
- return std::make_pair(StartPos, EndPos);
+ return {StartPos, EndPos};
}
// Whether the line has a NOLINT of type = `Type` that can suppress the
@@ -317,9 +339,7 @@ bool NoLintDirectiveHandler::Impl::diagHasNoLint(
SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO,
bool EnableNoLintBlocks) {
// Translate the diagnostic's SourceLocation to a raw file + offset pair.
- FileID File;
- unsigned int Pos = 0;
- std::tie(File, Pos) = SrcMgr.getDecomposedSpellingLoc(DiagLoc);
+ const auto [File, Pos] = SrcMgr.getDecomposedSpellingLoc(DiagLoc);
// We will only see NOLINTs in user-authored sources. No point reading the
// file if it is a <built-in>.
@@ -349,46 +369,21 @@ bool NoLintDirectiveHandler::Impl::diagHasNoLint(
return false;
// Do we have cached NOLINT block locations for this file?
- if (Cache.count(*FileName) == 0)
+ if (!Cache.contains(*FileName))
// Warning: heavy operation - need to read entire file.
generateCache(SrcMgr, *FileName, File, *Buffer, NoLintErrors);
return withinNoLintBlock(Cache[*FileName], Pos, DiagName);
}
-// Construct a [clang-tidy-nolint] diagnostic to do with the unmatched
-// NOLINT(BEGIN/END) pair.
-static tooling::Diagnostic makeNoLintError(const SourceManager &SrcMgr,
- FileID File,
- const NoLintToken &NoLint) {
- tooling::Diagnostic Error;
- Error.DiagLevel = tooling::Diagnostic::Error;
- Error.DiagnosticName = "clang-tidy-nolint";
- StringRef Message =
- (NoLint.Type == NoLintType::NoLintBegin)
- ? ("unmatched 'NOLINTBEGIN' comment without a subsequent 'NOLINT"
- "END' comment")
- : ("unmatched 'NOLINTEND' comment without a previous 'NOLINT"
- "BEGIN' comment");
- SourceLocation Loc = SrcMgr.getComposedLoc(File, NoLint.Pos);
- Error.Message = tooling::DiagnosticMessage(Message, SrcMgr, Loc);
- return Error;
-}
-
// Find all NOLINT(BEGIN/END) blocks in a file and store in the cache.
void NoLintDirectiveHandler::Impl::generateCache(
const SourceManager &SrcMgr, StringRef FileName, FileID File,
StringRef Buffer, SmallVectorImpl<tooling::Diagnostic> &NoLintErrors) {
- // Read entire file to get all NOLINTs.
- SmallVector<NoLintToken> NoLints = getNoLints(Buffer);
-
- // Match each BEGIN with its corresponding END.
- SmallVector<NoLintToken> UnmatchedTokens;
- Cache[FileName] = formNoLintBlocks(std::move(NoLints), UnmatchedTokens);
-
- // Raise error for any BEGIN/END left over.
- for (const NoLintToken &NoLint : UnmatchedTokens)
- NoLintErrors.emplace_back(makeNoLintError(SrcMgr, File, NoLint));
+ // Read entire file to get all NOLINTs and match each BEGIN with its
+ // corresponding END, raising errors for any BEGIN or END that is unmatched.
+ Cache.try_emplace(FileName, formNoLintBlocks(getNoLints(Buffer), SrcMgr, File,
+ NoLintErrors));
}
//===----------------------------------------------------------------------===//
diff --git a/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.h b/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.h
index f66285672d04a..e862195abaabb 100644
--- a/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.h
+++ b/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.h
@@ -31,8 +31,6 @@ class NoLintDirectiveHandler {
public:
NoLintDirectiveHandler();
~NoLintDirectiveHandler();
- NoLintDirectiveHandler(const NoLintDirectiveHandler &) = delete;
- NoLintDirectiveHandler &operator=(const NoLintDirectiveHandler &) = delete;
bool shouldSuppress(DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Diag, llvm::StringRef DiagName,
``````````
</details>
https://github.com/llvm/llvm-project/pull/147553
More information about the cfe-commits
mailing list