//===--- 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; if (const auto *ConstructCallExpr = dyn_cast(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("call-move"); const auto *ReceivingExpr = Result.Nodes.getNodeAs("receiving-expr"); const auto *InvocationParm = Result.Nodes.getNodeAs("invocation-parm"); const auto *InvocationParmType = Result.Nodes.getNodeAs("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(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(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(ReceivingExpr); const auto *ReceivingConstructExpr = dyn_cast(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