1 //===--- AvoidBindCheck.cpp - clang-tidy--------------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 #include "AvoidBindCheck.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 #include "clang/Lex/Lexer.h" 13 #include <cassert> 14 #include <unordered_map> 15 16 using namespace clang::ast_matchers; 17 18 namespace clang { 19 namespace tidy { 20 namespace modernize { 21 22 namespace { 23 enum BindArgumentKind { BK_Temporary, BK_Placeholder, BK_CallExpr, BK_Other }; 24 25 struct BindArgument { 26 StringRef Tokens; 27 BindArgumentKind Kind = BK_Other; 28 size_t PlaceHolderIndex = 0; 29 }; 30 31 } // end namespace 32 33 static SmallVector<BindArgument, 4> 34 buildBindArguments(const MatchFinder::MatchResult &Result, const CallExpr *C) { 35 SmallVector<BindArgument, 4> BindArguments; 36 llvm::Regex MatchPlaceholder("^_([0-9]+)$"); 37 38 // Start at index 1 as first argument to bind is the function name. 39 for (size_t I = 1, ArgCount = C->getNumArgs(); I < ArgCount; ++I) { 40 const Expr *E = C->getArg(I); 41 BindArgument B; 42 if (const auto *M = dyn_cast<MaterializeTemporaryExpr>(E)) { 43 const auto *TE = M->GetTemporaryExpr(); 44 B.Kind = isa<CallExpr>(TE) ? BK_CallExpr : BK_Temporary; 45 } 46 47 B.Tokens = Lexer::getSourceText( 48 CharSourceRange::getTokenRange(E->getLocStart(), E->getLocEnd()), 49 *Result.SourceManager, Result.Context->getLangOpts()); 50 51 SmallVector<StringRef, 2> Matches; 52 if (B.Kind == BK_Other && MatchPlaceholder.match(B.Tokens, &Matches)) { 53 B.Kind = BK_Placeholder; 54 B.PlaceHolderIndex = std::stoi(Matches[1]); 55 } 56 BindArguments.push_back(B); 57 } 58 return BindArguments; 59 } 60 61 static void addPlaceholderArgs(const ArrayRef<BindArgument> Args, 62 llvm::raw_ostream &Stream) { 63 auto MaxPlaceholderIt = 64 std::max_element(Args.begin(), Args.end(), 65 [](const BindArgument &B1, const BindArgument &B2) { 66 return B1.PlaceHolderIndex < B2.PlaceHolderIndex; 67 }); 68 69 // Placeholders (if present) have index 1 or greater. 70 if (MaxPlaceholderIt == Args.end() || MaxPlaceholderIt->PlaceHolderIndex == 0) 71 return; 72 73 size_t PlaceholderCount = MaxPlaceholderIt->PlaceHolderIndex; 74 Stream << "("; 75 StringRef Delimiter = ""; 76 for (size_t I = 1; I <= PlaceholderCount; ++I) { 77 Stream << Delimiter << "auto && arg" << I; 78 Delimiter = ", "; 79 } 80 Stream << ")"; 81 } 82 83 static void addFunctionCallArgs(const ArrayRef<BindArgument> Args, 84 llvm::raw_ostream &Stream) { 85 StringRef Delimiter = ""; 86 for (const auto &B : Args) { 87 if (B.PlaceHolderIndex) 88 Stream << Delimiter << "arg" << B.PlaceHolderIndex; 89 else 90 Stream << Delimiter << B.Tokens; 91 Delimiter = ", "; 92 } 93 } 94 95 static bool isPlaceHolderIndexRepeated(const ArrayRef<BindArgument> Args) { 96 llvm::SmallSet<size_t, 4> PlaceHolderIndices; 97 for (const BindArgument &B : Args) { 98 if (B.PlaceHolderIndex) { 99 if (!PlaceHolderIndices.insert(B.PlaceHolderIndex).second) 100 return true; 101 } 102 } 103 return false; 104 } 105 106 void AvoidBindCheck::registerMatchers(MatchFinder *Finder) { 107 if (!getLangOpts().CPlusPlus14) // Need C++14 for generic lambdas. 108 return; 109 110 Finder->addMatcher( 111 callExpr(callee(namedDecl(hasName("::std::bind"))), 112 hasArgument(0, declRefExpr(to(functionDecl().bind("f"))))) 113 .bind("bind"), 114 this); 115 } 116 117 void AvoidBindCheck::check(const MatchFinder::MatchResult &Result) { 118 const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("bind"); 119 auto Diag = diag(MatchedDecl->getLocStart(), "prefer a lambda to std::bind"); 120 121 const auto Args = buildBindArguments(Result, MatchedDecl); 122 123 // Do not attempt to create fixits for nested call expressions. 124 // FIXME: Create lambda capture variables to capture output of calls. 125 // NOTE: Supporting nested std::bind will be more difficult due to placeholder 126 // sharing between outer and inner std:bind invocations. 127 if (llvm::any_of(Args, 128 [](const BindArgument &B) { return B.Kind == BK_CallExpr; })) 129 return; 130 131 // Do not attempt to create fixits when placeholders are reused. 132 // Unused placeholders are supported by requiring C++14 generic lambdas. 133 // FIXME: Support this case by deducing the common type. 134 if (isPlaceHolderIndexRepeated(Args)) 135 return; 136 137 const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("f"); 138 139 // std::bind can support argument count mismatch between its arguments and the 140 // bound function's arguments. Do not attempt to generate a fixit for such 141 // cases. 142 // FIXME: Support this case by creating unused lambda capture variables. 143 if (F->getNumParams() != Args.size()) 144 return; 145 146 std::string Buffer; 147 llvm::raw_string_ostream Stream(Buffer); 148 149 bool HasCapturedArgument = llvm::any_of( 150 Args, [](const BindArgument &B) { return B.Kind == BK_Other; }); 151 152 Stream << "[" << (HasCapturedArgument ? "=" : "") << "]"; 153 addPlaceholderArgs(Args, Stream); 154 Stream << " { return " << F->getName() << "("; 155 addFunctionCallArgs(Args, Stream); 156 Stream << "); };"; 157 158 Diag << FixItHint::CreateReplacement(MatchedDecl->getSourceRange(), Stream.str()); 159 } 160 161 } // namespace modernize 162 } // namespace tidy 163 } // namespace clang 164