Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 // | |
| 5 // This implements a Clang tool to rewrite all instances of | |
| 6 // scoped_refptr<T>'s implicit cast to T (operator T*) to an explicit call to | |
| 7 // the .get() method. | |
| 8 | |
| 9 #include <algorithm> | |
| 10 #include <memory> | |
| 11 #include <string> | |
| 12 | |
| 13 #include "clang/AST/ASTContext.h" | |
| 14 #include "clang/ASTMatchers/ASTMatchers.h" | |
| 15 #include "clang/ASTMatchers/ASTMatchersMacros.h" | |
| 16 #include "clang/ASTMatchers/ASTMatchFinder.h" | |
| 17 #include "clang/Basic/SourceManager.h" | |
| 18 #include "clang/Frontend/FrontendActions.h" | |
| 19 #include "clang/Lex/Lexer.h" | |
| 20 #include "clang/Tooling/CommonOptionsParser.h" | |
| 21 #include "clang/Tooling/Refactoring.h" | |
| 22 #include "clang/Tooling/Tooling.h" | |
| 23 #include "llvm/Support/CommandLine.h" | |
| 24 | |
| 25 using namespace clang::ast_matchers; | |
| 26 using clang::tooling::CommonOptionsParser; | |
| 27 using clang::tooling::Replacement; | |
| 28 using clang::tooling::Replacements; | |
| 29 using llvm::StringRef; | |
| 30 | |
| 31 namespace clang { | |
| 32 namespace ast_matchers { | |
| 33 | |
| 34 const internal::VariadicDynCastAllOfMatcher<Decl, CXXConversionDecl> | |
| 35 conversionDecl; | |
| 36 | |
| 37 AST_MATCHER(QualType, isBoolean) { | |
| 38 return Node->isBooleanType(); | |
| 39 } | |
| 40 | |
| 41 } // namespace ast_matchers | |
| 42 } // namespace clang | |
| 43 | |
| 44 namespace { | |
| 45 | |
| 46 // Returns true if expr needs to be put in parens (eg: when it is an operator | |
| 47 // syntactically). | |
| 48 bool NeedsParens(const clang::Expr* expr) { | |
| 49 if (llvm::dyn_cast<clang::UnaryOperator>(expr) || | |
| 50 llvm::dyn_cast<clang::BinaryOperator>(expr) || | |
| 51 llvm::dyn_cast<clang::ConditionalOperator>(expr)) { | |
| 52 return true; | |
| 53 } | |
| 54 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.
| |
| 55 llvm::dyn_cast<clang::CXXOperatorCallExpr>(expr)) { | |
| 56 return op->getOperator() != clang::OO_Call && | |
| 57 op->getOperator() != clang::OO_Subscript; | |
| 58 } | |
| 59 return false; | |
| 60 } | |
| 61 | |
| 62 class GetRewriterCallback : public MatchFinder::MatchCallback { | |
| 63 public: | |
| 64 explicit GetRewriterCallback(Replacements* replacements) | |
| 65 : replacements_(replacements) {} | |
| 66 virtual void run(const MatchFinder::MatchResult& result) override; | |
| 67 | |
| 68 private: | |
| 69 Replacements* const replacements_; | |
| 70 }; | |
| 71 | |
| 72 void GetRewriterCallback::run(const MatchFinder::MatchResult& result) { | |
| 73 const clang::CXXMemberCallExpr* const implicit_call = | |
| 74 result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("call"); | |
| 75 const clang::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); | |
| 76 | |
| 77 if (!implicit_call || !arg) | |
| 78 return; | |
| 79 | |
| 80 clang::CharSourceRange range = clang::CharSourceRange::getTokenRange( | |
| 81 result.SourceManager->getSpellingLoc(arg->getLocStart()), | |
| 82 result.SourceManager->getSpellingLoc(arg->getLocEnd())); | |
| 83 if (!range.isValid()) | |
| 84 return; // TODO(rsleevi): Log an error? | |
| 85 | |
| 86 // Handle cases where an implicit cast is being done by dereferencing a | |
| 87 // pointer to a scoped_refptr<> (sadly, it happens...) | |
| 88 // | |
| 89 // This rewrites both "*foo" and "*(foo)" as "foo->get()". | |
| 90 if (const clang::UnaryOperator* op = | |
| 91 llvm::dyn_cast<clang::UnaryOperator>(arg)) { | |
| 92 if (op->getOpcode() == clang::UO_Deref) { | |
| 93 const clang::Expr* const sub_expr = | |
| 94 op->getSubExpr()->IgnoreParenImpCasts(); | |
| 95 clang::CharSourceRange sub_expr_range = | |
| 96 clang::CharSourceRange::getTokenRange( | |
| 97 result.SourceManager->getSpellingLoc(sub_expr->getLocStart()), | |
| 98 result.SourceManager->getSpellingLoc(sub_expr->getLocEnd())); | |
| 99 if (!sub_expr_range.isValid()) | |
| 100 return; // TODO(rsleevi): Log an error? | |
| 101 std::string inner_text = clang::Lexer::getSourceText( | |
| 102 sub_expr_range, *result.SourceManager, result.Context->getLangOpts()); | |
| 103 if (inner_text.empty()) | |
| 104 return; // TODO(rsleevi): Log an error? | |
| 105 | |
| 106 if (NeedsParens(sub_expr)) { | |
| 107 inner_text.insert(0, "("); | |
| 108 inner_text.append(")"); | |
| 109 } | |
| 110 inner_text.append("->get()"); | |
| 111 replacements_->insert( | |
| 112 Replacement(*result.SourceManager, range, inner_text)); | |
| 113 return; | |
| 114 } | |
| 115 } | |
| 116 | |
| 117 std::string text = clang::Lexer::getSourceText( | |
| 118 range, *result.SourceManager, result.Context->getLangOpts()); | |
| 119 if (text.empty()) | |
| 120 return; // TODO(rsleevi): Log an error? | |
| 121 | |
| 122 // Unwrap any temporaries - for example, custom iterators that return | |
| 123 // scoped_refptr<T> as part of operator*. Any such iterators should also | |
| 124 // be declaring a scoped_refptr<T>* operator->, per C++03 24.4.1.1 (Table 72) | |
| 125 if (const clang::CXXBindTemporaryExpr* op = | |
| 126 llvm::dyn_cast<clang::CXXBindTemporaryExpr>(arg)) { | |
| 127 arg = op->getSubExpr(); | |
| 128 } | |
| 129 | |
| 130 // Handle iterators (which are operator* calls, followed by implicit | |
| 131 // conversions) by rewriting *it as it->get() | |
| 132 if (const clang::CXXOperatorCallExpr* op = | |
| 133 llvm::dyn_cast<clang::CXXOperatorCallExpr>(arg)) { | |
| 134 if (op->getOperator() == clang::OO_Star) { | |
| 135 text.erase(0, 1); | |
| 136 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.
| |
| 137 replacements_->insert(Replacement(*result.SourceManager, range, text)); | |
| 138 return; | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 // The only remaining calls should be non-dereferencing calls (eg: member | |
| 143 // calls), so a simple ".get()" appending should suffice. | |
| 144 if (NeedsParens(arg)) { | |
| 145 text.insert(0, "("); | |
| 146 text.append(")"); | |
| 147 } | |
| 148 text.append(".get()"); | |
| 149 replacements_->insert(Replacement(*result.SourceManager, range, text)); | |
| 150 } | |
| 151 | |
| 152 } // namespace | |
| 153 | |
| 154 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); | |
| 155 | |
| 156 int main(int argc, const char* argv[]) { | |
| 157 llvm::cl::OptionCategory category("Remove scoped_refptr conversions"); | |
| 158 CommonOptionsParser options(argc, argv, category); | |
| 159 clang::tooling::ClangTool tool(options.getCompilations(), | |
| 160 options.getSourcePathList()); | |
| 161 | |
| 162 MatchFinder match_finder; | |
| 163 | |
| 164 // Finds all calls to conversion operator member function. This catches calls | |
| 165 // to "operator T*", "operator Testable", and "operator bool" equally. | |
| 166 StatementMatcher overloaded_call_matcher = memberCallExpr( | |
| 167 thisPointerType(recordDecl(isSameOrDerivedFrom("::scoped_refptr"), | |
| 168 isTemplateInstantiation())), | |
| 169 callee(conversionDecl()), | |
| 170 on(id("arg", expr()))); | |
| 171 | |
| 172 // This catches both user-defined conversions (eg: "operator bool") and | |
| 173 // standard conversion sequence (C++03 13.3.3.1.1), such as converting a | |
| 174 // pointer to a bool. | |
| 175 StatementMatcher implicit_to_bool = | |
| 176 implicitCastExpr(hasImplicitDestinationType(isBoolean())); | |
| 177 | |
| 178 // Avoid converting calls to of "operator Testable" -> "bool" and calls of | |
| 179 // "operator T*" -> "bool". | |
| 180 StatementMatcher bool_conversion_matcher = hasParent(expr( | |
| 181 anyOf(expr(implicit_to_bool), expr(hasParent(expr(implicit_to_bool)))))); | |
| 182 | |
| 183 // Find all calls to an operator overload that do NOT (ultimately) result in | |
| 184 // being cast to a bool - eg: where it's being converted to T* and rewrite | |
| 185 // them to add a call to get(). | |
| 186 // | |
| 187 // All bool conversions will be handled with the Testable trick, but that | |
| 188 // can only be used once "operator T*" is removed, since otherwise it leaves | |
| 189 // the call ambiguous. | |
| 190 Replacements get_replacements; | |
| 191 GetRewriterCallback get_callback(&get_replacements); | |
| 192 match_finder.addMatcher(id("call", expr(overloaded_call_matcher)), | |
| 193 &get_callback); | |
| 194 | |
| 195 #if 0 | |
| 196 // Finds all temporary scoped_refptr<T>'s being assigned to a T*. Note that | |
| 197 // this will result in two callbacks--both the above callback to append get() | |
| 198 // and this callback will match. | |
| 199 match_finder.addMatcher( | |
| 200 id("var", | |
| 201 varDecl(hasInitializer(ignoringImpCasts( | |
| 202 id("call", expr(overloaded_call_matcher)))), | |
| 203 hasType(pointerType()))), | |
| 204 &callback); | |
| 205 match_finder.addMatcher( | |
| 206 binaryOperator( | |
| 207 hasOperatorName("="), | |
| 208 hasLHS(declRefExpr(to(id("var", varDecl(hasType(pointerType())))))), | |
| 209 hasRHS(ignoringParenImpCasts( | |
| 210 id("call", expr(overloaded_call_matcher))))), | |
| 211 &callback); | |
| 212 #endif | |
| 213 | |
| 214 std::unique_ptr<clang::tooling::FrontendActionFactory> factory = | |
| 215 clang::tooling::newFrontendActionFactory(&match_finder); | |
| 216 int result = tool.run(factory.get()); | |
| 217 if (result != 0) | |
| 218 return result; | |
| 219 | |
| 220 // Serialization format is documented in tools/clang/scripts/run_tool.py | |
| 221 llvm::outs() << "==== BEGIN EDITS ====\n"; | |
| 222 for (const auto& r : get_replacements) { | |
| 223 std::string replacement_text = r.getReplacementText().str(); | |
| 224 std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0'); | |
| 225 llvm::outs() << "r:" << r.getFilePath() << ":" << r.getOffset() << ":" | |
| 226 << r.getLength() << ":" << replacement_text << "\n"; | |
| 227 } | |
| 228 llvm::outs() << "==== END EDITS ====\n"; | |
| 229 | |
| 230 return 0; | |
| 231 } | |
| OLD | NEW |