Index: tools/clang/rewrite_scoped_refptr/RewriteScopedRefptr.cpp |
diff --git a/tools/clang/rewrite_scoped_refptr/RewriteScopedRefptr.cpp b/tools/clang/rewrite_scoped_refptr/RewriteScopedRefptr.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..be7223f3d7123c15168ca5719ae0d397a7c85cb8 |
--- /dev/null |
+++ b/tools/clang/rewrite_scoped_refptr/RewriteScopedRefptr.cpp |
@@ -0,0 +1,231 @@ |
+// Copyright (c) 2013 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. |
+// |
+// This implements a Clang tool to rewrite all instances of |
+// scoped_refptr<T>'s implicit cast to T (operator T*) to an explicit call to |
+// the .get() method. |
+ |
+#include <algorithm> |
+#include <memory> |
+#include <string> |
+ |
+#include "clang/AST/ASTContext.h" |
+#include "clang/ASTMatchers/ASTMatchers.h" |
+#include "clang/ASTMatchers/ASTMatchersMacros.h" |
+#include "clang/ASTMatchers/ASTMatchFinder.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" |
+ |
+using namespace clang::ast_matchers; |
+using clang::tooling::CommonOptionsParser; |
+using clang::tooling::Replacement; |
+using clang::tooling::Replacements; |
+using llvm::StringRef; |
+ |
+namespace clang { |
+namespace ast_matchers { |
+ |
+const internal::VariadicDynCastAllOfMatcher<Decl, CXXConversionDecl> |
+ conversionDecl; |
+ |
+AST_MATCHER(QualType, isBoolean) { |
+ return Node->isBooleanType(); |
+} |
+ |
+} // namespace ast_matchers |
+} // namespace clang |
+ |
+namespace { |
+ |
+// Returns true if expr needs to be put in parens (eg: when it is an operator |
+// syntactically). |
+bool NeedsParens(const clang::Expr* expr) { |
+ if (llvm::dyn_cast<clang::UnaryOperator>(expr) || |
+ llvm::dyn_cast<clang::BinaryOperator>(expr) || |
+ llvm::dyn_cast<clang::ConditionalOperator>(expr)) { |
+ return true; |
+ } |
+ if (const clang::CXXOperatorCallExpr* op = |
mdempsky
2014/08/07 22:25:10
Might be worth adding a comment explaining that th
dcheng
2014/08/07 23:11:21
Done.
|
+ llvm::dyn_cast<clang::CXXOperatorCallExpr>(expr)) { |
+ return op->getOperator() != clang::OO_Call && |
+ op->getOperator() != clang::OO_Subscript; |
+ } |
+ return false; |
+} |
+ |
+class GetRewriterCallback : public MatchFinder::MatchCallback { |
+ public: |
+ explicit GetRewriterCallback(Replacements* replacements) |
+ : replacements_(replacements) {} |
+ virtual void run(const MatchFinder::MatchResult& result) override; |
+ |
+ private: |
+ Replacements* const replacements_; |
+}; |
+ |
+void GetRewriterCallback::run(const MatchFinder::MatchResult& result) { |
+ const clang::CXXMemberCallExpr* const implicit_call = |
+ result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("call"); |
+ const clang::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); |
+ |
+ if (!implicit_call || !arg) |
+ return; |
+ |
+ clang::CharSourceRange range = clang::CharSourceRange::getTokenRange( |
+ result.SourceManager->getSpellingLoc(arg->getLocStart()), |
+ result.SourceManager->getSpellingLoc(arg->getLocEnd())); |
+ if (!range.isValid()) |
+ return; // TODO(rsleevi): Log an error? |
+ |
+ // Handle cases where an implicit cast is being done by dereferencing a |
+ // pointer to a scoped_refptr<> (sadly, it happens...) |
+ // |
+ // This rewrites both "*foo" and "*(foo)" as "foo->get()". |
+ if (const clang::UnaryOperator* op = |
+ llvm::dyn_cast<clang::UnaryOperator>(arg)) { |
+ if (op->getOpcode() == clang::UO_Deref) { |
+ const clang::Expr* const sub_expr = |
+ op->getSubExpr()->IgnoreParenImpCasts(); |
+ clang::CharSourceRange sub_expr_range = |
+ clang::CharSourceRange::getTokenRange( |
+ result.SourceManager->getSpellingLoc(sub_expr->getLocStart()), |
+ result.SourceManager->getSpellingLoc(sub_expr->getLocEnd())); |
+ if (!sub_expr_range.isValid()) |
+ return; // TODO(rsleevi): Log an error? |
+ std::string inner_text = clang::Lexer::getSourceText( |
+ sub_expr_range, *result.SourceManager, result.Context->getLangOpts()); |
+ if (inner_text.empty()) |
+ return; // TODO(rsleevi): Log an error? |
+ |
+ if (NeedsParens(sub_expr)) { |
+ inner_text.insert(0, "("); |
+ inner_text.append(")"); |
+ } |
+ inner_text.append("->get()"); |
+ replacements_->insert( |
+ Replacement(*result.SourceManager, range, inner_text)); |
+ return; |
+ } |
+ } |
+ |
+ std::string text = clang::Lexer::getSourceText( |
+ range, *result.SourceManager, result.Context->getLangOpts()); |
+ if (text.empty()) |
+ return; // TODO(rsleevi): Log an error? |
+ |
+ // Unwrap any temporaries - for example, custom iterators that return |
+ // scoped_refptr<T> as part of operator*. Any such iterators should also |
+ // be declaring a scoped_refptr<T>* operator->, per C++03 24.4.1.1 (Table 72) |
+ if (const clang::CXXBindTemporaryExpr* op = |
+ llvm::dyn_cast<clang::CXXBindTemporaryExpr>(arg)) { |
+ arg = op->getSubExpr(); |
+ } |
+ |
+ // Handle iterators (which are operator* calls, followed by implicit |
+ // conversions) by rewriting *it as it->get() |
+ if (const clang::CXXOperatorCallExpr* op = |
+ llvm::dyn_cast<clang::CXXOperatorCallExpr>(arg)) { |
+ if (op->getOperator() == clang::OO_Star) { |
+ text.erase(0, 1); |
+ text.append("->get()"); |
mdempsky
2014/08/07 22:25:10
Is it possible we'll NeedParens() here? E.g., I t
dcheng
2014/08/07 23:11:21
Added a comment per our discussion.
|
+ replacements_->insert(Replacement(*result.SourceManager, range, text)); |
+ return; |
+ } |
+ } |
+ |
+ // The only remaining calls should be non-dereferencing calls (eg: member |
+ // calls), so a simple ".get()" appending should suffice. |
+ if (NeedsParens(arg)) { |
+ text.insert(0, "("); |
+ text.append(")"); |
+ } |
+ text.append(".get()"); |
+ replacements_->insert(Replacement(*result.SourceManager, range, text)); |
+} |
+ |
+} // namespace |
+ |
+static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); |
+ |
+int main(int argc, const char* argv[]) { |
+ llvm::cl::OptionCategory category("Remove scoped_refptr conversions"); |
+ CommonOptionsParser options(argc, argv, category); |
+ clang::tooling::ClangTool tool(options.getCompilations(), |
+ options.getSourcePathList()); |
+ |
+ MatchFinder match_finder; |
+ |
+ // Finds all calls to conversion operator member function. This catches calls |
+ // to "operator T*", "operator Testable", and "operator bool" equally. |
+ StatementMatcher overloaded_call_matcher = memberCallExpr( |
+ thisPointerType(recordDecl(isSameOrDerivedFrom("::scoped_refptr"), |
+ isTemplateInstantiation())), |
+ callee(conversionDecl()), |
+ on(id("arg", expr()))); |
+ |
+ // This catches both user-defined conversions (eg: "operator bool") and |
+ // standard conversion sequence (C++03 13.3.3.1.1), such as converting a |
+ // pointer to a bool. |
+ StatementMatcher implicit_to_bool = |
+ implicitCastExpr(hasImplicitDestinationType(isBoolean())); |
+ |
+ // Avoid converting calls to of "operator Testable" -> "bool" and calls of |
+ // "operator T*" -> "bool". |
+ StatementMatcher bool_conversion_matcher = hasParent(expr( |
+ anyOf(expr(implicit_to_bool), expr(hasParent(expr(implicit_to_bool)))))); |
+ |
+ // Find all calls to an operator overload that do NOT (ultimately) result in |
+ // being cast to a bool - eg: where it's being converted to T* and rewrite |
+ // them to add a call to get(). |
+ // |
+ // All bool conversions will be handled with the Testable trick, but that |
+ // can only be used once "operator T*" is removed, since otherwise it leaves |
+ // the call ambiguous. |
+ Replacements get_replacements; |
+ GetRewriterCallback get_callback(&get_replacements); |
+ match_finder.addMatcher(id("call", expr(overloaded_call_matcher)), |
+ &get_callback); |
+ |
+#if 0 |
+ // Finds all temporary scoped_refptr<T>'s being assigned to a T*. Note that |
+ // this will result in two callbacks--both the above callback to append get() |
+ // and this callback will match. |
+ match_finder.addMatcher( |
+ id("var", |
+ varDecl(hasInitializer(ignoringImpCasts( |
+ id("call", expr(overloaded_call_matcher)))), |
+ hasType(pointerType()))), |
+ &callback); |
+ match_finder.addMatcher( |
+ binaryOperator( |
+ hasOperatorName("="), |
+ hasLHS(declRefExpr(to(id("var", varDecl(hasType(pointerType())))))), |
+ hasRHS(ignoringParenImpCasts( |
+ id("call", expr(overloaded_call_matcher))))), |
+ &callback); |
+#endif |
+ |
+ std::unique_ptr<clang::tooling::FrontendActionFactory> factory = |
+ clang::tooling::newFrontendActionFactory(&match_finder); |
+ int result = tool.run(factory.get()); |
+ if (result != 0) |
+ return result; |
+ |
+ // Serialization format is documented in tools/clang/scripts/run_tool.py |
+ llvm::outs() << "==== BEGIN EDITS ====\n"; |
+ for (const auto& r : get_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; |
+} |