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/STLExtras.h"
18#include "llvm/ADT/SmallSet.h"
19#include "llvm/ADT/SmallVector.h"
20#include "llvm/ADT/StringRef.h"
21#include "llvm/ADT/StringSet.h"
22#include "llvm/Support/Casting.h"
23#include "llvm/Support/FormatVariadic.h"
24#include "llvm/Support/Regex.h"
25#include "llvm/Support/raw_ostream.h"
26#include <algorithm>
27#include <cstddef>
28#include <string>
29
30using namespace clang::ast_matchers;
31
32namespace clang::tidy::modernize {
33
34namespace {
35
36enum BindArgumentKind { BK_Temporary, BK_Placeholder, BK_CallExpr, BK_Other };
37enum CaptureMode { CM_None, CM_ByRef, CM_ByValue };
38enum CaptureExpr { CE_None, CE_Var, CE_InitExpression };
39
40enum CallableType {
41 CT_Other, // unknown
42 CT_Function, // global or static function
43 CT_MemberFunction, // member function with implicit this
44 CT_Object, // object with operator()
45};
46
47enum CallableMaterializationKind {
48 CMK_Other, // unknown
49 CMK_Function, // callable is the name of a member or non-member function.
50 CMK_VariableRef, // callable is a simple expression involving a global or
51 // local variable.
52 CMK_CallExpression, // callable is obtained as the result of a call expression
53};
54
55struct BindArgument {
56 // A rough classification of the type of expression this argument was.
57 BindArgumentKind Kind = BK_Other;
58
59 // If this argument required a capture, a value indicating how it was
60 // captured.
61 CaptureMode CM = CM_None;
62
63 // Whether the argument is a simple variable (we can capture it directly),
64 // or an expression (we must introduce a capture variable).
65 CaptureExpr CE = CE_None;
66
67 // The exact spelling of this argument in the source code.
68 StringRef SourceTokens;
69
70 // The identifier of the variable within the capture list. This may be
71 // different from UsageIdentifier for example in the expression *d, where the
72 // variable is captured as d, but referred to as *d.
73 std::string CaptureIdentifier;
74
75 // If this is a placeholder or capture init expression, contains the tokens
76 // used to refer to this parameter from within the body of the lambda.
77 std::string UsageIdentifier;
78
79 // If Kind == BK_Placeholder, the index of the placeholder.
80 size_t PlaceHolderIndex = 0;
81
82 // True if the argument is used inside the lambda, false otherwise.
83 bool IsUsed = false;
84
85 // The actual Expr object representing this expression.
86 const Expr *E = nullptr;
87};
88
89struct CallableInfo {
90 CallableType Type = CT_Other;
91 CallableMaterializationKind Materialization = CMK_Other;
92 CaptureMode CM = CM_None;
93 CaptureExpr CE = CE_None;
94 StringRef SourceTokens;
95 std::string CaptureIdentifier;
96 std::string UsageIdentifier;
97 StringRef CaptureInitializer;
98 const FunctionDecl *Decl = nullptr;
99 bool DoesReturn = false;
100};
101
102struct LambdaProperties {
103 CallableInfo Callable;
104 SmallVector<BindArgument, 4> BindArguments;
105 StringRef BindNamespace;
106 bool IsFixitSupported = false;
107};
108
109} // end namespace
110
111static bool tryCaptureAsLocalVariable(const MatchFinder::MatchResult &Result,
112 BindArgument &B, const Expr *E);
113
114static bool tryCaptureAsMemberVariable(const MatchFinder::MatchResult &Result,
115 BindArgument &B, const Expr *E);
116
117static const Expr *ignoreTemporariesAndPointers(const Expr *E) {
118 if (const auto *T = dyn_cast<UnaryOperator>(Val: E))
119 return ignoreTemporariesAndPointers(E: T->getSubExpr());
120
121 const Expr *F = E->IgnoreImplicit();
122 if (E != F)
123 return ignoreTemporariesAndPointers(E: F);
124
125 return E;
126}
127
128static const Expr *ignoreTemporariesAndConstructors(const Expr *E) {
129 if (const auto *T = dyn_cast<CXXConstructExpr>(Val: E))
130 return ignoreTemporariesAndConstructors(E: T->getArg(Arg: 0));
131
132 const Expr *F = E->IgnoreImplicit();
133 if (E != F)
134 return ignoreTemporariesAndPointers(E: F);
135
136 return E;
137}
138
139static StringRef getSourceTextForExpr(const MatchFinder::MatchResult &Result,
140 const Expr *E) {
141 return Lexer::getSourceText(
142 Range: CharSourceRange::getTokenRange(E->getBeginLoc(), E->getEndLoc()),
143 SM: *Result.SourceManager, LangOpts: Result.Context->getLangOpts());
144}
145
146static bool isCallExprNamed(const Expr *E, StringRef Name) {
147 const auto *CE = dyn_cast<CallExpr>(Val: E->IgnoreImplicit());
148 if (!CE)
149 return false;
150 const auto *ND = dyn_cast<NamedDecl>(Val: CE->getCalleeDecl());
151 if (!ND)
152 return false;
153 return ND->getQualifiedNameAsString() == Name;
154}
155
156static void
157initializeBindArgumentForCallExpr(const MatchFinder::MatchResult &Result,
158 BindArgument &B, const CallExpr *CE,
159 unsigned &CaptureIndex) {
160 // std::ref(x) means to capture x by reference.
161 if (isCallExprNamed(CE, "boost::ref") || isCallExprNamed(CE, "std::ref")) {
162 B.Kind = BK_Other;
163 if (tryCaptureAsLocalVariable(Result, B, E: CE->getArg(Arg: 0)) ||
164 tryCaptureAsMemberVariable(Result, B, E: CE->getArg(Arg: 0))) {
165 B.CE = CE_Var;
166 } else {
167 // The argument to std::ref is an expression that produces a reference.
168 // Create a capture reference to hold it.
169 B.CE = CE_InitExpression;
170 B.UsageIdentifier = "capture" + llvm::utostr(X: CaptureIndex++);
171 }
172 // Strip off the reference wrapper.
173 B.SourceTokens = getSourceTextForExpr(Result, E: CE->getArg(Arg: 0));
174 B.CM = CM_ByRef;
175 } else {
176 B.Kind = BK_CallExpr;
177 B.CM = CM_ByValue;
178 B.CE = CE_InitExpression;
179 B.UsageIdentifier = "capture" + llvm::utostr(X: CaptureIndex++);
180 }
181 B.CaptureIdentifier = B.UsageIdentifier;
182}
183
184static bool anyDescendantIsLocal(const Stmt *Statement) {
185 if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Val: Statement)) {
186 const ValueDecl *Decl = DeclRef->getDecl();
187 if (const auto *Var = dyn_cast_or_null<VarDecl>(Val: Decl)) {
188 if (Var->isLocalVarDeclOrParm())
189 return true;
190 }
191 } else if (isa<CXXThisExpr>(Val: Statement))
192 return true;
193
194 return any_of(Range: Statement->children(), P: anyDescendantIsLocal);
195}
196
197static bool tryCaptureAsLocalVariable(const MatchFinder::MatchResult &Result,
198 BindArgument &B, const Expr *E) {
199 if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Val: E)) {
200 if (const auto *CE = dyn_cast<CXXConstructExpr>(Val: BTE->getSubExpr()))
201 return tryCaptureAsLocalVariable(Result, B, E: CE->getArg(Arg: 0));
202 return false;
203 }
204
205 const auto *DRE = dyn_cast<DeclRefExpr>(Val: E->IgnoreImplicit());
206 if (!DRE)
207 return false;
208
209 const auto *VD = dyn_cast<VarDecl>(Val: DRE->getDecl());
210 if (!VD || !VD->isLocalVarDeclOrParm())
211 return false;
212
213 B.CM = CM_ByValue;
214 B.UsageIdentifier = std::string(getSourceTextForExpr(Result, E));
215 B.CaptureIdentifier = B.UsageIdentifier;
216 return true;
217}
218
219static bool tryCaptureAsMemberVariable(const MatchFinder::MatchResult &Result,
220 BindArgument &B, const Expr *E) {
221 if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Val: E)) {
222 if (const auto *CE = dyn_cast<CXXConstructExpr>(Val: BTE->getSubExpr()))
223 return tryCaptureAsMemberVariable(Result, B, E: CE->getArg(Arg: 0));
224 return false;
225 }
226
227 E = E->IgnoreImplicit();
228 if (isa<CXXThisExpr>(Val: E)) {
229 // E is a direct use of "this".
230 B.CM = CM_ByValue;
231 B.UsageIdentifier = std::string(getSourceTextForExpr(Result, E));
232 B.CaptureIdentifier = "this";
233 return true;
234 }
235
236 const auto *ME = dyn_cast<MemberExpr>(Val: E);
237 if (!ME)
238 return false;
239
240 if (!ME->isLValue() || !isa<FieldDecl>(Val: ME->getMemberDecl()))
241 return false;
242
243 if (isa<CXXThisExpr>(Val: ME->getBase())) {
244 // E refers to a data member without an explicit "this".
245 B.CM = CM_ByValue;
246 B.UsageIdentifier = std::string(getSourceTextForExpr(Result, E));
247 B.CaptureIdentifier = "this";
248 return true;
249 }
250
251 return false;
252}
253
254static SmallVector<BindArgument, 4>
255buildBindArguments(const MatchFinder::MatchResult &Result,
256 const CallableInfo &Callable) {
257 SmallVector<BindArgument, 4> BindArguments;
258 static llvm::Regex MatchPlaceholder("^_([0-9]+)$");
259
260 const auto *BindCall = Result.Nodes.getNodeAs<CallExpr>(ID: "bind");
261
262 // Start at index 1 as first argument to bind is the function name.
263 unsigned CaptureIndex = 0;
264 for (size_t I = 1, ArgCount = BindCall->getNumArgs(); I < ArgCount; ++I) {
265
266 const Expr *E = BindCall->getArg(Arg: I);
267 BindArgument &B = BindArguments.emplace_back();
268
269 size_t ArgIndex = I - 1;
270 if (Callable.Type == CT_MemberFunction)
271 --ArgIndex;
272
273 bool IsObjectPtr = (I == 1 && Callable.Type == CT_MemberFunction);
274 B.E = E;
275 B.SourceTokens = getSourceTextForExpr(Result, E);
276
277 if (!Callable.Decl || ArgIndex < Callable.Decl->getNumParams() ||
278 IsObjectPtr)
279 B.IsUsed = true;
280
281 SmallVector<StringRef, 2> Matches;
282 const auto *DRE = dyn_cast<DeclRefExpr>(Val: E);
283 if (MatchPlaceholder.match(String: B.SourceTokens, Matches: &Matches) ||
284 // Check for match with qualifiers removed.
285 (DRE && MatchPlaceholder.match(String: DRE->getDecl()->getName(), Matches: &Matches))) {
286 B.Kind = BK_Placeholder;
287 B.PlaceHolderIndex = std::stoi(str: std::string(Matches[1]));
288 B.UsageIdentifier = "PH" + llvm::utostr(X: B.PlaceHolderIndex);
289 B.CaptureIdentifier = B.UsageIdentifier;
290 continue;
291 }
292
293 if (const auto *CE =
294 dyn_cast<CallExpr>(Val: ignoreTemporariesAndConstructors(E))) {
295 initializeBindArgumentForCallExpr(Result, B, CE, CaptureIndex);
296 continue;
297 }
298
299 if (tryCaptureAsLocalVariable(Result, B, E: B.E) ||
300 tryCaptureAsMemberVariable(Result, B, E: B.E))
301 continue;
302
303 // If it's not something we recognize, capture it by init expression to be
304 // safe.
305 B.Kind = BK_Other;
306 if (IsObjectPtr) {
307 B.CE = CE_InitExpression;
308 B.CM = CM_ByValue;
309 B.UsageIdentifier = "ObjectPtr";
310 B.CaptureIdentifier = B.UsageIdentifier;
311 } else if (anyDescendantIsLocal(B.E)) {
312 B.CE = CE_InitExpression;
313 B.CM = CM_ByValue;
314 B.CaptureIdentifier = "capture" + llvm::utostr(X: CaptureIndex++);
315 B.UsageIdentifier = B.CaptureIdentifier;
316 }
317 }
318 return BindArguments;
319}
320
321static int findPositionOfPlaceholderUse(ArrayRef<BindArgument> Args,
322 size_t PlaceholderIndex) {
323 for (size_t I = 0; I < Args.size(); ++I)
324 if (Args[I].PlaceHolderIndex == PlaceholderIndex)
325 return I;
326
327 return -1;
328}
329
330static void addPlaceholderArgs(const LambdaProperties &LP,
331 llvm::raw_ostream &Stream,
332 bool PermissiveParameterList) {
333
334 ArrayRef<BindArgument> Args = LP.BindArguments;
335
336 const auto *MaxPlaceholderIt =
337 std::max_element(first: Args.begin(), last: Args.end(),
338 comp: [](const BindArgument &B1, const BindArgument &B2) {
339 return B1.PlaceHolderIndex < B2.PlaceHolderIndex;
340 });
341
342 // Placeholders (if present) have index 1 or greater.
343 if (!PermissiveParameterList && (MaxPlaceholderIt == Args.end() ||
344 MaxPlaceholderIt->PlaceHolderIndex == 0))
345 return;
346
347 size_t PlaceholderCount = MaxPlaceholderIt->PlaceHolderIndex;
348 Stream << "(";
349 StringRef Delimiter = "";
350 for (size_t I = 1; I <= PlaceholderCount; ++I) {
351 Stream << Delimiter << "auto &&";
352
353 int ArgIndex = findPositionOfPlaceholderUse(Args, PlaceholderIndex: I);
354
355 if (ArgIndex != -1 && Args[ArgIndex].IsUsed)
356 Stream << " " << Args[ArgIndex].UsageIdentifier;
357 Delimiter = ", ";
358 }
359 if (PermissiveParameterList)
360 Stream << Delimiter << "auto && ...";
361 Stream << ")";
362}
363
364static void addFunctionCallArgs(ArrayRef<BindArgument> Args,
365 llvm::raw_ostream &Stream) {
366 StringRef Delimiter = "";
367
368 for (const BindArgument &B : Args) {
369 Stream << Delimiter;
370
371 if (B.Kind == BK_Placeholder) {
372 Stream << "std::forward<decltype(" << B.UsageIdentifier << ")>";
373 Stream << "(" << B.UsageIdentifier << ")";
374 } else if (B.CM != CM_None)
375 Stream << B.UsageIdentifier;
376 else
377 Stream << B.SourceTokens;
378
379 Delimiter = ", ";
380 }
381}
382
383static bool isPlaceHolderIndexRepeated(const ArrayRef<BindArgument> Args) {
384 llvm::SmallSet<size_t, 4> PlaceHolderIndices;
385 for (const BindArgument &B : Args) {
386 if (B.PlaceHolderIndex) {
387 if (!PlaceHolderIndices.insert(V: B.PlaceHolderIndex).second)
388 return true;
389 }
390 }
391 return false;
392}
393
394static std::vector<const FunctionDecl *>
395findCandidateCallOperators(const CXXRecordDecl *RecordDecl, size_t NumArgs) {
396 std::vector<const FunctionDecl *> Candidates;
397
398 for (const clang::CXXMethodDecl *Method : RecordDecl->methods()) {
399 OverloadedOperatorKind OOK = Method->getOverloadedOperator();
400
401 if (OOK != OverloadedOperatorKind::OO_Call)
402 continue;
403
404 if (Method->getNumParams() > NumArgs)
405 continue;
406
407 Candidates.push_back(Method);
408 }
409
410 // Find templated operator(), if any.
411 for (const clang::Decl *D : RecordDecl->decls()) {
412 const auto *FTD = dyn_cast<FunctionTemplateDecl>(D);
413 if (!FTD)
414 continue;
415 const FunctionDecl *FD = FTD->getTemplatedDecl();
416
417 OverloadedOperatorKind OOK = FD->getOverloadedOperator();
418 if (OOK != OverloadedOperatorKind::OO_Call)
419 continue;
420
421 if (FD->getNumParams() > NumArgs)
422 continue;
423
424 Candidates.push_back(FD);
425 }
426
427 return Candidates;
428}
429
430static bool isFixitSupported(const CallableInfo &Callee,
431 ArrayRef<BindArgument> Args) {
432 // Do not attempt to create fixits for nested std::bind or std::ref.
433 // Supporting nested std::bind will be more difficult due to placeholder
434 // sharing between outer and inner std::bind invocations, and std::ref
435 // requires us to capture some parameters by reference instead of by value.
436 if (any_of(Range&: Args, P: [](const BindArgument &B) {
437 return isCallExprNamed(E: B.E, Name: "boost::bind") ||
438 isCallExprNamed(E: B.E, Name: "std::bind");
439 })) {
440 return false;
441 }
442
443 // Do not attempt to create fixits when placeholders are reused.
444 // Unused placeholders are supported by requiring C++14 generic lambdas.
445 // FIXME: Support this case by deducing the common type.
446 if (isPlaceHolderIndexRepeated(Args))
447 return false;
448
449 // If we can't determine the Decl being used, don't offer a fixit.
450 if (!Callee.Decl)
451 return false;
452
453 if (Callee.Type == CT_Other || Callee.Materialization == CMK_Other)
454 return false;
455
456 return true;
457}
458
459const FunctionDecl *getCallOperator(const CXXRecordDecl *Callable,
460 size_t NumArgs) {
461 std::vector<const FunctionDecl *> Candidates =
462 findCandidateCallOperators(RecordDecl: Callable, NumArgs);
463 if (Candidates.size() != 1)
464 return nullptr;
465
466 return Candidates.front();
467}
468
469const FunctionDecl *
470getCallMethodDecl(const MatchFinder::MatchResult &Result, CallableType Type,
471 CallableMaterializationKind Materialization) {
472
473 const Expr *Callee = Result.Nodes.getNodeAs<Expr>(ID: "ref");
474 const Expr *CallExpression = ignoreTemporariesAndPointers(E: Callee);
475
476 if (Type == CT_Object) {
477 const auto *BindCall = Result.Nodes.getNodeAs<CallExpr>(ID: "bind");
478 size_t NumArgs = BindCall->getNumArgs() - 1;
479 return getCallOperator(Callable: Callee->getType()->getAsCXXRecordDecl(), NumArgs);
480 }
481
482 if (Materialization == CMK_Function) {
483 if (const auto *DRE = dyn_cast<DeclRefExpr>(Val: CallExpression))
484 return dyn_cast<FunctionDecl>(Val: DRE->getDecl());
485 }
486
487 // Maybe this is an indirect call through a function pointer or something
488 // where we can't determine the exact decl.
489 return nullptr;
490}
491
492static CallableType getCallableType(const MatchFinder::MatchResult &Result) {
493 const auto *CallableExpr = Result.Nodes.getNodeAs<Expr>(ID: "ref");
494
495 QualType QT = CallableExpr->getType();
496 if (QT->isMemberFunctionPointerType())
497 return CT_MemberFunction;
498
499 if (QT->isFunctionPointerType() || QT->isFunctionReferenceType() ||
500 QT->isFunctionType())
501 return CT_Function;
502
503 if (QT->isRecordType()) {
504 const CXXRecordDecl *Decl = QT->getAsCXXRecordDecl();
505 if (!Decl)
506 return CT_Other;
507
508 return CT_Object;
509 }
510
511 return CT_Other;
512}
513
514static CallableMaterializationKind
515getCallableMaterialization(const MatchFinder::MatchResult &Result) {
516 const auto *CallableExpr = Result.Nodes.getNodeAs<Expr>(ID: "ref");
517
518 const auto *NoTemporaries = ignoreTemporariesAndPointers(E: CallableExpr);
519
520 const auto *CE = dyn_cast<CXXConstructExpr>(Val: NoTemporaries);
521 const auto *FC = dyn_cast<CXXFunctionalCastExpr>(Val: NoTemporaries);
522 if ((isa<CallExpr>(Val: NoTemporaries)) || (CE && (CE->getNumArgs() > 0)) ||
523 (FC && (FC->getCastKind() == CK_ConstructorConversion)))
524 // CE is something that looks like a call, with arguments - either
525 // a function call or a constructor invocation.
526 return CMK_CallExpression;
527
528 if (isa<CXXFunctionalCastExpr>(Val: NoTemporaries) || CE)
529 return CMK_Function;
530
531 if (const auto *DRE = dyn_cast<DeclRefExpr>(Val: NoTemporaries)) {
532 if (isa<FunctionDecl>(Val: DRE->getDecl()))
533 return CMK_Function;
534 if (isa<VarDecl>(Val: DRE->getDecl()))
535 return CMK_VariableRef;
536 }
537
538 return CMK_Other;
539}
540
541static LambdaProperties
542getLambdaProperties(const MatchFinder::MatchResult &Result) {
543 const auto *CalleeExpr = Result.Nodes.getNodeAs<Expr>(ID: "ref");
544
545 LambdaProperties LP;
546
547 const auto *Bind = Result.Nodes.getNodeAs<CallExpr>(ID: "bind");
548 const auto *Decl = cast<FunctionDecl>(Val: Bind->getCalleeDecl());
549 const auto *NS = cast<NamespaceDecl>(Decl->getEnclosingNamespaceContext());
550 while (NS->isInlineNamespace())
551 NS = cast<NamespaceDecl>(NS->getDeclContext());
552 LP.BindNamespace = NS->getName();
553
554 LP.Callable.Type = getCallableType(Result);
555 LP.Callable.Materialization = getCallableMaterialization(Result);
556 LP.Callable.Decl =
557 getCallMethodDecl(Result, Type: LP.Callable.Type, Materialization: LP.Callable.Materialization);
558 if (LP.Callable.Decl)
559 if (const Type *ReturnType =
560 LP.Callable.Decl->getReturnType().getCanonicalType().getTypePtr())
561 LP.Callable.DoesReturn = !ReturnType->isVoidType();
562 LP.Callable.SourceTokens = getSourceTextForExpr(Result, E: CalleeExpr);
563 if (LP.Callable.Materialization == CMK_VariableRef) {
564 LP.Callable.CE = CE_Var;
565 LP.Callable.CM = CM_ByValue;
566 LP.Callable.UsageIdentifier =
567 std::string(getSourceTextForExpr(Result, E: CalleeExpr));
568 LP.Callable.CaptureIdentifier = std::string(
569 getSourceTextForExpr(Result, E: ignoreTemporariesAndPointers(E: CalleeExpr)));
570 } else if (LP.Callable.Materialization == CMK_CallExpression) {
571 LP.Callable.CE = CE_InitExpression;
572 LP.Callable.CM = CM_ByValue;
573 LP.Callable.UsageIdentifier = "Func";
574 LP.Callable.CaptureIdentifier = "Func";
575 LP.Callable.CaptureInitializer = getSourceTextForExpr(Result, E: CalleeExpr);
576 }
577
578 LP.BindArguments = buildBindArguments(Result, Callable: LP.Callable);
579
580 LP.IsFixitSupported = isFixitSupported(Callee: LP.Callable, Args: LP.BindArguments);
581
582 return LP;
583}
584
585static bool emitCapture(llvm::StringSet<> &CaptureSet, StringRef Delimiter,
586 CaptureMode CM, CaptureExpr CE, StringRef Identifier,
587 StringRef InitExpression, raw_ostream &Stream) {
588 if (CM == CM_None)
589 return false;
590
591 // This capture has already been emitted.
592 if (CaptureSet.count(Key: Identifier) != 0)
593 return false;
594
595 Stream << Delimiter;
596
597 if (CM == CM_ByRef)
598 Stream << "&";
599 Stream << Identifier;
600 if (CE == CE_InitExpression)
601 Stream << " = " << InitExpression;
602
603 CaptureSet.insert(key: Identifier);
604 return true;
605}
606
607static void emitCaptureList(const LambdaProperties &LP,
608 const MatchFinder::MatchResult &Result,
609 raw_ostream &Stream) {
610 llvm::StringSet<> CaptureSet;
611 bool AnyCapturesEmitted = false;
612
613 AnyCapturesEmitted = emitCapture(
614 CaptureSet, Delimiter: "", CM: LP.Callable.CM, CE: LP.Callable.CE,
615 Identifier: LP.Callable.CaptureIdentifier, InitExpression: LP.Callable.CaptureInitializer, Stream);
616
617 for (const BindArgument &B : LP.BindArguments) {
618 if (B.CM == CM_None || !B.IsUsed)
619 continue;
620
621 StringRef Delimiter = AnyCapturesEmitted ? ", " : "";
622
623 if (emitCapture(CaptureSet, Delimiter, CM: B.CM, CE: B.CE, Identifier: B.CaptureIdentifier,
624 InitExpression: B.SourceTokens, Stream))
625 AnyCapturesEmitted = true;
626 }
627}
628
629static ArrayRef<BindArgument>
630getForwardedArgumentList(const LambdaProperties &P) {
631 ArrayRef<BindArgument> Args = ArrayRef(P.BindArguments);
632 if (P.Callable.Type != CT_MemberFunction)
633 return Args;
634
635 return Args.drop_front();
636}
637AvoidBindCheck::AvoidBindCheck(StringRef Name, ClangTidyContext *Context)
638 : ClangTidyCheck(Name, Context),
639 PermissiveParameterList(Options.get(LocalName: "PermissiveParameterList", Default: false)) {}
640
641void AvoidBindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
642 Options.store(Options&: Opts, LocalName: "PermissiveParameterList", Value: PermissiveParameterList);
643}
644
645void AvoidBindCheck::registerMatchers(MatchFinder *Finder) {
646 Finder->addMatcher(
647 NodeMatch: callExpr(
648 callee(InnerMatcher: namedDecl(hasAnyName("::boost::bind", "::std::bind"))),
649 hasArgument(
650 N: 0, InnerMatcher: anyOf(expr(hasType(InnerMatcher: memberPointerType())).bind(ID: "ref"),
651 expr(hasParent(materializeTemporaryExpr().bind(ID: "ref"))),
652 expr().bind(ID: "ref"))))
653 .bind(ID: "bind"),
654 Action: this);
655}
656
657void AvoidBindCheck::check(const MatchFinder::MatchResult &Result) {
658 const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>(ID: "bind");
659
660 LambdaProperties LP = getLambdaProperties(Result);
661 auto Diag =
662 diag(Loc: MatchedDecl->getBeginLoc(),
663 Description: formatv(Fmt: "prefer a lambda to {0}::bind", Vals&: LP.BindNamespace).str());
664 if (!LP.IsFixitSupported)
665 return;
666
667 const auto *Ref = Result.Nodes.getNodeAs<Expr>(ID: "ref");
668
669 std::string Buffer;
670 llvm::raw_string_ostream Stream(Buffer);
671
672 Stream << "[";
673 emitCaptureList(LP, Result, Stream);
674 Stream << "]";
675
676 ArrayRef<BindArgument> FunctionCallArgs = ArrayRef(LP.BindArguments);
677
678 addPlaceholderArgs(LP, Stream, PermissiveParameterList);
679
680 Stream << " { ";
681
682 if (LP.Callable.DoesReturn) {
683 Stream << "return ";
684 }
685
686 if (LP.Callable.Type == CT_Function) {
687 StringRef SourceTokens = LP.Callable.SourceTokens;
688 SourceTokens.consume_front(Prefix: "&");
689 Stream << SourceTokens;
690 } else if (LP.Callable.Type == CT_MemberFunction) {
691 const auto *MethodDecl = dyn_cast<CXXMethodDecl>(Val: LP.Callable.Decl);
692 const BindArgument &ObjPtr = FunctionCallArgs.front();
693
694 if (MethodDecl->getOverloadedOperator() == OO_Call) {
695 Stream << "(*" << ObjPtr.UsageIdentifier << ')';
696 } else {
697 if (!isa<CXXThisExpr>(Val: ignoreTemporariesAndPointers(E: ObjPtr.E))) {
698 Stream << ObjPtr.UsageIdentifier;
699 Stream << "->";
700 }
701 Stream << MethodDecl->getNameAsString();
702 }
703 } else {
704 switch (LP.Callable.CE) {
705 case CE_Var:
706 if (LP.Callable.UsageIdentifier != LP.Callable.CaptureIdentifier) {
707 Stream << "(" << LP.Callable.UsageIdentifier << ")";
708 break;
709 }
710 [[fallthrough]];
711 case CE_InitExpression:
712 Stream << LP.Callable.UsageIdentifier;
713 break;
714 default:
715 Stream << getSourceTextForExpr(Result, E: Ref);
716 }
717 }
718
719 Stream << "(";
720
721 addFunctionCallArgs(Args: getForwardedArgumentList(P: LP), Stream);
722 Stream << "); }";
723
724 Diag << FixItHint::CreateReplacement(MatchedDecl->getSourceRange(),
725 Stream.str());
726}
727
728} // namespace clang::tidy::modernize
729

source code of clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.cpp