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

            Bug ID: 128194
           Summary: [LTO] "missing resolution data" for a vtable declared
                    as an extern "C" array in a file that also constructs
                    the class
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: lto
          Assignee: unassigned at gcc dot gnu.org
          Reporter: clopez at igalia dot com
  Target Milestone: ---

Created attachment 65975
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65975&action=edit
testcase

Linking with -flto fails with

    lto1: fatal error: missing resolution data for _ZTV7Derived

when a translation unit declares the vtable of a class as an extern "C" array,
using its mangled name, and also has code that constructs that class, while the
vtable itself is emitted in another translation unit (the one with the key
function). The code that constructs the class doesn't even need to run: in the
testcase below it is in a function that nobody calls. Without -flto the same
files link fine and the program works.

I'm aware that _ZTV7Derived is a reserved name, so this is outside of what the
standard guarantees. Still, GCC seems to know what is going on, because if I
change the testcase so that it links (for example by removing unusedMake() from
b.cpp), lto1 warns at link time that the "virtual table of type 'struct
Derived' violates one definition rule" and that a "variable of same assembler
name as the virtual table is defined in another translation unit", and the
program works. In the failing case, however, there is only the fatal error and
nothing that points to the cause. I would expect either the link to succeed, as
it does without LTO, or a diagnostic about the clash.

We hit this in WebKit, because of a security check in its generated JavaScript
bindings. Before a binding hands a C++ object to JavaScript, it makes sure that
the object really is of the class it expects by comparing its vtable pointer
with the vtable of that class, and it gets the address of that vtable by
declaring it as extern "C" { extern void* _ZTVN7WebCore...E[]; }. Many of these
bindings also construct or destroy the class through inline functions, so
building WebKit with GCC and LTO fails with this error for one class after
another.

I reproduced it with GCC 14.2.0 (x86_64, Debian) and 13.4.0 (aarch64 cross
compiler), and also got it with 15.2.0 when building WebKit. On Compiler
Explorer (x86-64), GCC 7.3 and older link the testcase and the program works,
while 6.5, 7.4, and every release from 8.1 up to 16.2 and trunk fail. Those are
the releases that include r257412, the fix for PR 81004 that added the check
that gives this error, so this looks like a regression from that change. It
could also be that the resolution data was already being lost before, and the
check only made it visible.

The closest existing report I found is PR 101382, where two declarations with
the same assembler name in one C file make the link fail with the same error.
Here b.o also has two declarations with the assembler name _ZTV7Derived, the
extern "C" array and the vtable that "new Derived" uses, but the resolution
file (a.res, kept by adding -save-temps to the link) lists the name only once
for b.o:

    b.o 4
    ... PREVAILING_DEF_IRONLY _Z16hasDerivedVTableP4Base
    ... PREVAILING_DEF_IRONLY _Z10unusedMakev
    ... RESOLVED_IR _ZTV7Derived
    ... UNDEF _Znwm

My guess is that lto1 gets resolution data for only one of the two
declarations, but I haven't checked that in the GCC sources.

The testcase is attached as lto-missing-resolution-vtable.tar.gz, and it's also
on Compiler Explorer at https://godbolt.org/z/d3KEsqPdx with gcc 16.2, where
switching the compiler to 7.3 shows it linking. These are its files:

--- vt.h
struct Base {
    virtual ~Base();
};

struct Derived : Base {
    ~Derived();
};

--- a.cpp
#include "vt.h"

Base::~Base() { }
Derived::~Derived() { }

Base* make()
{
    return new Derived;
}

--- b.cpp
#include "vt.h"

extern "C" void* _ZTV7Derived[];

bool hasDerivedVTable(Base* object)
{
    return *reinterpret_cast<void**>(object) == &_ZTV7Derived[2];
}

Base* unusedMake()
{
    return new Derived;
}

--- main.cpp
struct Base;
Base* make();
bool hasDerivedVTable(Base*);

int main()
{
    return hasDerivedVTable(make()) ? 0 : 1;
}

--- commands
$ g++ -O2 -flto -c a.cpp b.cpp main.cpp
$ g++ -O2 -flto a.o b.o main.o
lto1: fatal error: missing resolution data for _ZTV7Derived
compilation terminated.
lto-wrapper: fatal error: /usr/bin/g++ returned 1 exit status
compilation terminated.
/usr/bin/ld: error: lto-wrapper failed
collect2: error: ld returned 1 exit status

$ g++ -O2 -c a.cpp b.cpp main.cpp && g++ -O2 a.o b.o main.o && ./a.out && echo
ok
ok

The error goes away if I remove unusedMake() from b.cpp, if I define the
constructor of Derived out of line in a.cpp, or if I compile b.cpp with
-fno-lto. In the larger testcase I started from, declaring the array hidden,
weak or const, declaring it as char instead of void*, or declaring it inside
the function with an asm label didn't help.

Reply via email to