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

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

Checks

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

Commit Message

Marek Polacek Oct. 26, 2022, 6:26 p.m. UTC
  On Wed, Oct 26, 2022 at 12:42:27PM -0400, Jason Merrill wrote:
> On 10/26/22 12:10, Marek Polacek wrote:
> > As before, I've tested this patch twice, once with -Wdangling-reference
> > enabled by default, once with -Wdangling-reference enabled by -Wextra.
> > The couple of FAILs I saw were true positives (e.g., rv-conv1.C, rv-func2.C).
> 
> I might actually add it to -Wall, the false positive rate sounds pretty low
> at this point.

Done (and invoke.texi adjusted).
 
> > +static tree
> > +do_warn_dangling_reference (tree expr)
> > +{
> > +  STRIP_NOPS (expr);
> > +  switch (TREE_CODE (expr))
> > +    {
> > +    case CALL_EXPR:
> > +      {
> > +	tree fndecl = cp_get_callee_fndecl_nofold (expr);
> > +	if (!fndecl
> > +	    || warning_suppressed_p (fndecl, OPT_Wdangling_reference)
> > +	    || !warning_enabled_at (DECL_SOURCE_LOCATION (fndecl),
> > +				    OPT_Wdangling_reference)
> > +	    /* 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))))
> 
> Maybe TYPE_REF_OBJ_P?  Either way is fine.

Adjusted.
 
> > --- a/gcc/cp/typeck.cc
> > +++ b/gcc/cp/typeck.cc
> > @@ -11246,6 +11246,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 a variable with
> > +     static storage duration rather than one of its parameters, suppress
> > +     the warning.  */
> > +  if (warn_dangling_reference
> > +      && TYPE_REF_P (functype)
> > +      && bare_retval
> 
> You probably need to check VAR_P (bare_retval) here, static_flag is used for
> various other things in other tree codes.

Added VAR_P.
 
> > +      && TREE_STATIC (bare_retval))
> > +    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 64f77e8367a..0a3a174cdc8 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,42 @@ 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 checked as well, but only their object argument:
> > +
> > +@smallexample
> > +struct S @{
> > +   const S& self () @{ return *this; @}
> > +@};
> > +const S& s = S().self(); // s is dangling
> > +@end smallexample
> > +
> > +This warning is enabled by @option{-Wextra}.
> 
> It would be good to mention using #pragma to disable the warning around safe
> functions.

Yes, text added.

Now that the warning is in -Wall, I've added -Wno-dangling-reference to
g++.dg/warn/Wdangling-pointer-2.C.

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

-- >8 --
This patch implements a new experimental warning (enabled by -Wall) 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 a variable with static storage
duration.)

It warns when the function in question is a member function, but only if
the function is invoked on a temporary object, otherwise the warning
would emit loads of warnings for valid code like obj.emplace<T>({0}, 0).
It does detect the dangling reference in:

  struct S {
    const S& self () { return *this; }
  };
  const S& s = S().self();

It warns in member initializer lists as well:

  const int& f(const int& i) { return i; }
  struct S {
    const int &r;
    S() : r(f(10)) { }
  };

I've run the testsuite/bootstrap with the warning enabled by default.
There were just a few FAILs, all of which look like genuine bugs.
A bootstrap with the warning enabled by default passed as well.

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 a #pragma and code to suppress the
warning.

	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): ...here.  Don't return false just
	because a ck_base is missing.  Use expr_represents_temporary_p.
	(do_warn_dangling_reference): New.
	(maybe_warn_dangling_reference): New.
	(extend_ref_init_temps): Call maybe_warn_dangling_reference.
	* typeck.cc (check_return_expr): Suppress -Wdangling-reference
	warnings.

gcc/ChangeLog:

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

libstdc++-v3/ChangeLog:

	* include/bits/locale_classes.tcc: Add #pragma to disable
	-Wdangling-reference with std::use_facet.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp23/elision4.C: Use -Wdangling-reference, add dg-warning.
	* g++.dg/cpp23/elision7.C: Likewise.
	* g++.dg/warn/Wdangling-pointer-2.C: Use -Wno-dangling-reference.
	* g++.dg/warn/Wdangling-reference1.C: New test.
	* g++.dg/warn/Wdangling-reference2.C: New test.
	* g++.dg/warn/Wdangling-reference3.C: New test.
---
 gcc/c-family/c.opt                            |   4 +
 gcc/cp/call.cc                                | 148 ++++++++++++++++--
 gcc/cp/cp-tree.h                              |   4 +-
 gcc/cp/typeck.cc                              |  11 ++
 gcc/doc/invoke.texi                           |  51 +++++-
 gcc/testsuite/g++.dg/cpp23/elision4.C         |   5 +-
 gcc/testsuite/g++.dg/cpp23/elision7.C         |   3 +-
 .../g++.dg/warn/Wdangling-pointer-2.C         |   2 +-
 .../g++.dg/warn/Wdangling-reference1.C        | 144 +++++++++++++++++
 .../g++.dg/warn/Wdangling-reference2.C        |  28 ++++
 .../g++.dg/warn/Wdangling-reference3.C        |  24 +++
 libstdc++-v3/include/bits/locale_classes.tcc  |   3 +
 12 files changed, 409 insertions(+), 18 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference1.C
 create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference2.C
 create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference3.C


base-commit: f896c13489d22b30d01257bc8316ab97b3359d1c
  

Comments

Jason Merrill Oct. 26, 2022, 6:42 p.m. UTC | #1
On 10/26/22 14:26, Marek Polacek wrote:
> On Wed, Oct 26, 2022 at 12:42:27PM -0400, Jason Merrill wrote:
>> On 10/26/22 12:10, Marek Polacek wrote:
>>> As before, I've tested this patch twice, once with -Wdangling-reference
>>> enabled by default, once with -Wdangling-reference enabled by -Wextra.
>>> The couple of FAILs I saw were true positives (e.g., rv-conv1.C, rv-func2.C).
>>
>> I might actually add it to -Wall, the false positive rate sounds pretty low
>> at this point.
> 
> Done (and invoke.texi adjusted).
>   
>>> +static tree
>>> +do_warn_dangling_reference (tree expr)
>>> +{
>>> +  STRIP_NOPS (expr);
>>> +  switch (TREE_CODE (expr))
>>> +    {
>>> +    case CALL_EXPR:
>>> +      {
>>> +	tree fndecl = cp_get_callee_fndecl_nofold (expr);
>>> +	if (!fndecl
>>> +	    || warning_suppressed_p (fndecl, OPT_Wdangling_reference)
>>> +	    || !warning_enabled_at (DECL_SOURCE_LOCATION (fndecl),
>>> +				    OPT_Wdangling_reference)
>>> +	    /* 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))))
>>
>> Maybe TYPE_REF_OBJ_P?  Either way is fine.
> 
> Adjusted.
>   
>>> --- a/gcc/cp/typeck.cc
>>> +++ b/gcc/cp/typeck.cc
>>> @@ -11246,6 +11246,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 a variable with
>>> +     static storage duration rather than one of its parameters, suppress
>>> +     the warning.  */
>>> +  if (warn_dangling_reference
>>> +      && TYPE_REF_P (functype)
>>> +      && bare_retval
>>
>> You probably need to check VAR_P (bare_retval) here, static_flag is used for
>> various other things in other tree codes.
> 
> Added VAR_P.
>   
>>> +      && TREE_STATIC (bare_retval))
>>> +    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 64f77e8367a..0a3a174cdc8 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,42 @@ 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 checked as well, but only their object argument:
>>> +
>>> +@smallexample
>>> +struct S @{
>>> +   const S& self () @{ return *this; @}
>>> +@};
>>> +const S& s = S().self(); // s is dangling
>>> +@end smallexample
>>> +
>>> +This warning is enabled by @option{-Wextra}.
>>
>> It would be good to mention using #pragma to disable the warning around safe
>> functions.
> 
> Yes, text added.
> 
> Now that the warning is in -Wall, I've added -Wno-dangling-reference to
> g++.dg/warn/Wdangling-pointer-2.C.
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

OK, thanks.

> -- >8 --
> This patch implements a new experimental warning (enabled by -Wall) 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 a variable with static storage
> duration.)
> 
> It warns when the function in question is a member function, but only if
> the function is invoked on a temporary object, otherwise the warning
> would emit loads of warnings for valid code like obj.emplace<T>({0}, 0).
> It does detect the dangling reference in:
> 
>    struct S {
>      const S& self () { return *this; }
>    };
>    const S& s = S().self();
> 
> It warns in member initializer lists as well:
> 
>    const int& f(const int& i) { return i; }
>    struct S {
>      const int &r;
>      S() : r(f(10)) { }
>    };
> 
> I've run the testsuite/bootstrap with the warning enabled by default.
> There were just a few FAILs, all of which look like genuine bugs.
> A bootstrap with the warning enabled by default passed as well.
> 
> 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 a #pragma and code to suppress the
> warning.
> 
> 	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): ...here.  Don't return false just
> 	because a ck_base is missing.  Use expr_represents_temporary_p.
> 	(do_warn_dangling_reference): New.
> 	(maybe_warn_dangling_reference): New.
> 	(extend_ref_init_temps): Call maybe_warn_dangling_reference.
> 	* typeck.cc (check_return_expr): Suppress -Wdangling-reference
> 	warnings.
> 
> gcc/ChangeLog:
> 
> 	* doc/invoke.texi: Document -Wdangling-reference.
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/bits/locale_classes.tcc: Add #pragma to disable
> 	-Wdangling-reference with std::use_facet.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp23/elision4.C: Use -Wdangling-reference, add dg-warning.
> 	* g++.dg/cpp23/elision7.C: Likewise.
> 	* g++.dg/warn/Wdangling-pointer-2.C: Use -Wno-dangling-reference.
> 	* g++.dg/warn/Wdangling-reference1.C: New test.
> 	* g++.dg/warn/Wdangling-reference2.C: New test.
> 	* g++.dg/warn/Wdangling-reference3.C: New test.
> ---
>   gcc/c-family/c.opt                            |   4 +
>   gcc/cp/call.cc                                | 148 ++++++++++++++++--
>   gcc/cp/cp-tree.h                              |   4 +-
>   gcc/cp/typeck.cc                              |  11 ++
>   gcc/doc/invoke.texi                           |  51 +++++-
>   gcc/testsuite/g++.dg/cpp23/elision4.C         |   5 +-
>   gcc/testsuite/g++.dg/cpp23/elision7.C         |   3 +-
>   .../g++.dg/warn/Wdangling-pointer-2.C         |   2 +-
>   .../g++.dg/warn/Wdangling-reference1.C        | 144 +++++++++++++++++
>   .../g++.dg/warn/Wdangling-reference2.C        |  28 ++++
>   .../g++.dg/warn/Wdangling-reference3.C        |  24 +++
>   libstdc++-v3/include/bits/locale_classes.tcc  |   3 +
>   12 files changed, 409 insertions(+), 18 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference1.C
>   create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference2.C
>   create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-reference3.C
> 
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index 01d480759ae..070f85c81d2 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++, Wall)
> +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..951b9fd2a88 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,121 @@ initialize_reference (tree type, tree expr,
>     return expr;
>   }
>   
> +/* Helper for maybe_warn_dangling_reference to find a problematic CALL_EXPR
> +   that initializes the LHS (and at least one of its arguments represents
> +   a temporary, as outlined in maybe_warn_dangling_reference), or NULL_TREE
> +   if none found.  For instance:
> +
> +     const S& s = S().self(); // S::self (&TARGET_EXPR <...>)
> +     const int& r = (42, f(1)); // f(1)
> +     const int& t = b ? f(1) : f(2); // f(1)
> +     const int& u = b ? f(1) : f(g); // f(1)
> +     const int& v = b ? f(g) : f(2); // f(2)
> +     const int& w = b ? f(g) : f(g); // NULL_TREE
> +     const int& y = (f(1), 42); // NULL_TREE
> +     const int& z = f(f(1)); // f(f(1))
> +
> +   EXPR is the initializer.  */
> +
> +static tree
> +do_warn_dangling_reference (tree expr)
> +{
> +  STRIP_NOPS (expr);
> +  switch (TREE_CODE (expr))
> +    {
> +    case CALL_EXPR:
> +      {
> +	tree fndecl = cp_get_callee_fndecl_nofold (expr);
> +	if (!fndecl
> +	    || warning_suppressed_p (fndecl, OPT_Wdangling_reference)
> +	    || !warning_enabled_at (DECL_SOURCE_LOCATION (fndecl),
> +				    OPT_Wdangling_reference)
> +	    /* 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_OBJ_P (TREE_TYPE (TREE_TYPE (fndecl))))
> +	  return NULL_TREE;
> +
> +	/* Here we're looking to see if any of the arguments is a temporary
> +	   initializing a reference parameter.  */
> +	for (int i = 0; i < call_expr_nargs (expr); ++i)
> +	  {
> +	    tree arg = CALL_EXPR_ARG (expr, i);
> +	    /* Check that this argument initializes a reference, except for
> +	       the argument initializing the object of a member function.  */
> +	    if (!DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
> +		&& !TYPE_REF_P (TREE_TYPE (arg)))
> +	      continue;
> +	    /* It could also be another call taking a temporary and returning
> +	       it and initializing this reference parameter.  */
> +	    if (do_warn_dangling_reference (arg))
> +	      return expr;
> +	    STRIP_NOPS (arg);
> +	    if (TREE_CODE (arg) == ADDR_EXPR)
> +	      arg = TREE_OPERAND (arg, 0);
> +	    if (expr_represents_temporary_p (arg))
> +	      return expr;
> +	  /* Don't warn about member function like:
> +	      std::any a(...);
> +	      S& s = a.emplace<S>({0}, 0);
> +	     which constructs a new object and returns a reference to it, but
> +	     we still want to detect:
> +	       struct S { const S& self () { return *this; } };
> +	       const S& s = S().self();
> +	     where 's' dangles.  If we've gotten here, the object this function
> +	     is invoked on is not a temporary.  */
> +	    if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl))
> +	      break;
> +	  }
> +	return NULL_TREE;
> +      }
> +    case COMPOUND_EXPR:
> +      return do_warn_dangling_reference (TREE_OPERAND (expr, 1));
> +    case COND_EXPR:
> +      if (tree t = do_warn_dangling_reference (TREE_OPERAND (expr, 1)))
> +	return t;
> +      return do_warn_dangling_reference (TREE_OPERAND (expr, 2));
> +    case PAREN_EXPR:
> +      return do_warn_dangling_reference (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.
> +
> +   DECL is the reference being initialized, INIT is the initializer.  */
> +
> +static void
> +maybe_warn_dangling_reference (const_tree decl, tree init)
> +{
> +  if (!warn_dangling_reference)
> +    return;
> +  if (!TYPE_REF_P (TREE_TYPE (decl)))
> +    return;
> +  if (tree call = do_warn_dangling_reference (init))
> +    {
> +      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);
> +    }
> +}
> +
>   /* If *P is an xvalue expression, prevent temporary lifetime extension if it
>      gets used to initialize a reference.  */
>   
> @@ -13525,6 +13646,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;
> +
> +  maybe_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 867096b08c6..40f5bf802c3 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)
> @@ -4567,6 +4566,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 ab6979bcc50..4605f734e2d 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -11246,6 +11246,17 @@ 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 a variable with
> +     static storage duration rather than one of its parameters, suppress
> +     the warning.  */
> +  if (warn_dangling_reference
> +      && TYPE_REF_P (functype)
> +      && bare_retval
> +      && VAR_P (bare_retval)
> +      && TREE_STATIC (bare_retval))
> +    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 64f77e8367a..9f0e5460861 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,54 @@ 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 checked as well, but only their object argument:
> +
> +@smallexample
> +struct S @{
> +   const S& self () @{ return *this; @}
> +@};
> +const S& s = S().self(); // s is dangling
> +@end smallexample
> +
> +Certain functions are safe in this respect, for example @code{std::use_facet}:
> +they take and return a reference, but they don't return one of its arguments,
> +which can fool the warning.  Such functions can be excluded from the warning
> +by wrapping them in a @code{#pragma}:
> +
> +@smallexample
> +#pragma GCC diagnostic push
> +#pragma GCC diagnostic ignored "-Wdangling-reference"
> +const T& foo (const T&) @{ @dots{} @}
> +#pragma GCC diagnostic pop
> +@end smallexample
> +
> +This warning is enabled by @option{-Wall}.
> +
>   @item -Wdelete-non-virtual-dtor @r{(C++ and Objective-C++ only)}
>   @opindex Wdelete-non-virtual-dtor
>   @opindex Wno-delete-non-virtual-dtor
> 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-pointer-2.C b/gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C
> index 151418f3c32..802ac9cd954 100644
> --- a/gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C
> +++ b/gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C
> @@ -1,5 +1,5 @@
>   /* { dg-do compile }
> -   { dg-options "-O1 -Wall -Wno-class-memaccess" } */
> +   { dg-options "-O1 -Wall -Wno-class-memaccess -Wno-dangling-reference" } */
>   
>   struct A { A (); };
>   
> 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..97c81ee716c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C
> @@ -0,0 +1,144 @@
> +// PR c++/106393
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wdangling-reference" }
> +
> +const int& f(const int& i) { return i; }
> +const int& f_(const int& i) { return i; }
> +const int& h(int);
> +const int& rp(const 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.  No dangling reference.
> +const int& r11 = g ? f(10) : 9;
> +const int& r12 = g ? 9 : f(10);
> +// r12 = f (f ((const int &) &TARGET_EXPR <D.2459, 1>))
> +const int& r13 = f(f(1)); // { dg-warning "dangling reference" }
> +const int& r14 = f(f_(1)); // { dg-warning "dangling reference" }
> +const int& r15 = f(g ? f(1) : f(2)); // { dg-warning "dangling reference" }
> +const int& r16 = f(*&f(1)); // { dg-warning "dangling reference" }
> +const int& r17 = rp(&f(1));
> +const int& r18 = rp(&f(g));
> +const int& r19 = h(f(1));
> +// Other forms of initializers.
> +const int& r20(f(10)); // { dg-warning "dangling reference" }
> +const int& r21(f(10)); // { dg-warning "dangling reference" }
> +// Returns a ref, but doesn't have a parameter of reference type.
> +const int& r22 = h(10);
> +const int& r23 = g ? h(10) : f(10); // { dg-warning "dangling reference" }
> +const int& r24 = g ? f(10) : h(10); // { dg-warning "dangling reference" }
> +const int& r25 = g ? h(10) : (1, f(10)); // { dg-warning "dangling reference" }
> +const int& r26 = g ? (1, f(10)) : h(10); // { dg-warning "dangling reference" }
> +const int& r29 = f((f_(1), 1)); // { dg-warning "dangling reference" }
> +const int& r30 = f((f_(1), g));
> +
> +struct Z {
> +  operator int() { return 42; }
> +};
> +
> +const int& r27 = f(Z()); // { dg-warning "dangling reference" }
> +const int& r28 = f(true ? Z() : Z()); // { dg-warning "dangling reference" }
> +
> +const int& operator|(const int &, Z);
> +const int& r31 = 1 | Z(); // { dg-warning "dangling reference" }
> +
> +// 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" }
> +
> +// Try returning a subobject.
> +const B& bar (const D& d) { return d; }
> +const B& b6 = bar (D{}); // { dg-warning "dangling reference" }
> +const B& baz (const C& c) { return c.d; }
> +const B& b7 = baz (C{}); // { dg-warning "dangling reference" }
> +const D& qux (const C& c) { return c.d; }
> +const D& d1 = qux (C{}); // { dg-warning "dangling reference" }
> +
> +struct E {
> +  E(int);
> +};
> +const E& operator*(const E&);
> +const E& b8 = *E(1); // { dg-warning "dangling reference" }
> +
> +struct F : virtual B { };
> +struct G : virtual B { };
> +struct H : F, G { };
> +const B& yum (const F& f) { return f; }
> +const B& b9 = yum (F{}); // { dg-warning "dangling reference" }
> +const B& lox (const H& h) { return h; }
> +const B& b10 = lox (H{}); // { dg-warning "dangling reference" }
> +
> +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& refmax = max(n - 1, n + 1); // { dg-warning "dangling reference" }
> +
> +struct Y {
> +  operator int&();
> +  operator int&&();
> +  const int& foo(const int&);
> +};
> +
> +// x1 = Y::operator int&& (&TARGET_EXPR <D.2410, {}>)
> +int&& x1 = Y(); // { dg-warning "dangling reference" }
> +int&& x2 = Y{}; // { dg-warning "dangling reference" }
> +int& x3 = Y(); // { dg-warning "dangling reference" }
> +int& x4 = Y{}; // { dg-warning "dangling reference" }
> +const int& t1 = Y().foo(10); // { dg-warning "dangling reference" }
> 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));
> +}
> diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C
> new file mode 100644
> index 00000000000..4bc20c13b3f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C
> @@ -0,0 +1,24 @@
> +// PR c++/106393
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-Wdangling-reference" }
> +
> +struct A {
> +   int ar[4];
> +   int& operator[](int i) { return ar[i]; }
> +};
> +const int &r = A()[2]; // { dg-warning "dangling reference" }
> +
> +struct S {
> +  const S& self () { return *this; }
> +};
> +const S& s = S().self(); // { dg-warning "dangling reference" }
> +
> +struct G {
> +  const G& g() { return *this; }
> +};
> +
> +struct F {
> +  G& f();
> +};
> +
> +const G& g = F().f().g(); // { dg-warning "dangling reference" }
> diff --git a/libstdc++-v3/include/bits/locale_classes.tcc b/libstdc++-v3/include/bits/locale_classes.tcc
> index 64cd7534dc6..9cc4f238ee7 100644
> --- 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.
> 
> base-commit: f896c13489d22b30d01257bc8316ab97b3359d1c
  

Patch

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 01d480759ae..070f85c81d2 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++, Wall)
+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..951b9fd2a88 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,121 @@  initialize_reference (tree type, tree expr,
   return expr;
 }
 
+/* Helper for maybe_warn_dangling_reference to find a problematic CALL_EXPR
+   that initializes the LHS (and at least one of its arguments represents
+   a temporary, as outlined in maybe_warn_dangling_reference), or NULL_TREE
+   if none found.  For instance:
+
+     const S& s = S().self(); // S::self (&TARGET_EXPR <...>)
+     const int& r = (42, f(1)); // f(1)
+     const int& t = b ? f(1) : f(2); // f(1)
+     const int& u = b ? f(1) : f(g); // f(1)
+     const int& v = b ? f(g) : f(2); // f(2)
+     const int& w = b ? f(g) : f(g); // NULL_TREE
+     const int& y = (f(1), 42); // NULL_TREE
+     const int& z = f(f(1)); // f(f(1))
+
+   EXPR is the initializer.  */
+
+static tree
+do_warn_dangling_reference (tree expr)
+{
+  STRIP_NOPS (expr);
+  switch (TREE_CODE (expr))
+    {
+    case CALL_EXPR:
+      {
+	tree fndecl = cp_get_callee_fndecl_nofold (expr);
+	if (!fndecl
+	    || warning_suppressed_p (fndecl, OPT_Wdangling_reference)
+	    || !warning_enabled_at (DECL_SOURCE_LOCATION (fndecl),
+				    OPT_Wdangling_reference)
+	    /* 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_OBJ_P (TREE_TYPE (TREE_TYPE (fndecl))))
+	  return NULL_TREE;
+
+	/* Here we're looking to see if any of the arguments is a temporary
+	   initializing a reference parameter.  */
+	for (int i = 0; i < call_expr_nargs (expr); ++i)
+	  {
+	    tree arg = CALL_EXPR_ARG (expr, i);
+	    /* Check that this argument initializes a reference, except for
+	       the argument initializing the object of a member function.  */
+	    if (!DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
+		&& !TYPE_REF_P (TREE_TYPE (arg)))
+	      continue;
+	    /* It could also be another call taking a temporary and returning
+	       it and initializing this reference parameter.  */
+	    if (do_warn_dangling_reference (arg))
+	      return expr;
+	    STRIP_NOPS (arg);
+	    if (TREE_CODE (arg) == ADDR_EXPR)
+	      arg = TREE_OPERAND (arg, 0);
+	    if (expr_represents_temporary_p (arg))
+	      return expr;
+	  /* Don't warn about member function like:
+	      std::any a(...);
+	      S& s = a.emplace<S>({0}, 0);
+	     which constructs a new object and returns a reference to it, but
+	     we still want to detect:
+	       struct S { const S& self () { return *this; } };
+	       const S& s = S().self();
+	     where 's' dangles.  If we've gotten here, the object this function
+	     is invoked on is not a temporary.  */
+	    if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl))
+	      break;
+	  }
+	return NULL_TREE;
+      }
+    case COMPOUND_EXPR:
+      return do_warn_dangling_reference (TREE_OPERAND (expr, 1));
+    case COND_EXPR:
+      if (tree t = do_warn_dangling_reference (TREE_OPERAND (expr, 1)))
+	return t;
+      return do_warn_dangling_reference (TREE_OPERAND (expr, 2));
+    case PAREN_EXPR:
+      return do_warn_dangling_reference (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.
+
+   DECL is the reference being initialized, INIT is the initializer.  */
+
+static void
+maybe_warn_dangling_reference (const_tree decl, tree init)
+{
+  if (!warn_dangling_reference)
+    return;
+  if (!TYPE_REF_P (TREE_TYPE (decl)))
+    return;
+  if (tree call = do_warn_dangling_reference (init))
+    {
+      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);
+    }
+}
+
 /* If *P is an xvalue expression, prevent temporary lifetime extension if it
    gets used to initialize a reference.  */
 
@@ -13525,6 +13646,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;
+
+  maybe_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 867096b08c6..40f5bf802c3 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)
@@ -4567,6 +4566,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 ab6979bcc50..4605f734e2d 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -11246,6 +11246,17 @@  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 a variable with
+     static storage duration rather than one of its parameters, suppress
+     the warning.  */
+  if (warn_dangling_reference
+      && TYPE_REF_P (functype)
+      && bare_retval
+      && VAR_P (bare_retval)
+      && TREE_STATIC (bare_retval))
+    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 64f77e8367a..9f0e5460861 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,54 @@  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 checked as well, but only their object argument:
+
+@smallexample
+struct S @{
+   const S& self () @{ return *this; @}
+@};
+const S& s = S().self(); // s is dangling
+@end smallexample
+
+Certain functions are safe in this respect, for example @code{std::use_facet}:
+they take and return a reference, but they don't return one of its arguments,
+which can fool the warning.  Such functions can be excluded from the warning
+by wrapping them in a @code{#pragma}:
+
+@smallexample
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdangling-reference"
+const T& foo (const T&) @{ @dots{} @}
+#pragma GCC diagnostic pop
+@end smallexample
+
+This warning is enabled by @option{-Wall}.
+
 @item -Wdelete-non-virtual-dtor @r{(C++ and Objective-C++ only)}
 @opindex Wdelete-non-virtual-dtor
 @opindex Wno-delete-non-virtual-dtor
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-pointer-2.C b/gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C
index 151418f3c32..802ac9cd954 100644
--- a/gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C
+++ b/gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C
@@ -1,5 +1,5 @@ 
 /* { dg-do compile }
-   { dg-options "-O1 -Wall -Wno-class-memaccess" } */
+   { dg-options "-O1 -Wall -Wno-class-memaccess -Wno-dangling-reference" } */
 
 struct A { A (); };
 
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..97c81ee716c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference1.C
@@ -0,0 +1,144 @@ 
+// PR c++/106393
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wdangling-reference" }
+
+const int& f(const int& i) { return i; }
+const int& f_(const int& i) { return i; }
+const int& h(int);
+const int& rp(const 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.  No dangling reference.
+const int& r11 = g ? f(10) : 9;
+const int& r12 = g ? 9 : f(10);
+// r12 = f (f ((const int &) &TARGET_EXPR <D.2459, 1>))
+const int& r13 = f(f(1)); // { dg-warning "dangling reference" }
+const int& r14 = f(f_(1)); // { dg-warning "dangling reference" }
+const int& r15 = f(g ? f(1) : f(2)); // { dg-warning "dangling reference" }
+const int& r16 = f(*&f(1)); // { dg-warning "dangling reference" }
+const int& r17 = rp(&f(1));
+const int& r18 = rp(&f(g));
+const int& r19 = h(f(1));
+// Other forms of initializers.
+const int& r20(f(10)); // { dg-warning "dangling reference" }
+const int& r21(f(10)); // { dg-warning "dangling reference" }
+// Returns a ref, but doesn't have a parameter of reference type.
+const int& r22 = h(10);
+const int& r23 = g ? h(10) : f(10); // { dg-warning "dangling reference" }
+const int& r24 = g ? f(10) : h(10); // { dg-warning "dangling reference" }
+const int& r25 = g ? h(10) : (1, f(10)); // { dg-warning "dangling reference" }
+const int& r26 = g ? (1, f(10)) : h(10); // { dg-warning "dangling reference" }
+const int& r29 = f((f_(1), 1)); // { dg-warning "dangling reference" }
+const int& r30 = f((f_(1), g));
+
+struct Z {
+  operator int() { return 42; }
+};
+
+const int& r27 = f(Z()); // { dg-warning "dangling reference" }
+const int& r28 = f(true ? Z() : Z()); // { dg-warning "dangling reference" }
+
+const int& operator|(const int &, Z);
+const int& r31 = 1 | Z(); // { dg-warning "dangling reference" }
+
+// 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" }
+
+// Try returning a subobject.
+const B& bar (const D& d) { return d; }
+const B& b6 = bar (D{}); // { dg-warning "dangling reference" }
+const B& baz (const C& c) { return c.d; }
+const B& b7 = baz (C{}); // { dg-warning "dangling reference" }
+const D& qux (const C& c) { return c.d; }
+const D& d1 = qux (C{}); // { dg-warning "dangling reference" }
+
+struct E {
+  E(int);
+};
+const E& operator*(const E&);
+const E& b8 = *E(1); // { dg-warning "dangling reference" }
+
+struct F : virtual B { };
+struct G : virtual B { };
+struct H : F, G { };
+const B& yum (const F& f) { return f; }
+const B& b9 = yum (F{}); // { dg-warning "dangling reference" }
+const B& lox (const H& h) { return h; }
+const B& b10 = lox (H{}); // { dg-warning "dangling reference" }
+
+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& refmax = max(n - 1, n + 1); // { dg-warning "dangling reference" }
+
+struct Y {
+  operator int&();
+  operator int&&();
+  const int& foo(const int&);
+};
+
+// x1 = Y::operator int&& (&TARGET_EXPR <D.2410, {}>)
+int&& x1 = Y(); // { dg-warning "dangling reference" }
+int&& x2 = Y{}; // { dg-warning "dangling reference" }
+int& x3 = Y(); // { dg-warning "dangling reference" }
+int& x4 = Y{}; // { dg-warning "dangling reference" }
+const int& t1 = Y().foo(10); // { dg-warning "dangling reference" }
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));
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C b/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C
new file mode 100644
index 00000000000..4bc20c13b3f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wdangling-reference3.C
@@ -0,0 +1,24 @@ 
+// PR c++/106393
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wdangling-reference" }
+
+struct A {
+   int ar[4];
+   int& operator[](int i) { return ar[i]; }
+};
+const int &r = A()[2]; // { dg-warning "dangling reference" }
+
+struct S {
+  const S& self () { return *this; }
+};
+const S& s = S().self(); // { dg-warning "dangling reference" }
+
+struct G {
+  const G& g() { return *this; }
+};
+
+struct F {
+  G& f();
+};
+
+const G& g = F().f().g(); // { dg-warning "dangling reference" }
diff --git a/libstdc++-v3/include/bits/locale_classes.tcc b/libstdc++-v3/include/bits/locale_classes.tcc
index 64cd7534dc6..9cc4f238ee7 100644
--- 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.