// 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 #include #include #include #include #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/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Lex/Lexer.h" #include "clang/Lex/MacroArgs.h" #include "clang/Lex/PPCallbacks.h" #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/TargetSelect.h" #include "EditTracker.h" 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 kGMockMethodNamePrefix[] = "gmock_"; const char kMethodBlocklistParamName[] = "method-blocklist"; std::set& GetRewrittenLocs() { static auto& locations = *new std::set(); return locations; } template bool IsMatching(const MatcherType& matcher, const NodeType& node, clang::ASTContext& context) { return !match(matcher, node, context).empty(); } const clang::ast_matchers::internal:: VariadicDynCastAllOfMatcher unresolvedMemberExpr; const clang::ast_matchers::internal:: VariadicDynCastAllOfMatcher dependentScopeDeclRefExpr; const clang::ast_matchers::internal:: VariadicDynCastAllOfMatcher cxxDependentScopeMemberExpr; 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, InnerMatcher) { return InnerMatcher.matches(*Node.getTemplatedDecl(), Finder, Builder); } AST_MATCHER_P(clang::Decl, hasCanonicalDecl, clang::ast_matchers::internal::Matcher, InnerMatcher) { return InnerMatcher.matches(*Node.getCanonicalDecl(), 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, 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()) { 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; } class MethodBlocklist { public: explicit MethodBlocklist(const std::string& filepath) { if (!filepath.empty()) ParseInputFile(filepath); } bool Contains(const clang::FunctionDecl& method) const { if (!method.getDeclName().isIdentifier()) return false; auto it = method_to_classes_.find(method.getName()); if (it == method_to_classes_.end()) return false; // |method_context| is either // 1) a CXXRecordDecl (i.e. blink::Document) or // 2) a NamespaceDecl (i.e. blink::DOMWindowTimers). const clang::NamedDecl* method_context = clang::dyn_cast(method.getDeclContext()); if (!method_context) return false; if (!method_context->getDeclName().isIdentifier()) return false; const llvm::StringSet<>& classes = it->second; auto it2 = classes.find(method_context->getName()); if (it2 == classes.end()) return false; // No need to verify here that |actual_class| is in the |blink| namespace - // this will be done by other matchers elsewhere. // TODO(lukasza): Do we need to consider return type and/or param types? // TODO(lukasza): Do we need to consider param count? return true; } private: // Each line is expected to have the following format: // :::::: void ParseInputFile(const std::string& filepath) { llvm::ErrorOr> file_or_err = llvm::MemoryBuffer::getFile(filepath); if (std::error_code err = file_or_err.getError()) { llvm::errs() << "ERROR: Cannot open the file specified in --" << kMethodBlocklistParamName << " argument: " << filepath << ": " << err.message() << "\n"; assert(false); return; } llvm::line_iterator it(**file_or_err, true /* SkipBlanks */, '#'); for (; !it.is_at_eof(); ++it) { llvm::StringRef line = it->trim(); if (line.empty()) continue; // Split the line into ':::'-delimited parts. const size_t kExpectedNumberOfParts = 3; llvm::SmallVector parts; line.split(parts, ":::"); if (parts.size() != kExpectedNumberOfParts) { llvm::errs() << "ERROR: Parsing error - expected " << kExpectedNumberOfParts << " ':::'-delimited parts: " << filepath << ":" << it.line_number() << ": " << line << "\n"; assert(false); continue; } // Parse individual parts. llvm::StringRef class_name = parts[0]; llvm::StringRef method_name = parts[1]; // ignoring parts[2] - the (not so trustworthy) number of parameters. // Store the new entry. method_to_classes_[method_name].insert(class_name); } } // Stores methods to blacklist in a map: // method name -> class name -> set of all allowed numbers of arguments. llvm::StringMap> method_to_classes_; }; AST_MATCHER_P(clang::FunctionDecl, isBlocklistedMethod, MethodBlocklist, Blocklist) { return Blocklist.Contains(Node); } // 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, 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, 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, InnerMatcher) { if (Node.getNumDecls() == 0) return false; for (clang::NamedDecl* decl : Node.decls()) { if (!InnerMatcher.matches(*decl, Finder, Builder)) return false; } return true; } void PrintForDiagnostics(clang::raw_ostream& os, const clang::FunctionDecl& decl) { decl.getLocStart().print(os, decl.getASTContext().getSourceManager()); os << ": "; decl.getNameForDiagnostic(os, decl.getASTContext().getPrintingPolicy(), true); } template 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) { // blink::InternalSettings::trace method overrides // 1) blink::InternalSettingsGenerated::trace // (won't be renamed because it is in generated code) // 2) blink::Supplement::trace // (will be renamed). // It is safe to rename blink::InternalSettings::trace, because // both 1 and 2 will both be renamed (#1 via manual changes of the code // generator for DOM bindings and #2 via the clang tool). auto internal_settings_class_decl = cxxRecordDecl( hasName("InternalSettings"), hasParent(namespaceDecl(hasName("blink"), hasParent(translationUnitDecl())))); auto is_method_safe_to_rename = cxxMethodDecl( hasName("trace"), anyOf(hasParent(internal_settings_class_decl), // in .h file has(nestedNameSpecifier(specifiesType( // in .cpp file hasDeclaration(internal_settings_class_decl)))))); if (IsMatching(is_method_safe_to_rename, decl, decl.getASTContext())) return true; // For previously unknown conflicts, error out and require a human to // analyse the problem (rather than falling back to a potentially unsafe / // code semantics changing rename). llvm::errs() << "ERROR: "; PrintForDiagnostics(llvm::errs(), decl); llvm::errs() << " method overrides " << "some virtual methods that will be automatically renamed " << "and some that won't be renamed."; llvm::errs() << "\n"; for (auto it = decl.begin_overridden_methods(); it != decl.end_overridden_methods(); ++it) { if (MatchAllOverriddenMethods(**it, inner_matcher, finder, builder)) llvm::errs() << "Overriden method that will be renamed: "; else llvm::errs() << "Overriden method that will not be renamed: "; PrintForDiagnostics(llvm::errs(), **it); llvm::errs() << "\n"; } llvm::errs() << "\n"; assert(false); } // 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, InnerMatcher) { return MatchAllOverriddenMethods(Node, InnerMatcher, Finder, Builder); } // Matches |T::m| and/or |x->T::m| and/or |x->m| CXXDependentScopeMemberExpr // if member |m| comes from a type that matches the InnerMatcher. AST_MATCHER_P(clang::CXXDependentScopeMemberExpr, hasMemberFromType, clang::ast_matchers::internal::Matcher, InnerMatcher) { // Given |T::m| and/or |x->T::m| and/or |x->m| ... if (clang::NestedNameSpecifier* nestedNameSpecifier = Node.getQualifier()) { // ... if |T| is present, then InnerMatcher has to match |T|. clang::QualType qualType(nestedNameSpecifier->getAsType(), 0); return InnerMatcher.matches(qualType, Finder, Builder); } else { // ... if there is no |T|, then InnerMatcher has to match the type of |x|. clang::Expr* base_expr = Node.isImplicitAccess() ? nullptr : Node.getBase(); return base_expr && InnerMatcher.matches(base_expr->getType(), Finder, Builder); } } // Matches |const Class&| QualType if InnerMatcher matches |Class|. AST_MATCHER_P(clang::QualType, hasBaseType, clang::ast_matchers::internal::Matcher, InnerMatcher) { const clang::Type* type = Node.getTypePtrOrNull(); return type && InnerMatcher.matches(*type, 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 IsBlacklistedFunctionName(llvm::StringRef name) { // https://crbug.com/672902: Method names with an underscore are typically // mimicked after std library / are typically not originating from Blink. // Do not rewrite such names (like push_back, emplace_back, etc.). if (name.find('_') != llvm::StringRef::npos) return true; return false; } bool IsBlacklistedFreeFunctionName(llvm::StringRef name) { // swap() functions should match the signature of std::swap for ADL tricks. return name == "swap"; } bool IsBlacklistedInstanceMethodName(llvm::StringRef name) { static const char* kBlacklistedNames[] = { // We should avoid renaming the method names listed below, because // 1. They are used in templated code (e.g. in ) // 2. They (begin+end) are used in range-based for syntax sugar // - for (auto x : foo) { ... } // <- foo.begin() will be called. "begin", "end", "rbegin", "rend", "lock", "unlock", "try_lock", // https://crbug.com/672902: Should not rewrite names that mimick methods // from std library. "at", "back", "empty", "erase", "front", "insert", "length", "size", }; for (const auto& b : kBlacklistedNames) { if (name == b) return true; } return false; } bool IsBlacklistedMethodName(llvm::StringRef name) { return IsBlacklistedFunctionName(name) || IsBlacklistedInstanceMethodName(name); } bool IsBlacklistedFunction(const clang::FunctionDecl& decl) { if (!decl.getDeclName().isIdentifier()) return false; clang::StringRef name = decl.getName(); return IsBlacklistedFunctionName(name) || IsBlacklistedFreeFunctionName(name); } bool IsBlacklistedMethod(const clang::CXXMethodDecl& decl) { if (!decl.getDeclName().isIdentifier()) return false; clang::StringRef name = decl.getName(); if (IsBlacklistedFunctionName(name)) return true; // Remaining cases are only applicable to instance methods. if (decl.isStatic()) return false; if (IsBlacklistedInstanceMethodName(name)) 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::InspectorBaseAgent")) return true; return false; } AST_MATCHER(clang::FunctionDecl, isBlacklistedFunction) { return IsBlacklistedFunction(Node); } AST_MATCHER(clang::CXXMethodDecl, isBlacklistedMethod) { return IsBlacklistedMethod(Node); } bool IsKnownTraitName(clang::StringRef name) { // This set of names is globally a type trait throughout chromium. return name == "safeToCompareToEmptyOrDeleted"; } AST_MATCHER(clang::VarDecl, isKnownTraitName) { return Node.getDeclName().isIdentifier() && IsKnownTraitName(Node.getName()); } AST_MATCHER(clang::Decl, isDeclInGeneratedFile) { // This matcher mimics the built-in isExpansionInFileMatching matcher from // llvm/tools/clang/include/clang/ASTMatchers/ASTMatchers.h, except: // - It special cases some files (e.g. doesn't skip renaming of identifiers // from gen/blink/core/ComputedStyleBase.h) const clang::SourceManager& source_manager = Node.getASTContext().getSourceManager(); // TODO(lukasza): Consider using getSpellingLoc below. // The built-in isExpansionInFileMatching matcher uses getExpansionLoc below. // We could consider using getSpellingLoc (which properly handles things like // SETTINGS_GETTERS_AND_SETTERS macro which is defined in generated code // (gen/blink/core/SettingsMacros.h), but expanded in non-generated code // (third_party/WebKit/Source/core/frame/Settings.h). clang::SourceLocation loc = source_manager.getExpansionLoc(Node.getLocStart()); // TODO(lukasza): jump out of scratch space if token concatenation was used. if (loc.isInvalid()) return false; const clang::FileEntry* file_entry = source_manager.getFileEntryForID(source_manager.getFileID(loc)); if (!file_entry) return false; bool is_generated_file = false; bool is_computed_style_base_cpp = llvm::sys::path::filename(file_entry->getName()) .equals("ComputedStyleBase.h"); for (auto it = llvm::sys::path::begin(file_entry->getName()); it != llvm::sys::path::end(file_entry->getName()); ++it) { if (it->equals("gen")) { is_generated_file = true; break; } } // ComputedStyleBase is intentionally not treated as a generated file, since // style definitions are split between generated and non-generated code. It's // easier to have the tool just automatically rewrite references to generated // code as well, with a small manual patch to fix the code generators. return is_generated_file && !is_computed_style_base_cpp; } // 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 CanBeEvaluatedAtCompileTime(const clang::Stmt* stmt, const clang::ASTContext& context) { auto* expr = clang::dyn_cast(stmt); if (!expr) { // If the statement is not an expression then it's a constant. return true; } // Function calls create non-consistent behaviour. For some template // instantiations they can be constexpr while for others they are not, which // changes the output of isEvaluatable(). if (expr->hasNonTrivialCall(context)) return false; // Recurse on children. If they are all const (or are uses of template // input) then the statement can be considered const. For whatever reason the // below checks can give different-and-less-consistent responses if we call // them on a complex expression than if we call them on the most primitive // pieces (some pieces would say false but the whole thing says true). for (auto* child : expr->children()) { if (!CanBeEvaluatedAtCompileTime(child, context)) return false; } // If the expression depends on template input, we can not call // isEvaluatable() on it as it will do bad things/crash. if (!expr->isInstantiationDependent()) { // If the expression can be evaluated at compile time, then it should have a // kFoo style name. Otherwise, not. return expr->isEvaluatable(context); } // We do our best to figure out special cases as we come across them here, for // template dependent situations. Some cases in code are only considered // instantiation dependent for some template instantiations! Which is // terrible! So most importantly we try to match isEvaluatable in those cases. switch (expr->getStmtClass()) { case clang::Stmt::CXXThisExprClass: return false; case clang::Stmt::DeclRefExprClass: { auto* declref = clang::dyn_cast(expr); auto* decl = declref->getDecl(); if (auto* vardecl = clang::dyn_cast(decl)) { if (auto* initializer = vardecl->getInit()) return CanBeEvaluatedAtCompileTime(initializer, context); return false; } break; } default: break; } // Otherwise, we consider depending on template parameters to not interfere // with being const.. with exceptions hopefully covered above. return true; } 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; if (decl.isConstexpr()) return true; // Parameters should not be renamed to |kFooBar| style (even if they are // const and have an initializer (aka default value)). if (clang::isa(&decl)) 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; return CanBeEvaluatedAtCompileTime(initializer, context); } AST_MATCHER_P(clang::QualType, hasString, std::string, ExpectedString) { return ExpectedString == Node.getAsString(); } bool ShouldPrefixFunctionName(const std::string& old_method_name) { // Functions that are named similarily to a type - they should be prefixed // with a "Get" prefix. static const char* kConflictingMethods[] = {"accumulatorMap", "animationWorklet", "attrNodeList", "audioWorklet", "binaryType", "blob", "channelCountMode", "color", "compositorElementId", "constructionStack", "controlSize", "counterDirectives", "counterMaps", "document", "dragOperation", "element", "emptyChromeClient", "emptyEditorClient", "emptySpellCheckerClient", "entryType", "error", "eventTargetDataMap", "fileUtilities", "font", "frame", "frameBlameContext", "frontend", "gridCell", "harfBuzzFontCache", "hash", "heapObjectHeader", "heapObjectSet", "iconURL", "image", "infoMap", "inputMethodController", "inputType", "interpolationTypes", "intervalArena", "layout", "layoutBlock", "layoutObject", "layoutSize", "lineCap", "lineEndings", "lineJoin", "listItems", "locationInBackingMap", "matchedProperties", "midpointState", "modifiers", "mouseEvent", "name", "navigationType", "node", "notificationManager", "originAccessMap", "outcome", "pagePopup", "paintWorklet", "path", "position", "presentationAttributeCache", "processingInstruction", "qualifiedNameCache", "readyState", "referrer", "referrerPolicy", "relList", "resource", "response", "restrictedKeyMap", "sandboxSupport", "screenInfo", "screenOrientationController", "scrollAnimator", "scrollbarPainterMap", "scrollbarSet", "selectionInDOMTree", "selectionInFlatTree", "selectionVisualRectMap", "selectorTextCache", "settings", "shadowRootType", "signalingState", "snapshotById", "state", "stickyConstraintsMap", "string", "styleSharingList", "styleSheet", "supplementable", "text", "textAlign", "textBaseline", "textDirection", "theme", "thread", "timing", "topLevelBlameContext", "type", "vector", "visibleSelection", "visibleSelectionInFlatTree", "weakHeapObjectSet", "webFrame", "widget", "wordBoundaries", "workerThread", "worldId", "worldMap", "wrapperTypeInfo"}; for (const auto& conflicting_method : kConflictingMethods) { if (old_method_name == conflicting_method) return true; } return false; } AST_MATCHER(clang::FunctionDecl, shouldPrefixFunctionName) { return Node.getDeclName().isIdentifier() && ShouldPrefixFunctionName(Node.getName().str()); } 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 {}; // class DerivedFoo : 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. // DerivedFoo f3(); // <- |DerivedFoo| matched by isDerivedFrom(...) below. // |type_with_same_name_as_function| matcher matches Bar and Foo return types. auto type_with_same_name_as_function = qualType(anyOf( // hasString matches the type as spelled (Bar above). hasString(name), // hasDeclaration matches resolved type (Foo or DerivedFoo above). hasDeclaration(namedDecl(hasName(name))))); // |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() // |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(anyOf( // For functions and non-virtual or base method implementations just // compare with the immediate return type. functionDecl(returns(type_containing_same_name_as_function), unless(cxxMethodDecl(isOverride()))), // For methods that override one or more methods, compare with the return // type of the *base* methods. cxxMethodDecl(isOverride(), forEachOverridden(returns( type_containing_same_name_as_function))), // And also check hardcoded list of function names to prefix with "Get". shouldPrefixFunctionName())); if (IsMatching(conflict_matcher, decl, context)) 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(decl) && original_name.empty()) return false; // This is a type trait that appears in consumers of WTF as well as inside // WTF. We want it to be named in this_style_of_case accordingly. if (IsKnownTraitName(original_name)) { name = CamelCaseToUnderscoreCase(original_name); return true; } // 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; // Or names are spelt with underscore casing. While they are actually // compile consts, the author wrote it explicitly as a variable not as // a constant (they would have used kFormat otherwise here), so preserve // it rather than try to mangle a kFormat out of it. if (original_name.find('_') != StringRef::npos) 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(&decl)) return GetNameForDecl(*function, context, name); if (auto* var = clang::dyn_cast(&decl)) return GetNameForDecl(*var, context, name); if (auto* field = clang::dyn_cast(&decl)) return GetNameForDecl(*field, context, name); if (auto* function_template = clang::dyn_cast(&decl)) return GetNameForDecl(*function_template, context, name); if (auto* enumc = clang::dyn_cast(&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 struct TargetNodeTraits; template <> struct TargetNodeTraits { 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 { 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 { 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 { static clang::SourceLocation GetLoc( const clang::DependentScopeDeclRefExpr& expr) { return expr.getLocation(); } static const char* GetName() { return "expr"; } }; template <> struct TargetNodeTraits { static clang::SourceLocation GetLoc( const clang::CXXDependentScopeMemberExpr& expr) { return expr.getMemberLoc(); } static const char* GetName() { return "expr"; } }; template <> struct TargetNodeTraits { 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 { 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 { static clang::SourceLocation GetLoc(const clang::UnresolvedMemberExpr& expr) { return expr.getMemberLoc(); } static const char* GetName() { return "expr"; } static const char* GetType() { return "UnresolvedMemberExpr"; } }; template <> struct TargetNodeTraits { static clang::SourceLocation GetLoc( const clang::UnresolvedUsingValueDecl& decl) { return decl.getNameInfo().getLoc(); } static const char* GetName() { return "decl"; } static const char* GetType() { return "UnresolvedUsingValueDecl"; } }; template class RewriterBase : public MatchFinder::MatchCallback { public: explicit RewriterBase(std::set* replacements, RenameCategory category) : replacements_(replacements), edit_tracker_(category) {} const TargetNode& GetTargetNode(const MatchFinder::MatchResult& result) { const TargetNode* target_node = result.Nodes.getNodeAs( TargetNodeTraits::GetName()); assert(target_node); return *target_node; } bool GenerateReplacement(const MatchFinder::MatchResult& result, clang::SourceLocation loc, llvm::StringRef old_name, std::string new_name, Replacement* replacement) { 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) != ""; if (!verified_out_of_scratch_space) loc = source_manager.getImmediateMacroCallerLoc(loc); } if (!verified_out_of_scratch_space) 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 // for dealing with toFooBar -> ToFooBar method renaming when the method // name is built using macro token concatenation like to##macroArgument - in // this case we should only rewrite "t" -> "T" and leave "o##macroArgument" // untouched. llvm::StringRef expected_old_text = old_name; llvm::StringRef new_text = new_name; if (loc.isMacroID() && expected_old_text.substr(1) == new_text.substr(1)) { 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()); if (actual_old_text != expected_old_text) return false; if (replacement) { // If there's already a replacement for this location, don't emit any // other replacements to avoid potential naming conflicts. This is // primarily to avoid problems when a function and a parameter are defined // by the same macro argument. if (!GetRewrittenLocs().emplace(spell).second) return false; *replacement = Replacement(source_manager, range, new_text); } return true; } virtual clang::SourceLocation GetTargetLoc( const MatchFinder::MatchResult& result) { return TargetNodeTraits::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 = GetTargetLoc(result); if (loc.isInvalid()) return; Replacement replacement; if (!GenerateReplacement(result, loc, old_name, new_name, &replacement)) return; replacements_->insert(std::move(replacement)); edit_tracker_.Add(*result.SourceManager, loc, old_name, new_name); } const EditTracker* edit_tracker() const { return &edit_tracker_; } private: std::set* const replacements_; EditTracker edit_tracker_; }; template RenameCategory GetCategory(); template <> RenameCategory GetCategory() { return RenameCategory::kField; } template <> RenameCategory GetCategory() { return RenameCategory::kVariable; } template <> RenameCategory GetCategory() { return RenameCategory::kFunction; } template <> RenameCategory GetCategory() { return RenameCategory::kFunction; } template <> RenameCategory GetCategory() { return RenameCategory::kEnumValue; } template <> RenameCategory GetCategory() { return RenameCategory::kUnresolved; } template <> RenameCategory GetCategory() { return RenameCategory::kUnresolved; } template class DeclRewriterBase : public RewriterBase { public: using Base = RewriterBase; explicit DeclRewriterBase(std::set* replacements) : Base(replacements, GetCategory()) {} void run(const MatchFinder::MatchResult& result) override { const DeclNode* decl = result.Nodes.getNodeAs("decl"); if (!decl->getDeclName().isIdentifier()) return; assert(decl); llvm::StringRef old_name = decl->getName(); // Return early if there's no name to be renamed. if (!decl->getIdentifier()) return; // Get the new name. std::string new_name; if (!GetNameForDecl(*decl, *result.Context, new_name)) return; // If false, the name was not suitable for renaming. // Check if we are able to rewrite the decl (to avoid rewriting if the // decl's identifier is part of macro##Token##Concatenation). clang::SourceLocation decl_loc = TargetNodeTraits::GetLoc(*decl); if (!Base::GenerateReplacement(result, decl_loc, old_name, new_name, nullptr)) return; Base::AddReplacement(result, old_name, std::move(new_name)); } }; using FieldDeclRewriter = DeclRewriterBase; using VarDeclRewriter = DeclRewriterBase; using MemberRewriter = DeclRewriterBase; using DeclRefRewriter = DeclRewriterBase; using FieldDeclRefRewriter = DeclRewriterBase; using FunctionDeclRewriter = DeclRewriterBase; using FunctionRefRewriter = DeclRewriterBase; using ConstructorInitializerRewriter = DeclRewriterBase; using MethodDeclRewriter = DeclRewriterBase; using MethodRefRewriter = DeclRewriterBase; using MethodMemberRewriter = DeclRewriterBase; using EnumConstantDeclRewriter = DeclRewriterBase; using EnumConstantDeclRefRewriter = DeclRewriterBase; using UnresolvedLookupRewriter = DeclRewriterBase; using UnresolvedMemberRewriter = DeclRewriterBase; using UsingDeclRewriter = DeclRewriterBase; class GMockMemberRewriter : public DeclRewriterBase { public: using Base = DeclRewriterBase; explicit GMockMemberRewriter(std::set* replacements) : Base(replacements) {} std::unique_ptr CreatePreprocessorCallbacks() { return std::make_unique(this); } clang::SourceLocation GetTargetLoc( const MatchFinder::MatchResult& result) override { // Find location of the gmock_##MockedMethod identifier. clang::SourceLocation target_loc = Base::GetTargetLoc(result); // Find location of EXPECT_CALL or ON_CALL macro invocation. clang::SourceLocation macro_call_loc = result.SourceManager->getExpansionLoc(target_loc); // Map |macro_call_loc| to argument location (location of the method name // that needs renaming). auto it = gmock_macro_call_to_2nd_arg.find(macro_call_loc); if (it == gmock_macro_call_to_2nd_arg.end()) return clang::SourceLocation(); return it->second; } private: std::map gmock_macro_call_to_2nd_arg; // Called from PPCallbacks with the locations of EXPECT_CALL and ON_CALL macro // invocation. Example: // EXPECT_CALL(my_mock, myMethod(123, 456)); // ^- expansion_loc ^- actual_arg_loc void RecordGMockMacroInvocation(clang::SourceLocation expansion_loc, clang::SourceLocation second_arg_loc) { gmock_macro_call_to_2nd_arg[expansion_loc] = second_arg_loc; } class PPCallbacks : public clang::PPCallbacks { public: explicit PPCallbacks(GMockMemberRewriter* rewriter) : rewriter_(rewriter) {} ~PPCallbacks() override {} void MacroExpands(const clang::Token& name, const clang::MacroDefinition& def, clang::SourceRange range, const clang::MacroArgs* args) override { clang::IdentifierInfo* id = name.getIdentifierInfo(); if (!id) return; if (id->getName() != "EXPECT_CALL" && id->getName() != "ON_CALL") return; if (def.getMacroInfo()->getNumParams() != 2) return; // TODO(lukasza): Should check if def.getMacroInfo()->getDefinitionLoc() // is in testing/gmock/include/gmock/gmock-spec-builders.h but I don't // know how to get clang::SourceManager to call getFileName. rewriter_->RecordGMockMacroInvocation( name.getLocation(), args->getUnexpArgument(1)->getLocation()); } private: GMockMemberRewriter* rewriter_; }; }; clang::DeclarationName GetUnresolvedName( const clang::UnresolvedMemberExpr& expr) { return expr.getMemberName(); } clang::DeclarationName GetUnresolvedName( const clang::DependentScopeDeclRefExpr& expr) { return expr.getDeclName(); } clang::DeclarationName GetUnresolvedName( const clang::CXXDependentScopeMemberExpr& expr) { return expr.getMember(); } clang::DeclarationName GetUnresolvedName( const clang::UnresolvedUsingValueDecl& decl) { return decl.getDeclName(); } // Returns whether |expr_node| is used as a callee in the AST (i.e. if // |expr_node| needs to resolve to a method or a function). bool IsCallee(const clang::Expr& expr, clang::ASTContext& context) { auto matcher = stmt(hasParent(callExpr(callee(equalsNode(&expr))))); return IsMatching(matcher, expr, context); } // Returns whether |decl| will be used as a callee in the AST (i.e. if the value // brought by the using declaration will resolve to a method or a function). bool IsCallee(const clang::UnresolvedUsingValueDecl& decl, clang::ASTContext& /* context */) { // Caller (i.e. GuessNameForUnresolvedDependentNode) should have already // filtered out fields before calling |IsCallee|. clang::IdentifierInfo* info = GetUnresolvedName(decl).getAsIdentifierInfo(); assert(info); bool name_looks_like_a_field = info->getName().startswith(kBlinkFieldPrefix); assert(!name_looks_like_a_field); // Looking just at clang::UnresolvedUsingValueDecl, we cannot tell whether it // refers to something callable or not. Since fields should have been already // filtered out before calling IsCallee (see the assert above), let's assume // that |using Base::foo| refers to a method. return true; } template class UnresolvedRewriterBase : public RewriterBase { public: using Base = RewriterBase; explicit UnresolvedRewriterBase(std::set* replacements) : RewriterBase(replacements, RenameCategory::kUnresolved) {} void run(const MatchFinder::MatchResult& result) override { const TargetNode& node = Base::GetTargetNode(result); clang::DeclarationName decl_name = GetUnresolvedName(node); switch (decl_name.getNameKind()) { // Do not rewrite this: // return operator T*(); // into this: // return Operator type - parameter - 0 - 0 * T * (); case clang::DeclarationName::NameKind::CXXConversionFunctionName: case clang::DeclarationName::NameKind::CXXOperatorName: case clang::DeclarationName::NameKind::CXXLiteralOperatorName: return; default: break; } // Make sure there is an old name + extract the old name. clang::IdentifierInfo* info = GetUnresolvedName(node).getAsIdentifierInfo(); if (!info) return; llvm::StringRef old_name = info->getName(); // Try to guess a new name. std::string new_name; if (GuessNameForUnresolvedDependentNode(node, *result.Context, old_name, new_name)) Base::AddReplacement(result, old_name, std::move(new_name)); } private: // This method calculates a new name for nodes that depend on template // parameters (http://en.cppreference.com/w/cpp/language/dependent_name). The // renaming is based on crude heuristics, because such nodes are not bound to // a specific decl until template instantiation - at the point of rename, one // cannot tell whether the node will eventually resolve to a field / method / // constant / etc. // // The method returns false if no renaming should be done. // Otherwise the method returns true and sets |new_name|. bool GuessNameForUnresolvedDependentNode(const TargetNode& node, clang::ASTContext& context, llvm::StringRef old_name, std::string& new_name) { // |m_fieldName| -> |field_name_|. if (old_name.startswith(kBlinkFieldPrefix)) { std::string field_name = old_name.substr(strlen(kBlinkFieldPrefix)); if (field_name.find('_') == std::string::npos) { new_name = CamelCaseToUnderscoreCase(field_name) + "_"; return true; } } // |T::myMethod(...)| -> |T::MyMethod(...)|. if ((old_name.find('_') == std::string::npos) && IsCallee(node, context) && !IsBlacklistedMethodName(old_name)) { new_name = old_name; new_name[0] = clang::toUppercase(old_name[0]); if (ShouldPrefixFunctionName(old_name)) new_name = "Get" + new_name; return true; } // In the future we can consider more heuristics: // - "s_" and "g_" prefixes // - "ALL_CAPS" // - |T::myStaticField| -> |T::kMyStaticField| // (but have to be careful not to rename |value| in WTF/TypeTraits.h?) return false; } }; using UnresolvedDependentMemberRewriter = UnresolvedRewriterBase; using UnresolvedUsingValueDeclRewriter = UnresolvedRewriterBase; using DependentScopeDeclRefExprRewriter = UnresolvedRewriterBase; using CXXDependentScopeMemberExprRewriter = UnresolvedRewriterBase; class SourceFileCallbacks : public clang::tooling::SourceFileCallbacks { public: explicit SourceFileCallbacks(GMockMemberRewriter* gmock_member_rewriter) : gmock_member_rewriter_(gmock_member_rewriter) { assert(gmock_member_rewriter); } ~SourceFileCallbacks() override {} // clang::tooling::SourceFileCallbacks override: bool handleBeginSource(clang::CompilerInstance& compiler) override { compiler.getPreprocessor().addPPCallbacks( gmock_member_rewriter_->CreatePreprocessorCallbacks()); return true; } private: GMockMemberRewriter* gmock_member_rewriter_; }; } // 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."); llvm::cl::opt blocklisted_methods_file( kMethodBlocklistParamName, llvm::cl::value_desc("filepath"), llvm::cl::desc("file listing methods to be blocked (not renamed)")); CommonOptionsParser options(argc, argv, category); MethodBlocklist method_blocklist(blocklisted_methods_file); clang::tooling::ClangTool tool(options.getCompilations(), options.getSourcePathList()); MatchFinder match_finder; std::set replacements; // Blink namespace matchers ======== auto blink_namespace_decl = namespaceDecl(anyOf(hasName("blink"), hasName("WTF")), hasParent(translationUnitDecl())); auto protocol_namespace_decl = namespaceDecl(hasName("protocol"), hasParent(namespaceDecl(hasName("blink"), hasParent(translationUnitDecl())))); // Given top-level compilation unit: // namespace WTF { // void foo() {} // } // matches |foo|. auto decl_under_blink_namespace = decl(hasAncestor(blink_namespace_decl), unless(hasAncestor(protocol_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(hasCanonicalDecl(isDeclInGeneratedFile()))); // 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(builtinType(), enumType()))), unless(hasAncestor(recordDecl( has(cxxMethodDecl(isUserProvided(), isInstanceMethod())))))); auto var_decl_matcher = id("decl", varDecl(in_blink_namespace, unless(is_type_trait_value))); // For known trait names, rename every instance anywhere in the codebase. auto type_trait_decl_matcher = id("decl", varDecl(isKnownTraitName())); 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); match_finder.addMatcher(type_trait_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 type_trait_ref_matcher = id("expr", declRefExpr(to(type_trait_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); match_finder.addMatcher(type_trait_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(), // Functions that look like blocked static methods. isBlocklistedMethod(method_blocklist))), 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(anyOf(isBlacklistedMethod(), isBlocklistedMethod(method_blocklist))))); 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 void F(T) { } // template H(T) { } // H>(...); // matches |F| in |H>|. // // 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 (for non-dependent fields / methods) ======== // 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); // Unresolved using value decls ======== // Example: // template // class BaseClass { // public: // unsigned long m_size; // }; // template // class DerivedClass : protected BaseClass { // private: // using Base = BaseClass; // using Base::m_size; // <- |m_size| here is matched by // void method() { // |unresolved_using_value_decl_matcher|. // m_size = 123; // <- |m_size| here is matched by // } // |unresolved_dependent_using_matcher|. // }; auto unresolved_dependent_using_matcher = expr(id("expr", unresolvedMemberExpr(allOverloadsMatch(allOf( in_blink_namespace, unresolvedUsingValueDecl()))))); UnresolvedDependentMemberRewriter unresolved_dependent_member_rewriter( &replacements); match_finder.addMatcher(unresolved_dependent_using_matcher, &unresolved_dependent_member_rewriter); auto unresolved_using_value_decl_matcher = decl(id("decl", unresolvedUsingValueDecl(in_blink_namespace))); UnresolvedUsingValueDeclRewriter unresolved_using_value_decl_rewriter( &replacements); match_finder.addMatcher(unresolved_using_value_decl_matcher, &unresolved_using_value_decl_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); // Matches any QualType that refers to a blink type: // - const blink::Foo& // - blink::Foo* // - blink::Foo auto blink_qual_type_base_matcher = hasBaseType(hasUnqualifiedDesugaredType( anyOf(enumType(hasDeclaration(in_blink_namespace)), injectedClassNameType(hasDeclaration(in_blink_namespace)), recordType(hasDeclaration(in_blink_namespace)), templateSpecializationType(hasDeclaration(in_blink_namespace)), templateTypeParmType(hasDeclaration(in_blink_namespace))))); auto blink_qual_type_matcher = qualType(anyOf( blink_qual_type_base_matcher, pointsTo(blink_qual_type_base_matcher), references(blink_qual_type_base_matcher))); // Template-dependent decl lookup ======== // Given // template void f() { T::foo(); } // matches |T::foo|. auto dependent_scope_decl_ref_expr_matcher = expr(id("expr", dependentScopeDeclRefExpr(has(nestedNameSpecifier( specifiesType(blink_qual_type_matcher)))))); DependentScopeDeclRefExprRewriter dependent_scope_decl_ref_expr_rewriter( &replacements); match_finder.addMatcher(dependent_scope_decl_ref_expr_matcher, &dependent_scope_decl_ref_expr_rewriter); // Template-dependent member lookup ======== // Given // template // class Foo { // void f() { T::foo(); } // void g(T x) { x.bar(); } // }; // matches |T::foo| and |x.bar|. auto cxx_dependent_scope_member_expr_matcher = expr(id("expr", cxxDependentScopeMemberExpr( hasMemberFromType(blink_qual_type_matcher)))); CXXDependentScopeMemberExprRewriter cxx_dependent_scope_member_expr_rewriter( &replacements); match_finder.addMatcher(cxx_dependent_scope_member_expr_matcher, &cxx_dependent_scope_member_expr_rewriter); // GMock calls lookup ======== // Given // EXPECT_CALL(obj, myMethod(...)) // or // ON_CALL(obj, myMethod(...)) // will match obj.gmock_myMethod(...) call generated by the macros // (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); // Prepare and run the tool. SourceFileCallbacks source_file_callbacks(&gmock_member_rewriter); std::unique_ptr factory = clang::tooling::newFrontendActionFactory(&match_finder, &source_file_callbacks); int result = tool.run(factory.get()); if (result != 0) return result; // Supplemental data for the Blink rename rebase helper. std::vector all_edit_trackers{ field_decl_rewriter.edit_tracker(), var_decl_rewriter.edit_tracker(), enum_member_decl_rewriter.edit_tracker(), member_rewriter.edit_tracker(), decl_ref_rewriter.edit_tracker(), enum_member_ref_rewriter.edit_tracker(), member_ref_rewriter.edit_tracker(), function_decl_rewriter.edit_tracker(), function_ref_rewriter.edit_tracker(), method_decl_rewriter.edit_tracker(), method_ref_rewriter.edit_tracker(), method_member_rewriter.edit_tracker(), constructor_initializer_rewriter.edit_tracker(), unresolved_lookup_rewriter.edit_tracker(), unresolved_member_rewriter.edit_tracker(), unresolved_dependent_member_rewriter.edit_tracker(), unresolved_using_value_decl_rewriter.edit_tracker(), using_decl_rewriter.edit_tracker(), dependent_scope_decl_ref_expr_rewriter.edit_tracker(), cxx_dependent_scope_member_expr_rewriter.edit_tracker(), gmock_member_rewriter.edit_tracker(), }; llvm::outs() << "==== BEGIN TRACKED EDITS ====\n"; for (const EditTracker* edit_tracker : all_edit_trackers) edit_tracker->SerializeTo(llvm::outs()); llvm::outs() << "==== END TRACKED EDITS ====\n"; // 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; }