https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128157

            Bug ID: 128157
           Summary: virtual call in a base destructor through a virtual
                    base resolved to a more-derived override at -O2
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Keywords: wrong-code
          Severity: normal
          Priority: P3
         Component: ipa
          Assignee: unassigned at gcc dot gnu.org
          Reporter: yqtian668 at gmail dot com
  Target Milestone: ---
              Host: x86_64-pc-linux-gnu
            Target: x86_64-pc-linux-gnu
             Build: x86_64-pc-linux-gnu

Created attachment 65953
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65953&action=edit
Preprocessed testcase (gcc-dtor-vbase-most-derived-override.ii)

At `-O2`, GCC resolves a virtual call made by `D::~D()` to `E::f()`, even
though virtual dispatch
during `D::~D()` must use the final overrider in `D`. The executable prints 5
instead of 4.

## Reproducer

```cpp
#include <cstdio>
struct A {
  virtual int f() = 0;
};
struct B {
  virtual void g() {}
};
struct C : B, A {};
struct D : virtual C {
  ~D() { std::printf("%d\n", static_cast<C&>(*this).f()); }
  int f() override { return 4; }
};
struct E : D {
  int f() override { return 5; }
};
int main() {
  E e;
}
```

Command:

```text
g++ -std=c++11 -O2 testcase.cpp -o test
./test
```

**Actual behavior:** The executable prints `5`. GCC resolves the virtual call
in `D::~D()` to the
virtual thunk for `E::f()`.

**Expected behavior:** The executable should print `4`. The conversion from `D`
to its virtual
base `C` is valid while `D` is being destroyed under [class.cdtor]/3. Under
[class.cdtor]/4, a
virtual call on the object under destruction selects the final overrider in the
destructor's
class, not an override in a more-derived class. The call must therefore select
`D::f()`.

https://eel.is/c++draft/class.cdtor#3
https://eel.is/c++draft/class.cdtor#4

GCC prints `4` at `-O0`, `-O1`, and `-Os`. GCC also prints `4` at `-O2` when
`-fno-devirtualize`, `-fno-inline`, `-fno-tree-forwprop`, or
`-fno-strict-aliasing` is added.
Clang trunk prints `4` at `-O2`.

## Regression evidence

GCC 4.9.4 prints `4` at `-O2`. GCC 5.5.0, GCC 10.5.0, GCC 13.3.0, and current
trunk print `5`.
The parent of r5-3634, commit `8e1ba78f1b8eedd6c65c6f0e6d6d09a801de5d3d`, also
prints `5`,
so this regression predates that change. The exact first bad commit has not
been identified.

## Suspected cause

The initial IPA devirtualization analysis records both valid targets:

```text
virtual int D::_ZTvn8_n32_N1D1fEv()
virtual int E::_ZTvn8_n32_N1E1fEv()
```

After inlining `E::~E()` and `D::~D()` into `main`, `forwprop2` asks
`possible_polymorphic_call_targets` about the guarded `OBJ_TYPE_REF`
expressions. Both queries are
resolved to the thunk for `E::f()`. The pass deletes the guarded alternatives
and the residual
virtual call, then leaves `printf ("%d\n", 5)`.

The context at that point uses the outer object `e` of type `E` and is marked
as possibly in
construction or destruction. The likely failure is in the construction-base
target walk: `C` is
a virtual base of `D`, and the walk from the complete `E` object reaches `C`
without retaining
`D::f()` as a possible overrider.

## Compiler version

```text
Using built-in specs.
COLLECT_GCC=/opt/gcc/bin/g++
COLLECT_LTO_WRAPPER=/opt/gcc/libexec/gcc/x86_64-pc-linux-gnu/17.0.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: /src/configure --prefix=/opt/gcc --enable-languages=c,c++
--disable-multilib --disable-bootstrap --enable-checking=yes
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 17.0.0 20261025 (experimental) (GCC)
```

Verified with GCC trunk commit `cd7a85774a992c4759ebd03a26ce03cbb934e4d9`.

Reply via email to