https://github.com/QixiQAQ updated https://github.com/llvm/llvm-project/pull/202495
>From 98fef51122104728d2d7d751525a327f952b6f32 Mon Sep 17 00:00:00 2001 From: tongjinxuan <[email protected]> Date: Tue, 9 Jun 2026 15:08:54 +0800 Subject: [PATCH] [clangd] Replay macro definitions from preamble for clang-tidy checks Clang-tidy checkers observe preprocessor events via PPCallbacks. When using a preamble, macro definitions in the preamble region of the main file are not replayed during the main-file build, causing checkers like bugprone-reserved-identifier to miss them. This patch extends ReplayPreamble::replay() to also replay MacroDefined events for macros defined directly in the preamble region of the open file, similar to how InclusionDirective events are already replayed. Fixes: https://github.com/clangd/clangd/issues/2501 --- clang-tools-extra/clangd/ParsedAST.cpp | 43 +++++++++++---- .../clangd/unittests/ReplayPeambleTests.cpp | 52 +++++++++++++++++++ 2 files changed, 85 insertions(+), 10 deletions(-) diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp index e2a49f384a3e9..d52e182176342 100644 --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -142,16 +142,18 @@ class ReplayPreamble : private PPCallbacks { // Attach preprocessor hooks such that preamble events will be injected at // the appropriate time. // Events will be delivered to the *currently registered* PP callbacks. - static void attach(std::vector<Inclusion> Includes, CompilerInstance &Clang, - const PreambleBounds &PB) { + static void attach(std::vector<Inclusion> Includes, + const MainFileMacros Macros, + CompilerInstance &Clang, + const PreambleBounds &PB) { auto &PP = Clang.getPreprocessor(); auto *ExistingCallbacks = PP.getPPCallbacks(); // No need to replay events if nobody is listening. if (!ExistingCallbacks) return; PP.addPPCallbacks(std::unique_ptr<PPCallbacks>(new ReplayPreamble( - std::move(Includes), ExistingCallbacks, Clang.getSourceManager(), PP, - Clang.getLangOpts(), PB))); + std::move(Includes), std::move(Macros), ExistingCallbacks, + Clang.getSourceManager(), PP, Clang.getLangOpts(), PB))); // We're relying on the fact that addPPCallbacks keeps the old PPCallbacks // around, creating a chaining wrapper. Guard against other implementations. assert(PP.getPPCallbacks() != ExistingCallbacks && @@ -159,10 +161,12 @@ class ReplayPreamble : private PPCallbacks { } private: - ReplayPreamble(std::vector<Inclusion> Includes, PPCallbacks *Delegate, - const SourceManager &SM, Preprocessor &PP, - const LangOptions &LangOpts, const PreambleBounds &PB) - : Includes(std::move(Includes)), Delegate(Delegate), SM(SM), PP(PP) { +ReplayPreamble(std::vector<Inclusion> Includes, MainFileMacros Macros, + PPCallbacks *Delegate, const SourceManager &SM, + Preprocessor &PP, const LangOptions &LangOpts, + const PreambleBounds &PB) + : Includes(std::move(Includes)), Macros(std::move(Macros)), Delegate(Delegate), + SM(SM), PP(PP) { // Only tokenize the preamble section of the main file, as we are not // interested in the rest of the tokens. MainFileTokens = syntax::tokenize( @@ -193,6 +197,24 @@ class ReplayPreamble : private PPCallbacks { } void replay() { + // Replay macro definitions from the preamble region of the main file, + // so that clang-tidy checks can observe them. + for (const auto &[SID, Refs] : Macros.MacroRefs) { + for (const auto &Ref : Refs) { + if (!Ref.IsDefinition) + continue; + auto Loc = SM.getComposedLoc(SM.getMainFileID(), Ref.StartOffset); + Token Tok; + if (Lexer::getRawToken(Loc, Tok, SM, PP.getLangOpts(), false)) + continue; + if (auto *II = PP.getIdentifierInfo(Tok.getRawIdentifier())) { + Tok.setIdentifierInfo(II); + Tok.setKind(tok::identifier); + if (auto *MD = PP.getLocalMacroDirective(II)) + Delegate->MacroDefined(Tok, MD); + } + } + } for (const auto &Inc : Includes) { OptionalFileEntryRef File; if (Inc.Resolved != "") @@ -250,6 +272,7 @@ class ReplayPreamble : private PPCallbacks { } const std::vector<Inclusion> Includes; + const MainFileMacros Macros; PPCallbacks *Delegate; const SourceManager &SM; Preprocessor &PP; @@ -667,8 +690,8 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs, Includes = Preamble->Includes; Includes.MainFileIncludes = Patch->preambleIncludes(); // Replay the preamble includes so that clang-tidy checks can see them. - ReplayPreamble::attach(Patch->preambleIncludes(), *Clang, - Patch->modifiedBounds()); + ReplayPreamble::attach(Patch->preambleIncludes(), Patch->mainFileMacros(), + *Clang, Patch->modifiedBounds()); PI = *Preamble->Pragmas; } // Important: collectIncludeStructure is registered *after* ReplayPreamble! diff --git a/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp b/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp index 3200b6b3cb98d..77ec04abf5125 100644 --- a/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp +++ b/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp @@ -67,6 +67,7 @@ struct Inclusion { }; static std::vector<Inclusion> Includes; static std::vector<syntax::Token> SkippedFiles; +static std::vector<std::string> DefinedMacros; struct ReplayPreamblePPCallback : public PPCallbacks { const SourceManager &SM; explicit ReplayPreamblePPCallback(const SourceManager &SM) : SM(SM) {} @@ -84,6 +85,11 @@ struct ReplayPreamblePPCallback : public PPCallbacks { SrcMgr::CharacteristicKind) override { SkippedFiles.emplace_back(FilenameTok); } + + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override { + DefinedMacros.push_back(MacroNameTok.getIdentifierInfo()->getName().str()); + } }; struct ReplayPreambleCheck : public tidy::ClangTidyCheck { ReplayPreambleCheck(StringRef Name, tidy::ClangTidyContext *Context) @@ -107,6 +113,52 @@ MATCHER_P(rangeIs, R, "") { return arg.beginOffset() == R.Begin && arg.endOffset() == R.End; } +TEST(ReplayPreambleTest, MacroDefinitions) { + DefinedMacros.clear(); + + TestTU TU; + TU.ClangTidyProvider = addTidyChecks(CheckName); + TU.Code = R"cpp( + #ifndef _TEST_H + #define _TEST_H + #define _TEST_MACRO + #endif + )cpp"; + + Config Cfg; + Cfg.Diagnostics.ClangTidy.FastCheckFilter = Config::FastCheckPolicy::Loose; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + + TU.build(); + + EXPECT_THAT(DefinedMacros, testing::Contains(std::string("_TEST_H"))); + EXPECT_THAT(DefinedMacros, testing::Contains(std::string("_TEST_MACRO"))); +} + +TEST(ReplayPreambleTest, MacroDefinitionsPartialPreamble) { + DefinedMacros.clear(); + + TestTU TU; + TU.ClangTidyProvider = addTidyChecks(CheckName); + TU.Code = R"cpp( + #ifndef _TEST_H + #define _TEST_H + void unused(void); + #define _TEST_MACRO + #endif + )cpp"; + + Config Cfg; + Cfg.Diagnostics.ClangTidy.FastCheckFilter = Config::FastCheckPolicy::Loose; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + + TU.build(); + + // Both macros should be seen by clang-tidy + EXPECT_THAT(DefinedMacros, testing::Contains(std::string("_TEST_H"))); + EXPECT_THAT(DefinedMacros, testing::Contains(std::string("_TEST_MACRO"))); +} + TEST(ReplayPreambleTest, IncludesAndSkippedFiles) { TestTU TU; // This check records inclusion directives replayed by clangd. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
