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 // Calls to an overloaded operator also need parens, except for foo(...) and |
| 55 // foo[...] expressions. |
| 56 if (const clang::CXXOperatorCallExpr* op = |
| 57 llvm::dyn_cast<clang::CXXOperatorCallExpr>(expr)) { |
| 58 return op->getOperator() != clang::OO_Call && |
| 59 op->getOperator() != clang::OO_Subscript; |
| 60 } |
| 61 return false; |
| 62 } |
| 63 |
| 64 class GetRewriterCallback : public MatchFinder::MatchCallback { |
| 65 public: |
| 66 explicit GetRewriterCallback(Replacements* replacements) |
| 67 : replacements_(replacements) {} |
| 68 virtual void run(const MatchFinder::MatchResult& result) override; |
| 69 |
| 70 private: |
| 71 Replacements* const replacements_; |
| 72 }; |
| 73 |
| 74 void GetRewriterCallback::run(const MatchFinder::MatchResult& result) { |
| 75 const clang::CXXMemberCallExpr* const implicit_call = |
| 76 result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("call"); |
| 77 const clang::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); |
| 78 |
| 79 if (!implicit_call || !arg) |
| 80 return; |
| 81 |
| 82 clang::CharSourceRange range = clang::CharSourceRange::getTokenRange( |
| 83 result.SourceManager->getSpellingLoc(arg->getLocStart()), |
| 84 result.SourceManager->getSpellingLoc(arg->getLocEnd())); |
| 85 if (!range.isValid()) |
| 86 return; // TODO(rsleevi): Log an error? |
| 87 |
| 88 // Handle cases where an implicit cast is being done by dereferencing a |
| 89 // pointer to a scoped_refptr<> (sadly, it happens...) |
| 90 // |
| 91 // This rewrites both "*foo" and "*(foo)" as "foo->get()". |
| 92 if (const clang::UnaryOperator* op = |
| 93 llvm::dyn_cast<clang::UnaryOperator>(arg)) { |
| 94 if (op->getOpcode() == clang::UO_Deref) { |
| 95 const clang::Expr* const sub_expr = |
| 96 op->getSubExpr()->IgnoreParenImpCasts(); |
| 97 clang::CharSourceRange sub_expr_range = |
| 98 clang::CharSourceRange::getTokenRange( |
| 99 result.SourceManager->getSpellingLoc(sub_expr->getLocStart()), |
| 100 result.SourceManager->getSpellingLoc(sub_expr->getLocEnd())); |
| 101 if (!sub_expr_range.isValid()) |
| 102 return; // TODO(rsleevi): Log an error? |
| 103 std::string inner_text = clang::Lexer::getSourceText( |
| 104 sub_expr_range, *result.SourceManager, result.Context->getLangOpts()); |
| 105 if (inner_text.empty()) |
| 106 return; // TODO(rsleevi): Log an error? |
| 107 |
| 108 if (NeedsParens(sub_expr)) { |
| 109 inner_text.insert(0, "("); |
| 110 inner_text.append(")"); |
| 111 } |
| 112 inner_text.append("->get()"); |
| 113 replacements_->insert( |
| 114 Replacement(*result.SourceManager, range, inner_text)); |
| 115 return; |
| 116 } |
| 117 } |
| 118 |
| 119 std::string text = clang::Lexer::getSourceText( |
| 120 range, *result.SourceManager, result.Context->getLangOpts()); |
| 121 if (text.empty()) |
| 122 return; // TODO(rsleevi): Log an error? |
| 123 |
| 124 // Unwrap any temporaries - for example, custom iterators that return |
| 125 // scoped_refptr<T> as part of operator*. Any such iterators should also |
| 126 // be declaring a scoped_refptr<T>* operator->, per C++03 24.4.1.1 (Table 72) |
| 127 if (const clang::CXXBindTemporaryExpr* op = |
| 128 llvm::dyn_cast<clang::CXXBindTemporaryExpr>(arg)) { |
| 129 arg = op->getSubExpr(); |
| 130 } |
| 131 |
| 132 // Handle iterators (which are operator* calls, followed by implicit |
| 133 // conversions) by rewriting *it as it->get() |
| 134 if (const clang::CXXOperatorCallExpr* op = |
| 135 llvm::dyn_cast<clang::CXXOperatorCallExpr>(arg)) { |
| 136 if (op->getOperator() == clang::OO_Star) { |
| 137 // Note that this doesn't rewrite **it correctly, since it should be |
| 138 // rewritten using parens, e.g. (*it)->get(). However, this shouldn't |
| 139 // happen frequently, if at all, since it would likely indicate code is |
| 140 // storing pointers to a scoped_refptr in a container. |
| 141 text.erase(0, 1); |
| 142 text.append("->get()"); |
| 143 replacements_->insert(Replacement(*result.SourceManager, range, text)); |
| 144 return; |
| 145 } |
| 146 } |
| 147 |
| 148 // The only remaining calls should be non-dereferencing calls (eg: member |
| 149 // calls), so a simple ".get()" appending should suffice. |
| 150 if (NeedsParens(arg)) { |
| 151 text.insert(0, "("); |
| 152 text.append(")"); |
| 153 } |
| 154 text.append(".get()"); |
| 155 replacements_->insert(Replacement(*result.SourceManager, range, text)); |
| 156 } |
| 157 |
| 158 } // namespace |
| 159 |
| 160 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); |
| 161 |
| 162 int main(int argc, const char* argv[]) { |
| 163 llvm::cl::OptionCategory category("Remove scoped_refptr conversions"); |
| 164 CommonOptionsParser options(argc, argv, category); |
| 165 clang::tooling::ClangTool tool(options.getCompilations(), |
| 166 options.getSourcePathList()); |
| 167 |
| 168 MatchFinder match_finder; |
| 169 |
| 170 // Finds all calls to conversion operator member function. This catches calls |
| 171 // to "operator T*", "operator Testable", and "operator bool" equally. |
| 172 StatementMatcher overloaded_call_matcher = memberCallExpr( |
| 173 thisPointerType(recordDecl(isSameOrDerivedFrom("::scoped_refptr"), |
| 174 isTemplateInstantiation())), |
| 175 callee(conversionDecl()), |
| 176 on(id("arg", expr()))); |
| 177 |
| 178 // This catches both user-defined conversions (eg: "operator bool") and |
| 179 // standard conversion sequence (C++03 13.3.3.1.1), such as converting a |
| 180 // pointer to a bool. |
| 181 StatementMatcher implicit_to_bool = |
| 182 implicitCastExpr(hasImplicitDestinationType(isBoolean())); |
| 183 |
| 184 // Avoid converting calls to of "operator Testable" -> "bool" and calls of |
| 185 // "operator T*" -> "bool". |
| 186 StatementMatcher bool_conversion_matcher = hasParent(expr( |
| 187 anyOf(expr(implicit_to_bool), expr(hasParent(expr(implicit_to_bool)))))); |
| 188 |
| 189 // Find all calls to an operator overload that do NOT (ultimately) result in |
| 190 // being cast to a bool - eg: where it's being converted to T* and rewrite |
| 191 // them to add a call to get(). |
| 192 // |
| 193 // All bool conversions will be handled with the Testable trick, but that |
| 194 // can only be used once "operator T*" is removed, since otherwise it leaves |
| 195 // the call ambiguous. |
| 196 Replacements get_replacements; |
| 197 GetRewriterCallback get_callback(&get_replacements); |
| 198 match_finder.addMatcher(id("call", expr(overloaded_call_matcher)), |
| 199 &get_callback); |
| 200 |
| 201 #if 0 |
| 202 // Finds all temporary scoped_refptr<T>'s being assigned to a T*. Note that |
| 203 // this will result in two callbacks--both the above callback to append get() |
| 204 // and this callback will match. |
| 205 match_finder.addMatcher( |
| 206 id("var", |
| 207 varDecl(hasInitializer(ignoringImpCasts( |
| 208 id("call", expr(overloaded_call_matcher)))), |
| 209 hasType(pointerType()))), |
| 210 &callback); |
| 211 match_finder.addMatcher( |
| 212 binaryOperator( |
| 213 hasOperatorName("="), |
| 214 hasLHS(declRefExpr(to(id("var", varDecl(hasType(pointerType())))))), |
| 215 hasRHS(ignoringParenImpCasts( |
| 216 id("call", expr(overloaded_call_matcher))))), |
| 217 &callback); |
| 218 #endif |
| 219 |
| 220 std::unique_ptr<clang::tooling::FrontendActionFactory> factory = |
| 221 clang::tooling::newFrontendActionFactory(&match_finder); |
| 222 int result = tool.run(factory.get()); |
| 223 if (result != 0) |
| 224 return result; |
| 225 |
| 226 // Serialization format is documented in tools/clang/scripts/run_tool.py |
| 227 llvm::outs() << "==== BEGIN EDITS ====\n"; |
| 228 for (const auto& r : get_replacements) { |
| 229 std::string replacement_text = r.getReplacementText().str(); |
| 230 std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0'); |
| 231 llvm::outs() << "r:" << r.getFilePath() << ":" << r.getOffset() << ":" |
| 232 << r.getLength() << ":" << replacement_text << "\n"; |
| 233 } |
| 234 llvm::outs() << "==== END EDITS ====\n"; |
| 235 |
| 236 return 0; |
| 237 } |
OLD | NEW |