forked from OSchip/llvm-project
[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 https://github.com/clangd/clangd/issues/266 Reviewers: sammccall Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D75286
This commit is contained in:
parent
15f1fe1506
commit
b9d9968f63
|
@ -296,57 +296,54 @@ static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line,
|
|||
return true;
|
||||
}
|
||||
|
||||
llvm::Optional<StringRef> getBuffer(const SourceManager &SM, FileID File,
|
||||
bool AllowIO) {
|
||||
// This is similar to the implementation of SourceManager::getBufferData(),
|
||||
// but uses ContentCache::getRawBuffer() rather than getBuffer() if
|
||||
// AllowIO=false, to avoid triggering file I/O if the file contents aren't
|
||||
// already mapped.
|
||||
bool CharDataInvalid = false;
|
||||
const SrcMgr::SLocEntry &Entry = SM.getSLocEntry(File, &CharDataInvalid);
|
||||
if (CharDataInvalid || !Entry.isFile())
|
||||
return llvm::None;
|
||||
const SrcMgr::ContentCache *Cache = Entry.getFile().getContentCache();
|
||||
const llvm::MemoryBuffer *Buffer =
|
||||
AllowIO ? Cache->getBuffer(SM.getDiagnostics(), SM.getFileManager(),
|
||||
SourceLocation(), &CharDataInvalid)
|
||||
: Cache->getRawBuffer();
|
||||
if (!Buffer || CharDataInvalid)
|
||||
return llvm::None;
|
||||
return Buffer->getBuffer();
|
||||
}
|
||||
|
||||
static bool LineIsMarkedWithNOLINT(const SourceManager &SM, SourceLocation Loc,
|
||||
unsigned DiagID,
|
||||
const ClangTidyContext &Context) {
|
||||
bool Invalid;
|
||||
const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
|
||||
if (Invalid)
|
||||
const ClangTidyContext &Context,
|
||||
bool AllowIO) {
|
||||
FileID File;
|
||||
unsigned Offset;
|
||||
std::tie(File, Offset) = SM.getDecomposedSpellingLoc(Loc);
|
||||
llvm::Optional<StringRef> Buffer = getBuffer(SM, File, AllowIO);
|
||||
if (!Buffer)
|
||||
return false;
|
||||
|
||||
// Check if there's a NOLINT on this line.
|
||||
const char *P = CharacterData;
|
||||
while (*P != '\0' && *P != '\r' && *P != '\n')
|
||||
++P;
|
||||
StringRef RestOfLine(CharacterData, P - CharacterData + 1);
|
||||
StringRef RestOfLine = Buffer->substr(Offset).split('\n').first;
|
||||
if (IsNOLINTFound("NOLINT", RestOfLine, DiagID, Context))
|
||||
return true;
|
||||
|
||||
// Check if there's a NOLINTNEXTLINE on the previous line.
|
||||
const char *BufBegin =
|
||||
SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
|
||||
if (Invalid || P == BufBegin)
|
||||
return false;
|
||||
|
||||
// Scan backwards over the current line.
|
||||
P = CharacterData;
|
||||
while (P != BufBegin && *P != '\n')
|
||||
--P;
|
||||
|
||||
// If we reached the begin of the file there is no line before it.
|
||||
if (P == BufBegin)
|
||||
return false;
|
||||
|
||||
// Skip over the newline.
|
||||
--P;
|
||||
const char *LineEnd = P;
|
||||
|
||||
// Now we're on the previous line. Skip to the beginning of it.
|
||||
while (P != BufBegin && *P != '\n')
|
||||
--P;
|
||||
|
||||
RestOfLine = StringRef(P, LineEnd - P + 1);
|
||||
if (IsNOLINTFound("NOLINTNEXTLINE", RestOfLine, DiagID, Context))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
StringRef PrevLine =
|
||||
Buffer->substr(0, Offset).rsplit('\n').first.rsplit('\n').second;
|
||||
return IsNOLINTFound("NOLINTNEXTLINE", PrevLine, DiagID, Context);
|
||||
}
|
||||
|
||||
static bool LineIsMarkedWithNOLINTinMacro(const SourceManager &SM,
|
||||
SourceLocation Loc, unsigned DiagID,
|
||||
const ClangTidyContext &Context) {
|
||||
const ClangTidyContext &Context,
|
||||
bool AllowIO) {
|
||||
while (true) {
|
||||
if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context))
|
||||
if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context, AllowIO))
|
||||
return true;
|
||||
if (!Loc.isMacroID())
|
||||
return false;
|
||||
|
@ -360,14 +357,13 @@ namespace tidy {
|
|||
|
||||
bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
|
||||
const Diagnostic &Info, ClangTidyContext &Context,
|
||||
bool CheckMacroExpansion) {
|
||||
bool AllowIO) {
|
||||
return Info.getLocation().isValid() &&
|
||||
DiagLevel != DiagnosticsEngine::Error &&
|
||||
DiagLevel != DiagnosticsEngine::Fatal &&
|
||||
(CheckMacroExpansion ? LineIsMarkedWithNOLINTinMacro
|
||||
: LineIsMarkedWithNOLINT)(Info.getSourceManager(),
|
||||
Info.getLocation(),
|
||||
Info.getID(), Context);
|
||||
LineIsMarkedWithNOLINTinMacro(Info.getSourceManager(),
|
||||
Info.getLocation(), Info.getID(),
|
||||
Context, AllowIO);
|
||||
}
|
||||
|
||||
} // namespace tidy
|
||||
|
|
|
@ -216,15 +216,12 @@ private:
|
|||
/// This does not handle suppression of notes following a suppressed diagnostic;
|
||||
/// that is left to the caller is it requires maintaining state in between calls
|
||||
/// to this function.
|
||||
/// The `CheckMacroExpansion` parameter determines whether the function should
|
||||
/// handle the case where the diagnostic is inside a macro expansion. A degree
|
||||
/// of control over this is needed because handling this case can require
|
||||
/// examining source files other than the one in which the diagnostic is
|
||||
/// located, and in some use cases we cannot rely on such other files being
|
||||
/// mapped in the SourceMapper.
|
||||
/// If `AllowIO` is false, the function does not attempt to read source files
|
||||
/// from disk which are not already mapped into memory; such files are treated
|
||||
/// as not containing a suppression comment.
|
||||
bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
|
||||
const Diagnostic &Info, ClangTidyContext &Context,
|
||||
bool CheckMacroExpansion = true);
|
||||
bool AllowIO = true);
|
||||
|
||||
/// A diagnostic consumer that turns each \c Diagnostic into a
|
||||
/// \c SourceManager-independent \c ClangTidyError.
|
||||
|
|
|
@ -310,14 +310,13 @@ ParsedAST::build(llvm::StringRef Version,
|
|||
// Check for suppression comment. Skip the check for diagnostics not
|
||||
// in the main file, because we don't want that function to query the
|
||||
// source buffer for preamble files. For the same reason, we ask
|
||||
// shouldSuppressDiagnostic not to follow macro expansions, since
|
||||
// those might take us into a preamble file as well.
|
||||
// shouldSuppressDiagnostic to avoid I/O.
|
||||
bool IsInsideMainFile =
|
||||
Info.hasSourceManager() &&
|
||||
isInsideMainFile(Info.getLocation(), Info.getSourceManager());
|
||||
if (IsInsideMainFile && tidy::shouldSuppressDiagnostic(
|
||||
DiagLevel, Info, *CTContext,
|
||||
/* CheckMacroExpansion = */ false)) {
|
||||
if (IsInsideMainFile &&
|
||||
tidy::shouldSuppressDiagnostic(DiagLevel, Info, *CTContext,
|
||||
/*AllowIO=*/false)) {
|
||||
return DiagnosticsEngine::Ignored;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -272,7 +272,11 @@ TEST(DiagnosticTest, ClangTidySuppressionComment) {
|
|||
double d = 8 / i; // NOLINT
|
||||
// NOLINTNEXTLINE
|
||||
double e = 8 / i;
|
||||
double f = [[8]] / i;
|
||||
#define BAD 8 / i
|
||||
double f = BAD; // NOLINT
|
||||
double g = [[8]] / i;
|
||||
#define BAD2 BAD
|
||||
double h = BAD2; // NOLINT
|
||||
}
|
||||
)cpp");
|
||||
TestTU TU = TestTU::withCode(Main.code());
|
||||
|
|
Loading…
Reference in New Issue