192 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "UnnecessaryValueParamCheck.h"10#include "../utils/DeclRefExprUtils.h"11#include "../utils/FixItHintUtils.h"12#include "../utils/Matchers.h"13#include "../utils/OptionsUtils.h"14#include "../utils/TypeTraits.h"15#include "clang/Frontend/CompilerInstance.h"16#include "clang/Lex/Lexer.h"17#include "clang/Lex/Preprocessor.h"18#include <optional>19 20using namespace clang::ast_matchers;21 22namespace clang::tidy::performance {23 24static std::string paramNameOrIndex(StringRef Name, size_t Index) {25 return (Name.empty() ? llvm::Twine('#') + llvm::Twine(Index + 1)26 : llvm::Twine('\'') + Name + llvm::Twine('\''))27 .str();28}29 30static bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl,31 ASTContext &Context) {32 auto Matches = match(33 traverse(TK_AsIs,34 decl(forEachDescendant(declRefExpr(35 equalsNode(&DeclRef),36 unless(hasAncestor(stmt(anyOf(forStmt(), cxxForRangeStmt(),37 whileStmt(), doStmt())))))))),38 Decl, Context);39 return Matches.empty();40}41 42UnnecessaryValueParamCheck::UnnecessaryValueParamCheck(43 StringRef Name, ClangTidyContext *Context)44 : ClangTidyCheck(Name, Context),45 Inserter(Options.getLocalOrGlobal("IncludeStyle",46 utils::IncludeSorter::IS_LLVM),47 areDiagsSelfContained()),48 AllowedTypes(49 utils::options::parseStringList(Options.get("AllowedTypes", ""))),50 IgnoreCoroutines(Options.get("IgnoreCoroutines", true)) {}51 52void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) {53 const auto ExpensiveValueParamDecl = parmVarDecl(54 hasType(qualType(55 hasCanonicalType(matchers::isExpensiveToCopy()),56 unless(anyOf(hasCanonicalType(referenceType()),57 hasDeclaration(namedDecl(58 matchers::matchesAnyListedName(AllowedTypes))))))),59 decl().bind("param"));60 Finder->addMatcher(61 traverse(TK_AsIs,62 functionDecl(63 hasBody(IgnoreCoroutines ? stmt(unless(coroutineBodyStmt()))64 : stmt()),65 isDefinition(), unless(isImplicit()),66 unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))),67 has(typeLoc(forEach(ExpensiveValueParamDecl))),68 decl().bind("functionDecl"))),69 this);70}71 72void UnnecessaryValueParamCheck::check(const MatchFinder::MatchResult &Result) {73 const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param");74 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("functionDecl");75 76 const TraversalKindScope RAII(*Result.Context, TK_AsIs);77 78 FunctionParmMutationAnalyzer *Analyzer =79 FunctionParmMutationAnalyzer::getFunctionParmMutationAnalyzer(80 *Function, *Result.Context, MutationAnalyzerCache);81 if (Analyzer->isMutated(Param))82 return;83 84 const bool IsConstQualified =85 Param->getType().getCanonicalType().isConstQualified();86 87 // If the parameter is non-const, check if it has a move constructor and is88 // only referenced once to copy-construct another object or whether it has a89 // move assignment operator and is only referenced once when copy-assigned.90 // In this case wrap DeclRefExpr with std::move() to avoid the unnecessary91 // copy.92 if (!IsConstQualified) {93 auto AllDeclRefExprs = utils::decl_ref_expr::allDeclRefExprs(94 *Param, *Function, *Result.Context);95 if (AllDeclRefExprs.size() == 1) {96 auto CanonicalType = Param->getType().getCanonicalType();97 const auto &DeclRefExpr = **AllDeclRefExprs.begin();98 99 if (!hasLoopStmtAncestor(DeclRefExpr, *Function, *Result.Context) &&100 ((utils::type_traits::hasNonTrivialMoveConstructor(CanonicalType) &&101 utils::decl_ref_expr::isCopyConstructorArgument(102 DeclRefExpr, *Function, *Result.Context)) ||103 (utils::type_traits::hasNonTrivialMoveAssignment(CanonicalType) &&104 utils::decl_ref_expr::isCopyAssignmentArgument(105 DeclRefExpr, *Function, *Result.Context)))) {106 handleMoveFix(*Param, DeclRefExpr, *Result.Context);107 return;108 }109 }110 }111 112 handleConstRefFix(*Function, *Param, *Result.Context);113}114 115void UnnecessaryValueParamCheck::registerPPCallbacks(116 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {117 Inserter.registerPreprocessor(PP);118}119 120void UnnecessaryValueParamCheck::storeOptions(121 ClangTidyOptions::OptionMap &Opts) {122 Options.store(Opts, "IncludeStyle", Inserter.getStyle());123 Options.store(Opts, "AllowedTypes",124 utils::options::serializeStringList(AllowedTypes));125 Options.store(Opts, "IgnoreCoroutines", IgnoreCoroutines);126}127 128void UnnecessaryValueParamCheck::onEndOfTranslationUnit() {129 MutationAnalyzerCache.clear();130}131 132void UnnecessaryValueParamCheck::handleConstRefFix(const FunctionDecl &Function,133 const ParmVarDecl &Param,134 ASTContext &Context) {135 const size_t Index =136 llvm::find(Function.parameters(), &Param) - Function.parameters().begin();137 const bool IsConstQualified =138 Param.getType().getCanonicalType().isConstQualified();139 140 auto Diag =141 diag(Param.getLocation(),142 "the %select{|const qualified }0parameter %1 of type %2 is copied "143 "for each "144 "invocation%select{ but only used as a const reference|}0; consider "145 "making it a %select{const |}0reference")146 << IsConstQualified << paramNameOrIndex(Param.getName(), Index)147 << Param.getType();148 // Do not propose fixes when:149 // 1. the ParmVarDecl is in a macro, since we cannot place them correctly150 // 2. the function is virtual as it might break overrides151 // 3. the function is an explicit template/ specialization.152 const auto *Method = llvm::dyn_cast<CXXMethodDecl>(&Function);153 if (Param.getBeginLoc().isMacroID() || (Method && Method->isVirtual()) ||154 Function.getTemplateSpecializationKind() == TSK_ExplicitSpecialization)155 return;156 for (const auto *FunctionDecl = &Function; FunctionDecl != nullptr;157 FunctionDecl = FunctionDecl->getPreviousDecl()) {158 const auto &CurrentParam = *FunctionDecl->getParamDecl(Index);159 Diag << utils::fixit::changeVarDeclToReference(CurrentParam, Context);160 // The parameter of each declaration needs to be checked individually as to161 // whether it is const or not as constness can differ between definition and162 // declaration.163 if (!CurrentParam.getType().getCanonicalType().isConstQualified()) {164 if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl(165 CurrentParam, Context, Qualifiers::Const))166 Diag << *Fix;167 }168 }169}170 171void UnnecessaryValueParamCheck::handleMoveFix(const ParmVarDecl &Param,172 const DeclRefExpr &CopyArgument,173 ASTContext &Context) {174 auto Diag =175 diag(CopyArgument.getBeginLoc(),176 "parameter %0 of type %1 is passed by value and only copied once; "177 "consider moving it to avoid unnecessary copies")178 << &Param << Param.getType();179 // Do not propose fixes in macros since we cannot place them correctly.180 if (CopyArgument.getBeginLoc().isMacroID())181 return;182 const auto &SM = Context.getSourceManager();183 auto EndLoc = Lexer::getLocForEndOfToken(CopyArgument.getLocation(), 0, SM,184 Context.getLangOpts());185 Diag << FixItHint::CreateInsertion(CopyArgument.getBeginLoc(), "std::move(")186 << FixItHint::CreateInsertion(EndLoc, ")")187 << Inserter.createIncludeInsertion(188 SM.getFileID(CopyArgument.getBeginLoc()), "<utility>");189}190 191} // namespace clang::tidy::performance192