c++: Implement -Wdangling-reference [PR106393]

Message ID 20221021232824.1093138-1-polacek@redhat.com
State Accepted
Headers
Series c++: Implement -Wdangling-reference [PR106393] |

Checks

Context Check Description
snail/gcc-patch-check success Github commit url

Commit Message

Marek Polacek Oct. 21, 2022, 11:28 p.m. UTC
  This patch implements a new experimental warning (enabled by -Wextra) to
detect references bound to temporaries whose lifetime has ended.  The
primary motivation is the Note in
<https://en.cppreference.com/w/cpp/algorithm/max>:

  Capturing the result of std::max by reference produces a dangling reference
  if one of the parameters is a temporary and that parameter is returned:

  int n = 1;
  const int& r = std::max(n-1, n+1); // r is dangling

That's because both temporaries for n-1 and n+1 are destroyed at the end
of the full expression.  With this warning enabled, you'll get:

g.C:3:12: warning: possibly dangling reference to a temporary [-Wdangling-reference]
    3 | const int& r = std::max(n-1, n+1);
      |            ^
g.C:3:24: note: the temporary was destroyed at the end of the full expression 'std::max<int>((n - 1), (n + 1))'
    3 | const int& r = std::max(n-1, n+1);
      |                ~~~~~~~~^~~~~~~~~~

The warning works by checking if a reference is initialized with a function
that returns a reference, and at least one parameter of the function is
a reference that is bound to a temporary.  It assumes that such a function
actually returns one of its arguments!  (I added code to check_return_expr
to suppress the warning when we've seen the definition of the function
and we can say that it can return something other than its parameter.)

It doesn't warn when the function in question is a member function, otherwise
it'd emit loads of warnings for valid code like obj.emplace<T>({0}, 0).

It warns in member initializer lists as well:

  const int& f(const int& i) { return i; }
  struct S {
    const int &r; // { dg-warning "dangling reference" }
    S() : r(f(10)) { } // { dg-message "destroyed" }
  };

I've run the testsuite/bootstrap with the warning enabled by default.
There were just a few FAILs:
* g++.dg/warn/Wdangling-pointer-2.C
* 20_util/any/misc/any_cast.cc
* 20_util/forward/c_neg.cc
* 20_util/forward/f_neg.cc
* experimental/any/misc/any_cast.cc
all of these look like genuine bugs.  A bootstrap with the warning
enabled by default passed.

When testing a previous version of the patch, there were many FAILs in
libstdc++'s 22_locale/; all of them because the warning triggered on

  const test_type& obj = std::use_facet<test_type>(std::locale());

