// Copyright 2016 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 "ListValueRewriter.h" #include <assert.h> #include <algorithm> #include "clang/AST/ASTContext.h" #include "clang/AST/ParentMap.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchersMacros.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/SourceManager.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/Refactoring.h" #include "llvm/ADT/STLExtras.h" using namespace clang::ast_matchers; using clang::tooling::Replacement; using llvm::StringRef; namespace { // Helper class for AppendRawCallback to visit each DeclRefExpr for a given // VarDecl. If it finds a DeclRefExpr it can't figure out how to rewrite, the // traversal will be terminated early. class CollectDeclRefExprVisitor : public clang::RecursiveASTVisitor<CollectDeclRefExprVisitor> { public: CollectDeclRefExprVisitor(clang::SourceManager* source_manager, clang::ASTContext* ast_context, const clang::VarDecl* decl, const clang::FunctionDecl* containing_function) : source_manager_(source_manager), ast_context_(ast_context), decl_(decl), is_valid_(decl->hasInit()), map_(containing_function->getBody()) {} // RecursiveASTVisitor: bool VisitDeclRefExpr(const clang::DeclRefExpr* expr) { if (expr->getDecl() != decl_) return true; const clang::Stmt* stmt = expr; while (stmt) { // TODO(dcheng): Add a const version of getParentIgnoreParenImpCasts. stmt = map_.getParentIgnoreParenImpCasts(const_cast<clang::Stmt*>(stmt)); if (clang::isa<clang::MemberExpr>(stmt)) { // Member expressions need no special rewriting since std::unique_ptr // overloads `.' and `->'. return is_valid_; } else if (auto* member_call_expr = clang::dyn_cast<clang::CXXMemberCallExpr>(stmt)) { return HandleMemberCallExpr(member_call_expr, expr); } else if (auto* binary_op = clang::dyn_cast<clang::BinaryOperator>(stmt)) { return HandleBinaryOp(binary_op); } else { // Can't handle this so cancel the rewrite. stmt->dump(); return false; } } assert(false); return false; } const std::set<clang::tooling::Replacement>& replacements() const { return replacements_; } private: bool HandleMemberCallExpr(const clang::CXXMemberCallExpr* member_call_expr, const clang::DeclRefExpr* decl_ref_expr) { // If this isn't a ListValue::Append() call, cancel the rewrite: it // will require manual inspection to determine if it's an ownership // transferring call or not. auto* method_decl = member_call_expr->getMethodDecl(); if (method_decl->getQualifiedNameAsString() != "base::ListValue::Append") return false; // Use-after-move is also a fatal error. if (!is_valid_) return false; is_valid_ = false; // Surround the DeclRefExpr with std::move(). replacements_.emplace(*source_manager_, decl_ref_expr->getLocStart(), 0, "std::move("); clang::SourceLocation end = clang::Lexer::getLocForEndOfToken( decl_ref_expr->getLocEnd(), 0, *source_manager_, ast_context_->getLangOpts()); replacements_.emplace(*source_manager_, end, 0, ")"); return true; } bool HandleBinaryOp(const clang::BinaryOperator* op) { if (op->isRelationalOp() || op->isEqualityOp() || op->isLogicalOp()) { // Supported binary operations for which no rewrites need to be done. return is_valid_; } if (!op->isAssignmentOp()) { // Pointer arithmetic or something else clever. Just cancel the rewrite. return false; } if (op->isCompoundAssignmentOp()) { // +=, -=, etc. Give up and cancel the rewrite. return false; } const clang::Expr* rhs = op->getRHS()->IgnoreParenImpCasts(); const clang::CXXNewExpr* new_expr = clang::dyn_cast<clang::CXXNewExpr>(rhs); if (!new_expr) { // The variable isn't being assigned the result of a new operation. Just // cancel the rewrite. return false; } is_valid_ = true; // Rewrite the assignment operation to use std::unique_ptr::reset(). clang::CharSourceRange range = clang::CharSourceRange::getCharRange( op->getOperatorLoc(), op->getRHS()->getLocStart()); replacements_.emplace(*source_manager_, range, ".reset("); clang::SourceLocation expr_end = clang::Lexer::getLocForEndOfToken( op->getLocEnd(), 0, *source_manager_, ast_context_->getLangOpts()); replacements_.emplace(*source_manager_, expr_end, 0, ")"); return true; } clang::SourceManager* const source_manager_; clang::ASTContext* const ast_context_; const clang::VarDecl* const decl_; // Tracks the state of |decl_| during the traversal. |decl_| becomes valid // upon initialization/assignment and becomes invalid when passed as an // argument to base::ListValue::Append(base::Value*). bool is_valid_; clang::ParentMap map_; std::set<clang::tooling::Replacement> replacements_; }; } // namespace ListValueRewriter::AppendCallback::AppendCallback( std::set<clang::tooling::Replacement>* replacements) : replacements_(replacements) {} void ListValueRewriter::AppendCallback::run( const MatchFinder::MatchResult& result) { // Delete `new base::*Value(' and `)'. auto* newExpr = result.Nodes.getNodeAs<clang::CXXNewExpr>("newExpr"); auto* argExpr = result.Nodes.getNodeAs<clang::Expr>("argExpr"); // Note that for the end loc, we use the expansion loc: the argument might be // a macro like true and false. clang::CharSourceRange pre_arg_range = clang::CharSourceRange::getCharRange( newExpr->getLocStart(), result.SourceManager->getExpansionLoc(argExpr->getLocStart())); replacements_->emplace(*result.SourceManager, pre_arg_range, ""); clang::CharSourceRange post_arg_range = clang::CharSourceRange::getTokenRange(newExpr->getLocEnd()); replacements_->emplace(*result.SourceManager, post_arg_range, ""); } ListValueRewriter::AppendBooleanCallback::AppendBooleanCallback( std::set<clang::tooling::Replacement>* replacements) : AppendCallback(replacements) {} void ListValueRewriter::AppendBooleanCallback::run( const MatchFinder::MatchResult& result) { // Replace 'Append' with 'AppendBoolean'. auto* callExpr = result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("callExpr"); clang::CharSourceRange call_range = clang::CharSourceRange::getTokenRange(callExpr->getExprLoc()); replacements_->emplace(*result.SourceManager, call_range, "AppendBoolean"); AppendCallback::run(result); } ListValueRewriter::AppendIntegerCallback::AppendIntegerCallback( std::set<clang::tooling::Replacement>* replacements) : AppendCallback(replacements) {} void ListValueRewriter::AppendIntegerCallback::run( const MatchFinder::MatchResult& result) { // Replace 'Append' with 'AppendInteger'. auto* callExpr = result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("callExpr"); clang::CharSourceRange call_range = clang::CharSourceRange::getTokenRange(callExpr->getExprLoc()); replacements_->emplace(*result.SourceManager, call_range, "AppendInteger"); AppendCallback::run(result); } ListValueRewriter::AppendDoubleCallback::AppendDoubleCallback( std::set<clang::tooling::Replacement>* replacements) : AppendCallback(replacements) {} void ListValueRewriter::AppendDoubleCallback::run( const MatchFinder::MatchResult& result) { // Replace 'Append' with 'AppendDouble'. auto* callExpr = result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("callExpr"); clang::CharSourceRange call_range = clang::CharSourceRange::getTokenRange(callExpr->getExprLoc()); replacements_->emplace(*result.SourceManager, call_range, "AppendDouble"); AppendCallback::run(result); } ListValueRewriter::AppendStringCallback::AppendStringCallback( std::set<clang::tooling::Replacement>* replacements) : AppendCallback(replacements) {} void ListValueRewriter::AppendStringCallback::run( const MatchFinder::MatchResult& result) { // Replace 'Append' with 'AppendString'. auto* callExpr = result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("callExpr"); clang::CharSourceRange call_range = clang::CharSourceRange::getTokenRange(callExpr->getExprLoc()); replacements_->emplace(*result.SourceManager, call_range, "AppendString"); AppendCallback::run(result); } ListValueRewriter::AppendReleasedUniquePtrCallback:: AppendReleasedUniquePtrCallback( std::set<clang::tooling::Replacement>* replacements) : replacements_(replacements) {} void ListValueRewriter::AppendReleasedUniquePtrCallback::run( const MatchFinder::MatchResult& result) { auto* object_expr = result.Nodes.getNodeAs<clang::Expr>("objectExpr"); bool arg_is_rvalue = object_expr->Classify(*result.Context).isRValue(); // Remove .release() auto* member_call = result.Nodes.getNodeAs<clang::CXXMemberCallExpr>("memberCall"); auto* member_expr = result.Nodes.getNodeAs<clang::MemberExpr>("memberExpr"); clang::CharSourceRange release_range = clang::CharSourceRange::getTokenRange( member_expr->getOperatorLoc(), member_call->getLocEnd()); replacements_->emplace(*result.SourceManager, release_range, arg_is_rvalue ? "" : ")"); if (arg_is_rvalue) return; // Insert `std::move(' for non-rvalue expressions. clang::CharSourceRange insertion_range = clang::CharSourceRange::getCharRange( object_expr->getLocStart(), object_expr->getLocStart()); replacements_->emplace(*result.SourceManager, insertion_range, "std::move("); } ListValueRewriter::AppendRawPtrCallback::AppendRawPtrCallback( std::set<clang::tooling::Replacement>* replacements) : replacements_(replacements) {} void ListValueRewriter::AppendRawPtrCallback::run( const MatchFinder::MatchResult& result) { auto* var_decl = result.Nodes.getNodeAs<clang::VarDecl>("varDecl"); // As an optimization, skip processing if it's already been visited, since // this match callback walks the entire function body. if (visited_.find(var_decl) != visited_.end()) return; visited_.insert(var_decl); auto* function_context = var_decl->getParentFunctionOrMethod(); assert(function_context && "local var not in function context?!"); auto* function_decl = clang::cast<clang::FunctionDecl>(function_context); auto* type_source_info = var_decl->getTypeSourceInfo(); assert(type_source_info && "no type source info for VarDecl?!"); // Don't bother trying to handle qualifiers. clang::QualType qual_type = var_decl->getType(); if (qual_type.hasQualifiers()) { return; } CollectDeclRefExprVisitor visitor(result.SourceManager, result.Context, var_decl, function_decl); if (!visitor.TraverseStmt(function_decl->getBody())) return; // Rewrite the variable type to use std::unique_ptr. clang::CharSourceRange type_range = clang::CharSourceRange::getTokenRange( type_source_info->getTypeLoc().getSourceRange()); std::string replacement_type = "std::unique_ptr<"; while (true) { const clang::Type* type = qual_type.getTypePtr(); if (auto* auto_type = type->getAs<clang::AutoType>()) { if (!auto_type->isDeduced()) { // If an AutoType isn't deduced, the rewriter can't do anything. return; } qual_type = auto_type->getDeducedType(); } else if (auto* pointer_type = type->getAs<clang::PointerType>()) { qual_type = pointer_type->getPointeeType(); } else { break; } } replacement_type += qual_type.getAsString(); replacement_type += ">"; replacements_->emplace(*result.SourceManager, type_range, replacement_type); // Initialized with `=' if (var_decl->hasInit() && var_decl->getInitStyle() == clang::VarDecl::CInit) { clang::SourceLocation name_end = clang::Lexer::getLocForEndOfToken( var_decl->getLocation(), 0, *result.SourceManager, result.Context->getLangOpts()); clang::CharSourceRange range = clang::CharSourceRange::getCharRange( name_end, var_decl->getInit()->getLocStart()); replacements_->emplace(*result.SourceManager, range, "("); clang::SourceLocation init_end = clang::Lexer::getLocForEndOfToken( var_decl->getInit()->getLocEnd(), 0, *result.SourceManager, result.Context->getLangOpts()); replacements_->emplace(*result.SourceManager, init_end, 0, ")"); } // Also append the collected replacements from visiting the DeclRefExprs. replacements_->insert(visitor.replacements().begin(), visitor.replacements().end()); } ListValueRewriter::ListValueRewriter( std::set<clang::tooling::Replacement>* replacements) : append_boolean_callback_(replacements), append_integer_callback_(replacements), append_double_callback_(replacements), append_string_callback_(replacements), append_released_unique_ptr_callback_(replacements), append_raw_ptr_callback_(replacements) {} void ListValueRewriter::RegisterMatchers(MatchFinder* match_finder) { auto is_list_append = cxxMemberCallExpr( callee(cxxMethodDecl(hasName("::base::ListValue::Append"))), argumentCountIs(1)); // base::ListValue::Append(new base::FundamentalValue(bool)) // => base::ListValue::AppendBoolean() match_finder->addMatcher( id("callExpr", cxxMemberCallExpr( is_list_append, hasArgument( 0, ignoringParenImpCasts(id( "newExpr", cxxNewExpr(has(cxxConstructExpr( hasDeclaration(cxxMethodDecl(hasName( "::base::FundamentalValue::FundamentalValue"))), argumentCountIs(1), hasArgument( 0, id("argExpr", expr(hasType(booleanType())))))))))))), &append_boolean_callback_); // base::ListValue::Append(new base::FundamentalValue(int)) // => base::ListValue::AppendInteger() match_finder->addMatcher( id("callExpr", cxxMemberCallExpr( is_list_append, hasArgument( 0, ignoringParenImpCasts(id( "newExpr", cxxNewExpr(has(cxxConstructExpr( hasDeclaration(cxxMethodDecl(hasName( "::base::FundamentalValue::FundamentalValue"))), argumentCountIs(1), hasArgument(0, id("argExpr", expr(hasType(isInteger()), unless(hasType( booleanType()))))))))))))), &append_integer_callback_); // base::ListValue::Append(new base::FundamentalValue(double)) // => base::ListValue::AppendDouble() match_finder->addMatcher( id("callExpr", cxxMemberCallExpr( is_list_append, hasArgument( 0, ignoringParenImpCasts(id( "newExpr", cxxNewExpr(has(cxxConstructExpr( hasDeclaration(cxxMethodDecl(hasName( "::base::FundamentalValue::FundamentalValue"))), argumentCountIs(1), hasArgument( 0, id("argExpr", expr(hasType( realFloatingPointType())))))))))))), &append_double_callback_); // base::ListValue::Append(new base::StringValue(...)) // => base::ListValue::AppendString() match_finder->addMatcher( id("callExpr", cxxMemberCallExpr( is_list_append, hasArgument( 0, ignoringParenImpCasts(id( "newExpr", cxxNewExpr(has(cxxConstructExpr( hasDeclaration(cxxMethodDecl( hasName("::base::StringValue::StringValue"))), argumentCountIs(1), hasArgument(0, id("argExpr", expr())))))))))), &append_string_callback_); auto is_unique_ptr_release = allOf(callee(cxxMethodDecl( hasName("release"), ofClass(cxxRecordDecl(hasName("::std::unique_ptr"))))), argumentCountIs(0)); // base::ListValue::Append(ReturnsUniquePtr().release()) // => base::ListValue::Append(ReturnsUniquePtr()) // or // base::ListValue::Append(unique_ptr_var.release()) // => base::ListValue::Append(std::move(unique_ptr_var)) match_finder->addMatcher( cxxMemberCallExpr( is_list_append, hasArgument( 0, ignoringParenImpCasts( id("memberCall", cxxMemberCallExpr(has(id("memberExpr", memberExpr())), is_unique_ptr_release, on(id("objectExpr", expr()))))))), &append_released_unique_ptr_callback_); // Simple versions of the following pattern. Note the callback itself does // much of the filtering (to detect use-after-move, things that aren't // assigned the result of a new expression, etc). // // base::ListValue* this_list = new base::ListValue; // this_list->AppendInteger(1); // that_list->Append(this_list); // // will be rewritten to // // std::unique_ptr<base::ListValue> this_list(new base::ListValue); // this_list->AppendInteger(1); // that_list->Append(std::move(this_list); match_finder->addMatcher( cxxMemberCallExpr( is_list_append, hasArgument( 0, ignoringParenImpCasts(id( "declRefExpr", declRefExpr(to(id( "varDecl", varDecl( hasLocalStorage(), anyOf(hasInitializer( // Note this won't match C++11 uniform // initialization syntax, since the // CXXNewExpr is wrapped in an // InitListExpr in that case. ignoringParenImpCasts(cxxNewExpr())), unless(hasInitializer(expr()))), unless(parmVarDecl()))))))))), &append_raw_ptr_callback_); }