FPar wrote: Yes, certainly!
The example below demonstrates a case where the `available_externally` vtable mismatches the actual definition, and holds a reference to a symbol that does not exist in the final program. You can see the mismatch by compiling with `-O1 -flto=thin`. The generated LLVM IR should be identical to when `~D` is declared as ` virtual inline ~D();`, and the test above mine https://github.com/llvm/llvm-project/blob/main/clang/test/CodeGenCXX/vtable-available-externally.cpp#L242 asserts that the vtable should not be emitted in that case. I cannot demonstrate and end-to-end miscompilation here. We have an interaction that tripped over this, where we insert a reference in `a.cpp` to that destructor that does not exist based on the available_externally vtable, which results in a linking error. ```c++ //--- h.h struct A { virtual void foo() = 0; virtual void bar() = 0; virtual ~A() = default; }; struct B { virtual ~B() = default; }; struct C : B, A { void bar() override {} }; struct D : C { void foo() override; // If the dtor declaration contains the inline specifier as below, clang does // not emit the vtable available_externally. Since D is defined inline outside // the class, the two declaration should have the exact same effect. // // virtual inline ~D(); virtual ~D(); }; // D is defined inline, but not declared inline. Because clang does not check // for the definition, it misses it when checking for unused virtual inline // functions. inline D::~D() = default; ``` ```c++ //--- a.cpp #include "h.h" // This call instantiates the vtable of D in this TU as available_externally. // The third and fourth entries in that vtable @_ZTV1D are the destructors // @_ZN1DD1Ev and @_ZN1DD0Ev. Since the key function `foo` is not defined in // this TU, the vtable should be external. The destructor is defined as inline // in this TU, but not used in this TU. The symbol might not exist in the final // program. The emitted available_externally definition however references these // inline destructors. // // @_ZTV1D = available_externally dso_local unnamed_addr constant { [6 x ptr], // [6 x ptr] } { [6 x ptr] [ptr null, ptr @_ZTI1D, ptr @_ZN1DD1Ev, // ---------- // ptr @_ZN1DD0Ev, ptr @_ZN1C3barEv, ptr @_ZN1D3fooEv], [6 x ptr] // [ptr inttoptr (i64 -8 to ptr), ptr @_ZTI1D, // ptr @_ZThn8_N1D3fooEv, ptr @_ZThn8_N1C3barEv, // ptr @_ZThn8_N1DD1Ev, ptr @_ZThn8_N1DD0Ev] }, align 8 D *e() { return new D; } ``` ```c++ //--- b.cpp #include "h.h" // Define the key function here to get the actual vtable to be emitted in this // TU. The compiler implements the complete object destructor of D (@_ZN1DD1Ev) // through the base object destructor of C (@_ZN1CD2Ev). @_ZN1DD1Ev does not // exist in this TU, and the available_externally vtable definition of D in // a.cpp contains a reference to a non-existing symbol. // // @_ZTV1D = dso_local unnamed_addr constant { [6 x ptr], [6 x ptr] } { // [6 x ptr] [ptr null, ptr @_ZTI1D, ptr @_ZN1CD2Ev, // ---------- // ptr @_ZN1DD0Ev, ptr @_ZN1C3barEv, ptr @_ZN1D3fooEv], [6 x ptr] // [ptr inttoptr (i64 -8 to ptr), ptr @_ZTI1D, // ptr @_ZThn8_N1D3fooEv, ptr @_ZThn8_N1C3barEv, // ptr @_ZThn8_N1DD1Ev, ptr @_ZThn8_N1DD0Ev] }, align 8 void D::foo() {} ``` https://github.com/llvm/llvm-project/pull/100785 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits