Skip to content

Commit b9d9968

Browse files
[clangd] Handle clang-tidy suppression comments for diagnostics inside macro expansions
Summary: Not handling this was a side-effect of being overly cautious when trying to avoid reading files for which clangd doesn't have the source mapped. Fixes clangd/clangd#266 Reviewers: sammccall Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D75286
1 parent 15f1fe1 commit b9d9968

File tree

4 files changed

+51
-55
lines changed

4 files changed

+51
-55
lines changed

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

+38-42
Original file line numberDiff line numberDiff line change
@@ -296,57 +296,54 @@ static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line,
296296
return true;
297297
}
298298

299+
llvm::Optional<StringRef> getBuffer(const SourceManager &SM, FileID File,
300+
bool AllowIO) {
301+
// This is similar to the implementation of SourceManager::getBufferData(),
302+
// but uses ContentCache::getRawBuffer() rather than getBuffer() if
303+
// AllowIO=false, to avoid triggering file I/O if the file contents aren't
304+
// already mapped.
305+
bool CharDataInvalid = false;
306+
const SrcMgr::SLocEntry &Entry = SM.getSLocEntry(File, &CharDataInvalid);
307+
if (CharDataInvalid || !Entry.isFile())
308+
return llvm::None;
309+
const SrcMgr::ContentCache *Cache = Entry.getFile().getContentCache();
310+
const llvm::MemoryBuffer *Buffer =
311+
AllowIO ? Cache->getBuffer(SM.getDiagnostics(), SM.getFileManager(),
312+
SourceLocation(), &CharDataInvalid)
313+
: Cache->getRawBuffer();
314+
if (!Buffer || CharDataInvalid)
315+
return llvm::None;
316+
return Buffer->getBuffer();
317+
}
318+
299319
static bool LineIsMarkedWithNOLINT(const SourceManager &SM, SourceLocation Loc,
300320
unsigned DiagID,
301-
const ClangTidyContext &Context) {
302-
bool Invalid;
303-
const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
304-
if (Invalid)
321+
const ClangTidyContext &Context,
322+
bool AllowIO) {
323+
FileID File;
324+
unsigned Offset;
325+
std::tie(File, Offset) = SM.getDecomposedSpellingLoc(Loc);
326+
llvm::Optional<StringRef> Buffer = getBuffer(SM, File, AllowIO);
327+
if (!Buffer)
305328
return false;
306329

307330
// Check if there's a NOLINT on this line.
308-
const char *P = CharacterData;
309-
while (*P != '\0' && *P != '\r' && *P != '\n')
310-
++P;
311-
StringRef RestOfLine(CharacterData, P - CharacterData + 1);
331+
StringRef RestOfLine = Buffer->substr(Offset).split('\n').first;
312332
if (IsNOLINTFound("NOLINT", RestOfLine, DiagID, Context))
313333
return true;
314334

315335
// Check if there's a NOLINTNEXTLINE on the previous line.
316-
const char *BufBegin =
317-
SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
318-
if (Invalid || P == BufBegin)
319-
return false;
320-
321-
// Scan backwards over the current line.
322-
P = CharacterData;
323-
while (P != BufBegin && *P != '\n')
324-
--P;
325-
326-
// If we reached the begin of the file there is no line before it.
327-
if (P == BufBegin)
328-
return false;
329-
330-
// Skip over the newline.
331-
--P;
332-
const char *LineEnd = P;
333-
334-
// Now we're on the previous line. Skip to the beginning of it.
335-
while (P != BufBegin && *P != '\n')
336-
--P;
337-
338-
RestOfLine = StringRef(P, LineEnd - P + 1);
339-
if (IsNOLINTFound("NOLINTNEXTLINE", RestOfLine, DiagID, Context))
340-
return true;
341-
342-
return false;
336+
StringRef PrevLine =
337+
Buffer->substr(0, Offset).rsplit('\n').first.rsplit('\n').second;
338+
return IsNOLINTFound("NOLINTNEXTLINE", PrevLine, DiagID, Context);
343339
}
344340

345341
static bool LineIsMarkedWithNOLINTinMacro(const SourceManager &SM,
346342
SourceLocation Loc, unsigned DiagID,
347-
const ClangTidyContext &Context) {
343+
const ClangTidyContext &Context,
344+
bool AllowIO) {
348345
while (true) {
349-
if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context))
346+
if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context, AllowIO))
350347
return true;
351348
if (!Loc.isMacroID())
352349
return false;
@@ -360,14 +357,13 @@ namespace tidy {
360357

361358
bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
362359
const Diagnostic &Info, ClangTidyContext &Context,
363-
bool CheckMacroExpansion) {
360+
bool AllowIO) {
364361
return Info.getLocation().isValid() &&
365362
DiagLevel != DiagnosticsEngine::Error &&
366363
DiagLevel != DiagnosticsEngine::Fatal &&
367-
(CheckMacroExpansion ? LineIsMarkedWithNOLINTinMacro
368-
: LineIsMarkedWithNOLINT)(Info.getSourceManager(),
369-
Info.getLocation(),
370-
Info.getID(), Context);
364+
LineIsMarkedWithNOLINTinMacro(Info.getSourceManager(),
365+
Info.getLocation(), Info.getID(),
366+
Context, AllowIO);
371367
}
372368

373369
} // namespace tidy

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

+4-7
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,12 @@ class ClangTidyContext {
216216
/// This does not handle suppression of notes following a suppressed diagnostic;
217217
/// that is left to the caller is it requires maintaining state in between calls
218218
/// to this function.
219-
/// The `CheckMacroExpansion` parameter determines whether the function should
220-
/// handle the case where the diagnostic is inside a macro expansion. A degree
221-
/// of control over this is needed because handling this case can require
222-
/// examining source files other than the one in which the diagnostic is
223-
/// located, and in some use cases we cannot rely on such other files being
224-
/// mapped in the SourceMapper.
219+
/// If `AllowIO` is false, the function does not attempt to read source files
220+
/// from disk which are not already mapped into memory; such files are treated
221+
/// as not containing a suppression comment.
225222
bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
226223
const Diagnostic &Info, ClangTidyContext &Context,
227-
bool CheckMacroExpansion = true);
224+
bool AllowIO = true);
228225

229226
/// A diagnostic consumer that turns each \c Diagnostic into a
230227
/// \c SourceManager-independent \c ClangTidyError.

clang-tools-extra/clangd/ParsedAST.cpp

+4-5
Original file line numberDiff line numberDiff line change
@@ -310,14 +310,13 @@ ParsedAST::build(llvm::StringRef Version,
310310
// Check for suppression comment. Skip the check for diagnostics not
311311
// in the main file, because we don't want that function to query the
312312
// source buffer for preamble files. For the same reason, we ask
313-
// shouldSuppressDiagnostic not to follow macro expansions, since
314-
// those might take us into a preamble file as well.
313+
// shouldSuppressDiagnostic to avoid I/O.
315314
bool IsInsideMainFile =
316315
Info.hasSourceManager() &&
317316
isInsideMainFile(Info.getLocation(), Info.getSourceManager());
318-
if (IsInsideMainFile && tidy::shouldSuppressDiagnostic(
319-
DiagLevel, Info, *CTContext,
320-
/* CheckMacroExpansion = */ false)) {
317+
if (IsInsideMainFile &&
318+
tidy::shouldSuppressDiagnostic(DiagLevel, Info, *CTContext,
319+
/*AllowIO=*/false)) {
321320
return DiagnosticsEngine::Ignored;
322321
}
323322
}

clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,11 @@ TEST(DiagnosticTest, ClangTidySuppressionComment) {
272272
double d = 8 / i; // NOLINT
273273
// NOLINTNEXTLINE
274274
double e = 8 / i;
275-
double f = [[8]] / i;
275+
#define BAD 8 / i
276+
double f = BAD; // NOLINT
277+
double g = [[8]] / i;
278+
#define BAD2 BAD
279+
double h = BAD2; // NOLINT
276280
}
277281
)cpp");
278282
TestTU TU = TestTU::withCode(Main.code());

0 commit comments

Comments
 (0)