blob: 413a37f4af9faecec8b06f0cf9a438af9b360241 [file] [log] [blame]
//===--- MoveConstArgCheck.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 "MoveConstArgCheck.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
namespace clang::tidy::performance {
static void replaceCallWithArg(const CallExpr *Call, DiagnosticBuilder &Diag,
const SourceManager &SM,
const LangOptions &LangOpts) {
const Expr *Arg = Call->getArg(0);
CharSourceRange BeforeArgumentsRange = Lexer::makeFileCharRange(
CharSourceRange::getCharRange(Call->getBeginLoc(), Arg->getBeginLoc()),
SM, LangOpts);
CharSourceRange AfterArgumentsRange = Lexer::makeFileCharRange(
CharSourceRange::getCharRange(Call->getEndLoc(),
Call->getEndLoc().getLocWithOffset(1)),
SM, LangOpts);
if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) {
Diag << FixItHint::CreateRemoval(BeforeArgumentsRange)
<< FixItHint::CreateRemoval(AfterArgumentsRange);
}
}
void MoveConstArgCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "CheckTriviallyCopyableMove", CheckTriviallyCopyableMove);
Options.store(Opts, "CheckMoveToConstRef", CheckMoveToConstRef);
}
void MoveConstArgCheck::registerMatchers(MatchFinder *Finder) {
auto MoveCallMatcher =
callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1),
unless(isInTemplateInstantiation()))
.bind("call-move");
Finder->addMatcher(MoveCallMatcher, this);
auto ConstTypeParmMatcher =
qualType(references(isConstQualified())).bind("invocation-parm-type");
auto RValueTypeParmMatcher =
qualType(rValueReferenceType()).bind("invocation-parm-type");
// Matches respective ParmVarDecl for a CallExpr or CXXConstructExpr.
auto ArgumentWithParamMatcher = forEachArgumentWithParam(
MoveCallMatcher, parmVarDecl(anyOf(hasType(ConstTypeParmMatcher),
hasType(RValueTypeParmMatcher)))
.bind("invocation-parm"));
// Matches respective types of arguments for a CallExpr or CXXConstructExpr
// and it works on calls through function pointers as well.
auto ArgumentWithParamTypeMatcher = forEachArgumentWithParamType(
MoveCallMatcher, anyOf(ConstTypeParmMatcher, RValueTypeParmMatcher));
Finder->addMatcher(
invocation(anyOf(ArgumentWithParamMatcher, ArgumentWithParamTypeMatcher))
.bind("receiving-expr"),
this);
}
bool IsRValueReferenceParam(const Expr *Invocation,
const QualType *InvocationParmType,
const Expr *Arg) {
if (Invocation && (*InvocationParmType)->isRValueReferenceType() &&
Arg->isLValue()) {
if (!Invocation->getType()->isRecordType())
return true;
else {
if (const auto *ConstructCallExpr =
dyn_cast<CXXConstructExpr>(Invocation)) {
if (const auto *ConstructorDecl = ConstructCallExpr->getConstructor()) {
if (!ConstructorDecl->isCopyOrMoveConstructor() &&
!ConstructorDecl->isDefaultConstructor())
return true;
}
}
}
}
return false;
}
void MoveConstArgCheck::check(const MatchFinder::MatchResult &Result) {
const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");
const auto *ReceivingExpr = Result.Nodes.getNodeAs<Expr>("receiving-expr");
const auto *InvocationParm =
Result.Nodes.getNodeAs<ParmVarDecl>("invocation-parm");
const auto *InvocationParmType =
Result.Nodes.getNodeAs<QualType>("invocation-parm-type");
// Skipping matchers which have been matched.
if (!ReceivingExpr && AlreadyCheckedMoves.contains(CallMove))
return;
if (ReceivingExpr)
AlreadyCheckedMoves.insert(CallMove);
const Expr *Arg = CallMove->getArg(0);
const QualType ArgType = Arg->getType().getCanonicalType();
SourceManager &SM = Result.Context->getSourceManager();
CharSourceRange MoveRange =
CharSourceRange::getCharRange(CallMove->getSourceRange());
CharSourceRange FileMoveRange =
Lexer::makeFileCharRange(MoveRange, SM, getLangOpts());
if (!FileMoveRange.isValid())
return;
bool IsConstArg = ArgType.isConstQualified();
bool IsTriviallyCopyable = ArgType.isTriviallyCopyableType(*Result.Context);
if (IsConstArg || IsTriviallyCopyable) {
if (const CXXRecordDecl *R = ArgType->getAsCXXRecordDecl()) {
// According to [expr.prim.lambda]p3, "whether the closure type is
// trivially copyable" property can be changed by the implementation of
// the language, so we shouldn't rely on it when issuing diagnostics.
if (R->isLambda())
return;
// Don't warn when the type is not copyable.
for (const auto *Ctor : R->ctors()) {
if (Ctor->isCopyConstructor() && Ctor->isDeleted())
return;
}
}
if (!IsConstArg && IsTriviallyCopyable && !CheckTriviallyCopyableMove)
return;
bool IsVariable = isa<DeclRefExpr>(Arg);
// std::move shouldn't be removed when an lvalue wrapped by std::move is
// passed to the function with an rvalue reference parameter.
bool IsRVRefParam =
IsRValueReferenceParam(ReceivingExpr, InvocationParmType, Arg);
const auto *Var =
IsVariable ? dyn_cast<DeclRefExpr>(Arg)->getDecl() : nullptr;
{
auto Diag = diag(FileMoveRange.getBegin(),
"std::move of the %select{|const }0"
"%select{expression|variable %5}1 "
"%select{|of the trivially-copyable type %6 }2"
"has no effect%select{; remove std::move()|}3"
"%select{| or make the variable non-const}4")
<< IsConstArg << IsVariable << IsTriviallyCopyable
<< IsRVRefParam
<< (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var
<< Arg->getType();
if (!IsRVRefParam)
replaceCallWithArg(CallMove, Diag, SM, getLangOpts());
}
if (IsRVRefParam) {
// Generate notes for an invocation with an rvalue reference parameter.
const auto *ReceivingCallExpr = dyn_cast<CallExpr>(ReceivingExpr);
const auto *ReceivingConstructExpr =
dyn_cast<CXXConstructExpr>(ReceivingExpr);
// Skipping the invocation which is a template instantiation.
if ((!ReceivingCallExpr || !ReceivingCallExpr->getDirectCallee() ||
ReceivingCallExpr->getDirectCallee()->isTemplateInstantiation()) &&
(!ReceivingConstructExpr ||
!ReceivingConstructExpr->getConstructor() ||
ReceivingConstructExpr->getConstructor()->isTemplateInstantiation()))
return;
const NamedDecl *FunctionName = nullptr;
FunctionName =
ReceivingCallExpr
? ReceivingCallExpr->getDirectCallee()->getUnderlyingDecl()
: ReceivingConstructExpr->getConstructor()->getUnderlyingDecl();
QualType NoRefType = (*InvocationParmType)->getPointeeType();
PrintingPolicy PolicyWithSuppressedTag(getLangOpts());
PolicyWithSuppressedTag.SuppressTagKeyword = true;
PolicyWithSuppressedTag.SuppressUnwrittenScope = true;
std::string ExpectParmTypeName =
NoRefType.getAsString(PolicyWithSuppressedTag);
if (!NoRefType->isPointerType()) {
NoRefType.addConst();
ExpectParmTypeName =
NoRefType.getAsString(PolicyWithSuppressedTag) + " &";
}
diag(InvocationParm->getLocation(),
"consider changing the %ordinal0 parameter of %1 from %2 to '%3'",
DiagnosticIDs::Note)
<< (InvocationParm->getFunctionScopeIndex() + 1) << FunctionName
<< *InvocationParmType << ExpectParmTypeName;
}
} else if (ReceivingExpr && CheckMoveToConstRef) {
if ((*InvocationParmType)->isRValueReferenceType())
return;
{
auto Diag = diag(FileMoveRange.getBegin(),
"passing result of std::move() as a const reference "
"argument; no move will actually happen");
replaceCallWithArg(CallMove, Diag, SM, getLangOpts());
}
if (const CXXRecordDecl *RecordDecl = ArgType->getAsCXXRecordDecl();
RecordDecl && !(RecordDecl->hasMoveConstructor() &&
RecordDecl->hasMoveAssignment())) {
const bool MissingMoveAssignment = !RecordDecl->hasMoveAssignment();
const bool MissingMoveConstructor = !RecordDecl->hasMoveConstructor();
const bool MissingBoth = MissingMoveAssignment && MissingMoveConstructor;
diag(RecordDecl->getLocation(),
"%0 is not move "
"%select{|assignable}1%select{|/}2%select{|constructible}3",
DiagnosticIDs::Note)
<< RecordDecl << MissingMoveAssignment << MissingBoth
<< MissingMoveConstructor;
}
}
}
} // namespace clang::tidy::performance