but this code looks valid -- std::use_facet doesn't return a reference
to its parameter.  Therefore I added code to suppress the warning when
the call is std::use_facet.  Now 22_locale/* pass even with the warning
on.  We could exclude more std:: functions like this if desirable.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

	PR c++/106393

gcc/c-family/ChangeLog:

	* c.opt (Wdangling-reference): New.

gcc/cp/ChangeLog:

	* call.cc (expr_represents_temporary_p): New, factored out of
	conv_binds_ref_to_temporary.
	(conv_binds_ref_to_temporary): Don't return false just because a ck_base
	is missing.  Use expr_represents_temporary_p.
	(find_initializing_call_expr): New.
	(do_warn_dangling_reference): New.
	(extend_ref_init_temps): Call do_warn_dangling_reference.
	* typeck.cc (check_return_expr): Suppress -Wdangling-reference
	warnings.

gcc/ChangeLog:

	* doc/invoke.texi: Document -Wdangling-reference.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp23/elision4.C: Use -Wdangling-reference, add dg-warning.
	* g++.dg/cpp23/elision7.C: Likewise.
	* g++.dg/warn/Wdangling-reference1.C: New test.
	* g++.dg/warn/Wdangling-reference2.C: New test.
---
 gcc/c-family/c.opt                            |   4 +
 gcc/cp/call.cc                                | 138 ++++++++++++++++--
 gcc/cp/cp-tree.h                              |   4 +-
 gcc/cp/typeck.cc                              |  10 ++
 gcc/doc/invoke.texi                           |  34 ++++-
 gcc/testsuite/g++.dg/cpp23/elision4.C         |   5 +-
 gcc/testsuite/g++.dg/cpp23/elision7.C         |   3 +-
 .../g++.dg/warn/Wdangling-reference1.C        | 103 +++++++++++++
 .../g++.dg/warn/Wdangling-reference2.C        |  28 ++++
 9 files changed, 312 insertions(+), 17 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference1.C
 create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference2.C


base-commit: 5792208f5124f687376f25798668d105d7ddb270
  

Comments

Jason Merrill Oct. 24, 2022, 5:30 p.m. UTC | #1
On 10/21/22 19:28, Marek Polacek wrote:
> This patch implements a new experimental warning (enabled by -Wextra) to
> detect references bound to temporaries whose lifetime has ended.  The

Great!

> primary motivation is the Note in
> <https://en.cppreference.com/w/cpp/algorithm/max>:
> 
>    Capturing the result of std::max by reference produces a dangling reference
>    if one of the parameters is a temporary and that parameter is returned:
> 
>    int n = 1;
>    const int& r = std::max(n-1, n+1); // r is dangling
> 
> That's because both temporaries for n-1 and n+1 are destroyed at the end
> of the full expression.  With this warning enabled, you'll get:
> 
> g.C:3:12: warning: possibly dangling reference to a temporary [-Wdangling-reference]
>      3 | const int& r = std::max(n-1, n+1);
>        |            ^
> g.C:3:24: note: the temporary was destroyed at the end of the full expression 'std::max<int>((n - 1), (n + 1))'
>      3 | const int& r = std::max(n-1, n+1);
>        |                ~~~~~~~~^~~~~~~~~~
> 
> The warning works by checking if a reference is initialized with a function
> that returns a reference, and at least one parameter of the function is
> a reference that is bound to a temporary.  It assumes that such a function
> actually returns one of its arguments!  (I added code to check_return_expr
> to suppress the warning when we've seen the definition of the function
> and we can say that it can return something other than its parameter.)

Hmm, that misses returning a reference to a subobject or container 
element that will also go away when the object is destroyed.  Does it 
also avoid a lot of false positives?

> It doesn't warn when the function in question is a member function, otherwise
> it'd emit loads of warnings for valid code like obj.emplace<T>({0}, 0).

We had discussed warning if the object argument is a temporary (and for 
the above check, the function returns *this)?

> It warns in member initializer lists as well:
> 
>    const int& f(const int& i) { return i; }
>    struct S {
>      const int &r; // { dg-warning "dangling reference" }
>      S() : r(f(10)) { } // { dg-message "destroyed" }
>    };
> 
> I've run the testsuite/bootstrap with the warning enabled by default.
> There were just a few FAILs:
> * g++.dg/warn/Wdangling-pointer-2.C
> * 20_util/any/misc/any_cast.cc
> * 20_util/forward/c_neg.cc
> * 20_util/forward/f_neg.cc
> * experimental/any/misc/any_cast.cc
> all of these look like genuine bugs.  A bootstrap with the warning
> enabled by default passed.
> 
> When testing a previous version of the patch, there were many FAILs in
> libstdc++'s 22_locale/; all of them because the warning triggered on
> 
>    const test_type& obj = std::use_facet<test_type>(std::locale());
> 
> but this code looks valid -- std::use_facet doesn't return a reference
> to its parameter.  Therefore I added code to suppress the warning when
> the call is std::use_facet.  Now 22_locale/* pass even with the warning
> on.  We could exclude more std:: functions like this if desirable.

Instead of adding special cases in the compiler, let's disable the 
warning around the definition of use_facet (and adjust the compiler as 
needed so that avoids the warning).

I was remembering range adaptors being a stated motivation for Nico's 
P2012, but looking back at the paper I now see that this problem was 
avoided for them by disallowing rvalue arguments to range composition.

> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> 	PR c++/106393
> 
> gcc/c-family/ChangeLog:
> 
> 	* c.opt (Wdangling-reference): New.
> 
> gcc/cp/ChangeLog:
> 
> 	* call.cc (expr_represents_temporary_p): New, factored out of
> 	conv_binds_ref_to_temporary.
> 	(conv_binds_ref_to_temporary): Don't return false just because a ck_base
> 	is missing.  Use expr_represents_temporary_p.
> 	(find_initializing_call_expr): New.
> 	(do_warn_dangling_reference): New.
> 	(extend_ref_init_temps): Call do_warn_dangling_reference.
> 	* typeck.cc (check_return_expr): Suppress -Wdangling-reference
> 	warnings.
> 
> gcc/ChangeLog:
> 
> 	* doc/invoke.texi: Document -Wdangling-reference.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp23/elision4.C: Use -Wdangling-reference, add dg-warning.
> 	* g++.dg/cpp23/elision7.C: Likewise.
> 	* g++.dg/warn/Wdangling-reference1.C: New test.
> 	* g++.dg/warn/Wdangling-reference2.C: New test.

Could use a test with a virtual base.

> ---
>   gcc/c-family/c.opt                            |   4 +
>   gcc/cp/call.cc                                | 138 ++++++++++++++++--
>   gcc/cp/cp-tree.h                              |   4 +-
>   gcc/cp/typeck.cc                              |  10 ++
>   gcc/doc/invoke.texi                           |  34 ++++-
>   gcc/testsuite/g++.dg/cpp23/elision4.C         |   5 +-
>   gcc/testsuite/g++.dg/cpp23/elision7.C         |   3 +-
>   .../g++.dg/warn/Wdangling-reference1.C        | 103 +++++++++++++
>   .../g++.dg/warn/Wdangling-reference2.C        |  28 ++++
>   9 files changed, 312 insertions(+), 17 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference1.C
>   create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference2.C
> 
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index 01d480759ae..02d79991aeb 100644
> --- a/gcc/c-family/c.opt
> +++ b/gcc/c-family/c.opt
> @@ -555,6 +555,10 @@ Wdangling-pointer=
>   C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_dangling_pointer) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) IntegerRange(0, 2)
>   Warn for uses of pointers to auto variables whose lifetime has ended.
>   
> +Wdangling-reference
> +C++ ObjC++ Var(warn_dangling_reference) Warning LangEnabledBy(C++ ObjC++, Wextra)
> +Warn when a reference is bound to a temporary whose lifetime has ended.
> +
>   Wdate-time
>   C ObjC C++ ObjC++ CPP(warn_date_time) CppReason(CPP_W_DATE_TIME) Var(cpp_warn_date_time) Init(0) Warning
>   Warn about __TIME__, __DATE__ and __TIMESTAMP__ usage.
> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> index 6a34e9c2ae1..43e607987a0 100644
> --- a/gcc/cp/call.cc
> +++ b/gcc/cp/call.cc
> @@ -9313,6 +9313,16 @@ conv_binds_ref_to_prvalue (conversion *c)
>     return conv_is_prvalue (next_conversion (c));
>   }
>   
> +/* True iff EXPR represents a (subobject of a) temporary.  */
> +
> +static bool
> +expr_represents_temporary_p (tree expr)
> +{
> +  while (handled_component_p (expr))
> +    expr = TREE_OPERAND (expr, 0);
> +  return TREE_CODE (expr) == TARGET_EXPR;
> +}
> +
>   /* True iff C is a conversion that binds a reference to a temporary.
>      This is a superset of conv_binds_ref_to_prvalue: here we're also
>      interested in xvalues.  */
> @@ -9330,18 +9340,14 @@ conv_binds_ref_to_temporary (conversion *c)
>          struct Derived : Base {};
>          const Base& b(Derived{});
>        where we bind 'b' to the Base subobject of a temporary object of type
> -     Derived.  The subobject is an xvalue; the whole object is a prvalue.  */
> -  if (c->kind != ck_base)
> -    return false;
> -  c = next_conversion (c);
> -  if (c->kind == ck_identity && c->u.expr)
> -    {
> -      tree expr = c->u.expr;
> -      while (handled_component_p (expr))
> -	expr = TREE_OPERAND (expr, 0);
> -      if (TREE_CODE (expr) == TARGET_EXPR)
> -	return true;
> -    }
> +     Derived.  The subobject is an xvalue; the whole object is a prvalue.
> +
> +     The ck_base doesn't have to be present for cases like X{}.m.  */
> +  if (c->kind == ck_base)
> +    c = next_conversion (c);
> +  if (c->kind == ck_identity && c->u.expr
> +      && expr_represents_temporary_p (c->u.expr))
> +    return true;
>     return false;
>   }
>   
> @@ -13428,6 +13434,111 @@ initialize_reference (tree type, tree expr,
>     return expr;
>   }
>   
> +/* Helper for do_warn_dangling_reference to find a non-nested CALL_EXPR
> +   that initializes the LHS, or NULL_TREE if none found.  For instance:
> +
> +     const int& r = (42, f(1)); // f(1)
> +     const int& t = b ? f(1) : f(2); // f(1)
> +     const int& z = (f(1), 42); // NULL_TREE
> +
> +   EXPR is the initializer.  */
> +
> +static tree
> +find_initializing_call_expr (tree expr)
> +{
> +  STRIP_NOPS (expr);
> +  switch (TREE_CODE (expr))
> +    {
> +    case CALL_EXPR:
> +      return expr;
> +    case COMPOUND_EXPR:
> +      return find_initializing_call_expr (TREE_OPERAND (expr, 1));
> +    case COND_EXPR:
> +      if (tree t = find_initializing_call_expr (TREE_OPERAND (expr, 1)))
> +	return t;
> +      return find_initializing_call_expr (TREE_OPERAND (expr, 2));

For COND_EXPR I think we want to check both sides, in case there are 
calls on both sides but only the second one has a problematic temporary.

> +    case PAREN_EXPR:
> +      return find_initializing_call_expr (TREE_OPERAND (expr, 0));
> +    default:
> +      return NULL_TREE;
> +    }
> +}
> +
> +/* Implement -Wdangling-reference, to detect cases like
> +
> +     int n = 1;
> +     const int& r = std::max(n - 1, n + 1); // r is dangling
> +
> +   This creates temporaries from the arguments, returns a reference to
> +   one of the temporaries, but both temporaries are destroyed at the end
> +   of the full expression.
> +
> +   This works by checking if a reference is initialized with a function
> +   that returns a reference, and at least one parameter of the function
> +   is a reference that is bound to a temporary.  It assumes that such a
> +   function actually returns one of its arguments.
> +
> +   This warning doesn't warn when the function in question is a member
> +   function.
> +
> +   DECL is the reference being initialized, CALL is the initializer.  */
> +
> +static void
> +do_warn_dangling_reference (const_tree decl, tree call)
> +{
> +  if (!warn_dangling_reference)
> +    return;
> +  if (!TYPE_REF_P (TREE_TYPE (decl)))
> +    return;
> +  call = find_initializing_call_expr (call);
> +  if (call == NULL_TREE)
> +    return;
> +
> +  tree fndecl = cp_get_callee_fndecl_nofold (call);
> +  if (!fndecl
> +      || warning_suppressed_p (fndecl, OPT_Wdangling_reference)
> +      /* Don't warn about member functions; the warning would trigger in
> +	 valid code like
> +	   std::any a(...);
> +	   S& s = a.emplace<S>({0}, 0);
> +	 which constructs a new object and returns a reference to it.  */
> +      || DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
> +      /* It seems unreasonable to warn about operator functions.  */
> +      || DECL_OVERLOADED_OPERATOR_P (fndecl)

I guess I'd expect false positives on << and >> because of iostreams, do 
you see false positives with other operators?

> +      /* If the function doesn't return a reference, don't warn.  This can
> +	 be e.g.
> +	   const int& z = std::min({1, 2, 3, 4, 5, 6, 7});
> +	 which doesn't dangle: std::min here returns an int.  */
> +      || !TYPE_REF_P (TREE_TYPE (TREE_TYPE (fndecl))))
> +    return;
> +
> +  /* Mitigate some known cases.  */
> +  if (decl_in_std_namespace_p (fndecl))
> +    if (tree name = DECL_NAME (fndecl))
> +      if (id_equal (name, "use_facet"))
> +	return;
> +
> +  for (int i = 0; i < call_expr_nargs (call); ++i)
> +    {
> +      /* We're looking to see if ARG is something like
> +	  (const int &) &TARGET_EXPR <...>.  */
> +      tree arg = CALL_EXPR_ARG (call, i);
> +      STRIP_NOPS (arg);
> +      if (TREE_CODE (arg) == ADDR_EXPR)
> +	arg = TREE_OPERAND (arg, 0);
> +      if (expr_represents_temporary_p (arg))
> +	{
> +	  auto_diagnostic_group d;
> +	  if (warning_at (DECL_SOURCE_LOCATION (decl),
> +			  OPT_Wdangling_reference,
> +			  "possibly dangling reference to a temporary"))
> +	    inform (EXPR_LOCATION (call), "the temporary was destroyed at "
> +		    "the end of the full expression %qE", call);
> +	  return;
> +	}
> +    }
> +}
> +
>   /* If *P is an xvalue expression, prevent temporary lifetime extension if it
>      gets used to initialize a reference.  */
>   
> @@ -13525,6 +13636,9 @@ extend_ref_init_temps (tree decl, tree init, vec<tree, va_gc> **cleanups,
>     tree type = TREE_TYPE (init);
>     if (processing_template_decl)
>       return init;
> +
> +  do_warn_dangling_reference (decl, init);
> +
>     if (TYPE_REF_P (type))
>       init = extend_ref_init_temps_1 (decl, init, cleanups, cond_guard);
>     else
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 60a25101049..8ddf55ce2b0 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -459,7 +459,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
>         TI_PENDING_TEMPLATE_FLAG.
>         TEMPLATE_PARMS_FOR_INLINE.
>         DELETE_EXPR_USE_VEC (in DELETE_EXPR).
> -      (TREE_CALLS_NEW) (in _EXPR or _REF) (commented-out).
>         ICS_ELLIPSIS_FLAG (in _CONV)
>         DECL_INITIALIZED_P (in VAR_DECL)
>         TYPENAME_IS_CLASS_P (in TYPENAME_TYPE)
> @@ -4558,6 +4557,9 @@ get_vec_init_expr (tree t)
>      When appearing in a CONSTRUCTOR, the expression is an unconverted
>      compound literal.
>   
> +   When appearing in a CALL_EXPR, it means that it is a call to
> +   a constructor.
> +
>      When appearing in a FIELD_DECL, it means that this field
>      has been duly initialized in its constructor.  */
>   #define TREE_HAS_CONSTRUCTOR(NODE) (TREE_LANG_FLAG_4 (NODE))
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 16e7d85793d..5a22eee8ebf 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -11238,6 +11238,16 @@ check_return_expr (tree retval, bool *no_warning)
>     if (processing_template_decl)
>       return saved_retval;
>   
> +  /* A naive attempt to reduce the number of -Wdangling-reference false
> +     positives: if we know that this function can return something other
> +     than one of its parameters, suppress the warning.  */
> +  if (warn_dangling_reference
> +      && TYPE_REF_P (functype)
> +      && bare_retval
> +      && (!REFERENCE_REF_P (bare_retval)
> +	  || TREE_CODE (TREE_OPERAND (bare_retval, 0)) != PARM_DECL))
> +    suppress_warning (current_function_decl, OPT_Wdangling_reference);
> +
>     /* Actually copy the value returned into the appropriate location.  */
>     if (retval && retval != result)
>       retval = cp_build_init_expr (result, retval);
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index 09548c4528c..28bb395ce6c 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -249,7 +249,8 @@ in the following sections.
>   -Wno-class-conversion  -Wclass-memaccess @gol
>   -Wcomma-subscript  -Wconditionally-supported @gol
>   -Wno-conversion-null  -Wctad-maybe-unsupported @gol
> --Wctor-dtor-privacy  -Wno-delete-incomplete @gol
> +-Wctor-dtor-privacy  -Wdangling-reference @gol
> +-Wno-delete-incomplete @gol
>   -Wdelete-non-virtual-dtor  -Wno-deprecated-array-compare @gol
>   -Wdeprecated-copy -Wdeprecated-copy-dtor @gol
>   -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion @gol
> @@ -3627,6 +3628,36 @@ public static member functions.  Also warn if there are no non-private
>   methods, and there's at least one private member function that isn't
>   a constructor or destructor.
>   
> +@item -Wdangling-reference @r{(C++ and Objective-C++ only)}
> +@opindex Wdangling-reference
> +@opindex Wno-dangling-reference
> +Warn when a reference is bound to a temporary whose lifetime has ended.
> +For example:
> +
> +@smallexample
> +int n = 1;
> +const int& r = std::max(n - 1, n + 1); // r is dangling
> +@end smallexample
> +
> +In the example above, two temporaries are created, one for each
> +argument, and a reference to one of the temporaries is returned.
> +However, both temporaries are destroyed at the end of the full
> +expression, so the reference @code{r} is dangling.  This warning
> +also detects dangling references in member initializer lists:
> +
> +@smallexample
> +const int& f(const int& i) @{ return i; @}
> +struct S @{
> +  const int &r; // r is dangling
> +  S() : r(f(10)) @{ @}
> +@};
> +@end smallexample
> +
> +Member functions are not checked.  Certain standard functions, such
> +as @code{std::use_facet}, are also excluded from checking.
> +
> +This warning is enabled by @option{-Wextra}.
> +
>   @item -Wdelete-non-virtual-dtor @r{(C++ and Objective-C++ only)}
>   @opindex Wdelete-non-virtual-dtor
>   @opindex Wno-delete-non-virtual-dtor
> @@ -5936,6 +5967,7 @@ name is still supported, but the newer name is more descriptive.)
>   
>   @gccoptlist{-Wclobbered  @gol
>   -Wcast-function-type  @gol
> +-Wdangling-reference @r{(C++ only)} @gol
>   -Wdeprecated-copy @r{(C++ only)} @gol
>   -Wempty-body  @gol
>   -Wenum-conversion @r{(C only)} @gol
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C
> index c19b86b8b5f..d39053ad741 100644
> --- a/gcc/testsuite/g++.dg/cpp23/elision4.C
> +++ b/gcc/testsuite/g++.dg/cpp23/elision4.C
> @@ -1,5 +1,6 @@
>   // PR c++/101165 - P2266R1 - Simpler implicit move
>   // { dg-do compile { target c++23 } }
> +// { dg-options "-Wdangling-reference" }
>   // Test from P2266R1, $ 5.2. LibreOffice OString constructor.
>   
>   struct X {
> @@ -33,6 +34,6 @@ T& temporary2(T&& x) { return static_cast<T&>(x); }
>   void
>   test ()
>   {
> -  int& r1 = temporary1 (42);
> -  int& r2 = temporary2 (42);
> +  int& r1 = temporary1 (42); // { dg-warning "dangling reference" }
> +  int& r2 = temporary2 (42); // { dg-warning "dangling reference" }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C
> index 19fa89ae133..0045842b34f 100644
> --- a/gcc/testsuite/g++.dg/cpp23/elision7.C
> +++ b/gcc/testsuite/g++.dg/cpp23/elision7.C
> @@ -1,5 +1,6 @@
>   // PR c++/101165 - P2266R1 - Simpler implicit move
>   // { dg-do compile { target c++23 } }
> +// { dg-options "-Wdangling-reference" }
>   
>   struct X {
>     X ();
> @@ -68,5 +69,5 @@ f7 (T &&t)
>   void
>   do_f7 ()
>   {
> -  const int &x = f7 (0);
> +  const int &x = f7 (0); // { dg-warning "dangling reference" }
>   }
> diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C
> new file mode 100644
> index 00000000000..e0324d38f67
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C
> @@ -0,0 +1,103 @@
> +// PR c++/106393
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wdangling-reference" }
> +
> +const int& f(const int& i) { return i; }
> +const int& h(int);
> +int g;
> +const int& globref(const int&) { return g; }
> +struct X {
> +  int* i;
> +  operator const int&() const { return *i; }
> +};
> +X x{&g};
> +
> +const int& r1 = f(10); // { dg-warning "dangling reference" }
> +// r2 = _ZGR2r2_ = (int) *f ((const int &) &TARGET_EXPR <D.2429, 10>) + 1; (const int &) &_ZGR2r2_
> +const int& r2 = f(10) + 1;
> +// Don't warn here, we have
> +//   r3 = f (X::operator const int& (&x))
> +const int& r3 = f(x);
> +// Don't warn here, because we've seen the definition of globref
> +// and could figure out that it may not return one of its parms.
> +// Questionable -- it can also hide bugs --, but it helps here.

We could suppress specifically for the case of returning a variable with 
static storage duration?

> +const int& r4 = globref(1);
> +const int& r5 = (42, f(10)); // { dg-warning "dangling reference" }
> +const int& r6 = (f(10), 42);
> +const int& r7 = (f(10)); // { dg-warning "dangling reference" }
> +const int& r8 = g ? f(10) : f(9); // { dg-warning "dangling reference" }
> +const int& r9 = (42, g ? f(10) : f(9)); // { dg-warning "dangling reference" }
> +const int& r10 = (g ? f(10) : f(9), 42);
> +// Binds to a reference temporary for r11.
> +const int& r11 = g ? f(10) : 9;

Why no warning?

> +// Invalid, but we don't warn here yet.
> +// r12 = f (f ((const int &) &TARGET_EXPR <D.2459, 1>))
> +const int& r12 = f(f(1));

This should be a simple recursion?

> +const int& r13 = h(f(1));
> +// Other forms of initializers.
> +const int& r14(f(10)); // { dg-warning "dangling reference" }
> +const int& r15(f(10)); // { dg-warning "dangling reference" }
> +// Returns a ref, but doesn't have a parameter of reference type.
> +const int& r16 = h(10);
> +
> +// OK: the reference is bound to the 10 so still valid at the point
> +// where it's copied into i1.
> +int i1 = f(10);
> +
> +int
> +test1 ()
> +{
> +  const int &lr = f(10); // { dg-warning "dangling reference" }
> +  int i2 = f(10);
> +  return lr;
> +}
> +
> +struct B { };
> +struct D : B { };
> +struct C {
> +  D d;
> +};
> +
> +C c;
> +D d;
> +
> +using U = D[3];
> +
> +const B& frotz(const D&);
> +const B& b1 = frotz(C{}.d); // { dg-warning "dangling reference" }
> +const B& b2 = frotz(D{}); // { dg-warning "dangling reference" }
> +const B& b3 = frotz(c.d);
> +const B& b4 = frotz(d);
> +const B& b5 = frotz(U{}[0]); // { dg-warning "dangling reference" }
> +
> +struct E {
> +  E(int);
> +};
> +const E& operator*(const E&);
> +const E& b6 = *E(1);
> +
> +struct S {
> +  const int &r; // { dg-warning "dangling reference" }
> +  S() : r(f(10)) { } // { dg-message "destroyed" }
> +};
> +
> +// From cppreference.
> +template<class T>
> +const T& max(const T& a, const T& b)
> +{
> +    return (a < b) ? b : a;
> +}
> +
> +int n = 1;
> +const int& r20 = max(n - 1, n + 1); // { dg-warning "dangling reference" }
> +
> +// Don't warn about member functions.
> +struct Y {
> +  operator int&&();
> +  const int& foo(const int&);
> +};
> +
> +// x1 = Y::operator int&& (&TARGET_EXPR <D.2410, {}>)
> +int&& x1 = Y();
> +int&& x2 = Y{};
> +const int& t1 = Y().foo(10);
> diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C
> new file mode 100644
> index 00000000000..dafdb43f1b9
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C
> @@ -0,0 +1,28 @@
> +// PR c++/106393
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wdangling-reference" }
> +
> +namespace std {
> +struct any {};
> +template <typename _ValueType> _ValueType any_cast(any &&);
> +template <typename _Tp> struct remove_reference { using type = _Tp; };
> +template <typename _Tp> _Tp forward(typename remove_reference<_Tp>::type);
> +template <typename _Tp> typename remove_reference<_Tp>::type move(_Tp);
> +} // namespace std
> +
> +const int &r = std::any_cast<int&>(std::any()); // { dg-warning "dangling reference" }
> +
> +template <class T> struct C {
> +  T t_; // { dg-warning "dangling reference" }
> +  C(T);
> +  template <class U> C(U c) : t_(std::forward<T>(c.t_)) {}
> +};
> +struct A {};
> +struct B {
> +  B(A);
> +};
> +int main() {
> +  A a;
> +  C<A> ca(a);
> +  C<B &&>(std::move(ca));
> +}
> 
> base-commit: 5792208f5124f687376f25798668d105d7ddb270
  
Jonathan Wakely Oct. 25, 2022, 11:34 a.m. UTC | #2
On Mon, 24 Oct 2022 at 18:30, Jason Merrill wrote:
>
> On 10/21/22 19:28, Marek Polacek wrote:
> > When testing a previous version of the patch, there were many FAILs in
> > libstdc++'s 22_locale/; all of them because the warning triggered on
> >
> >    const test_type& obj = std::use_facet<test_type>(std::locale());
> >
> > but this code looks valid -- std::use_facet doesn't return a reference
> > to its parameter.  Therefore I added code to suppress the warning when
> > the call is std::use_facet.  Now 22_locale/* pass even with the warning
> > on.  We could exclude more std:: functions like this if desirable.
>
> Instead of adding special cases in the compiler, let's disable the
> warning around the definition of use_facet (and adjust the compiler as
> needed so that avoids the warning).

I assume you mean using #pragma here. If we disable it around the
definition of use_facet, will that disable it for callers of
use_facet, or only within the definition of use_facet itself?
  
Jonathan Wakely Oct. 25, 2022, 11:50 a.m. UTC | #3
On Sat, 22 Oct 2022 at 00:28, Marek Polacek wrote:
> I've run the testsuite/bootstrap with the warning enabled by default.
> There were just a few FAILs:
> * g++.dg/warn/Wdangling-pointer-2.C
> * 20_util/any/misc/any_cast.cc
> * 20_util/forward/c_neg.cc
> * 20_util/forward/f_neg.cc

These two are XFAIL tests, they're checking for a required static
assert due to misusing std::forward, so I don't think we want to
change them to remove a "bug" (they're meant to be buggy).
I think they should use -Wno-dangling-reference

> * experimental/any/misc/any_cast.cc
> all of these look like genuine bugs.  A bootstrap with the warning
> enabled by default passed.


I've attached a patch for the two any_cast.cc tests that avoids
creating dangling references, without changing the intended behaviour
of the tests.
diff --git a/libstdc++-v3/testsuite/20_util/any/misc/any_cast.cc b/libstdc++-v3/testsuite/20_util/any/misc/any_cast.cc
index 8d63dfbba9b..43f90c336c9 100644
--- a/libstdc++-v3/testsuite/20_util/any/misc/any_cast.cc
+++ b/libstdc++-v3/testsuite/20_util/any/misc/any_cast.cc
@@ -93,7 +93,8 @@ void test03()
   MoveEnabled m;
   MoveEnabled m2 = any_cast<MoveEnabled>(any(m));
   VERIFY(move_count == 1);
-  MoveEnabled&& m3 = any_cast<MoveEnabled&&>(any(m));
+  auto rvalue = [](MoveEnabled&&) { };
+  rvalue(any_cast<MoveEnabled&&>(any(m))); // No move construction, just a cast.
   VERIFY(move_count == 1);
 }
 
diff --git a/libstdc++-v3/testsuite/experimental/any/misc/any_cast.cc b/libstdc++-v3/testsuite/experimental/any/misc/any_cast.cc
index f3ce83ad702..98347f6d37d 100644
--- a/libstdc++-v3/testsuite/experimental/any/misc/any_cast.cc
+++ b/libstdc++-v3/testsuite/experimental/any/misc/any_cast.cc
@@ -92,7 +92,8 @@ void test03()
   MoveEnabled m;
   MoveEnabled m2 = any_cast<MoveEnabled>(any(m));
   VERIFY(move_count == 1);
-  MoveEnabled&& m3 = any_cast<MoveEnabled&&>(any(m));
+  auto rvalue = [](MoveEnabled&&) { }; // Can only be called with an rvalue.
+  rvalue(any_cast<MoveEnabled&&>(any(m))); // Doesn't move, just casts to rval.
   VERIFY(move_count == 1);
   struct MoveDeleted
   {
@@ -101,8 +102,9 @@ void test03()
     MoveDeleted(const MoveDeleted&) = default;
   };
   MoveDeleted md;
-  MoveDeleted&& md2 = any_cast<MoveDeleted>(any(std::move(md)));
-  MoveDeleted&& md3 = any_cast<MoveDeleted&&>(any(std::move(md)));
+  auto rvalue2 = [](MoveDeleted&&) { }; // Can only be called with an rvalue.
+  rvalue2(any_cast<MoveDeleted>(any(std::move(md))));
+  rvalue2(any_cast<MoveDeleted&&>(any(std::move(md))));
 }
 
 void test05()
  
Marek Polacek Oct. 25, 2022, 1:14 p.m. UTC | #4
On Tue, Oct 25, 2022 at 12:34:50PM +0100, Jonathan Wakely wrote:
> On Mon, 24 Oct 2022 at 18:30, Jason Merrill wrote:
> >
> > On 10/21/22 19:28, Marek Polacek wrote:
> > > When testing a previous version of the patch, there were many FAILs in
> > > libstdc++'s 22_locale/; all of them because the warning triggered on
> > >
> > >    const test_type& obj = std::use_facet<test_type>(std::locale());
> > >
> > > but this code looks valid -- std::use_facet doesn't return a reference
> > > to its parameter.  Therefore I added code to suppress the warning when
> > > the call is std::use_facet.  Now 22_locale/* pass even with the warning
> > > on.  We could exclude more std:: functions like this if desirable.
> >
> > Instead of adding special cases in the compiler, let's disable the
> > warning around the definition of use_facet (and adjust the compiler as
> > needed so that avoids the warning).
> 
> I assume you mean using #pragma here. If we disable it around the
> definition of use_facet, will that disable it for callers of
> use_facet, or only within the definition of use_facet itself?

Right, a #pragma will not help, it would only disable the warning
within the definition of use_facet itself.

Another way would be to use some kind of attribute on use_facet but
I didn't find any that would be relevant in this scenario (so I guess
we'd have to add one).

Marek
  
Marek Polacek Oct. 25, 2022, 3:24 p.m. UTC | #5
On Tue, Oct 25, 2022 at 12:50:36PM +0100, Jonathan Wakely wrote:
> On Sat, 22 Oct 2022 at 00:28, Marek Polacek wrote:
> > I've run the testsuite/bootstrap with the warning enabled by default.
> > There were just a few FAILs:
> > * g++.dg/warn/Wdangling-pointer-2.C
> > * 20_util/any/misc/any_cast.cc
> > * 20_util/forward/c_neg.cc
> > * 20_util/forward/f_neg.cc
> 
> These two are XFAIL tests, they're checking for a required static
> assert due to misusing std::forward, so I don't think we want to
> change them to remove a "bug" (they're meant to be buggy).
> I think they should use -Wno-dangling-reference

I think we don't have to do anything: -Wdangling-reference is only 
enabled by -Wextra and that isn't used in these tests.  I was just
curious about the warning's impact.

> > * experimental/any/misc/any_cast.cc
> > all of these look like genuine bugs.  A bootstrap with the warning
> > enabled by default passed.
> 
> 
> I've attached a patch for the two any_cast.cc tests that avoids
> creating dangling references, without changing the intended behaviour
> of the tests.

Thanks a lot!

Marek
  
Jason Merrill Oct. 25, 2022, 3:39 p.m. UTC | #6
On 10/25/22 09:14, Marek Polacek wrote:
> On Tue, Oct 25, 2022 at 12:34:50PM +0100, Jonathan Wakely wrote:
>> On Mon, 24 Oct 2022 at 18:30, Jason Merrill wrote:
>>>
>>> On 10/21/22 19:28, Marek Polacek wrote:
>>>> When testing a previous version of the patch, there were many FAILs in
>>>> libstdc++'s 22_locale/; all of them because the warning triggered on
>>>>
>>>>     const test_type& obj = std::use_facet<test_type>(std::locale());
>>>>
>>>> but this code looks valid -- std::use_facet doesn't return a reference
>>>> to its parameter.  Therefore I added code to suppress the warning when
>>>> the call is std::use_facet.  Now 22_locale/* pass even with the warning
>>>> on.  We could exclude more std:: functions like this if desirable.
>>>
>>> Instead of adding special cases in the compiler, let's disable the
>>> warning around the definition of use_facet (and adjust the compiler as
>>> needed so that avoids the warning).
>>
>> I assume you mean using #pragma here. If we disable it around the
>> definition of use_facet, will that disable it for callers of
>> use_facet, or only within the definition of use_facet itself?
> 
> Right, a #pragma will not help, it would only disable the warning
> within the definition of use_facet itself.

That's why I mentioned adjusting the compiler, i.e. check 
warning_enabled_at (DECL_SOURCE_LOCATION (fn)

Jason
  
Marek Polacek Oct. 25, 2022, 4:35 p.m. UTC | #7
On Tue, Oct 25, 2022 at 11:39:31AM -0400, Jason Merrill wrote:
> On 10/25/22 09:14, Marek Polacek wrote:
> > On Tue, Oct 25, 2022 at 12:34:50PM +0100, Jonathan Wakely wrote:
> > > On Mon, 24 Oct 2022 at 18:30, Jason Merrill wrote:
> > > > 
> > > > On 10/21/22 19:28, Marek Polacek wrote:
> > > > > When testing a previous version of the patch, there were many FAILs in
> > > > > libstdc++'s 22_locale/; all of them because the warning triggered on
> > > > > 
> > > > >     const test_type& obj = std::use_facet<test_type>(std::locale());
> > > > > 
> > > > > but this code looks valid -- std::use_facet doesn't return a reference
> > > > > to its parameter.  Therefore I added code to suppress the warning when
> > > > > the call is std::use_facet.  Now 22_locale/* pass even with the warning
> > > > > on.  We could exclude more std:: functions like this if desirable.
> > > > 
> > > > Instead of adding special cases in the compiler, let's disable the
> > > > warning around the definition of use_facet (and adjust the compiler as
> > > > needed so that avoids the warning).
> > > 
> > > I assume you mean using #pragma here. If we disable it around the
> > > definition of use_facet, will that disable it for callers of
> > > use_facet, or only within the definition of use_facet itself?
> > 
> > Right, a #pragma will not help, it would only disable the warning
> > within the definition of use_facet itself.
> 
> That's why I mentioned adjusting the compiler, i.e. check warning_enabled_at
> (DECL_SOURCE_LOCATION (fn)

Ah, like with -Wdeprecated-copy.  I think I fixed this:

--- a/libstdc++-v3/include/bits/locale_classes.tcc
+++ b/libstdc++-v3/include/bits/locale_classes.tcc
@@ -127,6 +127,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
    *  @return  Reference to facet of type Facet.
    *  @throw  std::bad_cast if @p __loc doesn't contain a facet of type _Facet.
   */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdangling-reference"
   template<typename _Facet>
     const _Facet&
     use_facet(const locale& __loc)
@@ -141,6 +143,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       return static_cast<const _Facet&>(*__facets[__i]);
 #endif
     }
+#pragma GCC diagnostic pop


   // Generic version does nothing.

and then just check warning_enabled_at.  Nice!

Marek
  

Patch

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 01d480759ae..02d79991aeb 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -555,6 +555,10 @@  Wdangling-pointer=
 C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_dangling_pointer) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) IntegerRange(0, 2)
 Warn for uses of pointers to auto variables whose lifetime has ended.
 
+Wdangling-reference
+C++ ObjC++ Var(warn_dangling_reference) Warning LangEnabledBy(C++ ObjC++, Wextra)
+Warn when a reference is bound to a temporary whose lifetime has ended.
+
 Wdate-time
 C ObjC C++ ObjC++ CPP(warn_date_time) CppReason(CPP_W_DATE_TIME) Var(cpp_warn_date_time) Init(0) Warning
 Warn about __TIME__, __DATE__ and __TIMESTAMP__ usage.
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 6a34e9c2ae1..43e607987a0 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9313,6 +9313,16 @@  conv_binds_ref_to_prvalue (conversion *c)
   return conv_is_prvalue (next_conversion (c));
 }
 
