Bug 81420 - When a reference is bound to a member in the base of a temporary, lifetime of the temporary is not extended
Summary: When a reference is bound to a member in the base of a temporary, lifetime of...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 8.0
: P3 normal
Target Milestone: 8.2
Assignee: Jason Merrill
URL:
Keywords: wrong-code
Depends on:
Blocks:
 
Reported: 2017-07-12 19:11 UTC by Ryan Brown
Modified: 2023-08-11 20:35 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2018-05-01 00:00:00


Attachments
untested hackish patch (562 bytes, patch)
2018-05-01 21:11 UTC, Marc Glisse
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Ryan Brown 2017-07-12 19:11:12 UTC
This is a followup to bug #54293. The incorrect behavior can be seen by modifying the test introduced for that bug [0] to use a base class:

int d;

struct A
{
  int i;
  ~A() { ++d; };
};

struct B : A {};

int main()
{
  {
    const int &r = B().i;
    if (d != 0)
      return 1;
  }
  if (d != 1)
    return 1;
}

This test passes for clang HEAD and fails for GCC HEAD.

[0] https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/testsuite/g%2B%2B.dg/init/ref19.C?view=markup&pathrev=240819
Comment 1 Ed Catmur 2018-01-08 01:59:50 UTC
icc 18 also has this bug. MSVC 19 2017 (with /permissive-) miscompiles by copying the bound subobject to a separate complete object before binding, so the reference does not dangle but the derived and base class objects are still destructed prematurely.
Comment 2 Marc Glisse 2018-05-01 20:24:41 UTC
Yes, this lifetime extension mechanism seems super fragile in gcc. Binding to a base fails, binding to an array element fails

int d;

struct A
{
  int i[2];
  ~A() { ++d; };
};

int main()
{
  // int&&r = A().i[0];
  const int &r = A().i[0];
  if (d != 0)
    __builtin_abort();
}

even a regular member fails if referenced with a slightly different syntax (bug 53288).
Comment 3 Marc Glisse 2018-05-01 21:11:10 UTC
Created attachment 44050 [details]
untested hackish patch

This seems to help a bit, but it doesn't feel like the right approach.
Comment 4 Jason Merrill 2018-05-23 03:53:29 UTC
Author: jason
Date: Wed May 23 03:52:56 2018
New Revision: 260563

URL: https://gcc.gnu.org/viewcvs?rev=260563&root=gcc&view=rev
Log:
	PR c++/81420 - not extending temporary lifetime.

	* call.c (extend_ref_init_temps_1): Handle ARRAY_REF.
	* class.c (build_base_path): Avoid redundant move of an rvalue.

Added:
    trunk/gcc/testsuite/g++.dg/cpp0x/temp-extend1.C
Modified:
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/call.c
    trunk/gcc/cp/class.c
Comment 5 Jason Merrill 2018-05-24 14:29:31 UTC
Author: jason
Date: Thu May 24 14:28:59 2018
New Revision: 260673

URL: https://gcc.gnu.org/viewcvs?rev=260673&root=gcc&view=rev
Log:
	PR c++/81420 - not extending temporary lifetime.

	* call.c (extend_ref_init_temps_1): Handle ARRAY_REF.
	* class.c (build_base_path): Avoid redundant move of an rvalue.

Added:
    branches/gcc-8-branch/gcc/testsuite/g++.dg/cpp0x/temp-extend1.C
Modified:
    branches/gcc-8-branch/gcc/cp/ChangeLog
    branches/gcc-8-branch/gcc/cp/call.c
    branches/gcc-8-branch/gcc/cp/class.c
Comment 6 Martin Liška 2018-11-20 08:10:13 UTC
Jason: Can the bug be marked as resolved?
Comment 7 Ion Lupascu 2022-07-06 13:14:50 UTC
Not really, The issue still persists

Check this example:
#include <iostream>
#include <limits>
#include <optional>

std::optional<double> getValue(){
    return 3.14;
}

int main(){
    const double &v = *getValue();
    std::cout<<"v:=" << v  << std::endl;
    if (v == 0.0) {
        __builtin_abort();
    }
    return 0;
}

compiled with GCC trunk (v13) and the following flags: -O3 -std=c++17 -Werror -Wall -Wextra

returns:
Program returned: 139
v:=0

link to play: https://godbolt.org/z/fPhxqe1h3

clang behaves correctly till v 10, it extends the lifetime, in v10 it catches with dangling-gsl check. when adding a more complex value rather than double clang doesn't catch it but it extends the lifetime. e.g.:

#include <iostream>
#include <limits>
#include <optional>

namespace
{

struct Data{
    double v;
};

} // namespace

std::optional<Data> getValue(){
    return Data{3.14};
}

int main(){
    const double &v = getValue()->v;
    std::cout<<"v:=" << v  << std::endl;
    if (v == 0.0) {
        __builtin_abort();
    }
    return 0;
}

link to play : https://godbolt.org/z/WoeGqhn7E

where GCC catches with the following error:
<source>: In function 'int main()':
<source>:20:25: error: using a dangling pointer to an unnamed temporary [-Werror=dangling-pointer=]
   20 |     std::cout<<"v:=" << v  << std::endl;
      |                         ^
<source>:19:31: note: unnamed temporary defined here
   19 |     const double &v = getValue()->v;
      |                       ~~~~~~~~^~
cc1plus: all warnings being treated as errors
ASM generation compiler returned: 1


GCC must first fix the temporary values lifetime when bounded by reference.
Comment 8 Jason Merrill 2023-08-11 20:35:11 UTC
(In reply to Ion Lupascu from comment #7)
> Not really, The issue still persists
> 
> std::optional<double> getValue(){
...
>     const double &v = *getValue();
...
>     const double &v = getValue()->v;

Neither of these bind the reference to a prvalue; std::optional operator* returns a reference, and operator-> returns a pointer, so in both cases we're binding the reference to an xvalue, and GCC is correct not to extend the temporary.

Fixed in 8.2.