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

Reply via email to