[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