+/* True iff EXPR represents a (subobject of a) temporary.  */
+
+static bool
+expr_represents_temporary_p (tree expr)
+{
+  while (handled_component_p (expr))
+    expr = TREE_OPERAND (expr, 0);
+  return TREE_CODE (expr) == TARGET_EXPR;
+}
+
 /* True iff C is a conversion that binds a reference to a temporary.
    This is a superset of conv_binds_ref_to_prvalue: here we're also
    interested in xvalues.  */
@@ -9330,18 +9340,14 @@  conv_binds_ref_to_temporary (conversion *c)
        struct Derived : Base {};
        const Base& b(Derived{});
      where we bind 'b' to the Base subobject of a temporary object of type
-     Derived.  The subobject is an xvalue; the whole object is a prvalue.  */
-  if (c->kind != ck_base)
-    return false;
-  c = next_conversion (c);
-  if (c->kind == ck_identity && c->u.expr)
-    {
-      tree expr = c->u.expr;
-      while (handled_component_p (expr))
-	expr = TREE_OPERAND (expr, 0);
-      if (TREE_CODE (expr) == TARGET_EXPR)
-	return true;
-    }
+     Derived.  The subobject is an xvalue; the whole object is a prvalue.
+
+     The ck_base doesn't have to be present for cases like X{}.m.  */
+  if (c->kind == ck_base)
+    c = next_conversion (c);
+  if (c->kind == ck_identity && c->u.expr
+      && expr_represents_temporary_p (c->u.expr))
+    return true;
   return false;
 }
 
