On Mon, 2018-08-20 at 18:54 -0400, Marek Polacek wrote:
> On Mon, Aug 20, 2018 at 05:18:49PM -0400, David Malcolm wrote:
> > As of r263675 it's now possible to tell the diagnostics subsystem
> > that
> > the warning and note are related by using an auto_diagnostic_group
> > instance [1], so please can this read:
> > 
> >           if (can_do_nrvo_p (arg, functype))
> >             {
> >               auto_diagnostic_group d;
> >               if (warning (OPT_Wpessimizing_move, "moving a local
> > object "
> >                            "in a return statement prevents copy
> > elision"))
> >                 inform (input_location, "remove %<std::move%>
> > call");
> >             }
> 
> Sure:
> 
> Bootstrapped/regtested on x86_64-linux, ok for trunk?

Thanks!  The changed part looks good, but I can't really speak to the
rest of the patch.

(BTW, could we emit a fix-it hint as part of that "inform" call?  How
good is the location information at this point?)

> 2018-08-20  Marek Polacek  <pola...@redhat.com>
> 
>       PR c++/86981, Implement -Wpessimizing-move.
>       * c.opt (Wpessimizing-move): New option.
> 
>       * typeck.c (decl_in_std_namespace_p): New.
>       (is_std_move_p): New.
>       (can_do_nrvo_p): New, factored out of ...
>       (check_return_expr): ... here.  Warn about potentially harmful
>       std::move in a return statement.
> 
>       * doc/invoke.texi: Document -Wpessimizing-move.
> 
>       * g++.dg/cpp0x/Wpessimizing-move1.C: New test.
>       * g++.dg/cpp0x/Wpessimizing-move2.C: New test.
>       * g++.dg/cpp0x/Wpessimizing-move3.C: New test.
>       * g++.dg/cpp0x/Wpessimizing-move4.C: New test.
>       * g++.dg/cpp1z/Wpessimizing-move1.C: New test.
> 
> diff --git gcc/c-family/c.opt gcc/c-family/c.opt
> index 9980bfac11c..76840dd77ad 100644
> --- gcc/c-family/c.opt
> +++ gcc/c-family/c.opt
> @@ -937,6 +937,10 @@ Wpedantic
>  C ObjC C++ ObjC++ CPP(cpp_pedantic) CppReason(CPP_W_PEDANTIC)
> Warning
>  ; Documented in common.opt
>  
> +Wpessimizing-move
> +C++ ObjC++ Var(warn_pessimizing_move) Warning LangEnabledBy(C++
> ObjC++, Wall)
> +Warn about calling std::move on a local object in a return statement
> preventing copy elision.
> +
>  Wpmf-conversions
>  C++ ObjC++ Var(warn_pmf2ptr) Init(1) Warning
>  Warn when converting the type of pointers to member functions.
> diff --git gcc/cp/typeck.c gcc/cp/typeck.c
> index 8c13ae9b19b..5fe47299772 100644
> --- gcc/cp/typeck.c
> +++ gcc/cp/typeck.c
> @@ -9113,6 +9113,58 @@ maybe_warn_about_returning_address_of_local
> (tree retval)
>    return false;
>  }
>  
> +/* Returns true if DECL is in the std namespace.  */
> +
> +static bool
> +decl_in_std_namespace_p (tree decl)
> +{
> +  return (decl != NULL_TREE
> +       && DECL_NAMESPACE_STD_P (decl_namespace_context (decl)));
> +}
> +
> +/* Returns true if FN, a CALL_EXPR, is a call to std::move.  */
> +
> +static bool
> +is_std_move_p (tree fn)
> +{
> +  /* std::move only takes one argument.  */
> +  if (call_expr_nargs (fn) != 1)
> +    return false;
> +
> +  tree fndecl = cp_get_callee_fndecl_nofold (fn);
> +  if (!decl_in_std_namespace_p (fndecl))
> +    return false;
> +
> +  tree name = DECL_NAME (fndecl);
> +  return name && id_equal (name, "move");
> +}
> +
> +/* Returns true if RETVAL is a good candidate for the NRVO as per
> +   [class.copy.elision].  FUNCTYPE is the type the function is
> declared
> +   to return.  */
> +
> +static bool
> +can_do_nrvo_p (tree retval, tree functype)
> +{
> +  tree result = DECL_RESULT (current_function_decl);
> +  return (retval != NULL_TREE
> +       && !processing_template_decl
> +       /* Must be a local, automatic variable.  */
> +       && VAR_P (retval)
> +       && DECL_CONTEXT (retval) == current_function_decl
> +       && !TREE_STATIC (retval)
> +       /* And not a lambda or anonymous union proxy.  */
> +       && !DECL_HAS_VALUE_EXPR_P (retval)
> +       && (DECL_ALIGN (retval) <= DECL_ALIGN (result))
> +       /* The cv-unqualified type of the returned value must be
> the
> +          same as the cv-unqualified return type of the
> +          function.  */
> +       && same_type_p ((TYPE_MAIN_VARIANT (TREE_TYPE (retval))),
> +                       (TYPE_MAIN_VARIANT (functype)))
> +       /* And the returned value must be non-volatile.  */
> +       && !TYPE_VOLATILE (TREE_TYPE (retval)));
> +}
> +
>  /* Check that returning RETVAL from the current function is valid.
>     Return an expression explicitly showing all conversions required
> to
>     change RETVAL into the function return type, and to assign it to
> @@ -9130,7 +9182,6 @@ check_return_expr (tree retval, bool
> *no_warning)
>       the declared type is incomplete.  */
>    tree functype;
>    int fn_returns_value_p;
> -  bool named_return_value_okay_p;
>  
>    *no_warning = false;
>  
> @@ -9342,24 +9393,7 @@ check_return_expr (tree retval, bool
> *no_warning)
>  
>       See finish_function and finalize_nrv for the rest of this
> optimization.  */
>  
> -  named_return_value_okay_p = 
> -    (retval != NULL_TREE
> -     && !processing_template_decl
> -     /* Must be a local, automatic variable.  */
> -     && VAR_P (retval)
> -     && DECL_CONTEXT (retval) == current_function_decl
> -     && ! TREE_STATIC (retval)
> -     /* And not a lambda or anonymous union proxy.  */
> -     && !DECL_HAS_VALUE_EXPR_P (retval)
> -     && (DECL_ALIGN (retval) <= DECL_ALIGN (result))
> -     /* The cv-unqualified type of the returned value must be the
> -        same as the cv-unqualified return type of the
> -        function.  */
> -     && same_type_p ((TYPE_MAIN_VARIANT (TREE_TYPE (retval))),
> -                     (TYPE_MAIN_VARIANT (functype)))
> -     /* And the returned value must be non-volatile.  */
> -     && ! TYPE_VOLATILE (TREE_TYPE (retval)));
> -     
> +  bool named_return_value_okay_p = can_do_nrvo_p (retval, functype);
>    if (fn_returns_value_p && flag_elide_constructors)
>      {
>        if (named_return_value_okay_p
> @@ -9375,6 +9409,35 @@ check_return_expr (tree retval, bool
> *no_warning)
>    if (!retval)
>      return NULL_TREE;
>  
> +  /* Warn about wrong usage of std::move in a return statement.  */
> +  if (!named_return_value_okay_p
> +      && warn_pessimizing_move
> +      /* C++98 doesn't know move.  */
> +      && (cxx_dialect >= cxx11)
> +      /* This is only interesting for class type.  */
> +      && CLASS_TYPE_P (functype)
> +      /* We're looking for *std::move<T&> (&arg).  */
> +      && REFERENCE_REF_P (retval)
> +      && TREE_CODE (TREE_OPERAND (retval, 0)) == CALL_EXPR)
> +    {
> +      tree fn = TREE_OPERAND (retval, 0);
> +      if (is_std_move_p (fn))
> +     {
> +       tree arg = CALL_EXPR_ARG (fn, 0);
> +       STRIP_NOPS (arg);
> +       if (TREE_CODE (arg) == ADDR_EXPR)
> +         arg = TREE_OPERAND (arg, 0);
> +       /* 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 (OPT_Wpessimizing_move, "moving a local
> object "
> +                        "in a return statement prevents copy
> elision"))
> +             inform (input_location, "remove %<std::move%>
> call");
> +         }
> +     }
> +    }
> +
>    /* Do any required conversions.  */
>    if (retval == result || DECL_CONSTRUCTOR_P
> (current_function_decl))
>      /* No conversions are required.  */
> diff --git gcc/doc/invoke.texi gcc/doc/invoke.texi
> index f8287153be1..7a79ba32fc7 100644
> --- gcc/doc/invoke.texi
> +++ gcc/doc/invoke.texi
> @@ -231,6 +231,7 @@ in the following sections.
>  -Wdelete-non-virtual-dtor -Wdeprecated-copy  -Wliteral-suffix @gol
>  -Wmultiple-inheritance @gol
>  -Wnamespaces  -Wnarrowing @gol
> +-Wpessimizing-move @gol
>  -Wnoexcept  -Wnoexcept-type  -Wclass-memaccess @gol
>  -Wnon-virtual-dtor  -Wreorder  -Wregister @gol
>  -Weffc++  -Wstrict-null-sentinel  -Wtemplates @gol
> @@ -3131,6 +3132,31 @@ The compiler rearranges the member
> initializers for @code{i}
>  and @code{j} to match the declaration order of the members, emitting
>  a warning to that effect.  This warning is enabled by @option{-
> Wall}.
>  
> +@item -Wno-pessimizing-move @r{(C++ and Objective-C++ only)}
> +@opindex Wpessimizing-move
> +@opindex Wno-pessimizing-move
> +This warning warns when a call to @code{std::move} prevents copy
> +elision.  A typical scenario when copy elision can occur is when
> returning in
> +a function with a class return type, when the expression being
> returned is the
> +name of a non-volatile automatic object, and is not a function
> parameter, and
> +has the same type as the function return type.
> +
> +@smallexample
> +struct T @{
> +@dots{}
> +@};
> +T fn()
> +@{
> +  T t;
> +  @dots{}
> +  return std::move (t);
> +@}
> +@end smallexample
> +
> +But in this example, the @code{std::move} call prevents copy
> elision.
> +
> +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
> @@ -4036,6 +4062,7 @@ Options} and @ref{Objective-C and Objective-C++ 
> Dialect Options}.
>  -Wnonnull-compare  @gol
>  -Wopenmp-simd @gol
>  -Wparentheses  @gol
> +-Wpessimizing-move @r{(only for C++)}  @gol
>  -Wpointer-sign  @gol
>  -Wreorder   @gol
>  -Wrestrict   @gol
> diff --git gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move1.C
> gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move1.C
> index e69de29bb2d..858bed6065e 100644
> --- gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move1.C
> +++ gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move1.C
> @@ -0,0 +1,132 @@
> +// PR c++/86981
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wpessimizing-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 g;
> +
> +T
> +fn1 ()
> +{
> +  T t;
> +  return std::move (t); // { dg-warning "moving a local object in a
> return statement prevents copy elision" }
> +}
> +
> +T
> +fn2 ()
> +{
> +  // Not a local variable.
> +  return std::move (g);
> +}
> +
> +int
> +fn3 ()
> +{
> +  int i = 42;
> +  // Not a class type.
> +  return std::move (i);
> +}
> +
> +T
> +fn4 (bool b)
> +{
> +  T t;
> +  if (b)
> +    throw std::move (t);
> +  return std::move (t); // { dg-warning "moving a local object in a
> return statement prevents copy elision" }
> +}
> +
> +T
> +fn5 (T t)
> +{
> +  // Function parameter; std::move is redundant but not pessimizing.
> +  return std::move (t);
> +}
> +
> +U
> +fn6 (T t, U u, bool b)
> +{
> +  if (b)
> +    return std::move (t);
> +  else
> +    // Function parameter; std::move is redundant but not
> pessimizing.
> +    return std::move (u);
> +}
> +
> +U
> +fn6 (bool b)
> +{
> +  T t;
> +  U u;
> +  if (b)
> +    return std::move (t);
> +  else
> +    return std::move (u); // { dg-warning "moving a local object in
> a return statement prevents copy elision" }
> +}
> +
> +T
> +fn7 ()
> +{
> +  static T t;
> +  // Non-local; don't warn.
> +  return std::move (t);
> +}
> +
> +T
> +fn8 ()
> +{
> +  return T();
> +}
> +
> +T
> +fn9 (int i)
> +{
> +  T t;
> +
> +  switch (i)
> +    {
> +    case 1:
> +      return std::move ((t)); // { dg-warning "moving a local object
> in a return statement prevents copy elision" }
> +    case 2:
> +      return (std::move (t)); // { dg-warning "moving a local object
> in a return statement prevents copy elision" }
> +    default:
> +      return (std::move ((t))); // { dg-warning "moving a local
> object in a return statement prevents copy elision" }
> +    }
> +}
> +
> +int
> +fn10 ()
> +{
> +  return std::move (42);
> +}
> diff --git gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move2.C
> gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move2.C
> index e69de29bb2d..0ee6e0535dc 100644
> --- gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move2.C
> +++ gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move2.C
> @@ -0,0 +1,14 @@
> +// PR c++/86981
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wpessimizing-move" }
> +
> +#include <string>
> +#include <tuple>
> +#include <utility>
> +
> +std::tuple<std::string, std::string>
> +foo ()
> +{
> +  std::pair<std::string, std::string> p;
> +  return std::move (p);
> +}
> diff --git gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move3.C
> gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move3.C
> index e69de29bb2d..a1af1230b68 100644
> --- gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move3.C
> +++ gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move3.C
> @@ -0,0 +1,59 @@
> +// PR c++/86981
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wpessimizing-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 "moving a local object in a
> return statement prevents copy elision" }
> +}
> +
> +template<typename Tp1, typename Tp2>
> +Tp1
> +fn2 ()
> +{
> +  Tp2 t;
> +  return std::move (t); // { dg-warning "moving a local object in a
> return statement prevents copy elision" }
> +}
> +
> +template<typename Tp1, typename Tp2>
> +Tp1
> +fn3 ()
> +{
> +  Tp2 t;
> +  return std::move (t);
> +}
> +
> +int
> +main ()
> +{
> +  fn1<T>();
> +  fn2<T, T>();
> +  fn3<U, T>();
> +}
> diff --git gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move4.C
> gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move4.C
> index e69de29bb2d..59e148e9f91 100644
> --- gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move4.C
> +++ gcc/testsuite/g++.dg/cpp0x/Wpessimizing-move4.C
> @@ -0,0 +1,46 @@
> +// PR c++/86981
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wpessimizing-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/cpp1z/Wpessimizing-move1.C
> gcc/testsuite/g++.dg/cpp1z/Wpessimizing-move1.C
> index e69de29bb2d..59741889707 100644
> --- gcc/testsuite/g++.dg/cpp1z/Wpessimizing-move1.C
> +++ gcc/testsuite/g++.dg/cpp1z/Wpessimizing-move1.C
> @@ -0,0 +1,18 @@
> +// PR c++/86981
> +// { dg-options "-Wpessimizing-move -std=c++17" }
> +
> +#include <utility>
> +#include <optional>
> +
> +struct T {
> +  T() { }
> +  T(const T&) { }
> +  T(T&&) { }
> +};
> +
> +std::optional<T>
> +fn ()
> +{
> +  T t;
> +  return std::move (t);
> +}

Reply via email to