On Wed, 2018-08-22 at 20:12 -0400, Marek Polacek wrote:
> Now that we have -Wpessimizing-move implemented, it was fairly easy
> to extend
> it to -Wredundant-move, which warns about redundant calls to
> std::move; it's
> supposed to detect cases where the compiler is obliged to treat an
> object as
> if it were an rvalue, so calling std::move is pointless.  Since we
> implement
> Core Issue 1579, it warns in more situations than clang++.  I
> described this
> in more detail in the manual entry.
> 
> David, I've also tweaked the warning to use fix-it hints; the point
> is to turn
> 
>   return std::move (x);
> 
> into
> 
>   return x;
> 
> I didn't fool around with looking for the locations of the parens;
> I've used a
> DECL_NAME hack instead.  I've verified it using -fdiagnostics-
> generate-patch,
> which produces e.g.:
> 
> @@ -27,7 +27,7 @@
>  f ()
>  {
>    T foo;
> -  return std::move (foo);
> +  return foo;
>  }
>  
> g++.dg/diagnostic/move1.C tests the fix-it hints for -Wredundant-move 
> and
> -Wpessimizing-move.

Thanks (and ideally, we'd have a precanned way for DejaGnu to test that
the fix-its hints work, and lead to code that resolves the diagnostics;
I've toyed around with doing so, but it's fiddly, involving a second
compile; ugh).

Regarding the fix-it hint code: I'm not convinced that it works in
every possible scenario.

Consider:

(A):

  namespace ns {
     int some_global;
  };

  int fn ()
  {
    return std::move (ns::some_global);
  }

which I believe will generate this bogus suggestion:

    return std::move (ns::some_global);
           ~~~~~~~~~~^~~~~~~~~~~~~~~~~
           some_global

since IIRC, DECL_NAME doesn't contain any explicit namespace for the
way the var was referred to.

(B):

#ifdef SOME_CONFIG_FLAG
# define CONFIGURY_GLOBAL global_a
#else
# define CONFIGURY_GLOBAL global_b
#endif

  int fn ()
  {
     return std::move (CONFIGURY_GLOBAL);
  }

which presumably will erroneously strip out the macro in the fix-it
hint:

     return std::move (CONFIGURY_GLOBAL);
            ~~~~~~~~~~^~~~~~~~~~~~~~~~~~
            global_a

rich_location will discard fix-it hints for locations that are in macro
expansions to try to avoid problems like this, but (B)'s location will 
be a non-macro location, I believe.

Hence my suggestion to, if possible, use the locations of the parens,
so that the fix-it hint preserves the user's spelling of the variable.
 
But I appreciate that it might be unreasonably difficult to get at
those locations.  Hence, I think the fix-it hint part of the patch is
probably good enough as-is: I'd rather not let "perfect be the enemy of
the good" here.

Dave