@@ -13428,6 +13434,111 @@  initialize_reference (tree type, tree expr,
   return expr;
 }
 
+/* Helper for do_warn_dangling_reference to find a non-nested CALL_EXPR
+   that initializes the LHS, or NULL_TREE if none found.  For instance:
+
+     const int& r = (42, f(1)); // f(1)
+     const int& t = b ? f(1) : f(2); // f(1)
+     const int& z = (f(1), 42); // NULL_TREE
+
+   EXPR is the initializer.  */
+
+static tree
+find_initializing_call_expr (tree expr)
+{
+  STRIP_NOPS (expr);
+  switch (TREE_CODE (expr))
+    {
+    case CALL_EXPR:
+      return expr;
+    case COMPOUND_EXPR:
+      return find_initializing_call_expr (TREE_OPERAND (expr, 1));
+    case COND_EXPR:
+      if (tree t = find_initializing_call_expr (TREE_OPERAND (expr, 1)))
+	return t;
+      return find_initializing_call_expr (TREE_OPERAND (expr, 2));
+    case PAREN_EXPR:
+      return find_initializing_call_expr (TREE_OPERAND (expr, 0));
+    default:
+      return NULL_TREE;
+    }
+}
+
+/* Implement -Wdangling-reference, to detect cases like
+
+     int n = 1;
+     const int& r = std::max(n - 1, n + 1); // r is dangling
+
+   This creates temporaries from the arguments, returns a reference to
+   one of the temporaries, but both temporaries are destroyed at the end
+   of the full expression.
+
+   This works by checking if a reference is initialized with a function
+   that returns a reference, and at least one parameter of the function
+   is a reference that is bound to a temporary.  It assumes that such a
+   function actually returns one of its arguments.
+
+   This warning doesn't warn when the function in question is a member
+   function.
+
+   DECL is the reference being initialized, CALL is the initializer.  */
+
+static void
+do_warn_dangling_reference (const_tree decl, tree call)
+{
+  if (!warn_dangling_reference)
+    return;
+  if (!TYPE_REF_P (TREE_TYPE (decl)))
+    return;
+  call = find_initializing_call_expr (call);
+  if (call == NULL_TREE)
+    return;
+
+  tree fndecl = cp_get_callee_fndecl_nofold (call);
+  if (!fndecl
+      || warning_suppressed_p (fndecl, OPT_Wdangling_reference)
+      /* Don't warn about member functions; the warning would trigger in
+	 valid code like
+	   std::any a(...);
+	   S& s = a.emplace<S>({0}, 0);
+	 which constructs a new object and returns a reference to it.  */
+      || DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
+      /* It seems unreasonable to warn about operator functions.  */
+      || DECL_OVERLOADED_OPERATOR_P (fndecl)
+      /* If the function doesn't return a reference, don't warn.  This can
+	 be e.g.
+	   const int& z = std::min({1, 2, 3, 4, 5, 6, 7});
+	 which doesn't dangle: std::min here returns an int.  */
+      || !TYPE_REF_P (TREE_TYPE (TREE_TYPE (fndecl))))
+    return;
+
+  /* Mitigate some known cases.  */
+  if (decl_in_std_namespace_p (fndecl))
+    if (tree name = DECL_NAME (fndecl))
+      if (id_equal (name, "use_facet"))
+	return;
+
+  for (int i = 0; i < call_expr_nargs (call); ++i)
+    {
+      /* We're looking to see if ARG is something like
+	  (const int &) &TARGET_EXPR <...>.  */
+      tree arg = CALL_EXPR_ARG (call, i);
+      STRIP_NOPS (arg);
+      if (TREE_CODE (arg) == ADDR_EXPR)
+	arg = TREE_OPERAND (arg, 0);
+      if (expr_represents_temporary_p (arg))
+	{
+	  auto_diagnostic_group d;
+	  if (warning_at (DECL_SOURCE_LOCATION (decl),
+			  OPT_Wdangling_reference,
+			  "possibly dangling reference to a temporary"))
+	    inform (EXPR_LOCATION (call), "the temporary was destroyed at "
+		    "the end of the full expression %qE", call);
+	  return;
+	}
+    }
+}
+
 /* If *P is an xvalue expression, prevent temporary lifetime extension if it
    gets used to initialize a reference.  */
 
