// Copyright (c) 2013 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. // // This implements a Clang tool to rewrite all instances of // scoped_refptr<T>'s implicit cast to T (operator T*) to an explicit call to // the .get() method. #include <assert.h> #include <algorithm> #include <memory> #include <string> #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchersMacros.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/SourceManager.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/TargetSelect.h" using namespace clang::ast_matchers; using clang::tooling::CommonOptionsParser; using clang::tooling::Replacement; using clang::tooling::Replacements; using llvm::StringRef; namespace clang { namespace ast_matchers { const internal::VariadicDynCastAllOfMatcher<Decl, CXXConversionDecl> conversionDecl; AST_MATCHER(QualType, isBoolean) { return Node->isBooleanType(); } } // namespace ast_matchers } // namespace clang namespace { // Returns true if expr needs to be put in parens (eg: when it is an operator // syntactically). bool NeedsParens(const clang::Expr* expr) { if (llvm::dyn_cast<clang::UnaryOperator>(expr) || llvm::dyn_cast<clang::BinaryOperator>(expr) || llvm::dyn_cast<clang::ConditionalOperator>(expr)) { return true; } // Calls to an overloaded operator also need parens, except for foo(...) and // foo[...] expressions. if (const clang::CXXOperatorCallExpr* op = llvm::dyn_cast<clang::CXXOperatorCallExpr>(expr)) { return op->getOperator() != clang::OO_Call && op->getOperator() != clang::OO_Subscript; } return false; } Replacement RewriteImplicitToExplicitConversion( const MatchFinder::MatchResult& result, const clang::Expr* expr) { clang::CharSourceRange range = clang::CharSourceRange::getTokenRange( result.SourceManager->getSpellingLoc(expr->getLocStart()), result.SourceManager->getSpellingLoc(expr->getLocEnd())); assert(range.isValid() && "Invalid range!"); // Handle cases where an implicit cast is being done by dereferencing a // pointer to a scoped_refptr<> (sadly, it happens...) // // This rewrites both "*foo" and "*(foo)" as "foo->get()". if (const clang::UnaryOperator* op = llvm::dyn_cast<clang::UnaryOperator>(expr)) { if (op->getOpcode() == clang::UO_Deref) { const clang::Expr* const sub_expr = op->getSubExpr()->IgnoreParenImpCasts(); clang::CharSourceRange sub_expr_range = clang::CharSourceRange::getTokenRange( result.SourceManager->getSpellingLoc(sub_expr->getLocStart()), result.SourceManager->getSpellingLoc(sub_expr->getLocEnd())); assert(sub_expr_range.isValid() && "Invalid subexpression range!"); std::string inner_text = clang::Lexer::getSourceText( sub_expr_range, *result.SourceManager, result.Context->getLangOpts()); assert(!inner_text.empty() && "No text for subexpression!"); if (NeedsParens(sub_expr)) { inner_text.insert(0, "("); inner_text.append(")"); } inner_text.append("->get()"); return Replacement(*result.SourceManager, range, inner_text); } } std::string text = clang::Lexer::getSourceText( range, *result.SourceManager, result.Context->getLangOpts()); assert(!text.empty() && "No text for expression!"); // Unwrap any temporaries - for example, custom iterators that return // scoped_refptr<T> as part of operator*. Any such iterators should also // be declaring a scoped_refptr<T>* operator->, per C++03 24.4.1.1 (Table 72) if (const clang::CXXBindTemporaryExpr* op = llvm::dyn_cast<clang::CXXBindTemporaryExpr>(expr)) { expr = op->getSubExpr(); } // Handle iterators (which are operator* calls, followed by implicit // conversions) by rewriting *it as it->get() if (const clang::CXXOperatorCallExpr* op = llvm::dyn_cast<clang::CXXOperatorCallExpr>(expr)) { if (op->getOperator() == clang::OO_Star) { // Note that this doesn't rewrite **it correctly, since it should be // rewritten using parens, e.g. (*it)->get(). However, this shouldn't // happen frequently, if at all, since it would likely indicate code is // storing pointers to a scoped_refptr in a container. text.erase(0, 1); text.append("->get()"); return Replacement(*result.SourceManager, range, text); } } // The only remaining calls should be non-dereferencing calls (eg: member // calls), so a simple ".get()" appending should suffice. if (NeedsParens(expr)) { text.insert(0, "("); text.append(")"); } text.append(".get()"); return Replacement(*result.SourceManager, range, text); } Replacement RewriteRawPtrToScopedRefptr(const MatchFinder::MatchResult& result, clang::SourceLocation begin, clang::SourceLocation end) { clang::CharSourceRange range = clang::CharSourceRange::getTokenRange( result.SourceManager->getSpellingLoc(begin), result.SourceManager->getSpellingLoc(end)); assert(range.isValid() && "Invalid range!"); std::string text = clang::Lexer::getSourceText( range, *result.SourceManager, result.Context->getLangOpts()); text.erase(text.rfind('*')); std::string replacement_text("scoped_refptr<"); replacement_text += text; replacement_text += ">"; return Replacement(*result.SourceManager, range, replacement_text); } class GetRewriterCallback : public MatchFinder::MatchCallback { public: explicit GetRewriterCallback(Replacements* replacements) : replacements_(replacements) {} virtual void run(const MatchFinder::MatchResult& result) override; private: Replacements* const replacements_; }; void GetRewriterCallback::run(const MatchFinder::MatchResult& result) { const clang::Expr* arg = result.Nodes.getNodeAs<clang::Expr>("arg"); assert(arg && "Unexpected match! No Expr captured!"); auto err = replacements_->add(RewriteImplicitToExplicitConversion(result, arg)); assert(!err); } class VarRewriterCallback : public MatchFinder::MatchCallback { public: explicit VarRewriterCallback(Replacements* replacements) : replacements_(replacements) {} virtual void run(const MatchFinder::MatchResult& result) override; private: Replacements* const replacements_; }; void VarRewriterCallback::run(const MatchFinder::MatchResult& result) { const clang::DeclaratorDecl* const var_decl = result.Nodes.getNodeAs<clang::DeclaratorDecl>("var"); assert(var_decl && "Unexpected match! No VarDecl captured!"); const clang::TypeSourceInfo* tsi = var_decl->getTypeSourceInfo(); // TODO(dcheng): This mishandles a case where a variable has multiple // declarations, e.g.: // // in .h: // Foo* my_global_magical_foo; // // in .cc: // Foo* my_global_magical_foo = CreateFoo(); // // In this case, it will only rewrite the .cc definition. Oh well. This should // be rare enough that these cases can be manually handled, since the style // guide prohibits globals of non-POD type. auto err = replacements_->add(RewriteRawPtrToScopedRefptr( result, tsi->getTypeLoc().getBeginLoc(), tsi->getTypeLoc().getEndLoc())); assert(!err); } class FunctionRewriterCallback : public MatchFinder::MatchCallback { public: explicit FunctionRewriterCallback(Replacements* replacements) : replacements_(replacements) {} virtual void run(const MatchFinder::MatchResult& result) override; private: Replacements* const replacements_; }; void FunctionRewriterCallback::run(const MatchFinder::MatchResult& result) { const clang::FunctionDecl* const function_decl = result.Nodes.getNodeAs<clang::FunctionDecl>("fn"); assert(function_decl && "Unexpected match! No FunctionDecl captured!"); // If matched against an implicit conversion to a DeclRefExpr, make sure the // referenced declaration is of class type, e.g. the tool skips trying to // chase pointers/references to determine if the pointee is a scoped_refptr<T> // with local storage. Instead, let a human manually handle those cases. const clang::VarDecl* const var_decl = result.Nodes.getNodeAs<clang::VarDecl>("var"); if (var_decl && !var_decl->getTypeSourceInfo()->getType()->isClassType()) { return; } for (clang::FunctionDecl* f : function_decl->redecls()) { clang::SourceRange range = f->getReturnTypeSourceRange(); auto err = replacements_->add( RewriteRawPtrToScopedRefptr(result, range.getBegin(), range.getEnd())); assert(!err); } } class MacroRewriterCallback : public MatchFinder::MatchCallback { public: explicit MacroRewriterCallback(Replacements* replacements) : replacements_(replacements) {} virtual void run(const MatchFinder::MatchResult& result) override; private: Replacements* const replacements_; }; void MacroRewriterCallback::run(const MatchFinder::MatchResult& result) { const clang::Expr* const expr = result.Nodes.getNodeAs<clang::Expr>("expr"); assert(expr && "Unexpected match! No Expr captured!"); auto err = replacements_->add(RewriteImplicitToExplicitConversion(result, expr)); assert(!err); } } // namespace static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); int main(int argc, const char* argv[]) { // TODO(dcheng): Clang tooling should do this itself. // http://llvm.org/bugs/show_bug.cgi?id=21627 llvm::InitializeNativeTarget(); llvm::InitializeNativeTargetAsmParser(); llvm::cl::OptionCategory category("Remove scoped_refptr conversions"); CommonOptionsParser options(argc, argv, category); clang::tooling::ClangTool tool(options.getCompilations(), options.getSourcePathList()); MatchFinder match_finder; Replacements replacements; auto is_scoped_refptr = cxxRecordDecl(isSameOrDerivedFrom("::scoped_refptr"), isTemplateInstantiation()); // Finds all calls to conversion operator member function. This catches calls // to "operator T*", "operator Testable", and "operator bool" equally. auto base_matcher = cxxMemberCallExpr(thisPointerType(is_scoped_refptr), callee(conversionDecl()), on(id("arg", expr()))); // The heuristic for whether or not converting a temporary is 'unsafe'. An // unsafe conversion is one where a temporary scoped_refptr<T> is converted to // another type. The matcher provides an exception for a temporary // scoped_refptr that is the result of an operator call. In this case, assume // that it's the result of an iterator dereference, and the container itself // retains the necessary reference, since this is a common idiom to see in // loop bodies. auto is_unsafe_temporary_conversion = on(cxxBindTemporaryExpr(unless(has(cxxOperatorCallExpr())))); // Returning a scoped_refptr<T> as a T* is considered unsafe if either are // true: // - The scoped_refptr<T> is a temporary. // - The scoped_refptr<T> has local lifetime. auto returned_as_raw_ptr = hasParent( returnStmt(hasAncestor(id("fn", functionDecl(returns(pointerType())))))); // This matcher intentionally matches more than it should. For example, this // will match: // scoped_refptr<Foo>& foo = some_other_foo; // return foo; // The matcher callback filters out VarDecls that aren't a scoped_refptr<T>, // so those cases can be manually handled. auto is_local_variable = on(declRefExpr(to(id("var", varDecl(hasLocalStorage()))))); auto is_unsafe_return = anyOf(allOf(hasParent(implicitCastExpr(returned_as_raw_ptr)), is_local_variable), allOf(hasParent(implicitCastExpr( hasParent(exprWithCleanups(returned_as_raw_ptr)))), is_unsafe_temporary_conversion)); // This catches both user-defined conversions (eg: "operator bool") and // standard conversion sequence (C++03 13.3.3.1.1), such as converting a // pointer to a bool. auto implicit_to_bool = implicitCastExpr(hasImplicitDestinationType(isBoolean())); // Avoid converting calls to of "operator Testable" -> "bool" and calls of // "operator T*" -> "bool". auto bool_conversion_matcher = hasParent( expr(anyOf(implicit_to_bool, expr(hasParent(implicit_to_bool))))); auto is_logging_helper = functionDecl(anyOf(hasName("CheckEQImpl"), hasName("CheckNEImpl"))); auto is_gtest_helper = functionDecl( anyOf(cxxMethodDecl(ofClass(cxxRecordDecl(isSameOrDerivedFrom( hasName("::testing::internal::EqHelper")))), hasName("Compare")), hasName("::testing::internal::CmpHelperNE"))); auto is_gtest_assertion_result_ctor = cxxConstructorDecl(ofClass(cxxRecordDecl( isSameOrDerivedFrom(hasName("::testing::AssertionResult"))))); // Find all calls to an operator overload that are 'safe'. // // All bool conversions will be handled with the Testable trick, but that // can only be used once "operator T*" is removed, since otherwise it leaves // the call ambiguous. GetRewriterCallback get_callback(&replacements); match_finder.addMatcher( cxxMemberCallExpr( base_matcher, // Excluded since the conversion may be unsafe. unless(anyOf(is_unsafe_temporary_conversion, is_unsafe_return)), // Excluded since the conversion occurs inside a helper function that // the macro wraps. Letting this callback handle the rewrite would // result in an incorrect replacement that changes the helper function // itself. Instead, the right replacement is to rewrite the macro's // arguments. unless(hasAncestor(decl(anyOf(is_logging_helper, is_gtest_helper, is_gtest_assertion_result_ctor))))), &get_callback); // Find temporary scoped_refptr<T>'s being unsafely assigned to a T*. VarRewriterCallback var_callback(&replacements); auto initialized_with_temporary = has(ignoringImpCasts( cxxMemberCallExpr(base_matcher, is_unsafe_temporary_conversion))); match_finder.addMatcher( id("var", varDecl(hasInitializer(initialized_with_temporary), hasType(pointerType()))), &var_callback); match_finder.addMatcher( cxxConstructorDecl(forEachConstructorInitializer( allOf(withInitializer(initialized_with_temporary), forField(id("var", fieldDecl(hasType(pointerType()))))))), &var_callback); // Rewrite functions that unsafely turn a scoped_refptr<T> into a T* when // returning a value. FunctionRewriterCallback fn_callback(&replacements); match_finder.addMatcher(cxxMemberCallExpr(base_matcher, is_unsafe_return), &fn_callback); // Rewrite logging / gtest expressions that result in an implicit conversion. // Luckily, the matchers don't need to handle the case where one of the macro // arguments is NULL, such as: // CHECK_EQ(my_scoped_refptr, NULL) // because it simply doesn't compile--since NULL is actually of integral type, // this doesn't trigger scoped_refptr<T>'s implicit conversion. Since there is // no comparison overload for scoped_refptr<T> and int, this fails to compile. MacroRewriterCallback macro_callback(&replacements); // CHECK_EQ/CHECK_NE helpers. match_finder.addMatcher( callExpr(callee(is_logging_helper), argumentCountIs(3), hasAnyArgument(ignoringParenImpCasts( id("expr", expr(hasType(is_scoped_refptr))))), hasAnyArgument(ignoringParenImpCasts(hasType(pointerType()))), hasArgument(2, stringLiteral())), ¯o_callback); // ASSERT_EQ/ASSERT_NE/EXPECT_EQ/EXPECT_EQ, which use the same underlying // helper functions. Even though gtest has special handling for pointer to // NULL comparisons, it doesn't trigger in this case, so no special handling // is needed for the replacements. match_finder.addMatcher( callExpr(callee(is_gtest_helper), argumentCountIs(4), hasArgument(0, stringLiteral()), hasArgument(1, stringLiteral()), hasAnyArgument(id("expr", expr(hasType(is_scoped_refptr)))), hasAnyArgument(hasType(pointerType()))), ¯o_callback); // ASSERT_TRUE/EXPECT_TRUE helpers. Note that this matcher doesn't need to // handle ASSERT_FALSE/EXPECT_FALSE, because it gets coerced to bool before // being passed as an argument to AssertionResult's constructor. As a result, // GetRewriterCallback handles this case properly since the conversion isn't // hidden inside AssertionResult, and the generated replacement properly // rewrites the macro argument. // However, the tool does need to handle the _TRUE counterparts, since the // conversion occurs inside the constructor in those cases. match_finder.addMatcher( cxxConstructExpr( argumentCountIs(2), hasArgument(0, id("expr", expr(hasType(is_scoped_refptr)))), hasDeclaration(is_gtest_assertion_result_ctor)), ¯o_callback); std::unique_ptr<clang::tooling::FrontendActionFactory> factory = clang::tooling::newFrontendActionFactory(&match_finder); int result = tool.run(factory.get()); if (result != 0) return result; // Serialization format is documented in tools/clang/scripts/run_tool.py llvm::outs() << "==== BEGIN EDITS ====\n"; for (const auto& r : replacements) { std::string replacement_text = r.getReplacementText().str(); std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0'); llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset() << ":::" << r.getLength() << ":::" << replacement_text << "\n"; } llvm::outs() << "==== END EDITS ====\n"; return 0; }