clang-tools  16.0.0git
NoLintDirectiveHandler.cpp
Go to the documentation of this file.
1 //===-- clang-tools-extra/clang-tidy/NoLintDirectiveHandler.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 the NoLintDirectiveHandler class, which is used
10 /// to locate NOLINT comments in the file being analyzed, to decide whether a
11 /// diagnostic should be suppressed.
12 ///
13 //===----------------------------------------------------------------------===//
14 
15 #include "NoLintDirectiveHandler.h"
16 #include "GlobList.h"
17 #include "clang/Basic/LLVM.h"
18 #include "clang/Basic/SourceLocation.h"
19 #include "clang/Basic/SourceManager.h"
20 #include "clang/Tooling/Core/Diagnostic.h"
21 #include "llvm/ADT/ArrayRef.h"
22 #include "llvm/ADT/None.h"
23 #include "llvm/ADT/Optional.h"
24 #include "llvm/ADT/STLExtras.h"
25 #include "llvm/ADT/SmallVector.h"
26 #include "llvm/ADT/StringExtras.h"
27 #include "llvm/ADT/StringMap.h"
28 #include "llvm/ADT/StringSwitch.h"
29 #include <cassert>
30 #include <cstddef>
31 #include <iterator>
32 #include <string>
33 #include <tuple>
34 #include <type_traits>
35 #include <utility>
36 
37 namespace clang {
38 namespace tidy {
39 
40 //===----------------------------------------------------------------------===//
41 // NoLintType
42 //===----------------------------------------------------------------------===//
43 
44 // The type - one of NOLINT[NEXTLINE/BEGIN/END].
46 
47 // Convert a string like "NOLINTNEXTLINE" to its enum `Type::NoLintNextLine`.
48 // Return `None` if the string is unrecognized.
49 static Optional<NoLintType> strToNoLintType(StringRef Str) {
50  auto Type = llvm::StringSwitch<Optional<NoLintType>>(Str)
51  .Case("NOLINT", NoLintType::NoLint)
52  .Case("NOLINTNEXTLINE", NoLintType::NoLintNextLine)
53  .Case("NOLINTBEGIN", NoLintType::NoLintBegin)
54  .Case("NOLINTEND", NoLintType::NoLintEnd)
55  .Default(None);
56  return Type;
57 }
58 
59 //===----------------------------------------------------------------------===//
60 // NoLintToken
61 //===----------------------------------------------------------------------===//
62 
63 // Whitespace within a NOLINT's check list shall be ignored.
64 // "NOLINT( check1, check2 )" is equivalent to "NOLINT(check1,check2)".
65 // Return the check list with all extraneous whitespace removed.
66 static std::string trimWhitespace(StringRef Checks) {
67  SmallVector<StringRef> Split;
68  Checks.split(Split, ',');
69  for (StringRef &Check : Split)
70  Check = Check.trim();
71  return llvm::join(Split, ",");
72 }
73 
74 namespace {
75 
76 // Record the presence of a NOLINT comment - its type, location, checks -
77 // as parsed from the file's character contents.
78 class NoLintToken {
79 public:
80  // \param Checks:
81  // - If unspecified (i.e. `None`) then ALL checks are suppressed - equivalent
82  // to NOLINT(*).
83  // - An empty string means nothing is suppressed - equivalent to NOLINT().
84  // - Negative globs ignored (which would effectively disable the suppression).
85  NoLintToken(NoLintType Type, size_t Pos, const Optional<std::string> &Checks)
86  : Type(Type), Pos(Pos), ChecksGlob(std::make_unique<CachedGlobList>(
87  Checks.value_or("*"),
88  /*KeepNegativeGlobs=*/false)) {
89  if (Checks)
90  this->Checks = trimWhitespace(*Checks);
91  }
92 
93  // The type - one of NOLINT[NEXTLINE/BEGIN/END].
95 
96  // The location of the first character, "N", in "NOLINT".
97  size_t Pos;
98 
99  // If this NOLINT specifies checks, return the checks.
100  Optional<std::string> checks() const { return Checks; }
101 
102  // Whether this NOLINT applies to the provided check.
103  bool suppresses(StringRef Check) const { return ChecksGlob->contains(Check); }
104 
105 private:
106  Optional<std::string> Checks;
107  std::unique_ptr<CachedGlobList> ChecksGlob;
108 };
109 
110 } // namespace
111 
112 // Consume the entire buffer and return all `NoLintToken`s that were found.
113 static SmallVector<NoLintToken> getNoLints(StringRef Buffer) {
114  static constexpr llvm::StringLiteral NOLINT = "NOLINT";
115  SmallVector<NoLintToken> NoLints;
116 
117  size_t Pos = 0;
118  while (Pos < Buffer.size()) {
119  // Find NOLINT:
120  const size_t NoLintPos = Buffer.find(NOLINT, Pos);
121  if (NoLintPos == StringRef::npos)
122  break; // Buffer exhausted
123 
124  // Read [A-Z] characters immediately after "NOLINT", e.g. the "NEXTLINE" in
125  // "NOLINTNEXTLINE".
126  Pos = NoLintPos + NOLINT.size();
127  while (Pos < Buffer.size() && llvm::isAlpha(Buffer[Pos]))
128  ++Pos;
129 
130  // Is this a recognized NOLINT type?
131  const Optional<NoLintType> NoLintType =
132  strToNoLintType(Buffer.slice(NoLintPos, Pos));
133  if (!NoLintType)
134  continue;
135 
136  // Get checks, if specified.
137  Optional<std::string> Checks;
138  if (Pos < Buffer.size() && Buffer[Pos] == '(') {
139  size_t ClosingBracket = Buffer.find_first_of("\n)", ++Pos);
140  if (ClosingBracket != StringRef::npos && Buffer[ClosingBracket] == ')') {
141  Checks = Buffer.slice(Pos, ClosingBracket).str();
142  Pos = ClosingBracket + 1;
143  }
144  }
145 
146  NoLints.emplace_back(*NoLintType, NoLintPos, Checks);
147  }
148 
149  return NoLints;
150 }
151 
152 //===----------------------------------------------------------------------===//
153 // NoLintBlockToken
154 //===----------------------------------------------------------------------===//
155 
156 namespace {
157 
158 // Represents a source range within a pair of NOLINT(BEGIN/END) comments.
159 class NoLintBlockToken {
160 public:
161  NoLintBlockToken(NoLintToken Begin, const NoLintToken &End)
162  : Begin(std::move(Begin)), EndPos(End.Pos) {
163  assert(this->Begin.Type == NoLintType::NoLintBegin);
164  assert(End.Type == NoLintType::NoLintEnd);
165  assert(this->Begin.Pos < End.Pos);
166  assert(this->Begin.checks() == End.checks());
167  }
168 
169  // Whether the provided diagnostic is within and is suppressible by this block
170  // of NOLINT(BEGIN/END) comments.
171  bool suppresses(size_t DiagPos, StringRef DiagName) const {
172  return (Begin.Pos < DiagPos) && (DiagPos < EndPos) &&
173  Begin.suppresses(DiagName);
174  }
175 
176 private:
177  NoLintToken Begin;
178  size_t EndPos;
179 };
180 
181 } // namespace
182 
183 // Match NOLINTBEGINs with their corresponding NOLINTENDs and move them into
184 // `NoLintBlockToken`s. If any BEGINs or ENDs are left over, they are moved to
185 // `UnmatchedTokens`.
186 static SmallVector<NoLintBlockToken>
187 formNoLintBlocks(SmallVector<NoLintToken> NoLints,
188  SmallVectorImpl<NoLintToken> &UnmatchedTokens) {
189  SmallVector<NoLintBlockToken> CompletedBlocks;
190  SmallVector<NoLintToken> Stack;
191 
192  // Nested blocks must be fully contained within their parent block. What this
193  // means is that when you have a series of nested BEGIN tokens, the END tokens
194  // shall appear in the reverse order, starting with the closing of the
195  // inner-most block first, then the next level up, and so on. This is
196  // essentially a last-in-first-out/stack system.
197  for (NoLintToken &NoLint : NoLints) {
198  if (NoLint.Type == NoLintType::NoLintBegin)
199  // A new block is being started. Add it to the stack.
200  Stack.emplace_back(std::move(NoLint));
201  else if (NoLint.Type == NoLintType::NoLintEnd) {
202  if (!Stack.empty() && Stack.back().checks() == NoLint.checks())
203  // The previous block is being closed. Pop one element off the stack.
204  CompletedBlocks.emplace_back(Stack.pop_back_val(), NoLint);
205  else
206  // Trying to close the wrong block.
207  UnmatchedTokens.emplace_back(std::move(NoLint));
208  }
209  }
210 
211  llvm::move(Stack, std::back_inserter(UnmatchedTokens));
212  return CompletedBlocks;
213 }
214 
215 //===----------------------------------------------------------------------===//
216 // NoLintDirectiveHandler::Impl
217 //===----------------------------------------------------------------------===//
218 
220 public:
221  bool shouldSuppress(DiagnosticsEngine::Level DiagLevel,
222  const Diagnostic &Diag, StringRef DiagName,
223  SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
224  bool AllowIO, bool EnableNoLintBlocks);
225 
226 private:
227  bool diagHasNoLintInMacro(const Diagnostic &Diag, StringRef DiagName,
228  SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
229  bool AllowIO, bool EnableNoLintBlocks);
230 
231  bool diagHasNoLint(StringRef DiagName, SourceLocation DiagLoc,
232  const SourceManager &SrcMgr,
233  SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
234  bool AllowIO, bool EnableNoLintBlocks);
235 
236  void generateCache(const SourceManager &SrcMgr, StringRef FileName,
237  FileID File, StringRef Buffer,
238  SmallVectorImpl<tooling::Diagnostic> &NoLintErrors);
239 
240  llvm::StringMap<SmallVector<NoLintBlockToken>> Cache;
241 };
242 
244  DiagnosticsEngine::Level DiagLevel, const Diagnostic &Diag,
245  StringRef DiagName, SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
246  bool AllowIO, bool EnableNoLintBlocks) {
247  if (DiagLevel >= DiagnosticsEngine::Error)
248  return false;
249  return diagHasNoLintInMacro(Diag, DiagName, NoLintErrors, AllowIO,
250  EnableNoLintBlocks);
251 }
252 
253 // Look at the macro's spelling location for a NOLINT. If none is found, keep
254 // looking up the call stack.
255 bool NoLintDirectiveHandler::Impl::diagHasNoLintInMacro(
256  const Diagnostic &Diag, StringRef DiagName,
257  SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO,
258  bool EnableNoLintBlocks) {
259  SourceLocation DiagLoc = Diag.getLocation();
260  if (DiagLoc.isInvalid())
261  return false;
262  const SourceManager &SrcMgr = Diag.getSourceManager();
263  while (true) {
264  if (diagHasNoLint(DiagName, DiagLoc, SrcMgr, NoLintErrors, AllowIO,
265  EnableNoLintBlocks))
266  return true;
267  if (!DiagLoc.isMacroID())
268  return false;
269  DiagLoc = SrcMgr.getImmediateExpansionRange(DiagLoc).getBegin();
270  }
271  return false;
272 }
273 
274 // Look behind and ahead for '\n' characters. These mark the start and end of
275 // this line.
276 static std::pair<size_t, size_t> getLineStartAndEnd(StringRef Buffer,
277  size_t From) {
278  size_t StartPos = Buffer.find_last_of('\n', From) + 1;
279  size_t EndPos = std::min(Buffer.find('\n', From), Buffer.size());
280  return std::make_pair(StartPos, EndPos);
281 }
282 
283 // Whether the line has a NOLINT of type = `Type` that can suppress the
284 // diagnostic `DiagName`.
285 static bool lineHasNoLint(StringRef Buffer,
286  std::pair<size_t, size_t> LineStartAndEnd,
287  NoLintType Type, StringRef DiagName) {
288  // Get all NOLINTs on the line.
289  Buffer = Buffer.slice(LineStartAndEnd.first, LineStartAndEnd.second);
290  SmallVector<NoLintToken> NoLints = getNoLints(Buffer);
291 
292  // Do any of these NOLINTs match the desired type and diag name?
293  return llvm::any_of(NoLints, [&](const NoLintToken &NoLint) {
294  return NoLint.Type == Type && NoLint.suppresses(DiagName);
295  });
296 }
297 
298 // Whether the provided diagnostic is located within and is suppressible by a
299 // block of NOLINT(BEGIN/END) comments.
300 static bool withinNoLintBlock(ArrayRef<NoLintBlockToken> NoLintBlocks,
301  size_t DiagPos, StringRef DiagName) {
302  return llvm::any_of(NoLintBlocks, [&](const NoLintBlockToken &NoLintBlock) {
303  return NoLintBlock.suppresses(DiagPos, DiagName);
304  });
305 }
306 
307 // Get the file contents as a string.
308 static Optional<StringRef> getBuffer(const SourceManager &SrcMgr, FileID File,
309  bool AllowIO) {
310  return AllowIO ? SrcMgr.getBufferDataOrNone(File)
311  : SrcMgr.getBufferDataIfLoaded(File);
312 }
313 
314 // We will check for NOLINTs and NOLINTNEXTLINEs first. Checking for these is
315 // not so expensive (just need to parse the current and previous lines). Only if
316 // that fails do we look for NOLINT(BEGIN/END) blocks (which requires reading
317 // the entire file).
318 bool NoLintDirectiveHandler::Impl::diagHasNoLint(
319  StringRef DiagName, SourceLocation DiagLoc, const SourceManager &SrcMgr,
320  SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO,
321  bool EnableNoLintBlocks) {
322  // Translate the diagnostic's SourceLocation to a raw file + offset pair.
323  FileID File;
324  unsigned int Pos = 0;
325  std::tie(File, Pos) = SrcMgr.getDecomposedSpellingLoc(DiagLoc);
326 
327  // We will only see NOLINTs in user-authored sources. No point reading the
328  // file if it is a <built-in>.
329  Optional<StringRef> FileName = SrcMgr.getNonBuiltinFilenameForID(File);
330  if (!FileName)
331  return false;
332 
333  // Get file contents.
334  Optional<StringRef> Buffer = getBuffer(SrcMgr, File, AllowIO);
335  if (!Buffer)
336  return false;
337 
338  // Check if there's a NOLINT on this line.
339  auto ThisLine = getLineStartAndEnd(*Buffer, Pos);
340  if (lineHasNoLint(*Buffer, ThisLine, NoLintType::NoLint, DiagName))
341  return true;
342 
343  // Check if there's a NOLINTNEXTLINE on the previous line.
344  if (ThisLine.first > 0) {
345  auto PrevLine = getLineStartAndEnd(*Buffer, ThisLine.first - 1);
346  if (lineHasNoLint(*Buffer, PrevLine, NoLintType::NoLintNextLine, DiagName))
347  return true;
348  }
349 
350  // Check if this line is within a NOLINT(BEGIN/END) block.
351  if (!EnableNoLintBlocks)
352  return false;
353 
354  // Do we have cached NOLINT block locations for this file?
355  if (Cache.count(*FileName) == 0)
356  // Warning: heavy operation - need to read entire file.
357  generateCache(SrcMgr, *FileName, File, *Buffer, NoLintErrors);
358 
359  return withinNoLintBlock(Cache[*FileName], Pos, DiagName);
360 }
361 
362 // Construct a [clang-tidy-nolint] diagnostic to do with the unmatched
363 // NOLINT(BEGIN/END) pair.
364 static tooling::Diagnostic makeNoLintError(const SourceManager &SrcMgr,
365  FileID File,
366  const NoLintToken &NoLint) {
367  tooling::Diagnostic Error;
368  Error.DiagLevel = tooling::Diagnostic::Error;
369  Error.DiagnosticName = "clang-tidy-nolint";
370  StringRef Message =
372  ? ("unmatched 'NOLINTBEGIN' comment without a subsequent 'NOLINT"
373  "END' comment")
374  : ("unmatched 'NOLINTEND' comment without a previous 'NOLINT"
375  "BEGIN' comment");
376  SourceLocation Loc = SrcMgr.getComposedLoc(File, NoLint.Pos);
377  Error.Message = tooling::DiagnosticMessage(Message, SrcMgr, Loc);
378  return Error;
379 }
380 
381 // Find all NOLINT(BEGIN/END) blocks in a file and store in the cache.
382 void NoLintDirectiveHandler::Impl::generateCache(
383  const SourceManager &SrcMgr, StringRef FileName, FileID File,
384  StringRef Buffer, SmallVectorImpl<tooling::Diagnostic> &NoLintErrors) {
385  // Read entire file to get all NOLINTs.
386  SmallVector<NoLintToken> NoLints = getNoLints(Buffer);
387 
388  // Match each BEGIN with its corresponding END.
389  SmallVector<NoLintToken> UnmatchedTokens;
390  Cache[FileName] = formNoLintBlocks(std::move(NoLints), UnmatchedTokens);
391 
392  // Raise error for any BEGIN/END left over.
393  for (const NoLintToken &NoLint : UnmatchedTokens)
394  NoLintErrors.emplace_back(makeNoLintError(SrcMgr, File, NoLint));
395 }
396 
397 //===----------------------------------------------------------------------===//
398 // NoLintDirectiveHandler
399 //===----------------------------------------------------------------------===//
400 
402  : PImpl(std::make_unique<Impl>()) {}
403 
405 
407  DiagnosticsEngine::Level DiagLevel, const Diagnostic &Diag,
408  StringRef DiagName, SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
409  bool AllowIO, bool EnableNoLintBlocks) {
410  return PImpl->shouldSuppress(DiagLevel, Diag, DiagName, NoLintErrors, AllowIO,
411  EnableNoLintBlocks);
412 }
413 
414 } // namespace tidy
415 } // namespace clang
Loc
SourceLocation Loc
Definition: KernelNameRestrictionCheck.cpp:45
clang::tidy::NoLintDirectiveHandler::Impl
Definition: NoLintDirectiveHandler.cpp:219
clang::tidy::trimWhitespace
static std::string trimWhitespace(StringRef Checks)
Definition: NoLintDirectiveHandler.cpp:66
clang::tidy::NoLintType::NoLintEnd
@ NoLintEnd
Type
NodeType Type
Definition: HTMLGenerator.cpp:74
Checks
static cl::opt< std::string > Checks("checks", cl::desc(R"( Comma-separated list of globs with optional '-' prefix. Globs are processed in order of appearance in the list. Globs without '-' prefix add checks with matching names to the set, globs with the '-' prefix remove checks with matching names from the set of enabled checks. This option's value is appended to the value of the 'Checks' option in .clang-tidy file, if any. )"), cl::init(""), cl::cat(ClangTidyCategory))
clang::tidy::NoLintType
NoLintType
Definition: NoLintDirectiveHandler.cpp:45
clang::tidy::bugprone::Message
static const char Message[]
Definition: ReservedIdentifierCheck.cpp:31
clang::tidy::cppcoreguidelines::join
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
Definition: SpecialMemberFunctionsCheck.cpp:78
clang::tidy::getLineStartAndEnd
static std::pair< size_t, size_t > getLineStartAndEnd(StringRef Buffer, size_t From)
Definition: NoLintDirectiveHandler.cpp:276
clang::tidy::makeNoLintError
static tooling::Diagnostic makeNoLintError(const SourceManager &SrcMgr, FileID File, const NoLintToken &NoLint)
Definition: NoLintDirectiveHandler.cpp:364
clang::tidy::lineHasNoLint
static bool lineHasNoLint(StringRef Buffer, std::pair< size_t, size_t > LineStartAndEnd, NoLintType Type, StringRef DiagName)
Definition: NoLintDirectiveHandler.cpp:285
Pos
size_t Pos
Definition: NoLintDirectiveHandler.cpp:97
clang::tidy::NoLintDirectiveHandler::~NoLintDirectiveHandler
~NoLintDirectiveHandler()
clang::tidy::NoLintDirectiveHandler::shouldSuppress
bool shouldSuppress(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Diag, llvm::StringRef DiagName, llvm::SmallVectorImpl< tooling::Diagnostic > &NoLintErrors, bool AllowIO, bool EnableNoLintBlocks)
Definition: NoLintDirectiveHandler.cpp:406
Error
constexpr static llvm::SourceMgr::DiagKind Error
Definition: ConfigCompile.cpp:591
Diagnostic
DiagnosticCallback Diagnostic
Definition: ConfigCompile.cpp:100
clang::tidy::withinNoLintBlock
static bool withinNoLintBlock(ArrayRef< NoLintBlockToken > NoLintBlocks, size_t DiagPos, StringRef DiagName)
Definition: NoLintDirectiveHandler.cpp:300
clang::tidy::NoLintType::NoLintBegin
@ NoLintBegin
clang::tidy::strToNoLintType
static Optional< NoLintType > strToNoLintType(StringRef Str)
Definition: NoLintDirectiveHandler.cpp:49
GlobList.h
FileName
StringRef FileName
Definition: KernelNameRestrictionCheck.cpp:46
clang::tidy::NoLintType::NoLintNextLine
@ NoLintNextLine
clang::tidy::NoLintDirectiveHandler::Impl::shouldSuppress
bool shouldSuppress(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Diag, StringRef DiagName, SmallVectorImpl< tooling::Diagnostic > &NoLintErrors, bool AllowIO, bool EnableNoLintBlocks)
Definition: NoLintDirectiveHandler.cpp:243
clang::tidy::getBuffer
static Optional< StringRef > getBuffer(const SourceManager &SrcMgr, FileID File, bool AllowIO)
Definition: NoLintDirectiveHandler.cpp:308
clang::tidy::NoLintDirectiveHandler::NoLintDirectiveHandler
NoLintDirectiveHandler()
Definition: NoLintDirectiveHandler.cpp:401
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::NoLintType::NoLint
@ NoLint
clang::tidy::getNoLints
static SmallVector< NoLintToken > getNoLints(StringRef Buffer)
Definition: NoLintDirectiveHandler.cpp:113
clang::tidy::formNoLintBlocks
static SmallVector< NoLintBlockToken > formNoLintBlocks(SmallVector< NoLintToken > NoLints, SmallVectorImpl< NoLintToken > &UnmatchedTokens)
Definition: NoLintDirectiveHandler.cpp:187
NoLintDirectiveHandler.h