forked from OSchip/llvm-project
181 lines
5.9 KiB
C++
181 lines
5.9 KiB
C++
//===--- AvoidBindCheck.cpp - clang-tidy-----------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#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->getBeginLoc(), E->getEndLoc()),
|
|
*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->getBeginLoc(), "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
|