// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // Changes Blink-style names to Chrome-style names. Currently transforms: // fields: // int m_operationCount => int operation_count_ // variables (including parameters): // int mySuperVariable => int my_super_variable // constants: // const int maxThings => const int kMaxThings // free functions and methods: // void doThisThenThat() => void DoThisAndThat() #include <assert.h> #include <algorithm> #include <fstream> #include <memory> #include <string> #include <unordered_map> #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchersMacros.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/SourceManager.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/TargetSelect.h" #if defined(_WIN32) #include <windows.h> #else #include <sys/file.h> #include <unistd.h> #endif using namespace clang::ast_matchers; using clang::tooling::CommonOptionsParser; using clang::tooling::Replacement; using llvm::StringRef; namespace { const char kBlinkFieldPrefix[] = "m_"; const char kBlinkStaticMemberPrefix[] = "s_"; const char kGeneratedFileRegex[] = "^gen/|/gen/"; const clang::ast_matchers::internal:: VariadicDynCastAllOfMatcher<clang::Expr, clang::UnresolvedMemberExpr> unresolvedMemberExpr; AST_MATCHER(clang::FunctionDecl, isOverloadedOperator) { return Node.isOverloadedOperator(); } AST_MATCHER(clang::CXXMethodDecl, isInstanceMethod) { return Node.isInstance(); } AST_MATCHER_P(clang::FunctionTemplateDecl, templatedDecl, clang::ast_matchers::internal::Matcher<clang::FunctionDecl>, InnerMatcher) { return InnerMatcher.matches(*Node.getTemplatedDecl(), Finder, Builder); } // If |InnerMatcher| matches |top|, then the returned matcher will match: // - |top::function| // - |top::Class::method| // - |top::internal::Class::method| AST_MATCHER_P( clang::NestedNameSpecifier, hasTopLevelPrefix, clang::ast_matchers::internal::Matcher<clang::NestedNameSpecifier>, InnerMatcher) { const clang::NestedNameSpecifier* NodeToMatch = &Node; while (NodeToMatch->getPrefix()) NodeToMatch = NodeToMatch->getPrefix(); return InnerMatcher.matches(*NodeToMatch, Finder, Builder); } // This will narrow CXXCtorInitializers down for both FieldDecls and // IndirectFieldDecls (ie. anonymous unions and such). In both cases // getAnyMember() will return a FieldDecl which we can match against. AST_MATCHER_P(clang::CXXCtorInitializer, forAnyField, clang::ast_matchers::internal::Matcher<clang::FieldDecl>, InnerMatcher) { const clang::FieldDecl* NodeAsDecl = Node.getAnyMember(); return (NodeAsDecl != nullptr && InnerMatcher.matches(*NodeAsDecl, Finder, Builder)); } // Matches if all the overloads in the lookup set match the provided matcher. AST_MATCHER_P(clang::OverloadExpr, allOverloadsMatch, clang::ast_matchers::internal::Matcher<clang::NamedDecl>, InnerMatcher) { if (Node.getNumDecls() == 0) return false; for (clang::NamedDecl* decl : Node.decls()) { if (!InnerMatcher.matches(*decl, Finder, Builder)) return false; } return true; } template <typename T> bool MatchAllOverriddenMethods( const clang::CXXMethodDecl& decl, T&& inner_matcher, clang::ast_matchers::internal::ASTMatchFinder* finder, clang::ast_matchers::internal::BoundNodesTreeBuilder* builder) { bool override_matches = false; bool override_not_matches = false; for (auto it = decl.begin_overridden_methods(); it != decl.end_overridden_methods(); ++it) { if (MatchAllOverriddenMethods(**it, inner_matcher, finder, builder)) override_matches = true; else override_not_matches = true; } // If this fires we have a class overriding a method that matches, and a // method that does not match the inner matcher. In that case we will match // one ancestor method but not the other. If we rename one of the and not the // other it will break what this class overrides, disconnecting it from the // one we did not rename which creates a behaviour change. So assert and // demand the user to fix the code first (or add the method to our // blacklist T_T). if (override_matches || override_not_matches) assert(override_matches != override_not_matches); // If the method overrides something that doesn't match, so the method itself // doesn't match. if (override_not_matches) return false; // If the method overrides something that matches, so the method ifself // matches. if (override_matches) return true; return inner_matcher.matches(decl, finder, builder); } AST_MATCHER_P(clang::CXXMethodDecl, includeAllOverriddenMethods, clang::ast_matchers::internal::Matcher<clang::CXXMethodDecl>, InnerMatcher) { return MatchAllOverriddenMethods(Node, InnerMatcher, Finder, Builder); } bool IsMethodOverrideOf(const clang::CXXMethodDecl& decl, const char* class_name) { if (decl.getParent()->getQualifiedNameAsString() == class_name) return true; for (auto it = decl.begin_overridden_methods(); it != decl.end_overridden_methods(); ++it) { if (IsMethodOverrideOf(**it, class_name)) return true; } return false; } bool IsBlacklistedFunction(const clang::FunctionDecl& decl) { // swap() functions should match the signature of std::swap for ADL tricks. return decl.getName() == "swap"; } bool IsBlacklistedMethod(const clang::CXXMethodDecl& decl) { if (decl.isStatic()) return false; clang::StringRef name = decl.getName(); // These methods should never be renamed. static const char* kBlacklistMethods[] = {"trace", "traceImpl", "lock", "unlock", "try_lock"}; for (const auto& b : kBlacklistMethods) { if (name == b) return true; } // Iterator methods shouldn't be renamed to work with stl and range-for // loops. std::string ret_type = decl.getReturnType().getAsString(); if (ret_type.find("iterator") != std::string::npos || ret_type.find("Iterator") != std::string::npos) { static const char* kIteratorBlacklist[] = {"begin", "end", "rbegin", "rend"}; for (const auto& b : kIteratorBlacklist) { if (name == b) return true; } } // Subclasses of InspectorAgent will subclass "disable()" from both blink and // from gen/, which is problematic, but DevTools folks don't want to rename // it or split this up. So don't rename it at all. if (name.equals("disable") && IsMethodOverrideOf(decl, "blink::InspectorAgent")) return true; return false; } AST_MATCHER(clang::FunctionDecl, isBlacklistedFunction) { return IsBlacklistedFunction(Node); } AST_MATCHER(clang::CXXMethodDecl, isBlacklistedMethod) { return IsBlacklistedMethod(Node); } // Helper to convert from a camelCaseName to camel_case_name. It uses some // heuristics to try to handle acronyms in camel case names correctly. std::string CamelCaseToUnderscoreCase(StringRef input) { std::string output; bool needs_underscore = false; bool was_lowercase = false; bool was_uppercase = false; bool first_char = true; // Iterate in reverse to minimize the amount of backtracking. for (const unsigned char* i = input.bytes_end() - 1; i >= input.bytes_begin(); --i) { char c = *i; bool is_lowercase = clang::isLowercase(c); bool is_uppercase = clang::isUppercase(c); c = clang::toLowercase(c); // Transitioning from upper to lower case requires an underscore. This is // needed to handle names with acronyms, e.g. handledHTTPRequest needs a '_' // in 'dH'. This is a complement to the non-acronym case further down. if (was_uppercase && is_lowercase) needs_underscore = true; if (needs_underscore) { output += '_'; needs_underscore = false; } output += c; // Handles the non-acronym case: transitioning from lower to upper case // requires an underscore when emitting the next character, e.g. didLoad // needs a '_' in 'dL'. if (!first_char && was_lowercase && is_uppercase) needs_underscore = true; was_lowercase = is_lowercase; was_uppercase = is_uppercase; first_char = false; } std::reverse(output.begin(), output.end()); return output; } bool IsProbablyConst(const clang::VarDecl& decl, const clang::ASTContext& context) { clang::QualType type = decl.getType(); if (!type.isConstQualified()) return false; if (type.isVolatileQualified()) return false; // http://google.github.io/styleguide/cppguide.html#Constant_Names // Static variables that are const-qualified should use kConstantStyle naming. if (decl.getStorageDuration() == clang::SD_Static) return true; const clang::Expr* initializer = decl.getInit(); if (!initializer) return false; // If the expression is dependent on a template input, then we are not // sure if it can be compile-time generated as calling isEvaluatable() is // not valid on |initializer|. // TODO(crbug.com/581218): We could probably look at each compiled // instantiation of the template and see if they are all compile-time // isEvaluable(). if (initializer->isInstantiationDependent()) return false; // If the expression can be evaluated at compile time, then it should have a // kFoo style name. Otherwise, not. return initializer->isEvaluatable(context); } AST_MATCHER_P(clang::QualType, hasString, std::string, ExpectedString) { return ExpectedString == Node.getAsString(); } bool GetNameForDecl(const clang::FunctionDecl& decl, clang::ASTContext& context, std::string& name) { name = decl.getName().str(); name[0] = clang::toUppercase(name[0]); // Given // class Foo {}; // using Bar = Foo; // Bar f1(); // <- |Bar| would be matched by hasString("Bar") below. // Bar f2(); // <- |Bar| would be matched by hasName("Foo") below. // |type_with_same_name_as_function| matcher matches Bar and Foo return types. auto type_with_same_name_as_function = qualType(anyOf( hasString(name), // hasString matches the type as spelled (Bar above). hasDeclaration(namedDecl(hasName(name))))); // hasDeclaration matches // resolved type (Foo above). // |type_containing_same_name_as_function| matcher will match all of the // return types below: // - Foo foo() // Direct application of |type_with_same_name_as_function|. // - Foo* foo() // |hasDescendant| traverses references/pointers. // - RefPtr<Foo> foo() // |hasDescendant| traverses template arguments. auto type_containing_same_name_as_function = qualType(anyOf(type_with_same_name_as_function, hasDescendant(type_with_same_name_as_function))); // https://crbug.com/582312: Prepend "Get" if method name conflicts with // return type. auto conflict_matcher = functionDecl(returns(type_containing_same_name_as_function)); if (!match(conflict_matcher, decl, context).empty()) name = "Get" + name; return true; } bool GetNameForDecl(const clang::EnumConstantDecl& decl, clang::ASTContext& context, std::string& name) { StringRef original_name = decl.getName(); // If it's already correct leave it alone. if (original_name.size() >= 2 && original_name[0] == 'k' && clang::isUppercase(original_name[1])) return false; bool is_shouty = true; for (char c : original_name) { if (!clang::isUppercase(c) && !clang::isDigit(c) && c != '_') { is_shouty = false; break; } } if (is_shouty) return false; name = 'k'; // k prefix on enum values. name += original_name; name[1] = clang::toUppercase(name[1]); return true; } bool GetNameForDecl(const clang::FieldDecl& decl, clang::ASTContext& context, std::string& name) { StringRef original_name = decl.getName(); bool member_prefix = original_name.startswith(kBlinkFieldPrefix); StringRef rename_part = !member_prefix ? original_name : original_name.substr(strlen(kBlinkFieldPrefix)); name = CamelCaseToUnderscoreCase(rename_part); // Assume that prefix of m_ was intentional and always replace it with a // suffix _. if (member_prefix && name.back() != '_') name += '_'; return true; } bool GetNameForDecl(const clang::VarDecl& decl, clang::ASTContext& context, std::string& name) { StringRef original_name = decl.getName(); // Nothing to do for unnamed parameters. if (clang::isa<clang::ParmVarDecl>(decl)) { if (original_name.empty()) return false; // Check if |decl| and |decl.getLocation| are in sync. We need to skip // out-of-sync ParmVarDecls to avoid renaming buggy ParmVarDecls that // 1) have decl.getLocation() pointing at a parameter declaration without a // name, but 2) have decl.getName() retained from a template specialization // of a method. See also: https://llvm.org/bugs/show_bug.cgi?id=29145 clang::SourceLocation loc = context.getSourceManager().getSpellingLoc(decl.getLocation()); auto parents = context.getParents(decl); bool is_child_location_within_parent_source_range = std::all_of( parents.begin(), parents.end(), [&loc](const clang::ast_type_traits::DynTypedNode& parent) { clang::SourceLocation begin = parent.getSourceRange().getBegin(); clang::SourceLocation end = parent.getSourceRange().getEnd(); return (begin < loc) && (loc < end); }); if (!is_child_location_within_parent_source_range) return false; } // static class members match against VarDecls. Blink style dictates that // these should be prefixed with `s_`, so strip that off. Also check for `m_` // and strip that off too, for code that accidentally uses the wrong prefix. if (original_name.startswith(kBlinkStaticMemberPrefix)) original_name = original_name.substr(strlen(kBlinkStaticMemberPrefix)); else if (original_name.startswith(kBlinkFieldPrefix)) original_name = original_name.substr(strlen(kBlinkFieldPrefix)); bool is_const = IsProbablyConst(decl, context); if (is_const) { // Don't try to rename constants that already conform to Chrome style. if (original_name.size() >= 2 && original_name[0] == 'k' && clang::isUppercase(original_name[1])) return false; name = 'k'; name.append(original_name.data(), original_name.size()); name[1] = clang::toUppercase(name[1]); } else { name = CamelCaseToUnderscoreCase(original_name); // Non-const variables with static storage duration at namespace scope are // prefixed with `g_' to reduce the likelihood of a naming collision. const clang::DeclContext* decl_context = decl.getDeclContext(); if (name.find("g_") != 0 && decl.hasGlobalStorage() && decl_context->isNamespace()) name.insert(0, "g_"); } // Static members end with _ just like other members, but constants should // not. if (!is_const && decl.isStaticDataMember()) { name += '_'; } return true; } bool GetNameForDecl(const clang::FunctionTemplateDecl& decl, clang::ASTContext& context, std::string& name) { clang::FunctionDecl* templated_function = decl.getTemplatedDecl(); return GetNameForDecl(*templated_function, context, name); } bool GetNameForDecl(const clang::NamedDecl& decl, clang::ASTContext& context, std::string& name) { if (auto* function = clang::dyn_cast<clang::FunctionDecl>(&decl)) return GetNameForDecl(*function, context, name); if (auto* var = clang::dyn_cast<clang::VarDecl>(&decl)) return GetNameForDecl(*var, context, name); if (auto* field = clang::dyn_cast<clang::FieldDecl>(&decl)) return GetNameForDecl(*field, context, name); if (auto* function_template = clang::dyn_cast<clang::FunctionTemplateDecl>(&decl)) return GetNameForDecl(*function_template, context, name); if (auto* enumc = clang::dyn_cast<clang::EnumConstantDecl>(&decl)) return GetNameForDecl(*enumc, context, name); return false; } bool GetNameForDecl(const clang::UsingDecl& decl, clang::ASTContext& context, std::string& name) { assert(decl.shadow_size() > 0); // If a using declaration's targeted declaration is a set of overloaded // functions, it can introduce multiple shadowed declarations. Just using the // first one is OK, since overloaded functions have the same name, by // definition. return GetNameForDecl(*decl.shadow_begin()->getTargetDecl(), context, name); } template <typename Type> struct TargetNodeTraits; template <> struct TargetNodeTraits<clang::NamedDecl> { static clang::SourceLocation GetLoc(const clang::NamedDecl& decl) { return decl.getLocation(); } static const char* GetName() { return "decl"; } static const char* GetType() { return "NamedDecl"; } }; template <> struct TargetNodeTraits<clang::MemberExpr> { static clang::SourceLocation GetLoc(const clang::MemberExpr& expr) { return expr.getMemberLoc(); } static const char* GetName() { return "expr"; } static const char* GetType() { return "MemberExpr"; } }; template <> struct TargetNodeTraits<clang::DeclRefExpr> { static clang::SourceLocation GetLoc(const clang::DeclRefExpr& expr) { return expr.getLocation(); } static const char* GetName() { return "expr"; } static const char* GetType() { return "DeclRefExpr"; } }; template <> struct TargetNodeTraits<clang::CXXCtorInitializer> { static clang::SourceLocation GetLoc(const clang::CXXCtorInitializer& init) { assert(init.isWritten()); return init.getSourceLocation(); } static const char* GetName() { return "initializer"; } static const char* GetType() { return "CXXCtorInitializer"; } }; template <> struct TargetNodeTraits<clang::UnresolvedLookupExpr> { static clang::SourceLocation GetLoc(const clang::UnresolvedLookupExpr& expr) { return expr.getNameLoc(); } static const char* GetName() { return "expr"; } static const char* GetType() { return "UnresolvedLookupExpr"; } }; template <> struct TargetNodeTraits<clang::UnresolvedMemberExpr> { static clang::SourceLocation GetLoc(const clang::UnresolvedMemberExpr& expr) { return expr.getMemberLoc(); } static const char* GetName() { return "expr"; } static const char* GetType() { return "UnresolvedMemberExpr"; } }; template <typename DeclNode, typename TargetNode> class RewriterBase : public MatchFinder::MatchCallback { public: explicit RewriterBase(std::set<Replacement>* replacements) : replacements_(replacements) {} void run(const MatchFinder::MatchResult& result) override { const DeclNode* decl = result.Nodes.getNodeAs<DeclNode>("decl"); // If false, there's no name to be renamed. if (!decl->getIdentifier()) return; clang::SourceLocation decl_loc = TargetNodeTraits<clang::NamedDecl>::GetLoc(*decl); if (decl_loc.isMacroID()) { // Get the location of the spelling of the declaration. If token pasting // was used this will be in "scratch space" and we don't know how to get // from there back to/ the actual macro with the foo##bar text. So just // don't replace in that case. clang::SourceLocation spell = result.SourceManager->getSpellingLoc(decl_loc); if (strcmp(result.SourceManager->getBufferName(spell), "<scratch space>") == 0) return; } clang::ASTContext* context = result.Context; std::string new_name; if (!GetNameForDecl(*decl, *context, new_name)) return; // If false, the name was not suitable for renaming. llvm::StringRef old_name = decl->getName(); if (old_name == new_name) return; clang::SourceLocation loc = TargetNodeTraits<TargetNode>::GetLoc( *result.Nodes.getNodeAs<TargetNode>( TargetNodeTraits<TargetNode>::GetName())); clang::CharSourceRange range = clang::CharSourceRange::getTokenRange(loc); replacements_->emplace(*result.SourceManager, range, new_name); replacement_names_.emplace(old_name.str(), std::move(new_name)); } const std::unordered_map<std::string, std::string>& replacement_names() const { return replacement_names_; } private: std::set<Replacement>* const replacements_; std::unordered_map<std::string, std::string> replacement_names_; }; using FieldDeclRewriter = RewriterBase<clang::FieldDecl, clang::NamedDecl>; using VarDeclRewriter = RewriterBase<clang::VarDecl, clang::NamedDecl>; using MemberRewriter = RewriterBase<clang::FieldDecl, clang::MemberExpr>; using DeclRefRewriter = RewriterBase<clang::VarDecl, clang::DeclRefExpr>; using FieldDeclRefRewriter = RewriterBase<clang::FieldDecl, clang::DeclRefExpr>; using FunctionDeclRewriter = RewriterBase<clang::FunctionDecl, clang::NamedDecl>; using FunctionRefRewriter = RewriterBase<clang::FunctionDecl, clang::DeclRefExpr>; using ConstructorInitializerRewriter = RewriterBase<clang::FieldDecl, clang::CXXCtorInitializer>; using MethodDeclRewriter = RewriterBase<clang::CXXMethodDecl, clang::NamedDecl>; using MethodRefRewriter = RewriterBase<clang::CXXMethodDecl, clang::DeclRefExpr>; using MethodMemberRewriter = RewriterBase<clang::CXXMethodDecl, clang::MemberExpr>; using EnumConstantDeclRewriter = RewriterBase<clang::EnumConstantDecl, clang::NamedDecl>; using EnumConstantDeclRefRewriter = RewriterBase<clang::EnumConstantDecl, clang::DeclRefExpr>; using UnresolvedLookupRewriter = RewriterBase<clang::NamedDecl, clang::UnresolvedLookupExpr>; using UnresolvedMemberRewriter = RewriterBase<clang::NamedDecl, clang::UnresolvedMemberExpr>; using UsingDeclRewriter = RewriterBase<clang::UsingDecl, clang::NamedDecl>; } // namespace static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); int main(int argc, const char* argv[]) { // TODO(dcheng): Clang tooling should do this itself. // http://llvm.org/bugs/show_bug.cgi?id=21627 llvm::InitializeNativeTarget(); llvm::InitializeNativeTargetAsmParser(); llvm::cl::OptionCategory category( "rewrite_to_chrome_style: convert Blink style to Chrome style."); CommonOptionsParser options(argc, argv, category); clang::tooling::ClangTool tool(options.getCompilations(), options.getSourcePathList()); MatchFinder match_finder; std::set<Replacement> replacements; // Blink namespace matchers ======== auto blink_namespace_decl = namespaceDecl(anyOf(hasName("blink"), hasName("WTF")), hasParent(translationUnitDecl())); // Given top-level compilation unit: // namespace WTF { // void foo() {} // } // matches |foo|. auto decl_under_blink_namespace = decl(hasAncestor(blink_namespace_decl)); // Given top-level compilation unit: // void WTF::function() {} // void WTF::Class::method() {} // matches |WTF::function| and |WTF::Class::method| decls. auto decl_has_qualifier_to_blink_namespace = declaratorDecl(has(nestedNameSpecifier( hasTopLevelPrefix(specifiesNamespace(blink_namespace_decl))))); auto in_blink_namespace = decl( anyOf(decl_under_blink_namespace, decl_has_qualifier_to_blink_namespace, hasAncestor(decl_has_qualifier_to_blink_namespace)), unless(isExpansionInFileMatching(kGeneratedFileRegex))); // Field, variable, and enum declarations ======== // Given // int x; // struct S { // int y; // enum { VALUE }; // }; // matches |x|, |y|, and |VALUE|. auto field_decl_matcher = id("decl", fieldDecl(in_blink_namespace)); auto is_type_trait_value = varDecl(hasName("value"), hasStaticStorageDuration(), isPublic(), hasType(isConstQualified()), hasType(type(anyOf( booleanType(), enumType()))), unless(hasAncestor(recordDecl( has(cxxMethodDecl(isUserProvided(), isInstanceMethod())))))); auto var_decl_matcher = id("decl", varDecl(in_blink_namespace, unless(is_type_trait_value))); auto enum_member_decl_matcher = id("decl", enumConstantDecl(in_blink_namespace)); FieldDeclRewriter field_decl_rewriter(&replacements); match_finder.addMatcher(field_decl_matcher, &field_decl_rewriter); VarDeclRewriter var_decl_rewriter(&replacements); match_finder.addMatcher(var_decl_matcher, &var_decl_rewriter); EnumConstantDeclRewriter enum_member_decl_rewriter(&replacements); match_finder.addMatcher(enum_member_decl_matcher, &enum_member_decl_rewriter); // Field, variable, and enum references ======== // Given // bool x = true; // if (x) { // ... // } // matches |x| in if (x). auto member_matcher = id( "expr", memberExpr( member(field_decl_matcher), // Needed to avoid matching member references in functions (which will // be an ancestor of the member reference) synthesized by the // compiler, such as a synthesized copy constructor. // This skips explicitly defaulted functions as well, but that's OK: // there's nothing interesting to rewrite in those either. unless(hasAncestor(functionDecl(isDefaulted()))))); auto decl_ref_matcher = id("expr", declRefExpr(to(var_decl_matcher))); auto enum_member_ref_matcher = id("expr", declRefExpr(to(enum_member_decl_matcher))); MemberRewriter member_rewriter(&replacements); match_finder.addMatcher(member_matcher, &member_rewriter); DeclRefRewriter decl_ref_rewriter(&replacements); match_finder.addMatcher(decl_ref_matcher, &decl_ref_rewriter); EnumConstantDeclRefRewriter enum_member_ref_rewriter(&replacements); match_finder.addMatcher(enum_member_ref_matcher, &enum_member_ref_rewriter); // Member references in a non-member context ======== // Given // struct S { // typedef int U::*UnspecifiedBoolType; // operator UnspecifiedBoolType() { return s_ ? &U::s_ : 0; } // int s_; // }; // matches |&U::s_| but not |s_|. auto member_ref_matcher = id("expr", declRefExpr(to(field_decl_matcher))); FieldDeclRefRewriter member_ref_rewriter(&replacements); match_finder.addMatcher(member_ref_matcher, &member_ref_rewriter); // Non-method function declarations ======== // Given // void f(); // struct S { // void g(); // }; // matches |f| but not |g|. auto function_decl_matcher = id( "decl", functionDecl( unless(anyOf( // Methods are covered by the method matchers. cxxMethodDecl(), // Out-of-line overloaded operators have special names and should // never be renamed. isOverloadedOperator(), // Must be checked after filtering out overloaded operators to // prevent asserts about the identifier not being a simple name. isBlacklistedFunction())), in_blink_namespace)); FunctionDeclRewriter function_decl_rewriter(&replacements); match_finder.addMatcher(function_decl_matcher, &function_decl_rewriter); // Non-method function references ======== // Given // f(); // void (*p)() = &f; // matches |f()| and |&f|. auto function_ref_matcher = id( "expr", declRefExpr(to(function_decl_matcher), // Ignore template substitutions. unless(hasAncestor(substNonTypeTemplateParmExpr())))); FunctionRefRewriter function_ref_rewriter(&replacements); match_finder.addMatcher(function_ref_matcher, &function_ref_rewriter); // Method declarations ======== // Given // struct S { // void g(); // }; // matches |g|. // For a method to be considered for rewrite, it must not override something // that we're not rewriting. Any methods that we would not normally consider // but that override something we are rewriting should also be rewritten. So // we use includeAllOverriddenMethods() to check these rules not just for the // method being matched but for the methods it overrides also. auto is_blink_method = includeAllOverriddenMethods( allOf(in_blink_namespace, unless(isBlacklistedMethod()))); auto method_decl_matcher = id( "decl", cxxMethodDecl( unless(anyOf( // Overloaded operators have special names and should never be // renamed. isOverloadedOperator(), // Similarly, constructors, destructors, and conversion // functions should not be considered for renaming. cxxConstructorDecl(), cxxDestructorDecl(), cxxConversionDecl())), // Check this last after excluding things, to avoid // asserts about overriding non-blink and blink for the // same method. is_blink_method)); MethodDeclRewriter method_decl_rewriter(&replacements); match_finder.addMatcher(method_decl_matcher, &method_decl_rewriter); // Method references in a non-member context ======== // Given // S s; // s.g(); // void (S::*p)() = &S::g; // matches |&S::g| but not |s.g()|. auto method_ref_matcher = id( "expr", declRefExpr(to(method_decl_matcher), // Ignore template substitutions. unless(hasAncestor(substNonTypeTemplateParmExpr())))); MethodRefRewriter method_ref_rewriter(&replacements); match_finder.addMatcher(method_ref_matcher, &method_ref_rewriter); // Method references in a member context ======== // Given // S s; // s.g(); // void (S::*p)() = &S::g; // matches |s.g()| but not |&S::g|. auto method_member_matcher = id("expr", memberExpr(member(method_decl_matcher))); MethodMemberRewriter method_member_rewriter(&replacements); match_finder.addMatcher(method_member_matcher, &method_member_rewriter); // Initializers ======== // Given // struct S { // int x; // S() : x(2) {} // }; // matches each initializer in the constructor for S. auto constructor_initializer_matcher = cxxConstructorDecl(forEachConstructorInitializer(id( "initializer", cxxCtorInitializer(forAnyField(field_decl_matcher), isWritten())))); ConstructorInitializerRewriter constructor_initializer_rewriter( &replacements); match_finder.addMatcher(constructor_initializer_matcher, &constructor_initializer_rewriter); // Unresolved lookup expressions ======== // Given // template<typename T> void F(T) { } // template<void G(T)> H(T) { } // H<F<int>>(...); // matches |F| in |H<F<int>>|. // // UnresolvedLookupExprs are similar to DeclRefExprs that reference a // FunctionDecl, but are used when a candidate FunctionDecl can't be selected. // This commonly happens inside uninstantiated template definitions for one of // two reasons: // // 1. If the candidate declaration is a dependent FunctionTemplateDecl, the // actual overload can't be selected until template instantiation time. // 2. Alternatively, there might be multiple declarations in the candidate set // if the candidate function has overloads. If any of the function // arguments has a dependent type, then the actual overload can't be // selected until instantiation time either. // // Another instance where UnresolvedLookupExprs can appear is in a template // argument list, like the provided example. auto function_template_decl_matcher = id("decl", functionTemplateDecl(templatedDecl(function_decl_matcher))); auto method_template_decl_matcher = id("decl", functionTemplateDecl(templatedDecl(method_decl_matcher))); auto unresolved_lookup_matcher = expr(id( "expr", unresolvedLookupExpr( // In order to automatically rename an unresolved lookup, the lookup // candidates must either all be Blink functions/function templates or // all be Blink methods/method templates. Otherwise, we might end up // in a situation where the naming could change depending on the // selected candidate. anyOf(allOverloadsMatch(anyOf(function_decl_matcher, function_template_decl_matcher)), // Note: this matches references to methods in a non-member // context, e.g. Template<&Class::Method>. This and the // UnresolvedMemberExpr matcher below are analogous to how the // rewriter has both a MemberRefRewriter matcher to rewrite // &T::method and a MethodMemberRewriter matcher to rewriter // t.method(). allOverloadsMatch(anyOf(method_decl_matcher, method_template_decl_matcher)))))); UnresolvedLookupRewriter unresolved_lookup_rewriter(&replacements); match_finder.addMatcher(unresolved_lookup_matcher, &unresolved_lookup_rewriter); // Unresolved member expressions ======== // Similar to unresolved lookup expressions, but for methods in a member // context, e.g. var_with_templated_type.Method(). auto unresolved_member_matcher = expr(id( "expr", unresolvedMemberExpr( // Similar to UnresolvedLookupExprs, all the candidate methods must be // Blink methods/method templates. allOverloadsMatch( anyOf(method_decl_matcher, method_template_decl_matcher))))); UnresolvedMemberRewriter unresolved_member_rewriter(&replacements); match_finder.addMatcher(unresolved_member_matcher, &unresolved_member_rewriter); // Using declarations ======== // Given // using blink::X; // matches |using blink::X|. auto using_decl_matcher = id( "decl", usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(anyOf( var_decl_matcher, field_decl_matcher, function_decl_matcher, method_decl_matcher, function_template_decl_matcher, method_template_decl_matcher, enum_member_decl_matcher))))); UsingDeclRewriter using_decl_rewriter(&replacements); match_finder.addMatcher(using_decl_matcher, &using_decl_rewriter); std::unique_ptr<clang::tooling::FrontendActionFactory> factory = clang::tooling::newFrontendActionFactory(&match_finder); int result = tool.run(factory.get()); if (result != 0) return result; #if defined(_WIN32) HANDLE lockfd = CreateFile("rewrite-sym.lock", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); OVERLAPPED overlapped = {}; LockFileEx(lockfd, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &overlapped); #else int lockfd = open("rewrite-sym.lock", O_RDWR | O_CREAT, 0666); while (flock(lockfd, LOCK_EX)) { // :D } #endif std::ofstream replacement_db_file("rewrite-sym.txt", std::ios_base::out | std::ios_base::app); for (const auto& p : field_decl_rewriter.replacement_names()) replacement_db_file << "var:" << p.first << ":" << p.second << "\n"; for (const auto& p : var_decl_rewriter.replacement_names()) replacement_db_file << "var:" << p.first << ":" << p.second << "\n"; for (const auto& p : enum_member_decl_rewriter.replacement_names()) replacement_db_file << "enu:" << p.first << ":" << p.second << "\n"; for (const auto& p : function_decl_rewriter.replacement_names()) replacement_db_file << "fun:" << p.first << ":" << p.second << "\n"; for (const auto& p : method_decl_rewriter.replacement_names()) replacement_db_file << "fun:" << p.first << ":" << p.second << "\n"; replacement_db_file.close(); #if defined(_WIN32) UnlockFileEx(lockfd, 0, 1, 0, &overlapped); CloseHandle(lockfd); #else flock(lockfd, LOCK_UN); close(lockfd); #endif // Serialization format is documented in tools/clang/scripts/run_tool.py llvm::outs() << "==== BEGIN EDITS ====\n"; for (const auto& r : replacements) { std::string replacement_text = r.getReplacementText().str(); std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0'); llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset() << ":::" << r.getLength() << ":::" << replacement_text << "\n"; } llvm::outs() << "==== END EDITS ====\n"; return 0; }