blob: 1f86290a750e6da36918ae9538be4d9bfb5dabab [file] [log] [blame]
// 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 clang::tooling::Replacements;
using llvm::StringRef;
namespace {
// Hack: prevent the custom isDefaulted() from conflicting with the one defined
// in newer revisions of Clang.
namespace internal_hack {
// This is available in newer clang revisions... but alas, Chrome has not rolled
// that far yet.
AST_MATCHER(clang::FunctionDecl, isDefaulted) {
return Node.isDefaulted();
}
} // namespace internal_hack
const char kBlinkFieldPrefix[] = "m_";
const char kBlinkStaticMemberPrefix[] = "s_";
AST_MATCHER(clang::FunctionDecl, isOverloadedOperator) {
return Node.isOverloadedOperator();
}
AST_MATCHER_P(clang::FunctionTemplateDecl,
templatedDecl,
clang::ast_matchers::internal::Matcher<clang::FunctionDecl>,
InnerMatcher) {
return InnerMatcher.matches(*Node.getTemplatedDecl(), Finder, Builder);
}
// A method is from Blink if it is from the Blink namespace or overrides a
// method from the Blink namespace.
bool IsBlinkMethod(const clang::CXXMethodDecl& decl) {
auto* namespace_decl = clang::dyn_cast_or_null<clang::NamespaceDecl>(
decl.getParent()->getEnclosingNamespaceContext());
if (namespace_decl && namespace_decl->getParent()->isTranslationUnit() &&
(namespace_decl->getName() == "blink" ||
namespace_decl->getName() == "WTF"))
return true;
for (auto it = decl.begin_overridden_methods();
it != decl.end_overridden_methods(); ++it) {
if (IsBlinkMethod(**it))
return true;
}
return false;
}
AST_MATCHER(clang::CXXMethodDecl, isBlinkMethod) {
return IsBlinkMethod(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,
bool numbers_are_separated) {
std::string output;
bool needs_underscore = false;
bool was_lowercase = false;
bool was_number = 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_number = clang::isDigit(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 enabled, when entering or exiting a set of numbers, insert a '_'.
else if (!first_char && numbers_are_separated && !was_number && is_number)
needs_underscore = true;
else if (numbers_are_separated && was_number && !is_number)
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_number = is_number;
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);
}
bool GetNameForDecl(const clang::FunctionDecl& decl,
const clang::ASTContext& context,
std::string& name) {
StringRef original_name = decl.getName();
// Some functions shouldn't be renamed because reasons.
// - swap() methods should match the signature of std::swap for ADL tricks.
static const char* kBlacklist[] = {"swap"};
for (const auto& b : kBlacklist) {
if (original_name == b)
return false;
}
name = original_name.str();
name[0] = clang::toUppercase(name[0]);
return true;
}
bool GetNameForDecl(const clang::EnumConstantDecl& decl,
const clang::ASTContext& context,
std::string& name) {
StringRef original_name = decl.getName();
bool already_shouty = true;
for (char c : original_name) {
if (!clang::isUppercase(c) && !clang::isDigit(c) && c != '_') {
already_shouty = false;
break;
}
}
if (already_shouty)
return false;
name = CamelCaseToUnderscoreCase(original_name, true);
for (auto& c : name)
c = clang::toUppercase(c);
return true;
}
bool GetNameForDecl(const clang::CXXMethodDecl& decl,
const clang::ASTContext& context,
std::string& name) {
StringRef original_name = decl.getName();
if (!decl.isStatic()) {
// Some methods shouldn't be renamed because reasons.
static const char* kBlacklist[] = {"begin", "end", "rbegin", "rend",
"trace", "lock", "unlock", "try_lock"};
for (const auto& b : kBlacklist) {
if (original_name == b)
return false;
}
}
name = decl.getName().str();
name[0] = clang::toUppercase(name[0]);
return true;
}
bool GetNameForDecl(const clang::FieldDecl& decl,
const 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, false);
// 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,
const clang::ASTContext& context,
std::string& name) {
StringRef original_name = decl.getName();
// Nothing to do for unnamed parameters.
if (clang::isa<clang::ParmVarDecl>(decl) && original_name.empty())
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, false);
}
// 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,
const clang::ASTContext& context,
std::string& name) {
clang::FunctionDecl* templated_function = decl.getTemplatedDecl();
if (auto* method = clang::dyn_cast<clang::CXXMethodDecl>(templated_function))
return GetNameForDecl(*method, context, name);
return GetNameForDecl(*templated_function, context, name);
}
bool GetNameForDecl(const clang::UsingDecl& decl,
const 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.
clang::NamedDecl* shadowed_name = decl.shadow_begin()->getTargetDecl();
// Note: CXXMethodDecl must be checked before FunctionDecl, because
// CXXMethodDecl is derived from FunctionDecl.
if (auto* method = clang::dyn_cast<clang::CXXMethodDecl>(shadowed_name))
return GetNameForDecl(*method, context, name);
if (auto* function = clang::dyn_cast<clang::FunctionDecl>(shadowed_name))
return GetNameForDecl(*function, context, name);
if (auto* var = clang::dyn_cast<clang::VarDecl>(shadowed_name))
return GetNameForDecl(*var, context, name);
if (auto* field = clang::dyn_cast<clang::FieldDecl>(shadowed_name))
return GetNameForDecl(*field, context, name);
if (auto* function_template =
clang::dyn_cast<clang::FunctionTemplateDecl>(shadowed_name))
return GetNameForDecl(*function_template, context, name);
if (auto* enumc = clang::dyn_cast<clang::EnumConstantDecl>(shadowed_name))
return GetNameForDecl(*enumc, context, name);
return false;
}
template <typename Type>
struct TargetNodeTraits;
template <>
struct TargetNodeTraits<clang::NamedDecl> {
static const char kName[];
static clang::SourceLocation GetLoc(const clang::NamedDecl& decl) {
return decl.getLocation();
}
};
const char TargetNodeTraits<clang::NamedDecl>::kName[] = "decl";
template <>
struct TargetNodeTraits<clang::MemberExpr> {
static const char kName[];
static clang::SourceLocation GetLoc(const clang::MemberExpr& expr) {
return expr.getMemberLoc();
}
};
const char TargetNodeTraits<clang::MemberExpr>::kName[] = "expr";
template <>
struct TargetNodeTraits<clang::DeclRefExpr> {
static const char kName[];
static clang::SourceLocation GetLoc(const clang::DeclRefExpr& expr) {
return expr.getLocation();
}
};
const char TargetNodeTraits<clang::DeclRefExpr>::kName[] = "expr";
template <>
struct TargetNodeTraits<clang::CXXCtorInitializer> {
static const char kName[];
static clang::SourceLocation GetLoc(const clang::CXXCtorInitializer& init) {
assert(init.isWritten());
return init.getSourceLocation();
}
};
const char TargetNodeTraits<clang::CXXCtorInitializer>::kName[] = "initializer";
template <typename DeclNode, typename TargetNode>
class RewriterBase : public MatchFinder::MatchCallback {
public:
explicit RewriterBase(Replacements* replacements)
: replacements_(replacements) {}
void run(const MatchFinder::MatchResult& result) override {
const DeclNode* decl = result.Nodes.getNodeAs<DeclNode>("decl");
// If the decl originates inside a macro, just skip it completely.
clang::SourceLocation decl_loc =
TargetNodeTraits<clang::NamedDecl>::GetLoc(*decl);
if (decl_loc.isMacroID())
return;
// If false, there's no name to be renamed.
if (!decl->getIdentifier())
return;
// If false, the name was not suitable for renaming.
clang::ASTContext* context = result.Context;
std::string new_name;
if (!GetNameForDecl(*decl, *context, new_name))
return;
llvm::StringRef old_name = decl->getName();
if (old_name == new_name)
return;
clang::SourceLocation loc = TargetNodeTraits<TargetNode>::GetLoc(
*result.Nodes.getNodeAs<TargetNode>(
TargetNodeTraits<TargetNode>::kName));
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:
Replacements* 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 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 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;
Replacements replacements;
auto in_blink_namespace =
decl(hasAncestor(namespaceDecl(anyOf(hasName("blink"), hasName("WTF")),
hasParent(translationUnitDecl()))));
// The ^gen/ rule is used for production code, but the /gen/ one exists here
// too for making testing easier.
auto is_generated = decl(isExpansionInFileMatching("^gen/|/gen/"));
// 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, unless(is_generated)));
auto var_decl_matcher =
id("decl", varDecl(in_blink_namespace, unless(is_generated)));
auto enum_member_decl_matcher =
id("decl", enumConstantDecl(in_blink_namespace, unless(is_generated)));
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(internal_hack::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);
// 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())),
in_blink_namespace, unless(is_generated)));
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)));
FunctionRefRewriter function_ref_rewriter(&replacements);
match_finder.addMatcher(function_ref_matcher, &function_ref_rewriter);
// Method declarations ========
// Given
// struct S {
// void g();
// };
// matches |g|.
auto method_decl_matcher =
id("decl",
cxxMethodDecl(isBlinkMethod(),
unless(anyOf(is_generated,
// 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()))));
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)));
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(forField(field_decl_matcher), isWritten()))));
ConstructorInitializerRewriter constructor_initializer_rewriter(
&replacements);
match_finder.addMatcher(constructor_initializer_matcher,
&constructor_initializer_rewriter);
// Using declarations ========
// Given
// using blink::X;
// matches |using blink::X|.
auto function_template_decl_matcher =
id("decl",
functionTemplateDecl(
templatedDecl(anyOf(function_decl_matcher, method_decl_matcher)),
in_blink_namespace, unless(is_generated)));
UsingDeclRewriter using_decl_rewriter(&replacements);
match_finder.addMatcher(
id("decl",
usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(
anyOf(var_decl_matcher, field_decl_matcher, function_decl_matcher,
method_decl_matcher, function_template_decl_matcher,
enum_member_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;
}