[clang] [clang] Check inline defs when emitting speculative vtable (PR #100785)
Fabian Parzefall via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 26 13:23:57 PDT 2024
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
More information about the cfe-commits
mailing list