diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h --- a/clang/include/clang/ASTMatchers/ASTMatchers.h +++ b/clang/include/clang/ASTMatchers/ASTMatchers.h @@ -300,6 +300,26 @@ return RE.match(Filename); } +/// Matches statements that are (transitively) expanded from the named macro. +/// Does not match if only part of the statement is expanded from that macro or +/// if different parts of the the statement are expanded from different +/// appearances of the macro. +/// +/// FIXME: Change to be a polymorphic matcher that works on any syntactic +/// node. There's nothing `Stmt`-specific about it. +AST_MATCHER_P(clang::Stmt, isExpandedFromMacro, llvm::StringRef, MacroName) { + // Verifies that the statement' beginning and ending are both expanded from + // the same instance of the given macro. + auto& Context = Finder->getASTContext(); + auto B = + internal::getExpansionLocOfMacro(MacroName, Node.getBeginLoc(), Context); + if (!B) return false; + auto E = + internal::getExpansionLocOfMacro(MacroName, Node.getEndLoc(), Context); + if (!E) return false; + return *B == *E; +} + /// Matches declarations. /// /// Examples matches \c X, \c C, and the friend declaration inside \c C; diff --git a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h --- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -1872,6 +1872,13 @@ return Node.getSubStmt(); } +/// If \p Loc is (transitively) expanded from macro \p MacroName, returns the +/// location (in the chain of expansions) at which \p MacroName was +/// expanded. Since the macro may have been expanded inside a series of +/// expansions, that location may itself be a MacroID. +llvm::Optional +getExpansionLocOfMacro(StringRef MacroName, SourceLocation Loc, + const ASTContext &Context); } // namespace internal } // namespace ast_matchers diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp --- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp +++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp @@ -19,6 +19,7 @@ #include "clang/AST/PrettyPrinter.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/LLVM.h" +#include "clang/Lex/Lexer.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/None.h" @@ -595,6 +596,38 @@ return matchesNodeFullFast(Node); } +// Checks whether \p Loc points to a token with source text of \p TokenText. +static bool isTokenAtLoc(const SourceManager &SM, const LangOptions &LangOpts, + StringRef Text, SourceLocation Loc) { + llvm::SmallString<16> Buffer; + bool Invalid = false; + // Since `Loc` may point into an expansion buffer, which has no corresponding + // source, we need to look at the spelling location to read the actual source. + StringRef TokenText = clang::Lexer::getSpelling( + SM.getSpellingLoc(Loc), Buffer, SM, LangOpts, &Invalid); + return !Invalid && Text == TokenText; +} + +llvm::Optional +getExpansionLocOfMacro(StringRef MacroName, SourceLocation Loc, + const ASTContext &Context) { + auto& SM = Context.getSourceManager(); + const auto& LangOpts = Context.getLangOpts(); + while (Loc.isMacroID()) { + auto Expansion = SM.getSLocEntry(SM.getFileID(Loc)).getExpansion(); + if (Expansion.isMacroArgExpansion()) + // Check macro argument for an expansion of the given macro. For example, + // `F(G(3))`, where `MacroName` is `G`. + if (auto ArgLoc = getExpansionLocOfMacro( + MacroName, Expansion.getSpellingLoc(), Context)) + return ArgLoc; + Loc = Expansion.getExpansionLocStart(); + if (isTokenAtLoc(SM, LangOpts, MacroName, Loc)) + return Loc; + } + return llvm::None; +} + } // end namespace internal const internal::VariadicDynCastAllOfMatcher diff --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp --- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp +++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp @@ -18,6 +18,120 @@ namespace clang { namespace ast_matchers { +TEST(IsExpandedFromMacro, ShouldMatchInFile) { + std::string input = R"cc( +#define MY_MACRO(a) (4 + (a)) + void Test() { MY_MACRO(4); } + )cc"; + EXPECT_TRUE(matches(input, binaryOperator(isExpandedFromMacro("MY_MACRO")))); +} + +TEST(IsExpandedFromMacro, ShouldMatchNested) { + std::string input = R"cc( +#define MY_MACRO(a) (4 + (a)) +#define WRAPPER(a) MY_MACRO(a) + void Test() { WRAPPER(4); } + )cc"; + EXPECT_TRUE(matches(input, binaryOperator(isExpandedFromMacro("MY_MACRO")))); +} + +TEST(IsExpandedFromMacro, ShouldMatchIntermediate) { + std::string input = R"cc( +#define IMPL(a) (4 + (a)) +#define MY_MACRO(a) IMPL(a) +#define WRAPPER(a) MY_MACRO(a) + void Test() { WRAPPER(4); } + )cc"; + EXPECT_TRUE(matches(input, binaryOperator(isExpandedFromMacro("MY_MACRO")))); +} + +TEST(IsExpandedFromMacro, ShouldMatchTransitive) { + std::string input = R"cc( +#define MY_MACRO(a) (4 + (a)) +#define WRAPPER(a) MY_MACRO(a) + void Test() { WRAPPER(4); } + )cc"; + EXPECT_TRUE(matches(input, binaryOperator(isExpandedFromMacro("WRAPPER")))); +} + +TEST(IsExpandedFromMacro, ShouldMatchArgument) { + std::string input = R"cc( +#define MY_MACRO(a) (4 + (a)) + void Test() { + int x = 5; + MY_MACRO(x); + } + )cc"; + EXPECT_TRUE(matches(input, declRefExpr(isExpandedFromMacro("MY_MACRO")))); +} + +// Like IsExpandedFromMacroShouldMatchArgumentMacro, but the argument is itself +// a macro. +TEST(IsExpandedFromMacro, ShouldMatchArgumentMacroExpansion) { + std::string input = R"cc( +#define MY_MACRO(a) (4 + (a)) +#define IDENTITY(a) (a) + void Test() { + IDENTITY(MY_MACRO(2)); + } + )cc"; + EXPECT_TRUE(matches(input, binaryOperator(isExpandedFromMacro("IDENTITY")))); +} + +TEST(IsExpandedFromMacro, ShouldMatchWhenInArgument) { + std::string input = R"cc( +#define MY_MACRO(a) (4 + (a)) +#define IDENTITY(a) (a) + void Test() { + IDENTITY(MY_MACRO(2)); + } + )cc"; + EXPECT_TRUE(matches(input, binaryOperator(isExpandedFromMacro("MY_MACRO")))); +} + +TEST(IsExpandedFromMacro, ShouldMatchObjectMacro) { + std::string input = R"cc( +#define PLUS (2 + 2) + void Test() { + PLUS; + } + )cc"; + EXPECT_TRUE(matches(input, binaryOperator(isExpandedFromMacro("PLUS")))); +} + +TEST(IsExpandedFromMacro, ShouldNotMatchBeginOnly) { + std::string input = R"cc( +#define ONE_PLUS 1+ + void Test() { ONE_PLUS 4; } + )cc"; + EXPECT_TRUE( + notMatches(input, binaryOperator(isExpandedFromMacro("ONE_PLUS")))); +} + +TEST(IsExpandedFromMacro, ShouldNotMatchEndOnly) { + std::string input = R"cc( +#define PLUS_ONE +1 + void Test() { 4 PLUS_ONE; } + )cc"; + EXPECT_TRUE( + notMatches(input, binaryOperator(isExpandedFromMacro("PLUS_ONE")))); +} + +TEST(IsExpandedFromMacro, ShouldNotMatchDifferentMacro) { + std::string input = R"cc( +#define MY_MACRO(a) (4 + (a)) + void Test() { MY_MACRO(4); } + )cc"; + EXPECT_TRUE(notMatches(input, binaryOperator(isExpandedFromMacro("OTHER")))); +} + +TEST(IsExpandedFromMacro, ShouldNotMatchDifferentInstances) { + std::string input = R"cc( +#define FOUR 4 + void Test() { FOUR + FOUR; } + )cc"; + EXPECT_TRUE(notMatches(input, binaryOperator(isExpandedFromMacro("FOUR")))); +} TEST(AllOf, AllOverloadsWork) { const char Program[] =