> Bootstrapped/regtested on x86_64-linux, ok for trunk?
> 
> 2018-08-22  Marek Polacek  <pola...@redhat.com>
> 
>       PR c++/87029, Implement -Wredundant-move.
>       * c.opt (Wredundant-move): New option.
> 
>       * typeck.c (maybe_warn_pessimizing_move): Call
> convert_from_reference.
>       Warn about redundant moves.  Use fix-it hints.
> 
>       * doc/invoke.texi: Document -Wredundant-move.
> 
>       * g++.dg/cpp0x/Wredundant-move1.C: New test.
>       * g++.dg/cpp0x/Wredundant-move2.C: New test.
>       * g++.dg/cpp0x/Wredundant-move3.C: New test.
>       * g++.dg/cpp0x/Wredundant-move4.C: New test.
>       * g++.dg/diagnostic/move1.C: New test.
> 
> diff --git gcc/c-family/c.opt gcc/c-family/c.opt
> index 76840dd77ad..a92be089b40 100644
> --- gcc/c-family/c.opt
> +++ gcc/c-family/c.opt
> @@ -985,6 +985,10 @@ Wredundant-decls
>  C ObjC C++ ObjC++ Var(warn_redundant_decls) Warning
>  Warn about multiple declarations of the same object.
>  
> +Wredundant-move
> +C++ ObjC++ Var(warn_redundant_move) Warning LangEnabledBy(C++
> ObjC++, Wall)
> +Warn about redundant calls to std::move.
> +
>  Wregister
>  C++ ObjC++ Var(warn_register) Warning
>  Warn about uses of register storage specifier.
> diff --git gcc/cp/typeck.c gcc/cp/typeck.c
> index 122d9dcd4b3..3f0b263525e 100644
> --- gcc/cp/typeck.c
> +++ gcc/cp/typeck.c
> @@ -9185,7 +9185,7 @@ can_do_nrvo_p (tree retval, tree functype)
>  static void
>  maybe_warn_pessimizing_move (tree retval, tree functype)
>  {
> -  if (!warn_pessimizing_move)
> +  if (!(warn_pessimizing_move || warn_redundant_move))
>      return;
>  
>    /* C++98 doesn't know move.  */
> @@ -9207,14 +9207,31 @@ maybe_warn_pessimizing_move (tree retval,
> tree functype)
>         STRIP_NOPS (arg);
>         if (TREE_CODE (arg) == ADDR_EXPR)
>           arg = TREE_OPERAND (arg, 0);
> +       arg = convert_from_reference (arg);
>         /* Warn if we could do copy elision were it not for the
> move.  */
>         if (can_do_nrvo_p (arg, functype))
>           {
>             auto_diagnostic_group d;
> -           if (warning_at (location_of (retval),
> OPT_Wpessimizing_move,
> -                           "moving a local object in a return
> statement "
> -                           "prevents copy elision"))
> -             inform (location_of (retval), "remove %<std::move%>
> call");
> +           gcc_rich_location richloc (location_of (retval));
> +           tree name = DECL_NAME (arg);
> +           richloc.add_fixit_replace (IDENTIFIER_POINTER (name));
> +           warning_at (&richloc, OPT_Wpessimizing_move,
> +                       "moving a local object in a return
> statement "
> +                       "prevents copy elision");
> +         }
> +       /* Warn if the move is redundant.  It is redundant when we
> would
> +          do maybe-rvalue overload resolution even without
> std::move.  */
> +       else if (((VAR_P (arg) && !DECL_HAS_VALUE_EXPR_P (arg))
> +                 || TREE_CODE (arg) == PARM_DECL)
> +                && DECL_CONTEXT (arg) == current_function_decl
> +                && !TREE_STATIC (arg))
> +         {
> +           auto_diagnostic_group d;
> +           gcc_rich_location richloc (location_of (retval));
> +           tree name = DECL_NAME (arg);
> +           richloc.add_fixit_replace (IDENTIFIER_POINTER (name));
> +           warning_at (&richloc, OPT_Wredundant_move,
> +                       "redundant move in return statement");
>           }
>       }
>      }
> diff --git gcc/doc/invoke.texi gcc/doc/invoke.texi
> index e4148297a87..341499f49b7 100644
> --- gcc/doc/invoke.texi
> +++ gcc/doc/invoke.texi
> @@ -231,7 +231,7 @@ in the following sections.
>  -Wdelete-non-virtual-dtor -Wdeprecated-copy  -Wliteral-suffix @gol
>  -Wmultiple-inheritance @gol
>  -Wnamespaces  -Wnarrowing @gol
> --Wpessimizing-move @gol
> +-Wpessimizing-move  -Wredundant-move @gol
>  -Wnoexcept  -Wnoexcept-type  -Wclass-memaccess @gol
>  -Wnon-virtual-dtor  -Wreorder  -Wregister @gol
>  -Weffc++  -Wstrict-null-sentinel  -Wtemplates @gol
> @@ -3158,6 +3158,49 @@ But in this example, the @code{std::move} call
> prevents copy elision.
>  
>  This warning is enabled by @option{-Wall}.
>  
> +@item -Wno-redundant-move @r{(C++ and Objective-C++ only)}
> +@opindex Wredundant-move
> +@opindex Wno-redundant-move
> +This warning warns about redundant calls to @code{std::move}; that
> is, when
> +a move operation would have been performed even without the
> @code{std::move}
> +call.  This happens because the compiler is forced to treat the
> object as if
> +it were an rvalue in certain situations such as returning a local
> variable,
> +where copy elision isn't applicable.  Consider:
> +
> +@smallexample
> +struct T @{
> +@dots{}
> +@};
> +T fn(T t)
> +@{
> +  @dots{}
> +  return std::move (t);
> +@}
> +@end smallexample
> +
> +Here, the @code{std::move} call is redundant.  Because G++
> implements Core
> +Issue 1579, another example is:
> +
> +@smallexample
> +struct T @{ // convertible to U
> +@dots{}
> +@};
> +struct U @{
> +@dots{}
> +@};
> +U fn()
> +@{
> +  T t;
> +  @dots{}
> +  return std::move (t);
> +@}
> +@end smallexample
> +In this example, copy elision isn't applicable because the type of
> the
> +expression being returned and the function return type differ, yet
> G++
> +treats the return value as if it were designated by an rvalue.
> +
> +This warning is enabled by @option{-Wall}.
> +
>  @item -fext-numeric-literals @r{(C++ and Objective-C++ only)}
>  @opindex fext-numeric-literals
>  @opindex fno-ext-numeric-literals
> @@ -4065,6 +4108,7 @@ Options} and @ref{Objective-C and Objective-C++ 
> Dialect Options}.
>  -Wparentheses  @gol
>  -Wpessimizing-move @r{(only for C++)}  @gol
>  -Wpointer-sign  @gol
> +-Wredundant-move @r{(only for C++)}  @gol
>  -Wreorder   @gol
>  -Wrestrict   @gol
>  -Wreturn-type  @gol
> diff --git gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C
> gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C
> index e69de29bb2d..0269ffbbc1f 100644
> --- gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C
> +++ gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C
> @@ -0,0 +1,106 @@
> +// PR c++/87029
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wredundant-move" }
> +
> +// Define std::move.
> +namespace std {
> +  template<typename _Tp>
> +    struct remove_reference
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    constexpr typename std::remove_reference<_Tp>::type&&
> +    move(_Tp&& __t) noexcept
> +    { return static_cast<typename
> std::remove_reference<_Tp>::type&&>(__t); }
> +}
> +
> +struct T {
> +  T() { }
> +  T(const T&) { }
> +  T(T&&) { }
> +};
> +
> +struct U {
> +  U() { }
> +  U(const U&) { }
> +  U(U&&) { }
> +  U(T) { }
> +};
> +
> +T
> +fn1 (T t)
> +{
> +  return t;
> +}
> +
> +T
> +fn2 (T t)
> +{
> +  // Will use move even without std::move.
> +  return std::move (t); // { dg-warning "redundant move in return
> statement" }
> +}
> +
> +T
> +fn3 (const T t)
> +{
> +  // t is const: will decay into move.
> +  return t;
> +}
> +
> +T
> +fn4 (const T t)
> +{
> +  // t is const: will decay into move despite std::move, so it's
> redundant.
> +  return std::move (t); // { dg-warning "redundant move in return
> statement" }
> +}
> +
> +int
> +fn5 (int i)
> +{
> +  // Not a class type.
> +  return std::move (i);
> +}
> +
> +T
> +fn6 (T t, bool b)
> +{
> +  if (b)
> +    throw std::move (t);
> +  return std::move (t); // { dg-warning "redundant move in return
> statement" }
> +}
> +
> +U
> +fn7 (T t)
> +{
> +  // Core 1579 means we'll get a move here.
> +  return t;
> +}
> +
> +U
> +fn8 (T t)
> +{
> +  // Core 1579 means we'll get a move here.  Even without std::move.
> +  return std::move (t);  // { dg-warning "redundant move in return
> statement" }
> +}
> +
> +T
> +fn9 (T& t)
> +{
> +  // T is a reference and the move isn't redundant.
> +  return std::move (t);
> +}
> +
> +T
> +fn10 (T&& t)
> +{
> +  // T is a reference and the move isn't redundant.
> +  return std::move (t);
> +}
> diff --git gcc/testsuite/g++.dg/cpp0x/Wredundant-move2.C
> gcc/testsuite/g++.dg/cpp0x/Wredundant-move2.C
> index e69de29bb2d..f181afeeb84 100644
> --- gcc/testsuite/g++.dg/cpp0x/Wredundant-move2.C
> +++ gcc/testsuite/g++.dg/cpp0x/Wredundant-move2.C
> @@ -0,0 +1,57 @@
> +// PR c++/87029
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wredundant-move" }
> +
> +// Define std::move.
> +namespace std {
> +  template<typename _Tp>
> +    struct remove_reference
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    constexpr typename std::remove_reference<_Tp>::type&&
> +    move(_Tp&& __t) noexcept
> +    { return static_cast<typename
> std::remove_reference<_Tp>::type&&>(__t); }
> +}
> +
> +struct T { };
> +struct U { U(T); };
> +
> +template<typename Tp>
> +T
> +fn1 (T t)
> +{
> +  // Non-dependent type.
> +  return std::move (t); // { dg-warning "redundant move in return
> statement" }
> +}
> +
> +template<typename Tp1, typename Tp2>
> +Tp1
> +fn2 (Tp2 t)
> +{
> +  return std::move (t); // { dg-warning "redundant move in return
> statement" }
> +}
> +
> +template<typename Tp1, typename Tp2>
> +Tp1
> +fn3 (Tp2 t)
> +{
> +  return std::move (t); // { dg-warning "redundant move in return
> statement" }
> +}
> +
> +int
> +main ()
> +{
> +  T t;
> +  fn1<T>(t);
> +  fn2<T, T>(t);
> +  fn3<U, T>(t);
> +}
> diff --git gcc/testsuite/g++.dg/cpp0x/Wredundant-move3.C
> gcc/testsuite/g++.dg/cpp0x/Wredundant-move3.C
> index e69de29bb2d..7084134e370 100644
> --- gcc/testsuite/g++.dg/cpp0x/Wredundant-move3.C
> +++ gcc/testsuite/g++.dg/cpp0x/Wredundant-move3.C
> @@ -0,0 +1,43 @@
> +// PR c++/87029
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wredundant-move" }
> +
> +// Define std::move.
> +namespace std {
> +  template<typename _Tp>
> +    struct remove_reference
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    constexpr typename std::remove_reference<_Tp>::type&&
> +    move(_Tp&& __t) noexcept
> +    { return static_cast<typename
> std::remove_reference<_Tp>::type&&>(__t); }
> +}
> +
> +struct T { };
> +
> +T
> +fn1 (T t)
> +{
> +  return (1, std::move (t));
> +}
> +
> +T
> +fn2 (T t)
> +{
> +  return [&](){ return std::move (t); }();
> +}
> +
> +T
> +fn3 (T t)
> +{
> +  return [=](){ return std::move (t); }();
> +}
> diff --git gcc/testsuite/g++.dg/cpp0x/Wredundant-move4.C
> gcc/testsuite/g++.dg/cpp0x/Wredundant-move4.C
> index e69de29bb2d..aa89e46de99 100644
> --- gcc/testsuite/g++.dg/cpp0x/Wredundant-move4.C
> +++ gcc/testsuite/g++.dg/cpp0x/Wredundant-move4.C
> @@ -0,0 +1,86 @@
> +// PR c++/87029
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wredundant-move" }
> +
> +// Define std::move.
> +namespace std {
> +  template<typename _Tp>
> +    struct remove_reference
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    constexpr typename std::remove_reference<_Tp>::type&&
> +    move(_Tp&& __t) noexcept
> +    { return static_cast<typename
> std::remove_reference<_Tp>::type&&>(__t); }
> +}
> +
> +struct T {
> +  T() { }
> +  T(const T&) { }
> +  T(T&&) { }
> +};
> +
> +struct U {
> +  U() { }
> +  U(const U&) { }
> +  U(U&&) { }
> +  U(T) { }
> +};
> +
> +U
> +fn1 (T t, bool b)
> +{
> +  if (b)
> +    return t;
> +  else
> +    return std::move (t); // { dg-warning "redundant move in return
> statement" }
> +}
> +
> +U
> +fn2 (bool b)
> +{
> +  T t;
> +  if (b)
> +    return t;
> +  else
> +    return std::move (t); // { dg-warning "redundant move in return
> statement" }
> +}
> +
> +U
> +fn3 (bool b)
> +{
> +  static T t;
> +  if (b)
> +    return t;
> +  else
> +    return std::move (t);
> +}
> +
> +T g;
> +
> +U
> +fn4 (bool b)
> +{
> +  if (b)
> +    return g;
> +  else
> +    return std::move (g);
> +}
> +
> +long int
> +fn5 (bool b)
> +{
> +  int i = 42;
> +  if (b)
> +    return i;
> +  else
> +    return std::move (i);
> +}
> diff --git gcc/testsuite/g++.dg/diagnostic/move1.C
> gcc/testsuite/g++.dg/diagnostic/move1.C
> index e69de29bb2d..ddf630da843 100644
> --- gcc/testsuite/g++.dg/diagnostic/move1.C
> +++ gcc/testsuite/g++.dg/diagnostic/move1.C
> @@ -0,0 +1,47 @@
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-fdiagnostics-show-caret -Wall" }
> +
> +// Define std::move.
> +namespace std {
> +  template<typename _Tp>
> +    struct remove_reference
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    struct remove_reference<_Tp&&>
> +    { typedef _Tp   type; };
> +
> +  template<typename _Tp>
> +    constexpr typename std::remove_reference<_Tp>::type&&
> +    move(_Tp&& __t) noexcept
> +    { return static_cast<typename
> std::remove_reference<_Tp>::type&&>(__t); }
> +}
> +
> +struct T { };
> +
> +T
> +f1 ()
> +{
> +  T hello;
> +  return std::move (hello); /* { dg-warning "moving a local object"
> }
> +  { dg-begin-multiline-output "" }
> +   return std::move (hello);
> +          ~~~~~~~~~~^~~~~~~
> +          hello
> +  { dg-end-multiline-output "" } */
> +}
> +
> +T
> +f2 (T David)
> +{
> +  return std::move (David); /* { dg-warning "redundant move" }
> +  { dg-begin-multiline-output "" }
> +   return std::move (David);
> +          ~~~~~~~~~~^~~~~~~
> +          David
> +  { dg-end-multiline-output "" } */
> +}

Reply via email to