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