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( 112 callee(namedDecl(hasName("::std::bind"))), 113 hasArgument(0, declRefExpr(to(functionDecl().bind("f"))).bind("ref"))) 114 .bind("bind"), 115 this); 116 } 117 118 void AvoidBindCheck::check(const MatchFinder::MatchResult &Result) { 119 const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("bind"); 120 auto Diag = diag(MatchedDecl->getLocStart(), "prefer a lambda to std::bind"); 121 122 const auto Args = buildBindArguments(Result, MatchedDecl); 123 124 // Do not attempt to create fixits for nested call expressions. 125 // FIXME: Create lambda capture variables to capture output of calls. 126 // NOTE: Supporting nested std::bind will be more difficult due to placeholder 127 // sharing between outer and inner std:bind invocations. 128 if (llvm::any_of(Args, 129 [](const BindArgument &B) { return B.Kind == BK_CallExpr; })) 130 return; 131 132 // Do not attempt to create fixits when placeholders are reused. 133 // Unused placeholders are supported by requiring C++14 generic lambdas. 134 // FIXME: Support this case by deducing the common type. 135 if (isPlaceHolderIndexRepeated(Args)) 136 return; 137 138 const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("f"); 139 140 // std::bind can support argument count mismatch between its arguments and the 141 // bound function's arguments. Do not attempt to generate a fixit for such 142 // cases. 143 // FIXME: Support this case by creating unused lambda capture variables. 144 if (F->getNumParams() != Args.size()) 145 return; 146 147 std::string Buffer; 148 llvm::raw_string_ostream Stream(Buffer); 149 150 bool HasCapturedArgument = llvm::any_of( 151 Args, [](const BindArgument &B) { return B.Kind == BK_Other; }); 152 const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("ref"); 153 Stream << "[" << (HasCapturedArgument ? "=" : "") << "]"; 154 addPlaceholderArgs(Args, Stream); 155 Stream << " { return "; 156 Ref->printPretty(Stream, nullptr, Result.Context->getPrintingPolicy()); 157 Stream << "("; 158 addFunctionCallArgs(Args, Stream); 159 Stream << "); };"; 160 161 Diag << FixItHint::CreateReplacement(MatchedDecl->getSourceRange(), 162 Stream.str()); 163 } 164 165 } // namespace modernize 166 } // namespace tidy 167 } // namespace clang 168