| //===--- AvoidBindCheck.cpp - clang-tidy-----------------------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "AvoidBindCheck.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/Basic/LLVM.h" |
| #include "clang/Basic/LangOptions.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Lex/Lexer.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/SmallSet.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/Regex.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <algorithm> |
| #include <cstddef> |
| #include <string> |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace modernize { |
| |
| namespace { |
| |
| enum BindArgumentKind { BK_Temporary, BK_Placeholder, BK_CallExpr, BK_Other }; |
| |
| struct BindArgument { |
| StringRef Tokens; |
| BindArgumentKind Kind = BK_Other; |
| size_t PlaceHolderIndex = 0; |
| }; |
| |
| } // end namespace |
| |
| static SmallVector<BindArgument, 4> |
| buildBindArguments(const MatchFinder::MatchResult &Result, const CallExpr *C) { |
| SmallVector<BindArgument, 4> BindArguments; |
| llvm::Regex MatchPlaceholder("^_([0-9]+)$"); |
| |
| // Start at index 1 as first argument to bind is the function name. |
| for (size_t I = 1, ArgCount = C->getNumArgs(); I < ArgCount; ++I) { |
| const Expr *E = C->getArg(I); |
| BindArgument B; |
| if (const auto *M = dyn_cast<MaterializeTemporaryExpr>(E)) { |
| const auto *TE = M->GetTemporaryExpr(); |
| B.Kind = isa<CallExpr>(TE) ? BK_CallExpr : BK_Temporary; |
| } |
| |
| B.Tokens = Lexer::getSourceText( |
| CharSourceRange::getTokenRange(E->getLocStart(), E->getLocEnd()), |
| *Result.SourceManager, Result.Context->getLangOpts()); |
| |
| SmallVector<StringRef, 2> Matches; |
| if (B.Kind == BK_Other && MatchPlaceholder.match(B.Tokens, &Matches)) { |
| B.Kind = BK_Placeholder; |
| B.PlaceHolderIndex = std::stoi(Matches[1]); |
| } |
| BindArguments.push_back(B); |
| } |
| return BindArguments; |
| } |
| |
| static void addPlaceholderArgs(const ArrayRef<BindArgument> Args, |
| llvm::raw_ostream &Stream) { |
| auto MaxPlaceholderIt = |
| std::max_element(Args.begin(), Args.end(), |
| [](const BindArgument &B1, const BindArgument &B2) { |
| return B1.PlaceHolderIndex < B2.PlaceHolderIndex; |
| }); |
| |
| // Placeholders (if present) have index 1 or greater. |
| if (MaxPlaceholderIt == Args.end() || MaxPlaceholderIt->PlaceHolderIndex == 0) |
| return; |
| |
| size_t PlaceholderCount = MaxPlaceholderIt->PlaceHolderIndex; |
| Stream << "("; |
| StringRef Delimiter = ""; |
| for (size_t I = 1; I <= PlaceholderCount; ++I) { |
| Stream << Delimiter << "auto && arg" << I; |
| Delimiter = ", "; |
| } |
| Stream << ")"; |
| } |
| |
| static void addFunctionCallArgs(const ArrayRef<BindArgument> Args, |
| llvm::raw_ostream &Stream) { |
| StringRef Delimiter = ""; |
| for (const auto &B : Args) { |
| if (B.PlaceHolderIndex) |
| Stream << Delimiter << "arg" << B.PlaceHolderIndex; |
| else |
| Stream << Delimiter << B.Tokens; |
| Delimiter = ", "; |
| } |
| } |
| |
| static bool isPlaceHolderIndexRepeated(const ArrayRef<BindArgument> Args) { |
| llvm::SmallSet<size_t, 4> PlaceHolderIndices; |
| for (const BindArgument &B : Args) { |
| if (B.PlaceHolderIndex) { |
| if (!PlaceHolderIndices.insert(B.PlaceHolderIndex).second) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void AvoidBindCheck::registerMatchers(MatchFinder *Finder) { |
| if (!getLangOpts().CPlusPlus14) // Need C++14 for generic lambdas. |
| return; |
| |
| Finder->addMatcher( |
| callExpr( |
| callee(namedDecl(hasName("::std::bind"))), |
| hasArgument(0, declRefExpr(to(functionDecl().bind("f"))).bind("ref"))) |
| .bind("bind"), |
| this); |
| } |
| |
| void AvoidBindCheck::check(const MatchFinder::MatchResult &Result) { |
| const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("bind"); |
| auto Diag = diag(MatchedDecl->getLocStart(), "prefer a lambda to std::bind"); |
| |
| const auto Args = buildBindArguments(Result, MatchedDecl); |
| |
| // Do not attempt to create fixits for nested call expressions. |
| // FIXME: Create lambda capture variables to capture output of calls. |
| // NOTE: Supporting nested std::bind will be more difficult due to placeholder |
| // sharing between outer and inner std:bind invocations. |
| if (llvm::any_of(Args, |
| [](const BindArgument &B) { return B.Kind == BK_CallExpr; })) |
| return; |
| |
| // Do not attempt to create fixits when placeholders are reused. |
| // Unused placeholders are supported by requiring C++14 generic lambdas. |
| // FIXME: Support this case by deducing the common type. |
| if (isPlaceHolderIndexRepeated(Args)) |
| return; |
| |
| const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("f"); |
| |
| // std::bind can support argument count mismatch between its arguments and the |
| // bound function's arguments. Do not attempt to generate a fixit for such |
| // cases. |
| // FIXME: Support this case by creating unused lambda capture variables. |
| if (F->getNumParams() != Args.size()) |
| return; |
| |
| std::string Buffer; |
| llvm::raw_string_ostream Stream(Buffer); |
| |
| bool HasCapturedArgument = llvm::any_of( |
| Args, [](const BindArgument &B) { return B.Kind == BK_Other; }); |
| const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("ref"); |
| Stream << "[" << (HasCapturedArgument ? "=" : "") << "]"; |
| addPlaceholderArgs(Args, Stream); |
| Stream << " { return "; |
| Ref->printPretty(Stream, nullptr, Result.Context->getPrintingPolicy()); |
| Stream << "("; |
| addFunctionCallArgs(Args, Stream); |
| Stream << "); };"; |
| |
| Diag << FixItHint::CreateReplacement(MatchedDecl->getSourceRange(), |
| Stream.str()); |
| } |
| |
| } // namespace modernize |
| } // namespace tidy |
| } // namespace clang |