forked from OSchip/llvm-project
724 lines
23 KiB
C++
724 lines
23 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/STLExtras.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/ADT/StringSet.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "llvm/Support/FormatVariadic.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 };
|
|
enum CaptureMode { CM_None, CM_ByRef, CM_ByValue };
|
|
enum CaptureExpr { CE_None, CE_Var, CE_InitExpression };
|
|
|
|
enum CallableType {
|
|
CT_Other, // unknown
|
|
CT_Function, // global or static function
|
|
CT_MemberFunction, // member function with implicit this
|
|
CT_Object, // object with operator()
|
|
};
|
|
|
|
enum CallableMaterializationKind {
|
|
CMK_Other, // unknown
|
|
CMK_Function, // callable is the name of a member or non-member function.
|
|
CMK_VariableRef, // callable is a simple expression involving a global or
|
|
// local variable.
|
|
CMK_CallExpression, // callable is obtained as the result of a call expression
|
|
};
|
|
|
|
struct BindArgument {
|
|
// A rough classification of the type of expression this argument was.
|
|
BindArgumentKind Kind = BK_Other;
|
|
|
|
// If this argument required a capture, a value indicating how it was
|
|
// captured.
|
|
CaptureMode CM = CM_None;
|
|
|
|
// Whether the argument is a simple variable (we can capture it directly),
|
|
// or an expression (we must introduce a capture variable).
|
|
CaptureExpr CE = CE_None;
|
|
|
|
// The exact spelling of this argument in the source code.
|
|
StringRef SourceTokens;
|
|
|
|
// The identifier of the variable within the capture list. This may be
|
|
// different from UsageIdentifier for example in the expression *d, where the
|
|
// variable is captured as d, but referred to as *d.
|
|
std::string CaptureIdentifier;
|
|
|
|
// If this is a placeholder or capture init expression, contains the tokens
|
|
// used to refer to this parameter from within the body of the lambda.
|
|
std::string UsageIdentifier;
|
|
|
|
// If Kind == BK_Placeholder, the index of the placeholder.
|
|
size_t PlaceHolderIndex = 0;
|
|
|
|
// True if the argument is used inside the lambda, false otherwise.
|
|
bool IsUsed = false;
|
|
|
|
// The actual Expr object representing this expression.
|
|
const Expr *E = nullptr;
|
|
};
|
|
|
|
struct CallableInfo {
|
|
CallableType Type = CT_Other;
|
|
CallableMaterializationKind Materialization = CMK_Other;
|
|
CaptureMode CM = CM_None;
|
|
CaptureExpr CE = CE_None;
|
|
StringRef SourceTokens;
|
|
std::string CaptureIdentifier;
|
|
std::string UsageIdentifier;
|
|
StringRef CaptureInitializer;
|
|
const FunctionDecl *Decl = nullptr;
|
|
};
|
|
|
|
struct LambdaProperties {
|
|
CallableInfo Callable;
|
|
SmallVector<BindArgument, 4> BindArguments;
|
|
StringRef BindNamespace;
|
|
bool IsFixitSupported = false;
|
|
};
|
|
|
|
} // end namespace
|
|
|
|
static bool tryCaptureAsLocalVariable(const MatchFinder::MatchResult &Result,
|
|
BindArgument &B, const Expr *E);
|
|
|
|
static bool tryCaptureAsMemberVariable(const MatchFinder::MatchResult &Result,
|
|
BindArgument &B, const Expr *E);
|
|
|
|
static const Expr *ignoreTemporariesAndPointers(const Expr *E) {
|
|
if (const auto *T = dyn_cast<UnaryOperator>(E))
|
|
return ignoreTemporariesAndPointers(T->getSubExpr());
|
|
|
|
const Expr *F = E->IgnoreImplicit();
|
|
if (E != F)
|
|
return ignoreTemporariesAndPointers(F);
|
|
|
|
return E;
|
|
}
|
|
|
|
static const Expr *ignoreTemporariesAndConstructors(const Expr *E) {
|
|
if (const auto *T = dyn_cast<CXXConstructExpr>(E))
|
|
return ignoreTemporariesAndConstructors(T->getArg(0));
|
|
|
|
const Expr *F = E->IgnoreImplicit();
|
|
if (E != F)
|
|
return ignoreTemporariesAndPointers(F);
|
|
|
|
return E;
|
|
}
|
|
|
|
static StringRef getSourceTextForExpr(const MatchFinder::MatchResult &Result,
|
|
const Expr *E) {
|
|
return Lexer::getSourceText(
|
|
CharSourceRange::getTokenRange(E->getBeginLoc(), E->getEndLoc()),
|
|
*Result.SourceManager, Result.Context->getLangOpts());
|
|
}
|
|
|
|
static bool isCallExprNamed(const Expr *E, StringRef Name) {
|
|
const auto *CE = dyn_cast<CallExpr>(E->IgnoreImplicit());
|
|
if (!CE)
|
|
return false;
|
|
const auto *ND = dyn_cast<NamedDecl>(CE->getCalleeDecl());
|
|
if (!ND)
|
|
return false;
|
|
return ND->getQualifiedNameAsString() == Name;
|
|
}
|
|
|
|
static void
|
|
initializeBindArgumentForCallExpr(const MatchFinder::MatchResult &Result,
|
|
BindArgument &B, const CallExpr *CE,
|
|
unsigned &CaptureIndex) {
|
|
// std::ref(x) means to capture x by reference.
|
|
if (isCallExprNamed(CE, "boost::ref") || isCallExprNamed(CE, "std::ref")) {
|
|
B.Kind = BK_Other;
|
|
if (tryCaptureAsLocalVariable(Result, B, CE->getArg(0)) ||
|
|
tryCaptureAsMemberVariable(Result, B, CE->getArg(0))) {
|
|
B.CE = CE_Var;
|
|
} else {
|
|
// The argument to std::ref is an expression that produces a reference.
|
|
// Create a capture reference to hold it.
|
|
B.CE = CE_InitExpression;
|
|
B.UsageIdentifier = "capture" + llvm::utostr(CaptureIndex++);
|
|
}
|
|
// Strip off the reference wrapper.
|
|
B.SourceTokens = getSourceTextForExpr(Result, CE->getArg(0));
|
|
B.CM = CM_ByRef;
|
|
} else {
|
|
B.Kind = BK_CallExpr;
|
|
B.CM = CM_ByValue;
|
|
B.CE = CE_InitExpression;
|
|
B.UsageIdentifier = "capture" + llvm::utostr(CaptureIndex++);
|
|
}
|
|
B.CaptureIdentifier = B.UsageIdentifier;
|
|
}
|
|
|
|
static bool anyDescendantIsLocal(const Stmt *Statement) {
|
|
if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Statement)) {
|
|
const ValueDecl *Decl = DeclRef->getDecl();
|
|
if (const auto *Var = dyn_cast_or_null<VarDecl>(Decl)) {
|
|
if (Var->isLocalVarDeclOrParm())
|
|
return true;
|
|
}
|
|
} else if (isa<CXXThisExpr>(Statement))
|
|
return true;
|
|
|
|
return any_of(Statement->children(), anyDescendantIsLocal);
|
|
}
|
|
|
|
static bool tryCaptureAsLocalVariable(const MatchFinder::MatchResult &Result,
|
|
BindArgument &B, const Expr *E) {
|
|
if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E)) {
|
|
if (const auto *CE = dyn_cast<CXXConstructExpr>(BTE->getSubExpr()))
|
|
return tryCaptureAsLocalVariable(Result, B, CE->getArg(0));
|
|
return false;
|
|
}
|
|
|
|
const auto *DRE = dyn_cast<DeclRefExpr>(E->IgnoreImplicit());
|
|
if (!DRE)
|
|
return false;
|
|
|
|
const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
|
|
if (!VD || !VD->isLocalVarDeclOrParm())
|
|
return false;
|
|
|
|
B.CM = CM_ByValue;
|
|
B.UsageIdentifier = std::string(getSourceTextForExpr(Result, E));
|
|
B.CaptureIdentifier = B.UsageIdentifier;
|
|
return true;
|
|
}
|
|
|
|
static bool tryCaptureAsMemberVariable(const MatchFinder::MatchResult &Result,
|
|
BindArgument &B, const Expr *E) {
|
|
if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E)) {
|
|
if (const auto *CE = dyn_cast<CXXConstructExpr>(BTE->getSubExpr()))
|
|
return tryCaptureAsMemberVariable(Result, B, CE->getArg(0));
|
|
return false;
|
|
}
|
|
|
|
E = E->IgnoreImplicit();
|
|
if (isa<CXXThisExpr>(E)) {
|
|
// E is a direct use of "this".
|
|
B.CM = CM_ByValue;
|
|
B.UsageIdentifier = std::string(getSourceTextForExpr(Result, E));
|
|
B.CaptureIdentifier = "this";
|
|
return true;
|
|
}
|
|
|
|
const auto *ME = dyn_cast<MemberExpr>(E);
|
|
if (!ME)
|
|
return false;
|
|
|
|
if (!ME->isLValue() || !isa<FieldDecl>(ME->getMemberDecl()))
|
|
return false;
|
|
|
|
if (isa<CXXThisExpr>(ME->getBase())) {
|
|
// E refers to a data member without an explicit "this".
|
|
B.CM = CM_ByValue;
|
|
B.UsageIdentifier = std::string(getSourceTextForExpr(Result, E));
|
|
B.CaptureIdentifier = "this";
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static SmallVector<BindArgument, 4>
|
|
buildBindArguments(const MatchFinder::MatchResult &Result,
|
|
const CallableInfo &Callable) {
|
|
SmallVector<BindArgument, 4> BindArguments;
|
|
static llvm::Regex MatchPlaceholder("^_([0-9]+)$");
|
|
|
|
const auto *BindCall = Result.Nodes.getNodeAs<CallExpr>("bind");
|
|
|
|
// Start at index 1 as first argument to bind is the function name.
|
|
unsigned CaptureIndex = 0;
|
|
for (size_t I = 1, ArgCount = BindCall->getNumArgs(); I < ArgCount; ++I) {
|
|
|
|
const Expr *E = BindCall->getArg(I);
|
|
BindArgument &B = BindArguments.emplace_back();
|
|
|
|
size_t ArgIndex = I - 1;
|
|
if (Callable.Type == CT_MemberFunction)
|
|
--ArgIndex;
|
|
|
|
bool IsObjectPtr = (I == 1 && Callable.Type == CT_MemberFunction);
|
|
B.E = E;
|
|
B.SourceTokens = getSourceTextForExpr(Result, E);
|
|
|
|
if (!Callable.Decl || ArgIndex < Callable.Decl->getNumParams() ||
|
|
IsObjectPtr)
|
|
B.IsUsed = true;
|
|
|
|
SmallVector<StringRef, 2> Matches;
|
|
const auto *DRE = dyn_cast<DeclRefExpr>(E);
|
|
if (MatchPlaceholder.match(B.SourceTokens, &Matches) ||
|
|
// Check for match with qualifiers removed.
|
|
(DRE && MatchPlaceholder.match(DRE->getDecl()->getName(), &Matches))) {
|
|
B.Kind = BK_Placeholder;
|
|
B.PlaceHolderIndex = std::stoi(std::string(Matches[1]));
|
|
B.UsageIdentifier = "PH" + llvm::utostr(B.PlaceHolderIndex);
|
|
B.CaptureIdentifier = B.UsageIdentifier;
|
|
continue;
|
|
}
|
|
|
|
if (const auto *CE =
|
|
dyn_cast<CallExpr>(ignoreTemporariesAndConstructors(E))) {
|
|
initializeBindArgumentForCallExpr(Result, B, CE, CaptureIndex);
|
|
continue;
|
|
}
|
|
|
|
if (tryCaptureAsLocalVariable(Result, B, B.E) ||
|
|
tryCaptureAsMemberVariable(Result, B, B.E))
|
|
continue;
|
|
|
|
// If it's not something we recognize, capture it by init expression to be
|
|
// safe.
|
|
B.Kind = BK_Other;
|
|
if (IsObjectPtr) {
|
|
B.CE = CE_InitExpression;
|
|
B.CM = CM_ByValue;
|
|
B.UsageIdentifier = "ObjectPtr";
|
|
B.CaptureIdentifier = B.UsageIdentifier;
|
|
} else if (anyDescendantIsLocal(B.E)) {
|
|
B.CE = CE_InitExpression;
|
|
B.CM = CM_ByValue;
|
|
B.CaptureIdentifier = "capture" + llvm::utostr(CaptureIndex++);
|
|
B.UsageIdentifier = B.CaptureIdentifier;
|
|
}
|
|
}
|
|
return BindArguments;
|
|
}
|
|
|
|
static int findPositionOfPlaceholderUse(ArrayRef<BindArgument> Args,
|
|
size_t PlaceholderIndex) {
|
|
for (size_t I = 0; I < Args.size(); ++I)
|
|
if (Args[I].PlaceHolderIndex == PlaceholderIndex)
|
|
return I;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void addPlaceholderArgs(const LambdaProperties &LP,
|
|
llvm::raw_ostream &Stream,
|
|
bool PermissiveParameterList) {
|
|
|
|
ArrayRef<BindArgument> Args = LP.BindArguments;
|
|
|
|
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 (!PermissiveParameterList && (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 &&";
|
|
|
|
int ArgIndex = findPositionOfPlaceholderUse(Args, I);
|
|
|
|
if (ArgIndex != -1 && Args[ArgIndex].IsUsed)
|
|
Stream << " " << Args[ArgIndex].UsageIdentifier;
|
|
Delimiter = ", ";
|
|
}
|
|
if (PermissiveParameterList)
|
|
Stream << Delimiter << "auto && ...";
|
|
Stream << ")";
|
|
}
|
|
|
|
static void addFunctionCallArgs(ArrayRef<BindArgument> Args,
|
|
llvm::raw_ostream &Stream) {
|
|
StringRef Delimiter = "";
|
|
|
|
for (int I = 0, Size = Args.size(); I < Size; ++I) {
|
|
const BindArgument &B = Args[I];
|
|
|
|
Stream << Delimiter;
|
|
|
|
if (B.Kind == BK_Placeholder) {
|
|
Stream << "std::forward<decltype(" << B.UsageIdentifier << ")>";
|
|
Stream << "(" << B.UsageIdentifier << ")";
|
|
} else if (B.CM != CM_None)
|
|
Stream << B.UsageIdentifier;
|
|
else
|
|
Stream << B.SourceTokens;
|
|
|
|
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;
|
|
}
|
|
|
|
static std::vector<const FunctionDecl *>
|
|
findCandidateCallOperators(const CXXRecordDecl *RecordDecl, size_t NumArgs) {
|
|
std::vector<const FunctionDecl *> Candidates;
|
|
|
|
for (const clang::CXXMethodDecl *Method : RecordDecl->methods()) {
|
|
OverloadedOperatorKind OOK = Method->getOverloadedOperator();
|
|
|
|
if (OOK != OverloadedOperatorKind::OO_Call)
|
|
continue;
|
|
|
|
if (Method->getNumParams() > NumArgs)
|
|
continue;
|
|
|
|
Candidates.push_back(Method);
|
|
}
|
|
|
|
// Find templated operator(), if any.
|
|
for (const clang::Decl *D : RecordDecl->decls()) {
|
|
const auto *FTD = dyn_cast<FunctionTemplateDecl>(D);
|
|
if (!FTD)
|
|
continue;
|
|
const FunctionDecl *FD = FTD->getTemplatedDecl();
|
|
|
|
OverloadedOperatorKind OOK = FD->getOverloadedOperator();
|
|
if (OOK != OverloadedOperatorKind::OO_Call)
|
|
continue;
|
|
|
|
if (FD->getNumParams() > NumArgs)
|
|
continue;
|
|
|
|
Candidates.push_back(FD);
|
|
}
|
|
|
|
return Candidates;
|
|
}
|
|
|
|
static bool isFixitSupported(const CallableInfo &Callee,
|
|
ArrayRef<BindArgument> Args) {
|
|
// Do not attempt to create fixits for nested std::bind or std::ref.
|
|
// Supporting nested std::bind will be more difficult due to placeholder
|
|
// sharing between outer and inner std::bind invocations, and std::ref
|
|
// requires us to capture some parameters by reference instead of by value.
|
|
if (any_of(Args, [](const BindArgument &B) {
|
|
return isCallExprNamed(B.E, "boost::bind") ||
|
|
isCallExprNamed(B.E, "std::bind");
|
|
})) {
|
|
return false;
|
|
}
|
|
|
|
// 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 false;
|
|
|
|
// If we can't determine the Decl being used, don't offer a fixit.
|
|
if (!Callee.Decl)
|
|
return false;
|
|
|
|
if (Callee.Type == CT_Other || Callee.Materialization == CMK_Other)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
const FunctionDecl *getCallOperator(const CXXRecordDecl *Callable,
|
|
size_t NumArgs) {
|
|
std::vector<const FunctionDecl *> Candidates =
|
|
findCandidateCallOperators(Callable, NumArgs);
|
|
if (Candidates.size() != 1)
|
|
return nullptr;
|
|
|
|
return Candidates.front();
|
|
}
|
|
|
|
const FunctionDecl *
|
|
getCallMethodDecl(const MatchFinder::MatchResult &Result, CallableType Type,
|
|
CallableMaterializationKind Materialization) {
|
|
|
|
const Expr *Callee = Result.Nodes.getNodeAs<Expr>("ref");
|
|
const Expr *CallExpression = ignoreTemporariesAndPointers(Callee);
|
|
|
|
if (Type == CT_Object) {
|
|
const auto *BindCall = Result.Nodes.getNodeAs<CallExpr>("bind");
|
|
size_t NumArgs = BindCall->getNumArgs() - 1;
|
|
return getCallOperator(Callee->getType()->getAsCXXRecordDecl(), NumArgs);
|
|
}
|
|
|
|
if (Materialization == CMK_Function) {
|
|
if (const auto *DRE = dyn_cast<DeclRefExpr>(CallExpression))
|
|
return dyn_cast<FunctionDecl>(DRE->getDecl());
|
|
}
|
|
|
|
// Maybe this is an indirect call through a function pointer or something
|
|
// where we can't determine the exact decl.
|
|
return nullptr;
|
|
}
|
|
|
|
static CallableType getCallableType(const MatchFinder::MatchResult &Result) {
|
|
const auto *CallableExpr = Result.Nodes.getNodeAs<Expr>("ref");
|
|
|
|
QualType QT = CallableExpr->getType();
|
|
if (QT->isMemberFunctionPointerType())
|
|
return CT_MemberFunction;
|
|
|
|
if (QT->isFunctionPointerType() || QT->isFunctionReferenceType() ||
|
|
QT->isFunctionType())
|
|
return CT_Function;
|
|
|
|
if (QT->isRecordType()) {
|
|
const CXXRecordDecl *Decl = QT->getAsCXXRecordDecl();
|
|
if (!Decl)
|
|
return CT_Other;
|
|
|
|
return CT_Object;
|
|
}
|
|
|
|
return CT_Other;
|
|
}
|
|
|
|
static CallableMaterializationKind
|
|
getCallableMaterialization(const MatchFinder::MatchResult &Result) {
|
|
const auto *CallableExpr = Result.Nodes.getNodeAs<Expr>("ref");
|
|
|
|
const auto *NoTemporaries = ignoreTemporariesAndPointers(CallableExpr);
|
|
|
|
const auto *CE = dyn_cast<CXXConstructExpr>(NoTemporaries);
|
|
const auto *FC = dyn_cast<CXXFunctionalCastExpr>(NoTemporaries);
|
|
if ((isa<CallExpr>(NoTemporaries)) || (CE && (CE->getNumArgs() > 0)) ||
|
|
(FC && (FC->getCastKind() == CK_ConstructorConversion)))
|
|
// CE is something that looks like a call, with arguments - either
|
|
// a function call or a constructor invocation.
|
|
return CMK_CallExpression;
|
|
|
|
if (isa<CXXFunctionalCastExpr>(NoTemporaries) || CE)
|
|
return CMK_Function;
|
|
|
|
if (const auto *DRE = dyn_cast<DeclRefExpr>(NoTemporaries)) {
|
|
if (isa<FunctionDecl>(DRE->getDecl()))
|
|
return CMK_Function;
|
|
if (isa<VarDecl>(DRE->getDecl()))
|
|
return CMK_VariableRef;
|
|
}
|
|
|
|
return CMK_Other;
|
|
}
|
|
|
|
static LambdaProperties
|
|
getLambdaProperties(const MatchFinder::MatchResult &Result) {
|
|
const auto *CalleeExpr = Result.Nodes.getNodeAs<Expr>("ref");
|
|
|
|
LambdaProperties LP;
|
|
|
|
const auto *Bind = Result.Nodes.getNodeAs<CallExpr>("bind");
|
|
const auto *Decl = dyn_cast<FunctionDecl>(Bind->getCalleeDecl());
|
|
const auto *NS =
|
|
dyn_cast<NamespaceDecl>(Decl->getEnclosingNamespaceContext());
|
|
while (NS->isInlineNamespace())
|
|
NS = dyn_cast<NamespaceDecl>(NS->getDeclContext());
|
|
LP.BindNamespace = NS->getName();
|
|
|
|
LP.Callable.Type = getCallableType(Result);
|
|
LP.Callable.Materialization = getCallableMaterialization(Result);
|
|
LP.Callable.Decl =
|
|
getCallMethodDecl(Result, LP.Callable.Type, LP.Callable.Materialization);
|
|
LP.Callable.SourceTokens = getSourceTextForExpr(Result, CalleeExpr);
|
|
if (LP.Callable.Materialization == CMK_VariableRef) {
|
|
LP.Callable.CE = CE_Var;
|
|
LP.Callable.CM = CM_ByValue;
|
|
LP.Callable.UsageIdentifier =
|
|
std::string(getSourceTextForExpr(Result, CalleeExpr));
|
|
LP.Callable.CaptureIdentifier = std::string(
|
|
getSourceTextForExpr(Result, ignoreTemporariesAndPointers(CalleeExpr)));
|
|
} else if (LP.Callable.Materialization == CMK_CallExpression) {
|
|
LP.Callable.CE = CE_InitExpression;
|
|
LP.Callable.CM = CM_ByValue;
|
|
LP.Callable.UsageIdentifier = "Func";
|
|
LP.Callable.CaptureIdentifier = "Func";
|
|
LP.Callable.CaptureInitializer = getSourceTextForExpr(Result, CalleeExpr);
|
|
}
|
|
|
|
LP.BindArguments = buildBindArguments(Result, LP.Callable);
|
|
|
|
LP.IsFixitSupported = isFixitSupported(LP.Callable, LP.BindArguments);
|
|
|
|
return LP;
|
|
}
|
|
|
|
static bool emitCapture(llvm::StringSet<> &CaptureSet, StringRef Delimiter,
|
|
CaptureMode CM, CaptureExpr CE, StringRef Identifier,
|
|
StringRef InitExpression, raw_ostream &Stream) {
|
|
if (CM == CM_None)
|
|
return false;
|
|
|
|
// This capture has already been emitted.
|
|
if (CaptureSet.count(Identifier) != 0)
|
|
return false;
|
|
|
|
Stream << Delimiter;
|
|
|
|
if (CM == CM_ByRef)
|
|
Stream << "&";
|
|
Stream << Identifier;
|
|
if (CE == CE_InitExpression)
|
|
Stream << " = " << InitExpression;
|
|
|
|
CaptureSet.insert(Identifier);
|
|
return true;
|
|
}
|
|
|
|
static void emitCaptureList(const LambdaProperties &LP,
|
|
const MatchFinder::MatchResult &Result,
|
|
raw_ostream &Stream) {
|
|
llvm::StringSet<> CaptureSet;
|
|
bool AnyCapturesEmitted = false;
|
|
|
|
AnyCapturesEmitted = emitCapture(
|
|
CaptureSet, "", LP.Callable.CM, LP.Callable.CE,
|
|
LP.Callable.CaptureIdentifier, LP.Callable.CaptureInitializer, Stream);
|
|
|
|
for (const BindArgument &B : LP.BindArguments) {
|
|
if (B.CM == CM_None || !B.IsUsed)
|
|
continue;
|
|
|
|
StringRef Delimiter = AnyCapturesEmitted ? ", " : "";
|
|
|
|
if (emitCapture(CaptureSet, Delimiter, B.CM, B.CE, B.CaptureIdentifier,
|
|
B.SourceTokens, Stream))
|
|
AnyCapturesEmitted = true;
|
|
}
|
|
}
|
|
|
|
static ArrayRef<BindArgument>
|
|
getForwardedArgumentList(const LambdaProperties &P) {
|
|
ArrayRef<BindArgument> Args = makeArrayRef(P.BindArguments);
|
|
if (P.Callable.Type != CT_MemberFunction)
|
|
return Args;
|
|
|
|
return Args.drop_front();
|
|
}
|
|
AvoidBindCheck::AvoidBindCheck(StringRef Name, ClangTidyContext *Context)
|
|
: ClangTidyCheck(Name, Context),
|
|
PermissiveParameterList(Options.get("PermissiveParameterList", false)) {}
|
|
|
|
void AvoidBindCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "PermissiveParameterList", PermissiveParameterList);
|
|
}
|
|
|
|
void AvoidBindCheck::registerMatchers(MatchFinder *Finder) {
|
|
Finder->addMatcher(
|
|
callExpr(
|
|
callee(namedDecl(hasAnyName("::boost::bind", "::std::bind"))),
|
|
hasArgument(
|
|
0, anyOf(expr(hasType(memberPointerType())).bind("ref"),
|
|
expr(hasParent(materializeTemporaryExpr().bind("ref"))),
|
|
expr().bind("ref"))))
|
|
.bind("bind"),
|
|
this);
|
|
}
|
|
|
|
void AvoidBindCheck::check(const MatchFinder::MatchResult &Result) {
|
|
const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("bind");
|
|
|
|
LambdaProperties LP = getLambdaProperties(Result);
|
|
auto Diag =
|
|
diag(MatchedDecl->getBeginLoc(),
|
|
formatv("prefer a lambda to {0}::bind", LP.BindNamespace).str());
|
|
if (!LP.IsFixitSupported)
|
|
return;
|
|
|
|
const auto *Ref = Result.Nodes.getNodeAs<Expr>("ref");
|
|
|
|
std::string Buffer;
|
|
llvm::raw_string_ostream Stream(Buffer);
|
|
|
|
Stream << "[";
|
|
emitCaptureList(LP, Result, Stream);
|
|
Stream << "]";
|
|
|
|
ArrayRef<BindArgument> FunctionCallArgs = makeArrayRef(LP.BindArguments);
|
|
|
|
addPlaceholderArgs(LP, Stream, PermissiveParameterList);
|
|
|
|
if (LP.Callable.Type == CT_Function) {
|
|
StringRef SourceTokens = LP.Callable.SourceTokens;
|
|
SourceTokens.consume_front("&");
|
|
Stream << " { return " << SourceTokens;
|
|
} else if (LP.Callable.Type == CT_MemberFunction) {
|
|
const auto *MethodDecl = dyn_cast<CXXMethodDecl>(LP.Callable.Decl);
|
|
const BindArgument &ObjPtr = FunctionCallArgs.front();
|
|
|
|
Stream << " { ";
|
|
if (!isa<CXXThisExpr>(ignoreTemporariesAndPointers(ObjPtr.E))) {
|
|
Stream << ObjPtr.UsageIdentifier;
|
|
Stream << "->";
|
|
}
|
|
|
|
Stream << MethodDecl->getName();
|
|
} else {
|
|
Stream << " { return ";
|
|
switch (LP.Callable.CE) {
|
|
case CE_Var:
|
|
if (LP.Callable.UsageIdentifier != LP.Callable.CaptureIdentifier) {
|
|
Stream << "(" << LP.Callable.UsageIdentifier << ")";
|
|
break;
|
|
}
|
|
LLVM_FALLTHROUGH;
|
|
case CE_InitExpression:
|
|
Stream << LP.Callable.UsageIdentifier;
|
|
break;
|
|
default:
|
|
Stream << getSourceTextForExpr(Result, Ref);
|
|
}
|
|
}
|
|
|
|
Stream << "(";
|
|
|
|
addFunctionCallArgs(getForwardedArgumentList(LP), Stream);
|
|
Stream << "); }";
|
|
|
|
Diag << FixItHint::CreateReplacement(MatchedDecl->getSourceRange(),
|
|
Stream.str());
|
|
}
|
|
|
|
} // namespace modernize
|
|
} // namespace tidy
|
|
} // namespace clang
|