@@ -13525,6 +13636,9 @@  extend_ref_init_temps (tree decl, tree init, vec<tree, va_gc> **cleanups,
   tree type = TREE_TYPE (init);
   if (processing_template_decl)
     return init;
+
+  do_warn_dangling_reference (decl, init);
+
   if (TYPE_REF_P (type))
     init = extend_ref_init_temps_1 (decl, init, cleanups, cond_guard);
   else
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 60a25101049..8ddf55ce2b0 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -459,7 +459,6 @@  extern GTY(()) tree cp_global_trees[CPTI_MAX];
       TI_PENDING_TEMPLATE_FLAG.
       TEMPLATE_PARMS_FOR_INLINE.
       DELETE_EXPR_USE_VEC (in DELETE_EXPR).
-      (TREE_CALLS_NEW) (in _EXPR or _REF) (commented-out).
       ICS_ELLIPSIS_FLAG (in _CONV)
       DECL_INITIALIZED_P (in VAR_DECL)
       TYPENAME_IS_CLASS_P (in TYPENAME_TYPE)
@@ -4558,6 +4557,9 @@  get_vec_init_expr (tree t)
    When appearing in a CONSTRUCTOR, the expression is an unconverted
    compound literal.
 
+   When appearing in a CALL_EXPR, it means that it is a call to
+   a constructor.
+
    When appearing in a FIELD_DECL, it means that this field
    has been duly initialized in its constructor.  */
 #define TREE_HAS_CONSTRUCTOR(NODE) (TREE_LANG_FLAG_4 (NODE))
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 16e7d85793d..5a22eee8ebf 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -11238,6 +11238,16 @@  check_return_expr (tree retval, bool *no_warning)
   if (processing_template_decl)
     return saved_retval;
 
+  /* A naive attempt to reduce the number of -Wdangling-reference false
+     positives: if we know that this function can return something other
+     than one of its parameters, suppress the warning.  */
+  if (warn_dangling_reference
+      && TYPE_REF_P (functype)
+      && bare_retval
+      && (!REFERENCE_REF_P (bare_retval)
+	  || TREE_CODE (TREE_OPERAND (bare_retval, 0)) != PARM_DECL))
+    suppress_warning (current_function_decl, OPT_Wdangling_reference);
+
   /* Actually copy the value returned into the appropriate location.  */
   if (retval && retval != result)
     retval = cp_build_init_expr (result, retval);
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 09548c4528c..28bb395ce6c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -249,7 +249,8 @@  in the following sections.
 -Wno-class-conversion  -Wclass-memaccess @gol
 -Wcomma-subscript  -Wconditionally-supported @gol
 -Wno-conversion-null  -Wctad-maybe-unsupported @gol
--Wctor-dtor-privacy  -Wno-delete-incomplete @gol
+-Wctor-dtor-privacy  -Wdangling-reference @gol
+-Wno-delete-incomplete @gol
 -Wdelete-non-virtual-dtor  -Wno-deprecated-array-compare @gol
 -Wdeprecated-copy -Wdeprecated-copy-dtor @gol
 -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion @gol
@@ -3627,6 +3628,36 @@  public static member functions.  Also warn if there are no non-private
 methods, and there's at least one private member function that isn't
 a constructor or destructor.
 
+@item -Wdangling-reference @r{(C++ and Objective-C++ only)}
+@opindex Wdangling-reference
+@opindex Wno-dangling-reference
+Warn when a reference is bound to a temporary whose lifetime has ended.
+For example:
+
+@smallexample
+int n = 1;
+const int& r = std::max(n - 1, n + 1); // r is dangling
+@end smallexample
+
+In the example above, two temporaries are created, one for each
+argument, and a reference to one of the temporaries is returned.
+However, both temporaries are destroyed at the end of the full
+expression, so the reference @code{r} is dangling.  This warning
+also detects dangling references in member initializer lists:
+
+@smallexample
+const int& f(const int& i) @{ return i; @}
+struct S @{
+  const int &r; // r is dangling
+  S() : r(f(10)) @{ @}
+@};
+@end smallexample
+
+Member functions are not checked.  Certain standard functions, such
+as @code{std::use_facet}, are also excluded from checking.
+
+This warning is enabled by @option{-Wextra}.
+
 @item -Wdelete-non-virtual-dtor @r{(C++ and Objective-C++ only)}
 @opindex Wdelete-non-virtual-dtor
 @opindex Wno-delete-non-virtual-dtor
@@ -5936,6 +5967,7 @@  name is still supported, but the newer name is more descriptive.)
 
 @gccoptlist{-Wclobbered  @gol
 -Wcast-function-type  @gol
+-Wdangling-reference @r{(C++ only)} @gol
 -Wdeprecated-copy @r{(C++ only)} @gol
 -Wempty-body  @gol
 -Wenum-conversion @r{(C only)} @gol
diff --git a/gcc/testsuite/g++.dg/cpp23/elision4.C b/gcc/testsuite/g++.dg/cpp23/elision4.C
index c19b86b8b5f..d39053ad741 100644
--- a/gcc/testsuite/g++.dg/cpp23/elision4.C
+++ b/gcc/testsuite/g++.dg/cpp23/elision4.C
@@ -1,5 +1,6 @@ 
 // PR c++/101165 - P2266R1 - Simpler implicit move
 // { dg-do compile { target c++23 } }
+// { dg-options "-Wdangling-reference" }
 // Test from P2266R1, $ 5.2. LibreOffice OString constructor.
 
 struct X {
@@ -33,6 +34,6 @@  T& temporary2(T&& x) { return static_cast<T&>(x); }
 void
 test ()
 {
-  int& r1 = temporary1 (42);
-  int& r2 = temporary2 (42);
+  int& r1 = temporary1 (42); // { dg-warning "dangling reference" }
+  int& r2 = temporary2 (42); // { dg-warning "dangling reference" }
 }
diff --git a/gcc/testsuite/g++.dg/cpp23/elision7.C b/gcc/testsuite/g++.dg/cpp23/elision7.C
index 19fa89ae133..0045842b34f 100644
--- a/gcc/testsuite/g++.dg/cpp23/elision7.C
+++ b/gcc/testsuite/g++.dg/cpp23/elision7.C
@@ -1,5 +1,6 @@ 
 // PR c++/101165 - P2266R1 - Simpler implicit move
 // { dg-do compile { target c++23 } }
+// { dg-options "-Wdangling-reference" }
 
 struct X {
   X ();
@@ -68,5 +69,5 @@  f7 (T &&t)
 void
 do_f7 ()
 {
-  const int &x = f7 (0);
+  const int &x = f7 (0); // { dg-warning "dangling reference" }
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C
new file mode 100644
index 00000000000..e0324d38f67
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C
@@ -0,0 +1,103 @@ 
+// PR c++/106393
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wdangling-reference" }
+
+const int& f(const int& i) { return i; }
+const int& h(int);
+int g;
+const int& globref(const int&) { return g; }
+struct X {
+  int* i;
+  operator const int&() const { return *i; }
+};
+X x{&g};
+
+const int& r1 = f(10); // { dg-warning "dangling reference" }
+// r2 = _ZGR2r2_ = (int) *f ((const int &) &TARGET_EXPR <D.2429, 10>) + 1; (const int &) &_ZGR2r2_
+const int& r2 = f(10) + 1;
+// Don't warn here, we have
+//   r3 = f (X::operator const int& (&x))
+const int& r3 = f(x);
+// Don't warn here, because we've seen the definition of globref
+// and could figure out that it may not return one of its parms.
+// Questionable -- it can also hide bugs --, but it helps here.
+const int& r4 = globref(1);
+const int& r5 = (42, f(10)); // { dg-warning "dangling reference" }
+const int& r6 = (f(10), 42);
+const int& r7 = (f(10)); // { dg-warning "dangling reference" }
+const int& r8 = g ? f(10) : f(9); // { dg-warning "dangling reference" }
+const int& r9 = (42, g ? f(10) : f(9)); // { dg-warning "dangling reference" }
+const int& r10 = (g ? f(10) : f(9), 42);
+// Binds to a reference temporary for r11.
+const int& r11 = g ? f(10) : 9;
+// Invalid, but we don't warn here yet.
+// r12 = f (f ((const int &) &TARGET_EXPR <D.2459, 1>))
+const int& r12 = f(f(1));
+const int& r13 = h(f(1));
+// Other forms of initializers.
+const int& r14(f(10)); // { dg-warning "dangling reference" }
+const int& r15(f(10)); // { dg-warning "dangling reference" }
+// Returns a ref, but doesn't have a parameter of reference type.
+const int& r16 = h(10);
+
+// OK: the reference is bound to the 10 so still valid at the point
+// where it's copied into i1.
+int i1 = f(10);
+
+int
+test1 ()
+{
+  const int &lr = f(10); // { dg-warning "dangling reference" }
+  int i2 = f(10);
+  return lr;
+}
+
+struct B { };
+struct D : B { };
+struct C {
+  D d;
+};
+
+C c;
+D d;
+
+using U = D[3];
+
+const B& frotz(const D&);
+const B& b1 = frotz(C{}.d); // { dg-warning "dangling reference" }
+const B& b2 = frotz(D{}); // { dg-warning "dangling reference" }
+const B& b3 = frotz(c.d);
+const B& b4 = frotz(d);
+const B& b5 = frotz(U{}[0]); // { dg-warning "dangling reference" }
+
+struct E {
+  E(int);
+};
+const E& operator*(const E&);
+const E& b6 = *E(1);
+
+struct S {
+  const int &r; // { dg-warning "dangling reference" }
+  S() : r(f(10)) { } // { dg-message "destroyed" }
+};
+
+// From cppreference.
+template<class T>
+const T& max(const T& a, const T& b)
+{
+    return (a < b) ? b : a;
+}
+
+int n = 1;
+const int& r20 = max(n - 1, n + 1); // { dg-warning "dangling reference" }
+
+// Don't warn about member functions.
+struct Y {
+  operator int&&();
+  const int& foo(const int&);
+};
+
+// x1 = Y::operator int&& (&TARGET_EXPR <D.2410, {}>)
+int&& x1 = Y();
+int&& x2 = Y{};
+const int& t1 = Y().foo(10);
diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C
new file mode 100644
index 00000000000..dafdb43f1b9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference2.C
@@ -0,0 +1,28 @@ 
+// PR c++/106393
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wdangling-reference" }
+
+namespace std {
+struct any {};
+template <typename _ValueType> _ValueType any_cast(any &&);
+template <typename _Tp> struct remove_reference { using type = _Tp; };
+template <typename _Tp> _Tp forward(typename remove_reference<_Tp>::type);
+template <typename _Tp> typename remove_reference<_Tp>::type move(_Tp);
+} // namespace std
+
+const int &r = std::any_cast<int&>(std::any()); // { dg-warning "dangling reference" }
+
+template <class T> struct C {
+  T t_; // { dg-warning "dangling reference" }
+  C(T);
+  template <class U> C(U c) : t_(std::forward<T>(c.t_)) {}
+};
+struct A {};
+struct B {
+  B(A);
+};
+int main() {
+  A a;
+  C<A> ca(a);
+  C<B &&>(std::move(ca));
+}