clang-tools  3.9.0
AvoidBindCheck.cpp
Go to the documentation of this file.
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
std::unique_ptr< ast_matchers::MatchFinder > Finder
Definition: ClangTidy.cpp:210
static bool isPlaceHolderIndexRepeated(const ArrayRef< BindArgument > Args)
static void addFunctionCallArgs(const ArrayRef< BindArgument > Args, llvm::raw_ostream &Stream)
BindArgumentKind Kind
StringRef Tokens
static void addPlaceholderArgs(const ArrayRef< BindArgument > Args, llvm::raw_ostream &Stream)
static SmallVector< BindArgument, 4 > buildBindArguments(const MatchFinder::MatchResult &Result, const CallExpr *C)
const NamedDecl * Result
Definition: USRFinder.cpp:137
size_t PlaceHolderIndex