1c491c917SChris Cotter //===--- RvalueReferenceParamNotMovedCheck.cpp - clang-tidy ---------------===//
2c491c917SChris Cotter //
3c491c917SChris Cotter // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4c491c917SChris Cotter // See https://llvm.org/LICENSE.txt for license information.
5c491c917SChris Cotter // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6c491c917SChris Cotter //
7c491c917SChris Cotter //===----------------------------------------------------------------------===//
8c491c917SChris Cotter 
9c491c917SChris Cotter #include "RvalueReferenceParamNotMovedCheck.h"
10bd762846SChris Cotter #include "../utils/Matchers.h"
11c491c917SChris Cotter #include "clang/AST/ASTContext.h"
12c491c917SChris Cotter #include "clang/ASTMatchers/ASTMatchFinder.h"
13c491c917SChris Cotter 
14c491c917SChris Cotter using namespace clang::ast_matchers;
15c491c917SChris Cotter 
16c491c917SChris Cotter namespace clang::tidy::cppcoreguidelines {
17c491c917SChris Cotter 
18bd762846SChris Cotter using matchers::hasUnevaluatedContext;
19bd762846SChris Cotter 
20c491c917SChris Cotter namespace {
21c491c917SChris Cotter AST_MATCHER_P(LambdaExpr, valueCapturesVar, DeclarationMatcher, VarMatcher) {
22c491c917SChris Cotter   return std::find_if(Node.capture_begin(), Node.capture_end(),
23c491c917SChris Cotter                       [&](const LambdaCapture &Capture) {
24c491c917SChris Cotter                         return Capture.capturesVariable() &&
25c491c917SChris Cotter                                VarMatcher.matches(*Capture.getCapturedVar(),
26c491c917SChris Cotter                                                   Finder, Builder) &&
27c491c917SChris Cotter                                Capture.getCaptureKind() == LCK_ByCopy;
28c491c917SChris Cotter                       }) != Node.capture_end();
29c491c917SChris Cotter }
30c491c917SChris Cotter AST_MATCHER_P2(Stmt, argumentOf, bool, AllowPartialMove, StatementMatcher,
31c491c917SChris Cotter                Ref) {
32c491c917SChris Cotter   if (AllowPartialMove) {
33c491c917SChris Cotter     return stmt(anyOf(Ref, hasDescendant(Ref))).matches(Node, Finder, Builder);
34c491c917SChris Cotter   }
3501c8bf6fSPiotr Zegar   return Ref.matches(Node, Finder, Builder);
36c491c917SChris Cotter }
37c491c917SChris Cotter } // namespace
38c491c917SChris Cotter 
39c491c917SChris Cotter void RvalueReferenceParamNotMovedCheck::registerMatchers(MatchFinder *Finder) {
40c491c917SChris Cotter   auto ToParam = hasAnyParameter(parmVarDecl(equalsBoundNode("param")));
41c491c917SChris Cotter 
42c491c917SChris Cotter   StatementMatcher MoveCallMatcher =
43c491c917SChris Cotter       callExpr(
44bd762846SChris Cotter           argumentCountIs(1),
45c491c917SChris Cotter           anyOf(callee(functionDecl(hasName("::std::move"))),
46c491c917SChris Cotter                 callee(unresolvedLookupExpr(hasAnyDeclaration(
47c491c917SChris Cotter                     namedDecl(hasUnderlyingDecl(hasName("::std::move"))))))),
48c491c917SChris Cotter           hasArgument(
49c491c917SChris Cotter               0, argumentOf(
50c491c917SChris Cotter                      AllowPartialMove,
51c491c917SChris Cotter                      declRefExpr(to(equalsBoundNode("param"))).bind("ref"))),
52c491c917SChris Cotter           unless(hasAncestor(
53bd762846SChris Cotter               lambdaExpr(valueCapturesVar(equalsBoundNode("param"))))),
54bd762846SChris Cotter           unless(anyOf(hasAncestor(typeLoc()),
55bd762846SChris Cotter                        hasAncestor(expr(hasUnevaluatedContext())))))
56c491c917SChris Cotter           .bind("move-call");
57c491c917SChris Cotter 
58c491c917SChris Cotter   Finder->addMatcher(
59c491c917SChris Cotter       parmVarDecl(
60c491c917SChris Cotter           hasType(type(rValueReferenceType())), parmVarDecl().bind("param"),
61c491c917SChris Cotter           unless(hasType(references(qualType(
62c491c917SChris Cotter               anyOf(isConstQualified(), substTemplateTypeParmType()))))),
63c491c917SChris Cotter           optionally(hasType(qualType(references(templateTypeParmType(
64c491c917SChris Cotter               hasDeclaration(templateTypeParmDecl().bind("template-type"))))))),
6520d21028SPiotr Zegar           hasDeclContext(
6620d21028SPiotr Zegar               functionDecl(
6720d21028SPiotr Zegar                   isDefinition(), unless(isDeleted()), unless(isDefaulted()),
6820d21028SPiotr Zegar                   unless(cxxConstructorDecl(isMoveConstructor())),
6920d21028SPiotr Zegar                   unless(cxxMethodDecl(isMoveAssignmentOperator())), ToParam,
7020d21028SPiotr Zegar                   anyOf(cxxConstructorDecl(
7120d21028SPiotr Zegar                             optionally(hasDescendant(MoveCallMatcher))),
7220d21028SPiotr Zegar                         functionDecl(unless(cxxConstructorDecl()),
7320d21028SPiotr Zegar                                      optionally(hasBody(
7420d21028SPiotr Zegar                                          hasDescendant(MoveCallMatcher))))))
7520d21028SPiotr Zegar                   .bind("func"))),
76c491c917SChris Cotter       this);
77c491c917SChris Cotter }
78c491c917SChris Cotter 
79c491c917SChris Cotter void RvalueReferenceParamNotMovedCheck::check(
80c491c917SChris Cotter     const MatchFinder::MatchResult &Result) {
81c491c917SChris Cotter   const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param");
8220d21028SPiotr Zegar   const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("func");
83c491c917SChris Cotter   const auto *TemplateType =
84c491c917SChris Cotter       Result.Nodes.getNodeAs<TemplateTypeParmDecl>("template-type");
85c491c917SChris Cotter 
8620d21028SPiotr Zegar   if (!Param || !Function)
87c491c917SChris Cotter     return;
88c491c917SChris Cotter 
89c491c917SChris Cotter   if (IgnoreUnnamedParams && Param->getName().empty())
90c491c917SChris Cotter     return;
91c491c917SChris Cotter 
92bb6a98c8SAMS21   if (!Param->isUsed() && Param->hasAttr<UnusedAttr>())
93bb6a98c8SAMS21     return;
94bb6a98c8SAMS21 
95c491c917SChris Cotter   if (IgnoreNonDeducedTemplateTypes && TemplateType)
96c491c917SChris Cotter     return;
97c491c917SChris Cotter 
98c491c917SChris Cotter   if (TemplateType) {
99c491c917SChris Cotter     if (const FunctionTemplateDecl *FuncTemplate =
100c491c917SChris Cotter             Function->getDescribedFunctionTemplate()) {
101c491c917SChris Cotter       const TemplateParameterList *Params =
102c491c917SChris Cotter           FuncTemplate->getTemplateParameters();
103c491c917SChris Cotter       if (llvm::is_contained(*Params, TemplateType)) {
104c491c917SChris Cotter         // Ignore forwarding reference
105c491c917SChris Cotter         return;
106c491c917SChris Cotter       }
107c491c917SChris Cotter     }
108c491c917SChris Cotter   }
109c491c917SChris Cotter 
110c491c917SChris Cotter   const auto *MoveCall = Result.Nodes.getNodeAs<CallExpr>("move-call");
111c491c917SChris Cotter   if (!MoveCall) {
112c491c917SChris Cotter     diag(Param->getLocation(),
113c491c917SChris Cotter          "rvalue reference parameter %0 is never moved from "
114c491c917SChris Cotter          "inside the function body")
115c491c917SChris Cotter         << Param;
116c491c917SChris Cotter   }
117c491c917SChris Cotter }
118c491c917SChris Cotter 
119c491c917SChris Cotter RvalueReferenceParamNotMovedCheck::RvalueReferenceParamNotMovedCheck(
120c491c917SChris Cotter     StringRef Name, ClangTidyContext *Context)
121c491c917SChris Cotter     : ClangTidyCheck(Name, Context),
122*aaadaee7SCongcong Cai       AllowPartialMove(Options.get("AllowPartialMove", false)),
123*aaadaee7SCongcong Cai       IgnoreUnnamedParams(Options.get("IgnoreUnnamedParams", false)),
124c491c917SChris Cotter       IgnoreNonDeducedTemplateTypes(
125*aaadaee7SCongcong Cai           Options.get("IgnoreNonDeducedTemplateTypes", false)) {}
126c491c917SChris Cotter 
127c491c917SChris Cotter void RvalueReferenceParamNotMovedCheck::storeOptions(
128c491c917SChris Cotter     ClangTidyOptions::OptionMap &Opts) {
129c491c917SChris Cotter   Options.store(Opts, "AllowPartialMove", AllowPartialMove);
130c491c917SChris Cotter   Options.store(Opts, "IgnoreUnnamedParams", IgnoreUnnamedParams);
131c491c917SChris Cotter   Options.store(Opts, "IgnoreNonDeducedTemplateTypes",
132c491c917SChris Cotter                 IgnoreNonDeducedTemplateTypes);
133c491c917SChris Cotter }
134c491c917SChris Cotter 
135c491c917SChris Cotter } // namespace clang::tidy::cppcoreguidelines
136