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