Index: tools/clang/BindMigrate/PostTaskTransform.cpp |
diff --git a/tools/clang/BindMigrate/PostTaskTransform.cpp b/tools/clang/BindMigrate/PostTaskTransform.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c5680243c9bfcb7cd071092ed56bfb95443f6ebd |
--- /dev/null |
+++ b/tools/clang/BindMigrate/PostTaskTransform.cpp |
@@ -0,0 +1,152 @@ |
+// Copyright (c) 2011 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. |
+ |
+#include "PostTaskTransform.h" |
+ |
+#include "clang/AST/ASTContext.h" |
+#include "clang/AST/Expr.h" |
+#include "clang/AST/ExprCXX.h" |
+#include "clang/Rewrite/Rewriter.h" |
+ |
+#include "DiagnosticEmitter.h" |
+ |
+using namespace std; |
+ |
+namespace clang { |
+ |
+PostTaskTransform::PostTaskTransform(ASTContext* context, Rewriter* rewriter, |
+ DiagnosticEmitter* emitter) |
+ : context_(context), rewriter_(rewriter), error_emitter_(emitter) { |
+ interesting_classes_.insert("MessageLoop"); |
+ interesting_classes_.insert("MessageLoopProxy"); |
+} |
+ |
+bool PostTaskTransform::WouldTransformStmt(Stmt* statement) { |
+ return IsPostTaskNRMExpr(statement); |
+} |
+ |
+void PostTaskTransform::TransformStmt(Stmt* statement) { |
+ if (!IsPostTaskNRMExpr(statement)) return; |
+ |
+ // Catch the MessageLoop and MessageLoopProxy calls. |
+ CXXMemberCallExpr* mce = dyn_cast<CXXMemberCallExpr>(statement); |
+ RewriteIfNewRunnableSomething(mce->getArgs()[1]); |
+} |
+ |
+bool PostTaskTransform::IsPostTaskNRMExpr(Stmt* statement) { |
+ CXXMemberCallExpr* call = dyn_cast_or_null<CXXMemberCallExpr>(statement); |
+ if (!call) return false; |
+ CXXMethodDecl* method_decl = call->getMethodDecl(); |
+ if (!method_decl) return false; |
+ method_decl = method_decl->getCanonicalDecl(); |
+ if ("PostTask" != method_decl->getNameAsString()) { |
+ return false; |
+ } |
+ |
+ string classname = method_decl->getThisType(*context_) |
+ .getBaseTypeIdentifier()->getName(); |
+ |
+ if (interesting_classes_.find(classname) == interesting_classes_.end()) |
+ return false; |
+ |
+ if (call->getNumArgs() < 2) { |
+ error_emitter_->EmitError( |
+ statement->getLocStart(), |
+ "PostTask with less than 2 args?! Inconceivable!"); |
+ return false; |
+ } |
+ |
+ // Check that PostTask second arg is actually the ::NewRunnableMethod() call. |
+ CallExpr* nrm_ce = dyn_cast<CallExpr>(call->getArgs()[1]); |
+ if (!nrm_ce) return false; |
+ if (dyn_cast<CXXMemberCallExpr>(nrm_ce)) return false; |
+ FunctionDecl *nrm_fd = nrm_ce->getDirectCallee(); |
+ if (!nrm_fd) return false; |
+ std::string nrm_function_name = |
+ nrm_fd->getNameInfo().getName().getAsIdentifierInfo()->getName(); |
+ if ("NewRunnableMethod" != nrm_function_name) { |
+ return false; |
+ } |
+ |
+ if (nrm_ce->getNumArgs() < 2) { |
+ error_emitter_->EmitWarning( |
+ nrm_ce->getLocStart(), |
+ "NewRunnableMethod with less than 2 args?! Inconceivable!"); |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+static bool NoAddrefRelease(const CXXRecordDecl *record, void *user_data) { |
+ bool found_addref = false; |
+ bool found_release = false; |
+ |
+ for (CXXRecordDecl::method_iterator it = record->method_begin(), |
+ end = record->method_end(); it != end; ++it) { |
+ if (it->getAccess() == AS_public) { |
+ found_addref |= "AddRef" == it->getNameAsString(); |
+ found_release |= "Release" == it->getNameAsString(); |
+ } |
+ } |
+ |
+ return !(found_addref && found_release); |
+} |
+ |
+bool PostTaskTransform::IsRefcountedType(QualType type) { |
+ CXXRecordDecl* record = type->getAsCXXRecordDecl(); |
+ return !(NoAddrefRelease(record, NULL) && |
+ record->forallBases(&NoAddrefRelease, NULL, true)); |
+} |
+ |
+bool PostTaskTransform::RewriteIfNewRunnableSomething(Expr* post_task_arg) { |
+ // Skip any implicit conversions and any casts. We wantz the function!! |
+ post_task_arg = post_task_arg->IgnoreImplicit()->IgnoreParenCasts(); |
+ |
+ CallExpr* ce = dyn_cast<CallExpr>(post_task_arg); |
+ if (!ce) return false; |
+ FunctionDecl *fd = ce->getDirectCallee(); |
+ if (!fd) return false; |
+ |
+ // Okay, here's where it gets fun. We need to |
+ // (1) replace the NRM identifier text with base::Bind. |
+ // (2) Swap positions of the first two arguments. |
+ // (3) If we're calling a method, and the class is not refcounted, then |
+ // a DISABLE_RUNNABLE_METHOD_REFCOUNT or similar must have been used |
+ // to disable refcounting. Insert a base::Unretained(). |
+ |
+ // NewRunnableMethod -> base::Bind |
+ bool failure = rewriter_->ReplaceText( |
+ ce->getCallee()->getSourceRange(), "base::Bind"); |
+ |
+ Expr* arg1 = ce->getArgs()[0]; |
+ Expr* arg2 = ce->getArgs()[1]; |
+ |
+ |
+ // Swap args, and add unretained to the object if needed. |
+ failure |= rewriter_->ReplaceStmt(arg1, arg2); |
+ if (arg2->getType()->isMemberFunctionPointerType() && |
+ arg1->getType()->isPointerType() && |
+ arg1->getType()->getPointeeType()->isStructureOrClassType() && |
+ !IsRefcountedType(arg1->getType()->getPointeeType())) { |
+ // The rewriter works in the context of the soruce buffer. Though it |
+ // tries to be smart about mapping the source location into the |
+ // destination over multiple rewrites, it can get confused if you try |
+ // to modify the same region of text multiple times. Doing things in |
+ // order from "left to right" seems to keep the Rewritter happier. |
+ // |
+ // TODO(ajwong): See if the ARCMigrate source transaction system that |
+ // batches and combines modificaitons to be applied at the end can |
+ // be generalized and reused here. |
+ std::string unretained_arg("base::Unretained("); |
+ unretained_arg += rewriter_->ConvertToString(arg1); |
+ unretained_arg += ")"; |
+ failure |= rewriter_->ReplaceText(arg2->getSourceRange(), unretained_arg); |
+ } else { |
+ failure |= rewriter_->ReplaceStmt(arg2, arg1); |
+ } |
+ return failure; |
+} |
+ |
+} // namespace clang |