Index: clang-tidy/misc/AvoidStdBindCheck.h =================================================================== --- /dev/null +++ clang-tidy/misc/AvoidStdBindCheck.h @@ -0,0 +1,59 @@ +//===--- AvoidStdBindCheck.h - clang-tidy-----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_AVOID_STD_BIND_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_AVOID_STD_BIND_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Replace std::bind with a lambda. +/// +/// FIXME: Add support for function references and member function references. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-avoid-std-bind.html +class AvoidStdBindCheck : public ClangTidyCheck { +public: + AvoidStdBindCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + struct BindArgument { + StringRef Tokens; + struct PlaceHolderInfo + { + std::string Name; + size_t BindPlaceholderIndex; + size_t BindFunctionArgIndex; + }; + Optional PlaceHolder; + bool IsTemporaryExpr = false; + }; + + static std::vector + buildBindArguments(const ast_matchers::MatchFinder::MatchResult &Result, + const CallExpr *C); + + static void addPlaceholderArgs(const std::vector &Args, + const FunctionDecl *F, + llvm::raw_ostream &Stream); + static void addFunctionCallArgs( + const std::vector &Args, llvm::raw_ostream &Stream); +}; +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_AVOID_STD_BIND_H Index: clang-tidy/misc/AvoidStdBindCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/misc/AvoidStdBindCheck.cpp @@ -0,0 +1,148 @@ +//===--- AvoidStdBindCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include +#include "AvoidStdBindCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +AST_MATCHER(NamedDecl, isStdBind) { + return Node.isInStdNamespace() && (Node.getName() == "bind"); +} +} // end anonymous namespace + +static llvm::Regex MatchPlaceholder("^_([0-9]+)$"); + +void AvoidStdBindCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr(callee(namedDecl(isStdBind())), + hasArgument(0, declRefExpr(to(functionDecl().bind("f"))))) + .bind("bind"), + this); +} + +std::vector +AvoidStdBindCheck::buildBindArguments(const MatchFinder::MatchResult &Result, + const CallExpr *C) { + std::vector BindArguments; + for (size_t I = 1, ArgCount = C->getNumArgs(); I < ArgCount; ++I) { + const Expr *E = C->getArg(I); + BindArgument B; + B.IsTemporaryExpr = dyn_cast(E); + B.Tokens = Lexer::getSourceText( + CharSourceRange::getTokenRange(E->getLocStart(), E->getLocEnd()), + *Result.SourceManager, Result.Context->getLangOpts()); + + SmallVector Matches; + if (MatchPlaceholder.match(B.Tokens, &Matches)) + { B.PlaceHolder = BindArgument::PlaceHolderInfo{}; + B.PlaceHolder->Name = (llvm::Twine("arg") + Matches[1]).str(); + B.PlaceHolder->BindPlaceholderIndex = std::stoi(Matches[1]); + B.PlaceHolder->BindFunctionArgIndex = I-1; + } + BindArguments.push_back(B); + } + return BindArguments; +} + +void AvoidStdBindCheck::addPlaceholderArgs( + const std::vector &Args, const FunctionDecl *F, + llvm::raw_ostream &Stream) { + size_t PlaceholderCount = std::count_if( + Args.begin(), Args.end(), [](const auto &B) { return B.PlaceHolder; }); + + if (PlaceholderCount) + Stream << "("; + + StringRef Delimiter = ""; + for (size_t I = 0; I < PlaceholderCount; ++I) { + + // Bind placeholders start with index 1 so we have to do offsetting. + auto FindIt = + std::find_if(Args.begin(), Args.end(), [I](const BindArgument &B) { + return B.PlaceHolder && B.PlaceHolder->BindPlaceholderIndex == I + 1; + }); + assert(FindIt != Args.end()); + + const BindArgument::PlaceHolderInfo &P = *FindIt->PlaceHolder; + + const ParmVarDecl *PDecl = F->getParamDecl(P.BindFunctionArgIndex); + StringRef ParmTypeName = PDecl->getType().getAsString(); + Stream << Delimiter << ParmTypeName << " arg" << I + 1; + Delimiter = ", "; + } + + if (PlaceholderCount) + Stream << ")"; +} + +void AvoidStdBindCheck::addFunctionCallArgs( + const std::vector &Args, llvm::raw_ostream &Stream) { + StringRef Delimiter = ""; + for (const auto &B : Args) { + if (B.PlaceHolder) + Stream << Delimiter << B.PlaceHolder->Name; + else + Stream << Delimiter << B.Tokens; + Delimiter = ", "; + } + Stream << "); };"; +} + +void AvoidStdBindCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("bind"); + auto DiagnosticBuilder = + diag(MatchedDecl->getLocStart(), "avoid using std::bind"); + + if (!getLangOpts().CPlusPlus11) // Need C++11 for lambdas + return; + + std::string Buffer; + llvm::raw_string_ostream Stream(Buffer); + const std::vector Args = + buildBindArguments(Result, MatchedDecl); + + bool HasCapturedArgument = + std::find_if(Args.begin(), Args.end(), [](const BindArgument &B) { + return !B.IsTemporaryExpr && !B.PlaceHolder; + }) != Args.end(); + + const auto *F = Result.Nodes.getNodeAs("f"); + + // std::bind can support argument count mismatch between its arguments and the + // bound function's arguments. Do not attempt to generate a fixit for such + // cases. + if (F->getNumParams() != Args.size()) + return; + + Stream << "[" << (HasCapturedArgument ? "=" : "") << "]"; + addPlaceholderArgs(Args, F, Stream); + Stream << " { return " << F->getName() << "("; + addFunctionCallArgs(Args, Stream); + + SourceRange ReplacedRange( + MatchedDecl->getLocStart(), + Lexer::getLocForEndOfToken(MatchedDecl->getLocEnd(), 0, + *Result.SourceManager, + Result.Context->getLangOpts())); + + DiagnosticBuilder << FixItHint::CreateReplacement(ReplacedRange, + Stream.str()); +} + +} // namespace misc +} // namespace tidy +} // namespace clang Index: clang-tidy/misc/CMakeLists.txt =================================================================== --- clang-tidy/misc/CMakeLists.txt +++ clang-tidy/misc/CMakeLists.txt @@ -4,6 +4,7 @@ ArgumentCommentCheck.cpp AssertSideEffectCheck.cpp AssignOperatorSignatureCheck.cpp + AvoidStdBindCheck.cpp BoolPointerImplicitConversionCheck.cpp DefinitionsInHeadersCheck.cpp InaccurateEraseCheck.cpp Index: clang-tidy/misc/MiscTidyModule.cpp =================================================================== --- clang-tidy/misc/MiscTidyModule.cpp +++ clang-tidy/misc/MiscTidyModule.cpp @@ -13,6 +13,7 @@ #include "ArgumentCommentCheck.h" #include "AssertSideEffectCheck.h" #include "AssignOperatorSignatureCheck.h" +#include "AvoidStdBindCheck.h" #include "BoolPointerImplicitConversionCheck.h" #include "DefinitionsInHeadersCheck.h" #include "InaccurateEraseCheck.h" @@ -48,6 +49,8 @@ "misc-assert-side-effect"); CheckFactories.registerCheck( "misc-assign-operator-signature"); + CheckFactories.registerCheck( + "misc-avoid-std-bind"); CheckFactories.registerCheck( "misc-bool-pointer-implicit-conversion"); CheckFactories.registerCheck( Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -46,6 +46,7 @@ misc-argument-comment misc-assert-side-effect misc-assign-operator-signature + misc-avoid-std-bind misc-bool-pointer-implicit-conversion misc-definitions-in-headers misc-inaccurate-erase Index: docs/clang-tidy/checks/misc-avoid-std-bind.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/misc-avoid-std-bind.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-avoid-std-bind + +misc-avoid-std-bind +=================== + +Find uses of std::bind. Replace simple uses of std::bind with lambdas. Lambdas +will use value-capture where required. + +Right now it only handles free functions not member functions. + +std::bind can result in larger object files and binaries due to type +information that will not be produced by equivalent lambdas. + Index: test/clang-tidy/misc-avoid-std-bind.cpp =================================================================== --- /dev/null +++ test/clang-tidy/misc-avoid-std-bind.cpp @@ -0,0 +1,64 @@ +// RUN: %check_clang_tidy %s misc-avoid-std-bind %t + +namespace std { +inline namespace impl { +template +class bind_rt {}; + +template +bind_rt bind(Fp&&, Arguments&& ...); +} +} + +int add(int x, int y) { return x + y; } + +void f() +{ + auto clj = std::bind(add,2,2); + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: avoid using std::bind [misc-avoid-std-bind] +} + +// CHECK-FIXES: auto clj = [] { return add(2, 2); }; + +void g() +{ + int x = 2; + int y = 2; + auto clj = std::bind(add,x,y); + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: avoid using std::bind [misc-avoid-std-bind] +} + +// CHECK-FIXES: auto clj = [=] { return add(x, y); }; + +struct placeholder {}; +placeholder _1; +placeholder _2; + +void h() +{ + int x = 2; + auto clj = std::bind(add,x,_1); + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: avoid using std::bind [misc-avoid-std-bind] +} + +// CHECK-FIXES: auto clj = [=](int arg1) { return add(x, arg1); }; + +struct A; +struct B; +bool ABTest(const A&, const B&); + +void i() +{ + auto BATest = std::bind(ABTest, _2, _1); + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: avoid using std::bind [misc-avoid-std-bind] +} + +// CHECK-FIXES: auto BATest = [](const struct B & arg1, const struct A & arg2) { return ABTest(arg2, arg1); }; + +void j() +{ + auto clj = std::bind(add, 2, 2, 2); + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: avoid using std::bind [misc-avoid-std-bind] +} +// No fix is applied for argument mismatches. +// CHECK-FIXES: auto clj = std::bind(add, 2, 2, 2);