Chromium Code Reviews| Index: tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp |
| diff --git a/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp b/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp |
| index a268739ccd31eddba1b9f780e521fc96fd8e74d4..e6fba51e2d61d58a208100a582f12f1875814d6f 100644 |
| --- a/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp |
| +++ b/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp |
| @@ -44,6 +44,11 @@ namespace { |
| const char kBlinkFieldPrefix[] = "m_"; |
| const char kBlinkStaticMemberPrefix[] = "s_"; |
| const char kGeneratedFileRegex[] = "^gen/|/gen/"; |
| +const char kGMockMethodNamePrefix[] = "gmock_"; |
| +const int kGMockMaxArgOffset = 400; // ~8 lines of backtracking. |
| +const char kGMockExpectCallArgName[] = "call"; |
| +const char kGMockExpectCallFilename[] = |
| + "testing/gmock/include/gmock/gmock-spec-builders.h"; |
| template <typename MatcherType, typename NodeType> |
| bool IsMatching(const MatcherType& matcher, |
| @@ -79,6 +84,41 @@ AST_MATCHER_P(clang::FunctionTemplateDecl, |
| return InnerMatcher.matches(*Node.getTemplatedDecl(), Finder, Builder); |
| } |
| +// Matches a CXXMethodDecl of a method declared via MOCK_METHODx macro if such |
| +// method mocks a method matched by the InnerMatcher. For example if "foo" |
| +// matcher matches "interfaceMethod", then mocksMethod(foo()) will match |
| +// "gmock_interfaceMethod" declared by MOCK_METHOD_x(interfaceMethod). |
| +AST_MATCHER_P(clang::CXXMethodDecl, |
| + mocksMethod, |
| + clang::ast_matchers::internal::Matcher<clang::CXXMethodDecl>, |
| + InnerMatcher) { |
| + if (!Node.getDeclName().isIdentifier()) |
| + return false; |
| + |
| + llvm::StringRef method_name = Node.getName(); |
| + if (!method_name.startswith(kGMockMethodNamePrefix)) |
| + return false; |
| + |
| + llvm::StringRef mocked_method_name = |
| + method_name.substr(strlen(kGMockMethodNamePrefix)); |
| + for (const auto& potentially_mocked_method : Node.getParent()->methods()) { |
| + if (!potentially_mocked_method->isVirtual()) |
| + continue; |
| + |
| + clang::DeclarationName decl_name = potentially_mocked_method->getDeclName(); |
| + if (!decl_name.isIdentifier() || |
| + potentially_mocked_method->getName() != mocked_method_name) |
| + continue; |
| + if (potentially_mocked_method->getNumParams() != Node.getNumParams()) |
| + continue; |
| + |
| + if (InnerMatcher.matches(*potentially_mocked_method, Finder, Builder)) |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| // If |InnerMatcher| matches |top|, then the returned matcher will match: |
| // - |top::function| |
| // - |top::Class::method| |
| @@ -677,6 +717,43 @@ struct TargetNodeTraits<clang::UnresolvedUsingValueDecl> { |
| static const char* GetType() { return "UnresolvedUsingValueDecl"; } |
| }; |
| +bool JumpAboveMacroScratchSpace(const clang::SourceManager& source_manager, |
| + clang::SourceLocation* loc) { |
| + if (loc->isMacroID()) { |
| + // Try to jump "above" the scratch buffer if |loc| is inside |
| + // token##Concatenation. |
| + const int kMaxJumps = 5; |
| + bool verified_out_of_scratch_space = false; |
| + for (int i = 0; i < kMaxJumps && !verified_out_of_scratch_space; i++) { |
| + clang::SourceLocation spell = source_manager.getSpellingLoc(*loc); |
| + verified_out_of_scratch_space = |
| + source_manager.getBufferName(spell) != "<scratch space>"; |
| + if (!verified_out_of_scratch_space) |
| + *loc = source_manager.getImmediateMacroCallerLoc(*loc); |
| + } |
| + if (!verified_out_of_scratch_space) |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +llvm::StringRef GetActualText(const clang::ASTContext& context, |
| + const clang::SourceManager& source_manager, |
| + clang::SourceLocation loc, |
| + size_t length, |
| + clang::CharSourceRange* out_range) { |
|
dcheng
2017/01/06 06:58:45
Stylistically, I'm not a fan of optional output ar
Łukasz Anforowicz
2017/01/07 01:16:29
I've inlined GetActualText back into the only call
|
| + clang::SourceLocation spell = source_manager.getSpellingLoc(loc); |
| + clang::CharSourceRange range = clang::CharSourceRange::getCharRange( |
| + spell, spell.getLocWithOffset(length)); |
| + |
| + if (out_range) |
| + *out_range = range; |
| + |
| + return clang::Lexer::getSourceText(range, source_manager, |
| + context.getLangOpts()); |
| +} |
| + |
| template <typename TargetNode> |
| class RewriterBase : public MatchFinder::MatchCallback { |
| public: |
| @@ -698,21 +775,8 @@ class RewriterBase : public MatchFinder::MatchCallback { |
| const clang::ASTContext& context = *result.Context; |
| const clang::SourceManager& source_manager = *result.SourceManager; |
| - if (loc.isMacroID()) { |
| - // Try to jump "above" the scratch buffer if |loc| is inside |
| - // token##Concatenation. |
| - const int kMaxJumps = 5; |
| - bool verified_out_of_scratch_space = false; |
| - for (int i = 0; i < kMaxJumps && !verified_out_of_scratch_space; i++) { |
| - clang::SourceLocation spell = source_manager.getSpellingLoc(loc); |
| - verified_out_of_scratch_space = |
| - source_manager.getBufferName(spell) != "<scratch space>"; |
| - if (!verified_out_of_scratch_space) |
| - loc = source_manager.getImmediateMacroCallerLoc(loc); |
| - } |
| - if (!verified_out_of_scratch_space) |
| - return false; |
| - } |
| + if (!JumpAboveMacroScratchSpace(source_manager, &loc)) |
| + return false; |
| // If the edit affects only the first character of the identifier, then |
| // narrow down the edit to only this single character. This is important |
| @@ -726,32 +790,37 @@ class RewriterBase : public MatchFinder::MatchCallback { |
| expected_old_text = expected_old_text.substr(0, 1); |
| new_text = new_text.substr(0, 1); |
| } |
| - clang::SourceLocation spell = source_manager.getSpellingLoc(loc); |
| - clang::CharSourceRange range = clang::CharSourceRange::getCharRange( |
| - spell, spell.getLocWithOffset(expected_old_text.size())); |
| // We need to ensure that |actual_old_text| is the same as |
| // |expected_old_text| - it can be different if |actual_old_text| contains |
| // a macro argument (see DEFINE_WITH_TOKEN_CONCATENATION2 in |
| // macros-original.cc testcase). |
| - StringRef actual_old_text = clang::Lexer::getSourceText( |
| - range, source_manager, context.getLangOpts()); |
| + clang::CharSourceRange range; |
| + StringRef actual_old_text = GetActualText(context, source_manager, loc, |
| + expected_old_text.size(), &range); |
| if (actual_old_text != expected_old_text) |
| return false; |
| if (replacement) |
| *replacement = Replacement(source_manager, range, new_text); |
| + |
| return true; |
| } |
| + virtual clang::SourceLocation GetTargetLoc( |
| + const MatchFinder::MatchResult& result) { |
| + return TargetNodeTraits<TargetNode>::GetLoc(GetTargetNode(result)); |
| + } |
| + |
| void AddReplacement(const MatchFinder::MatchResult& result, |
| llvm::StringRef old_name, |
| std::string new_name) { |
| if (old_name == new_name) |
| return; |
| - clang::SourceLocation loc = |
| - TargetNodeTraits<TargetNode>::GetLoc(GetTargetNode(result)); |
| + clang::SourceLocation loc = GetTargetLoc(result); |
| + if (loc.isInvalid()) |
| + return; |
| Replacement replacement; |
| if (!GenerateReplacement(result, loc, old_name, new_name, &replacement)) |
| @@ -776,9 +845,14 @@ class DeclRewriterBase : public RewriterBase<TargetNode> { |
| explicit DeclRewriterBase(std::set<Replacement>* replacements) |
| : Base(replacements) {} |
| - void run(const MatchFinder::MatchResult& result) override { |
| + const DeclNode* GetDecl(const MatchFinder::MatchResult& result) { |
| const DeclNode* decl = result.Nodes.getNodeAs<DeclNode>("decl"); |
| assert(decl); |
| + return decl; |
| + } |
| + |
| + void run(const MatchFinder::MatchResult& result) override { |
| + const DeclNode* decl = GetDecl(result); |
| llvm::StringRef old_name = decl->getName(); |
| // Return early if there's no name to be renamed. |
| @@ -834,6 +908,72 @@ using UnresolvedMemberRewriter = |
| using UsingDeclRewriter = DeclRewriterBase<clang::UsingDecl, clang::NamedDecl>; |
| +class GMockMemberRewriter |
| + : public DeclRewriterBase<clang::CXXMethodDecl, clang::MemberExpr> { |
| + public: |
| + using Base = DeclRewriterBase<clang::CXXMethodDecl, clang::MemberExpr>; |
| + |
| + explicit GMockMemberRewriter(std::set<Replacement>* replacements) |
| + : Base(replacements) {} |
| + |
| + clang::SourceLocation GetTargetLoc( |
| + const MatchFinder::MatchResult& result) override { |
| + const clang::SourceManager& source_manager = *result.SourceManager; |
| + const clang::ASTContext& context = *result.Context; |
| + const clang::CXXMethodDecl* decl = GetDecl(result); |
| + llvm::StringRef old_name = decl->getName(); |
| + |
| + // This is the location of gmock_##MockedMethod identifier. |
| + clang::SourceLocation target_loc = Base::GetTargetLoc(result); |
| + if (!JumpAboveMacroScratchSpace(source_manager, &target_loc)) |
| + return clang::SourceLocation(); |
| + |
| + // Find |macro_arg_loc| that points at |methodName| in |
| + // EXPECT_CALL(obj, methodName(...)). |
| + clang::SourceLocation macro_arg_loc; |
| + for (int offset = 0; offset > -kGMockMaxArgOffset; offset--) { |
| + macro_arg_loc = target_loc.getLocWithOffset(offset); |
| + |
| + // Check if |macro_arg_loc| contains the mocked method name. |
| + llvm::StringRef actual_text_of_macro_arg = GetActualText( |
| + context, source_manager, macro_arg_loc, old_name.size(), nullptr); |
| + if (actual_text_of_macro_arg != old_name) |
| + continue; |
| + |
| + // Location inside macro definition where the macro argument is expanded. |
| + clang::SourceLocation macro_param_expansion_loc; |
| + if (!source_manager.isMacroArgExpansion(macro_arg_loc, |
| + ¯o_param_expansion_loc)) |
| + continue; |
| + |
| + // Check if |macro_param_expansion_loc| points here: |
| + // testing/gmock/include/gmock/gmock-spec-builders.h:1844:20: |
| + // 1843: #define GMOCK_EXPECT_CALL_IMPL_(obj, call) \ |
| + // 1844: ((obj).gmock_##call).InternalExpectedAt(__FILE__, __LINE_... |
| + // ^ here |
| + // 1845: #define EXPECT_CALL(obj, call) GMOCK_EXPECT_CALL_IMPL_(obj, c... |
| + clang::SourceLocation spell_loc = |
| + source_manager.getSpellingLoc(macro_param_expansion_loc); |
| + StringRef filename = source_manager.getFilename(spell_loc); |
| + if (!filename.endswith(kGMockExpectCallFilename)) |
| + continue; |
| + |
| + StringRef actual_text_of_macro_param_name = |
| + GetActualText(context, source_manager, macro_param_expansion_loc, |
| + strlen(kGMockExpectCallArgName), nullptr); |
| + if (actual_text_of_macro_param_name != kGMockExpectCallArgName) |
| + continue; |
| + |
| + // Found it! |
| + return macro_arg_loc; |
| + } |
| + |
| + llvm::errs() << "Couldn't rewrite GMock's EXPECT_CALL at " |
| + << target_loc.printToString(source_manager) << "\n"; |
| + return clang::SourceLocation(); |
| + } |
| +}; |
| + |
| clang::DeclarationName GetUnresolvedName( |
| const clang::UnresolvedMemberExpr& expr) { |
| return expr.getMemberName(); |
| @@ -1156,7 +1296,7 @@ int main(int argc, const char* argv[]) { |
| // S s; |
| // s.g(); |
| // void (S::*p)() = &S::g; |
| - // matches |&S::g| but not |s.g()|. |
| + // matches |&S::g| but not |s.g|. |
| auto method_ref_matcher = id( |
| "expr", declRefExpr(to(method_decl_matcher), |
| // Ignore template substitutions. |
| @@ -1170,7 +1310,7 @@ int main(int argc, const char* argv[]) { |
| // S s; |
| // s.g(); |
| // void (S::*p)() = &S::g; |
| - // matches |s.g()| but not |&S::g|. |
| + // matches |s.g| but not |&S::g|. |
| auto method_member_matcher = |
| id("expr", memberExpr(member(method_decl_matcher))); |
| @@ -1339,6 +1479,18 @@ int main(int argc, const char* argv[]) { |
| match_finder.addMatcher(cxx_dependent_scope_member_expr_matcher, |
| &cxx_dependent_scope_member_expr_rewriter); |
| + // GMock calls lookup ======== |
| + // Given |
| + // EXPECT_CALL(obj, myMethod(...)) |
| + // will match obj.gmock_myMethod(...) call generated by the macro |
| + // (but only if it mocks a Blink method). |
| + auto gmock_member_matcher = |
| + id("expr", memberExpr(hasDeclaration( |
| + decl(cxxMethodDecl(mocksMethod(method_decl_matcher)))))); |
| + GMockMemberRewriter gmock_member_rewriter(&replacements); |
| + match_finder.addMatcher(gmock_member_matcher, &gmock_member_rewriter); |
| + |
| + // Run all the matchers. |
| std::unique_ptr<clang::tooling::FrontendActionFactory> factory = |
| clang::tooling::newFrontendActionFactory(&match_finder); |
| int result = tool.run(factory.get()); |