Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 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 tool performs the following transformations on base::MessageLoop and | |
| 6 // related APIs: | |
| 7 // | |
| 8 // MessageLoop: | |
| 9 // - base::MessageLoop::PostTask* | |
| 10 // => base::MessageLoop::task_runner()->PostTask*() | |
|
dcheng
2015/05/06 17:31:18
Dumb question: why not just update MessageLoop::Po
Sami
2015/05/07 10:37:37
There is some more discussion on the linked design
| |
| 11 // | |
| 12 // Thread: | |
| 13 // - base::Thread::message_loop_proxy() | |
| 14 // => task_runner() | |
| 15 // | |
| 16 // MessageLoopProxy: | |
| 17 // - base::MessageLoopProxy | |
| 18 // => base::SingleThreadTaskRunner | |
| 19 // - scoped_refptr<base::MessageLoopProxy> | |
| 20 // => scoped_refptr<base::SingleThreadTaskRunner> | |
| 21 // - base::MessageLoopProxy::current() | |
| 22 // => base::ThreadTaskRunnerHandle::Get() | |
| 23 // - base::MessageLoop::message_loop_proxy() | |
| 24 // => base::MessageLoop::task_runner() (done) | |
| 25 // | |
| 26 | |
| 27 #include <memory> | |
| 28 #include "clang/AST/ExprCXX.h" | |
| 29 #include "clang/ASTMatchers/ASTMatchers.h" | |
| 30 #include "clang/ASTMatchers/ASTMatchFinder.h" | |
| 31 #include "clang/Basic/SourceManager.h" | |
| 32 #include "clang/Frontend/FrontendActions.h" | |
| 33 #include "clang/Lex/Lexer.h" | |
| 34 #include "clang/Tooling/CommonOptionsParser.h" | |
| 35 #include "clang/Tooling/Refactoring.h" | |
| 36 #include "clang/Tooling/Tooling.h" | |
| 37 #include "llvm/Support/CommandLine.h" | |
| 38 | |
| 39 using clang::ast_matchers::MatchFinder; | |
| 40 using clang::ast_matchers::anyOf; | |
| 41 using clang::ast_matchers::asString; | |
| 42 using clang::ast_matchers::callExpr; | |
| 43 using clang::ast_matchers::callee; | |
| 44 using clang::ast_matchers::decl; | |
| 45 using clang::ast_matchers::functionDecl; | |
| 46 using clang::ast_matchers::hasAncestor; | |
| 47 using clang::ast_matchers::hasAnyTemplateArgument; | |
| 48 using clang::ast_matchers::hasDescendant; | |
| 49 using clang::ast_matchers::hasName; | |
| 50 using clang::ast_matchers::hasType; | |
| 51 using clang::ast_matchers::isSameOrDerivedFrom; | |
| 52 using clang::ast_matchers::isTemplateInstantiation; | |
| 53 using clang::ast_matchers::memberCallExpr; | |
| 54 using clang::ast_matchers::methodDecl; | |
| 55 using clang::ast_matchers::ofClass; | |
| 56 using clang::ast_matchers::pointsTo; | |
| 57 using clang::ast_matchers::recordDecl; | |
| 58 using clang::ast_matchers::refersToType; | |
| 59 using clang::ast_matchers::templateSpecializationType; | |
| 60 using clang::ast_matchers::thisPointerType; | |
| 61 using clang::ast_matchers::unless; | |
| 62 using clang::ast_matchers::varDecl; | |
| 63 | |
| 64 using clang::tooling::CommonOptionsParser; | |
| 65 using clang::tooling::Replacement; | |
| 66 using clang::tooling::Replacements; | |
| 67 | |
| 68 using clang::CXXMemberCallExpr; | |
| 69 using clang::CallExpr; | |
| 70 using clang::Expr; | |
| 71 using clang::MemberExpr; | |
| 72 using clang::VarDecl; | |
| 73 | |
| 74 namespace { | |
| 75 | |
| 76 std::string ReplaceFirst(const std::string& input, | |
|
dcheng
2015/05/06 17:31:18
Consider using llvm::StringRef where appropriate,
Sami
2015/05/07 10:37:37
Neat, thanks for the tip. Done.
| |
| 77 const char* from, | |
| 78 const char* to) { | |
| 79 size_t pos = input.find(from); | |
| 80 if (pos == std::string::npos) | |
| 81 return input; | |
| 82 return input.substr(0, pos) + to + input.substr(pos + strlen(from)); | |
| 83 } | |
| 84 | |
| 85 // Handles conversion of the Post*Task APIs. | |
| 86 class PostTaskCallback : public MatchFinder::MatchCallback { | |
| 87 public: | |
| 88 PostTaskCallback(Replacements* replacements) : replacements_(replacements) {} | |
|
dcheng
2015/05/06 17:31:18
explicit
Sami
2015/05/07 10:37:37
Fixed (here and elsewhere).
| |
| 89 | |
| 90 virtual void run(const MatchFinder::MatchResult& result) override; | |
|
dcheng
2015/05/06 17:31:17
No virtual.
Sami
2015/05/07 10:37:37
Fixed (here and elsewhere).
| |
| 91 | |
| 92 private: | |
| 93 Replacements* const replacements_; | |
| 94 }; | |
| 95 | |
| 96 // Refactors base::MessageLoopProxy::current() callers. | |
| 97 class CurrentProxyCallback : public MatchFinder::MatchCallback { | |
| 98 public: | |
| 99 CurrentProxyCallback(Replacements* replacements) | |
| 100 : replacements_(replacements) {} | |
| 101 | |
| 102 virtual void run(const MatchFinder::MatchResult& result) override; | |
| 103 | |
| 104 private: | |
| 105 Replacements* const replacements_; | |
| 106 }; | |
| 107 | |
| 108 // Refactors callers to base::MessageLoop::message_loop_proxy() and | |
| 109 // base::Thread::message_loop_proxy(). | |
| 110 class ProxyGetterCallback : public MatchFinder::MatchCallback { | |
| 111 public: | |
| 112 ProxyGetterCallback(Replacements* replacements) | |
| 113 : replacements_(replacements) {} | |
| 114 | |
| 115 virtual void run(const MatchFinder::MatchResult& result) override; | |
| 116 | |
| 117 private: | |
| 118 Replacements* const replacements_; | |
| 119 }; | |
| 120 | |
| 121 // Rewrites variables of type MessageLoopProxy*. | |
| 122 class ProxyVariableCallback : public MatchFinder::MatchCallback { | |
| 123 public: | |
| 124 ProxyVariableCallback(Replacements* replacements) | |
| 125 : replacements_(replacements) {} | |
| 126 | |
| 127 virtual void run(const MatchFinder::MatchResult& result) override; | |
| 128 | |
| 129 private: | |
| 130 Replacements* const replacements_; | |
| 131 }; | |
| 132 | |
| 133 // Rewrites variables of type scoped_refptr<MessageLoopProxy>. | |
| 134 class RefPtrProxyVariableCallback : public MatchFinder::MatchCallback { | |
| 135 public: | |
| 136 RefPtrProxyVariableCallback(Replacements* replacements) | |
| 137 : replacements_(replacements) {} | |
| 138 | |
| 139 virtual void run(const MatchFinder::MatchResult& result) override; | |
| 140 | |
| 141 private: | |
| 142 Replacements* const replacements_; | |
| 143 }; | |
| 144 | |
| 145 class MessageLoopRefactorer { | |
| 146 public: | |
| 147 explicit MessageLoopRefactorer(Replacements* replacements) | |
| 148 : post_callback_(replacements), | |
| 149 current_proxy_callback_(replacements), | |
| 150 proxy_getter_callback_(replacements), | |
| 151 proxy_variable_callback_(replacements), | |
| 152 refptr_proxy_variable_callback_(replacements) {} | |
| 153 | |
| 154 void SetupMatchers(MatchFinder* match_finder); | |
| 155 | |
| 156 private: | |
| 157 PostTaskCallback post_callback_; | |
| 158 CurrentProxyCallback current_proxy_callback_; | |
| 159 ProxyGetterCallback proxy_getter_callback_; | |
| 160 ProxyVariableCallback proxy_variable_callback_; | |
| 161 RefPtrProxyVariableCallback refptr_proxy_variable_callback_; | |
| 162 }; | |
| 163 | |
| 164 void MessageLoopRefactorer::SetupMatchers(MatchFinder* match_finder) { | |
| 165 auto is_message_loop = recordDecl(isSameOrDerivedFrom("base::MessageLoop")); | |
| 166 auto is_thread = recordDecl(isSameOrDerivedFrom("base::Thread")); | |
| 167 auto is_message_loop_proxy = | |
| 168 recordDecl(isSameOrDerivedFrom("base::MessageLoopProxy")); | |
| 169 | |
| 170 // Matches calls to the Post*Task APIs. | |
| 171 auto post_matcher = | |
| 172 memberCallExpr(thisPointerType(is_message_loop), | |
| 173 callee(methodDecl(anyOf( | |
| 174 hasName("PostTask"), hasName("PostDelayedTask"), | |
| 175 hasName("PostNonNestableTask"), | |
| 176 hasName("PostNonNestableDelayedTask"))))).bind("call"); | |
| 177 | |
| 178 // Matches calls to MessageLoopProxy::current(). | |
| 179 auto current_proxy_matcher = | |
| 180 callExpr(callee(methodDecl(ofClass(is_message_loop_proxy), | |
| 181 hasName("current")))).bind("call"); | |
| 182 | |
| 183 auto message_loop_proxy_callee = | |
| 184 callee(methodDecl(hasName("message_loop_proxy"))); | |
| 185 | |
| 186 // Matches calls to MessageLoop::message_loop_proxy(). | |
| 187 auto loop_proxy_getter_matcher = | |
| 188 memberCallExpr(thisPointerType(is_message_loop), | |
| 189 message_loop_proxy_callee).bind("call"); | |
| 190 | |
| 191 // Matches calls to Thread::message_loop_proxy(). | |
| 192 auto thread_proxy_getter_matcher = | |
| 193 memberCallExpr(thisPointerType(is_thread), message_loop_proxy_callee) | |
| 194 .bind("call"); | |
| 195 | |
| 196 // Matches variables pointing to a MessageLoopProxy that aren't inside | |
| 197 // template instantiations (e.g., scoped_retpr<>). | |
| 198 auto proxy_variable_matcher = | |
| 199 varDecl(hasType(pointsTo(recordDecl(hasName("base::MessageLoopProxy")))), | |
| 200 unless(hasAncestor(decl(anyOf( | |
| 201 recordDecl(isTemplateInstantiation()), | |
| 202 functionDecl(isTemplateInstantiation())))))).bind("var"); | |
| 203 | |
| 204 // Matches scoped_refptr<MessageLoopProxy>. | |
| 205 auto refptr_proxy_variable_matcher = | |
| 206 varDecl(hasType(recordDecl(hasName("::scoped_refptr"))), | |
| 207 hasDescendant(templateSpecializationType(hasAnyTemplateArgument( | |
| 208 refersToType(asString("class base::MessageLoopProxy")))))) | |
| 209 .bind("var"); | |
| 210 | |
| 211 match_finder->addMatcher(current_proxy_matcher, ¤t_proxy_callback_); | |
| 212 match_finder->addMatcher(loop_proxy_getter_matcher, &proxy_getter_callback_); | |
| 213 match_finder->addMatcher(thread_proxy_getter_matcher, | |
| 214 &proxy_getter_callback_); | |
| 215 match_finder->addMatcher(post_matcher, &post_callback_); | |
| 216 match_finder->addMatcher(proxy_variable_matcher, &proxy_variable_callback_); | |
| 217 match_finder->addMatcher(refptr_proxy_variable_matcher, | |
| 218 &refptr_proxy_variable_callback_); | |
| 219 } | |
| 220 | |
| 221 void PostTaskCallback::run(const MatchFinder::MatchResult& result) { | |
| 222 const CXXMemberCallExpr* call = | |
| 223 result.Nodes.getNodeAs<CXXMemberCallExpr>("call"); | |
| 224 const Expr* obj = call->getImplicitObjectArgument(); | |
| 225 const MemberExpr* callee = clang::dyn_cast<MemberExpr>(call->getCallee()); | |
| 226 | |
| 227 clang::CharSourceRange range = | |
| 228 clang::CharSourceRange::getTokenRange(callee->getSourceRange()); | |
| 229 clang::CharSourceRange obj_range = | |
| 230 clang::CharSourceRange::getTokenRange(obj->getSourceRange()); | |
| 231 | |
| 232 std::string text = clang::Lexer::getSourceText(range, *result.SourceManager, | |
| 233 result.Context->getLangOpts()); | |
| 234 std::string obj_text = clang::Lexer::getSourceText( | |
| 235 obj_range, *result.SourceManager, result.Context->getLangOpts()); | |
| 236 std::string method = call->getMethodDecl()->getNameInfo().getAsString(); | |
| 237 | |
| 238 // Rewrite the call to go through the task runner. | |
| 239 bool is_arrow = callee->isArrow(); | |
| 240 std::string replacement = | |
| 241 obj_text + (is_arrow ? "->" : ".") + "task_runner()->" + method; | |
|
dcheng
2015/05/06 17:31:17
Isn't this always -> in Chrome?
Sami
2015/05/07 10:37:37
That's almost always the case, but some tests do h
| |
| 242 | |
| 243 // Rewrite MessageLoop::current() as ThreadTaskRunnerHandle::Get(). | |
|
dcheng
2015/05/06 17:31:18
Why is this chunk here? I would have expected to s
Sami
2015/05/07 10:37:37
CurrentProxyCallback matches calls to MessageLoopP
| |
| 244 if (obj_text.find("MessageLoop::current") != obj_text.npos || | |
| 245 obj_text.find("MessageLoopForIO::current") != obj_text.npos || | |
| 246 obj_text.find("MessageLoopForUI::current") != obj_text.npos) { | |
| 247 std::string ns = ""; | |
| 248 if (obj_text.find("base::") == 0) { | |
| 249 ns = "base::"; | |
| 250 } | |
| 251 replacement = ns + "ThreadTaskRunnerHandle::Get()->" + method; | |
| 252 } | |
| 253 | |
| 254 replacements_->insert(Replacement(*result.SourceManager, range, replacement)); | |
| 255 } | |
| 256 | |
| 257 void CurrentProxyCallback::run(const MatchFinder::MatchResult& result) { | |
| 258 const CallExpr* call = result.Nodes.getNodeAs<CallExpr>("call"); | |
| 259 clang::CharSourceRange range = | |
| 260 clang::CharSourceRange::getTokenRange(call->getSourceRange()); | |
| 261 std::string text = clang::Lexer::getSourceText(range, *result.SourceManager, | |
| 262 result.Context->getLangOpts()); | |
| 263 | |
| 264 std::string ns = ""; | |
| 265 if (text.find("base::") == 0) { | |
|
dcheng
2015/05/06 17:31:18
Hmm... I guess I should finish my patch to try to
Sami
2015/05/07 10:37:37
Got a link? Sounds useful :)
Although for this pa
| |
| 266 ns = "base::"; | |
| 267 } | |
| 268 std::string replacement = ns + "ThreadTaskRunnerHandle::Get()"; | |
| 269 replacements_->insert(Replacement(*result.SourceManager, range, replacement)); | |
| 270 } | |
| 271 | |
| 272 void ProxyGetterCallback::run(const MatchFinder::MatchResult& result) { | |
| 273 const CXXMemberCallExpr* call = | |
| 274 result.Nodes.getNodeAs<CXXMemberCallExpr>("call"); | |
| 275 const MemberExpr* callee = clang::dyn_cast<MemberExpr>(call->getCallee()); | |
| 276 clang::CharSourceRange range = | |
| 277 clang::CharSourceRange::getTokenRange(callee->getSourceRange()); | |
| 278 | |
| 279 std::string text = clang::Lexer::getSourceText(range, *result.SourceManager, | |
| 280 result.Context->getLangOpts()); | |
| 281 | |
| 282 text = ReplaceFirst(text, "message_loop_proxy", "task_runner"); | |
| 283 replacements_->insert(Replacement(*result.SourceManager, range, text)); | |
| 284 } | |
| 285 | |
| 286 void ProxyVariableCallback::run(const MatchFinder::MatchResult& result) { | |
| 287 const VarDecl* var = result.Nodes.getNodeAs<VarDecl>("var"); | |
| 288 clang::CharSourceRange range = | |
| 289 clang::CharSourceRange::getTokenRange(var->getSourceRange()); | |
| 290 | |
| 291 std::string text = clang::Lexer::getSourceText(range, *result.SourceManager, | |
| 292 result.Context->getLangOpts()); | |
| 293 | |
| 294 text = ReplaceFirst(text, "MessageLoopProxy", "SingleThreadTaskRunner"); | |
| 295 replacements_->insert(Replacement(*result.SourceManager, range, text)); | |
| 296 } | |
| 297 | |
| 298 void RefPtrProxyVariableCallback::run(const MatchFinder::MatchResult& result) { | |
| 299 const VarDecl* var = result.Nodes.getNodeAs<VarDecl>("var"); | |
| 300 clang::CharSourceRange range = | |
| 301 clang::CharSourceRange::getTokenRange(var->getSourceRange()); | |
| 302 | |
| 303 std::string text = clang::Lexer::getSourceText(range, *result.SourceManager, | |
| 304 result.Context->getLangOpts()); | |
| 305 | |
| 306 text = ReplaceFirst(text, "MessageLoopProxy", "SingleThreadTaskRunner"); | |
| 307 replacements_->insert(Replacement(*result.SourceManager, range, text)); | |
| 308 } | |
| 309 | |
| 310 } // namespace | |
| 311 | |
| 312 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); | |
| 313 | |
| 314 int main(int argc, const char* argv[]) { | |
| 315 llvm::cl::OptionCategory category("MessageLoop refactoring tool"); | |
| 316 CommonOptionsParser options(argc, argv, category); | |
| 317 clang::tooling::ClangTool tool(options.getCompilations(), | |
| 318 options.getSourcePathList()); | |
| 319 | |
| 320 Replacements replacements; | |
| 321 MessageLoopRefactorer refactorer(&replacements); | |
| 322 MatchFinder match_finder; | |
| 323 refactorer.SetupMatchers(&match_finder); | |
| 324 | |
| 325 std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory = | |
| 326 clang::tooling::newFrontendActionFactory(&match_finder); | |
| 327 int result = tool.run(frontend_factory.get()); | |
| 328 if (result != 0) | |
| 329 return result; | |
| 330 | |
| 331 // Each replacement line should have the following format: | |
| 332 // r:<file path>:<offset>:<length>:<replacement text> | |
| 333 // Only the <replacement text> field can contain embedded ":" characters. | |
| 334 // TODO(dcheng): Use a more clever serialization. Ideally we'd use the YAML | |
| 335 // serialization and then use clang-apply-replacements, but that would require | |
| 336 // copying and pasting a larger amount of boilerplate for all Chrome clang | |
| 337 // tools. | |
| 338 llvm::outs() << "==== BEGIN EDITS ====\n"; | |
| 339 for (const auto& r : replacements) { | |
| 340 llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset() | |
| 341 << ":::" << r.getLength() << ":::" << r.getReplacementText() | |
| 342 << "\n"; | |
| 343 } | |
| 344 llvm::outs() << "==== END EDITS ====\n"; | |
| 345 | |
| 346 return 0; | |
| 347 } | |
| OLD | NEW |