mboehme updated this revision to Diff 60507. http://reviews.llvm.org/D21223
Files: clang-tidy/misc/MoveConstantArgumentCheck.cpp docs/clang-tidy/checks/misc-move-const-arg.rst test/clang-tidy/misc-move-const-arg.cpp
Index: test/clang-tidy/misc-move-const-arg.cpp =================================================================== --- test/clang-tidy/misc-move-const-arg.cpp +++ test/clang-tidy/misc-move-const-arg.cpp @@ -71,3 +71,90 @@ f10<int>(1); f10<double>(1); } + +struct NonMoveable { + public: + NonMoveable(); + NonMoveable(const NonMoveable &); + + NonMoveable &operator=(const NonMoveable &); +}; + +void callByConstRef(const NonMoveable &); +void callByConstRef(int i, const NonMoveable &); + +void moveToConstReferencePositives() { + NonMoveable obj; + + // Basic case. + callByConstRef(std::move(obj)); + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: passing result of std::move() as + // CHECK-FIXES: callByConstRef(obj); + + // Also works for second argument. + callByConstRef(1, std::move(obj)); + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: passing result of std::move() as + // CHECK-FIXES: callByConstRef(1, obj); + + // Works if std::move() applied to a temporary. + callByConstRef(std::move(NonMoveable())); + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: passing result of std::move() as + // CHECK-FIXES: callByConstRef(NonMoveable()); + + // Works if calling a copy constructor. + NonMoveable other(std::move(obj)); + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: passing result of std::move() as + // CHECK-FIXES: NonMoveable other(obj); + + // Works if calling assignment operator. + other = std::move(obj); + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: passing result of std::move() as + // CHECK-FIXES: other = obj; +} + +struct Moveable { + public: + Moveable(); + Moveable(Moveable &&); + + Moveable &operator=(Moveable &&); +}; + +void callByValue(Moveable); + +void callByRValueRef(Moveable &&); + +template <class T> +void templateFunction(T obj) { + T other = std::move(obj); +} + +#define M3(T, obj) \ + do { \ + T other = std::move(obj); \ + } while (true) + +#define CALL(func) (func)() + +void moveToConstReferenceNegatives() { + // No warning when actual move takes place. + Moveable moveable; + callByValue(std::move(moveable)); + callByRValueRef(std::move(moveable)); + Moveable other(std::move(moveable)); + other = std::move(moveable); + + // No warning if std::move() not used. + NonMoveable non_moveable; + callByConstRef(non_moveable); + + // No warning if instantiating a template. + templateFunction(non_moveable); + + // No warning inside of macro expansions. + M3(NonMoveable, non_moveable); + + // No warning inside of macro expansion, even if the macro expansion is inside + // a lambda that is, in turn, an argument to a macro. + CALL([non_moveable] { M3(NonMoveable, non_moveable); }); +} Index: docs/clang-tidy/checks/misc-move-const-arg.rst =================================================================== --- docs/clang-tidy/checks/misc-move-const-arg.rst +++ docs/clang-tidy/checks/misc-move-const-arg.rst @@ -3,13 +3,26 @@ misc-move-const-arg =================== -The check warns if ``std::move()`` is called with a constant argument or an -argument of a trivially-copyable type, e.g.: +The check warns + + - if ``std::move()`` is called with a constant argument, + - if ``std::move()`` is called with an argument of a trivially-copyable type, + or + - if the result of ``std::move()`` is passed as a const reference argument. + +In all three cases, the check will suggest a fix that removes the +``std::move()``. + +Here are examples of each of the three cases: .. code:: c++ const string s; return std::move(s); // Warning: std::move of the const variable has no effect int x; return std::move(x); // Warning: std::move of the variable of a trivially-copyable type has no effect + + void f(const string &s); + string s; + f(std::move(s)); // Warning: passing result of std::move as a const reference argument; no move will actually happen Index: clang-tidy/misc/MoveConstantArgumentCheck.cpp =================================================================== --- clang-tidy/misc/MoveConstantArgumentCheck.cpp +++ clang-tidy/misc/MoveConstantArgumentCheck.cpp @@ -17,51 +17,103 @@ namespace tidy { namespace misc { +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->getLocStart(), Arg->getLocStart()), + SM, LangOpts); + CharSourceRange AfterArgumentsRange = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(Call->getLocEnd(), + Call->getLocEnd().getLocWithOffset(1)), + SM, LangOpts); + + if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) { + Diag << FixItHint::CreateRemoval(BeforeArgumentsRange) + << FixItHint::CreateRemoval(AfterArgumentsRange); + } +} + void MoveConstantArgumentCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus) return; - Finder->addMatcher(callExpr(callee(functionDecl(hasName("::std::move"))), - argumentCountIs(1), - unless(isInTemplateInstantiation())) - .bind("call-move"), + + auto MoveCallMatcher = + callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1), + unless(isInTemplateInstantiation())) + .bind("call-move"); + + Finder->addMatcher(MoveCallMatcher, this); + + auto ConstParamMatcher = forEachArgumentWithParam( + MoveCallMatcher, parmVarDecl(hasType(references(isConstQualified())))); + + Finder->addMatcher(callExpr(ConstParamMatcher).bind("receiving-expr"), this); + Finder->addMatcher(cxxConstructExpr(ConstParamMatcher).bind("receiving-expr"), this); } void MoveConstantArgumentCheck::check(const MatchFinder::MatchResult &Result) { const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move"); + const auto *ReceivingExpr = Result.Nodes.getNodeAs<Expr>("receiving-expr"); const Expr *Arg = CallMove->getArg(0); SourceManager &SM = Result.Context->getSourceManager(); + CharSourceRange MoveRange = + CharSourceRange::getCharRange(CallMove->getSourceRange()); + CharSourceRange FileMoveRange = + Lexer::makeFileCharRange(MoveRange, SM, getLangOpts()); + if (!FileMoveRange.isValid()) + return; + bool IsConstArg = Arg->getType().isConstQualified(); bool IsTriviallyCopyable = Arg->getType().isTriviallyCopyableType(*Result.Context); if (IsConstArg || IsTriviallyCopyable) { - auto MoveRange = CharSourceRange::getCharRange(CallMove->getSourceRange()); - auto FileMoveRange = Lexer::makeFileCharRange(MoveRange, SM, getLangOpts()); - if (!FileMoveRange.isValid()) - return; bool IsVariable = isa<DeclRefExpr>(Arg); auto Diag = diag(FileMoveRange.getBegin(), "std::move of the %select{|const }0" "%select{expression|variable}1 " "%select{|of a trivially-copyable type }2" "has no effect; remove std::move()") << IsConstArg << IsVariable << IsTriviallyCopyable; - auto BeforeArgumentsRange = Lexer::makeFileCharRange( - CharSourceRange::getCharRange(CallMove->getLocStart(), - Arg->getLocStart()), - SM, getLangOpts()); - auto AfterArgumentsRange = Lexer::makeFileCharRange( - CharSourceRange::getCharRange( - CallMove->getLocEnd(), CallMove->getLocEnd().getLocWithOffset(1)), - SM, getLangOpts()); - - if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) { - Diag << FixItHint::CreateRemoval(BeforeArgumentsRange) - << FixItHint::CreateRemoval(AfterArgumentsRange); + ReplaceCallWithArg(CallMove, Diag, SM, getLangOpts()); + } else if (ReceivingExpr) { + // Ignore macro body and argument expansions. + // + // Macro argument expansions need to be ignored to handle this case + // correctly: + // + // CALL([non_moveable] { + // MACRO(NonMoveable, non_moveable); + // }); + // + // where + // + // #define MACRO(T, obj) do { \ + // T other = std::move(obj); \ + // } while(true) + // + // #define CALL(func) (func)() + // + // Here, the std::move() is part of a macro expansion, but + // isMacroBodyExpansion() nevertheless returns false because that macro + // expansion is inside a lambda which, in turn, is an argument to a macro -- + // and that appears to take precendence. + if (SM.isMacroBodyExpansion(CallMove->getLocStart()) || + SM.isMacroArgExpansion(CallMove->getLocStart())) { + 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()); } }
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits