MSVC supports an extension allowing to delete an array of objects via pointer whose static type doesn't match its dynamic type. This is done via generation of special destructors - vector deleting destructors. MSVC's virtual tables always contain a pointer to the vector deleting destructor for classes with virtual destructors, so not having this extension implemented causes clang to generate code that is not compatible with the code generated by MSVC, because clang always puts a pointer to a scalar deleting destructor to the vtable. As a bonus the deletion of an array of polymorphic object will work just like it does with MSVC - no memory leaks and correct destructors are called. This patch will cause clang to emit code that is compatible with code produced by MSVC but not compatible with code produced with clang of older versions, so the new behavior can be disabled via passing -fclang-abi-compat=21 (or lower). This is yet another attempt to land vector deleting destructors support originally implemented by https://github.com/llvm/llvm-project/pull/133451. This PR contains fixes for issues reported in the original PR as well as fixes for issues related to operator delete[] search reported in several issues like https://github.com/llvm/llvm-project/pull/133950#issuecomment-2787510484 https://github.com/llvm/llvm-project/issues/134265 Fixes https://github.com/llvm/llvm-project/issues/19772
317 lines
8.5 KiB
C++
317 lines
8.5 KiB
C++
// RUN: %clang_cc1 %s -fno-rtti -triple=i386-pc-win32 -emit-llvm -fdump-vtable-layouts -o %t.ll > %t
|
|
// RUN: FileCheck --check-prefix=EMITS-VFTABLE %s < %t.ll
|
|
// RUN: FileCheck --check-prefix=NO-VFTABLE %s < %t.ll
|
|
// RUN: FileCheck %s < %t
|
|
|
|
struct A {
|
|
// CHECK-LABEL: VFTable for 'A' (3 entries)
|
|
// CHECK-NEXT: 0 | void A::f()
|
|
// CHECK-NEXT: 1 | void A::g()
|
|
// CHECK-NEXT: 2 | void A::h()
|
|
// CHECK-LABEL: VFTable indices for 'A' (3 entries)
|
|
// CHECK-NEXT: 0 | void A::f()
|
|
// CHECK-NEXT: 1 | void A::g()
|
|
// CHECK-NEXT: 2 | void A::h()
|
|
|
|
virtual void f();
|
|
virtual void g();
|
|
virtual void h();
|
|
int ia;
|
|
};
|
|
A a;
|
|
// EMITS-VFTABLE-DAG: @"??_7A@@6B@" = linkonce_odr unnamed_addr constant { [3 x ptr] }
|
|
void use(A *obj) { obj->f(); }
|
|
|
|
struct B : A {
|
|
// CHECK-LABEL: VFTable for 'A' in 'B' (5 entries)
|
|
// CHECK-NEXT: 0 | void B::f()
|
|
// CHECK-NEXT: 1 | void A::g()
|
|
// CHECK-NEXT: 2 | void A::h()
|
|
// CHECK-NEXT: 3 | void B::i()
|
|
// CHECK-NEXT: 4 | void B::j()
|
|
// CHECK-LABEL: VFTable indices for 'B' (3 entries)
|
|
// CHECK-NEXT: 0 | void B::f()
|
|
// CHECK-NEXT: 3 | void B::i()
|
|
// CHECK-NEXT: 4 | void B::j()
|
|
|
|
virtual void f(); // overrides A::f()
|
|
virtual void i();
|
|
virtual void j();
|
|
};
|
|
B b;
|
|
// EMITS-VFTABLE-DAG: @"??_7B@@6B@" = linkonce_odr unnamed_addr constant { [5 x ptr] }
|
|
void use(B *obj) { obj->f(); }
|
|
|
|
struct C {
|
|
// CHECK-LABEL: VFTable for 'C' (2 entries)
|
|
// CHECK-NEXT: 0 | C::~C() [vector deleting]
|
|
// CHECK-NEXT: 1 | void C::f()
|
|
// CHECK-LABEL: VFTable indices for 'C' (2 entries).
|
|
// CHECK-NEXT: 0 | C::~C() [vector deleting]
|
|
// CHECK-NEXT: 1 | void C::f()
|
|
|
|
virtual ~C();
|
|
virtual void f();
|
|
};
|
|
void C::f() {}
|
|
// NO-VFTABLE-NOT: @"??_7C@@6B@"
|
|
void use(C *obj) { obj->f(); }
|
|
|
|
struct D {
|
|
// CHECK-LABEL: VFTable for 'D' (2 entries)
|
|
// CHECK-NEXT: 0 | void D::f()
|
|
// CHECK-NEXT: 1 | D::~D() [vector deleting]
|
|
// CHECK-LABEL: VFTable indices for 'D' (2 entries)
|
|
// CHECK-NEXT: 0 | void D::f()
|
|
// CHECK-NEXT: 1 | D::~D() [vector deleting]
|
|
|
|
virtual void f();
|
|
virtual ~D();
|
|
};
|
|
D d;
|
|
// EMITS-VFTABLE-DAG: @"??_7D@@6B@" = linkonce_odr unnamed_addr constant { [2 x ptr] }
|
|
void use(D *obj) { obj->f(); }
|
|
|
|
struct E : A {
|
|
// CHECK-LABEL: VFTable for 'A' in 'E' (5 entries)
|
|
// CHECK-NEXT: 0 | void A::f()
|
|
// CHECK-NEXT: 1 | void A::g()
|
|
// CHECK-NEXT: 2 | void A::h()
|
|
// CHECK-NEXT: 3 | E::~E() [vector deleting]
|
|
// CHECK-NEXT: 4 | void E::i()
|
|
// CHECK-LABEL: VFTable indices for 'E' (2 entries).
|
|
// CHECK-NEXT: 3 | E::~E() [vector deleting]
|
|
// CHECK-NEXT: 4 | void E::i()
|
|
|
|
// ~E would be the key method, but it isn't used, and MS ABI has no key
|
|
// methods.
|
|
virtual ~E();
|
|
virtual void i();
|
|
};
|
|
void E::i() {}
|
|
// NO-VFTABLE-NOT: @"??_7E@@6B@"
|
|
void use(E *obj) { obj->i(); }
|
|
|
|
struct F : A {
|
|
// CHECK-LABEL: VFTable for 'A' in 'F' (5 entries)
|
|
// CHECK-NEXT: 0 | void A::f()
|
|
// CHECK-NEXT: 1 | void A::g()
|
|
// CHECK-NEXT: 2 | void A::h()
|
|
// CHECK-NEXT: 3 | void F::i()
|
|
// CHECK-NEXT: 4 | F::~F() [vector deleting]
|
|
// CHECK-LABEL: VFTable indices for 'F' (2 entries).
|
|
// CHECK-NEXT: 3 | void F::i()
|
|
// CHECK-NEXT: 4 | F::~F() [vector deleting]
|
|
|
|
virtual void i();
|
|
virtual ~F();
|
|
};
|
|
F f;
|
|
// EMITS-VFTABLE-DAG: @"??_7F@@6B@" = linkonce_odr unnamed_addr constant { [5 x ptr] }
|
|
void use(F *obj) { obj->i(); }
|
|
|
|
struct G : E {
|
|
// CHECK-LABEL: VFTable for 'A' in 'E' in 'G' (6 entries)
|
|
// CHECK-NEXT: 0 | void G::f()
|
|
// CHECK-NEXT: 1 | void A::g()
|
|
// CHECK-NEXT: 2 | void A::h()
|
|
// CHECK-NEXT: 3 | G::~G() [vector deleting]
|
|
// CHECK-NEXT: 4 | void E::i()
|
|
// CHECK-NEXT: 5 | void G::j()
|
|
// CHECK-LABEL: VFTable indices for 'G' (3 entries).
|
|
// CHECK-NEXT: 0 | void G::f()
|
|
// CHECK-NEXT: 3 | G::~G() [vector deleting]
|
|
// CHECK-NEXT: 5 | void G::j()
|
|
|
|
virtual void f(); // overrides A::f()
|
|
virtual ~G();
|
|
virtual void j();
|
|
};
|
|
void G::j() {}
|
|
// NO-VFTABLE-NOT: @"??_7G@@6B@"
|
|
void use(G *obj) { obj->j(); }
|
|
|
|
// Test that the usual Itanium-style key method does not emit a vtable.
|
|
struct H {
|
|
virtual void f();
|
|
};
|
|
void H::f() {}
|
|
// NO-VFTABLE-NOT: @"??_7H@@6B@"
|
|
|
|
struct Empty { };
|
|
|
|
struct I : Empty {
|
|
// CHECK-LABEL: VFTable for 'I' (2 entries)
|
|
// CHECK-NEXT: 0 | void I::f()
|
|
// CHECK-NEXT: 1 | void I::g()
|
|
virtual void f();
|
|
virtual void g();
|
|
};
|
|
|
|
I i;
|
|
void use(I *obj) { obj->f(); }
|
|
|
|
struct J {
|
|
// CHECK-LABEL: VFTable for 'J' (6 entries)
|
|
// CHECK-NEXT: 0 | void J::foo(long)
|
|
// CHECK-NEXT: 1 | void J::foo(int)
|
|
// CHECK-NEXT: 2 | void J::foo(short)
|
|
// CHECK-NEXT: 3 | void J::bar(long)
|
|
// CHECK-NEXT: 4 | void J::bar(int)
|
|
// CHECK-NEXT: 5 | void J::bar(short)
|
|
virtual void foo(short);
|
|
virtual void bar(short);
|
|
virtual void foo(int);
|
|
virtual void bar(int);
|
|
virtual void foo(long);
|
|
virtual void bar(long);
|
|
};
|
|
|
|
J j;
|
|
void use(J *obj) { obj->foo(42); }
|
|
|
|
struct K : J {
|
|
// CHECK-LABEL: VFTable for 'J' in 'K' (9 entries)
|
|
// CHECK-NEXT: 0 | void J::foo(long)
|
|
// CHECK-NEXT: 1 | void J::foo(int)
|
|
// CHECK-NEXT: 2 | void J::foo(short)
|
|
// CHECK-NEXT: 3 | void J::bar(long)
|
|
// CHECK-NEXT: 4 | void J::bar(int)
|
|
// CHECK-NEXT: 5 | void J::bar(short)
|
|
// CHECK-NEXT: 6 | void K::bar(double)
|
|
// CHECK-NEXT: 7 | void K::bar(float)
|
|
// CHECK-NEXT: 8 | void K::foo(float)
|
|
virtual void bar(float);
|
|
virtual void foo(float);
|
|
virtual void bar(double);
|
|
};
|
|
|
|
K k;
|
|
void use(K *obj) { obj->foo(42.0f); }
|
|
|
|
struct L : J {
|
|
// CHECK-LABEL: VFTable for 'J' in 'L' (9 entries)
|
|
// CHECK-NEXT: 0 | void J::foo(long)
|
|
// CHECK-NEXT: 1 | void L::foo(int)
|
|
// CHECK-NEXT: 2 | void J::foo(short)
|
|
// CHECK-NEXT: 3 | void J::bar(long)
|
|
// CHECK-NEXT: 4 | void J::bar(int)
|
|
// CHECK-NEXT: 5 | void J::bar(short)
|
|
// CHECK-NEXT: 6 | void L::foo(float)
|
|
// CHECK-NEXT: 7 | void L::bar(double)
|
|
// CHECK-NEXT: 8 | void L::bar(float)
|
|
|
|
// This case is interesting. Since the J::foo(int) override is the first method in
|
|
// the class, foo(float) precedes the bar(double) and bar(float) in the vftable.
|
|
virtual void foo(int);
|
|
virtual void bar(float);
|
|
virtual void foo(float);
|
|
virtual void bar(double);
|
|
};
|
|
|
|
L l;
|
|
void use(L *obj) { obj->foo(42.0f); }
|
|
|
|
struct M : J {
|
|
// CHECK-LABEL: VFTable for 'J' in 'M' (11 entries)
|
|
// CHECK-NEXT: 0 | void J::foo(long)
|
|
// CHECK-NEXT: 1 | void M::foo(int)
|
|
// CHECK-NEXT: 2 | void J::foo(short)
|
|
// CHECK-NEXT: 3 | void J::bar(long)
|
|
// CHECK-NEXT: 4 | void J::bar(int)
|
|
// CHECK-NEXT: 5 | void J::bar(short)
|
|
// CHECK-NEXT: 6 | void M::foo(float)
|
|
// CHECK-NEXT: 7 | void M::spam(long)
|
|
// CHECK-NEXT: 8 | void M::spam(int)
|
|
// CHECK-NEXT: 9 | void M::bar(double)
|
|
// CHECK-NEXT: 10 | void M::bar(float)
|
|
|
|
virtual void foo(int);
|
|
virtual void spam(int);
|
|
virtual void bar(float);
|
|
virtual void bar(double);
|
|
virtual void foo(float);
|
|
virtual void spam(long);
|
|
};
|
|
|
|
M m;
|
|
void use(M *obj) { obj->foo(42.0f); }
|
|
|
|
struct N {
|
|
// CHECK-LABEL: VFTable for 'N' (4 entries)
|
|
// CHECK-NEXT: 0 | void N::operator+(int)
|
|
// CHECK-NEXT: 1 | void N::operator+(short)
|
|
// CHECK-NEXT: 2 | void N::operator*(int)
|
|
// CHECK-NEXT: 3 | void N::operator*(short)
|
|
virtual void operator+(short);
|
|
virtual void operator*(short);
|
|
virtual void operator+(int);
|
|
virtual void operator*(int);
|
|
};
|
|
|
|
N n;
|
|
void use(N *obj) { obj->operator+(42); }
|
|
|
|
struct O { virtual A *f(); };
|
|
struct P : O { virtual B *f(); };
|
|
P p;
|
|
void use(O *obj) { obj->f(); }
|
|
void use(P *obj) { obj->f(); }
|
|
// CHECK-LABEL: VFTable for 'O' (1 entry)
|
|
// CHECK-NEXT: 0 | A *O::f()
|
|
|
|
// CHECK-LABEL: VFTable for 'O' in 'P' (1 entry)
|
|
// CHECK-NEXT: 0 | B *P::f()
|
|
|
|
struct Q {
|
|
// CHECK-LABEL: VFTable for 'Q' (2 entries)
|
|
// CHECK-NEXT: 0 | void Q::foo(int)
|
|
// CHECK-NEXT: 1 | void Q::bar(int)
|
|
void foo(short);
|
|
void bar(short);
|
|
virtual void bar(int);
|
|
virtual void foo(int);
|
|
};
|
|
|
|
Q q;
|
|
void use(Q *obj) { obj->foo(42); }
|
|
|
|
// Inherited non-virtual overloads don't participate in the ordering.
|
|
struct R : Q {
|
|
// CHECK-LABEL: VFTable for 'Q' in 'R' (4 entries)
|
|
// CHECK-NEXT: 0 | void Q::foo(int)
|
|
// CHECK-NEXT: 1 | void Q::bar(int)
|
|
// CHECK-NEXT: 2 | void R::bar(long)
|
|
// CHECK-NEXT: 3 | void R::foo(long)
|
|
virtual void bar(long);
|
|
virtual void foo(long);
|
|
};
|
|
|
|
R r;
|
|
void use(R *obj) { obj->foo(42l); }
|
|
|
|
struct S {
|
|
// CHECK-LABEL: VFTable for 'S' (1 entry).
|
|
// CHECK-NEXT: 0 | void S::f() [deleted]
|
|
virtual void f() = delete;
|
|
S();
|
|
// EMITS-VFTABLE-DAG: @"??_7S@@6B@" = linkonce_odr unnamed_addr constant { [1 x ptr] } { [1 x ptr] [ptr @_purecall] }
|
|
};
|
|
|
|
S::S() {}
|
|
|
|
struct T {
|
|
struct U {};
|
|
};
|
|
struct V : T {
|
|
// CHECK-LABEL: VFTable for 'V' (2 entries).
|
|
// CHECK-NEXT: 0 | void V::U()
|
|
// CHECK-NEXT: 1 | void V::f()
|
|
using T::U;
|
|
virtual void f();
|
|
virtual void U();
|
|
V();
|
|
};
|
|
|
|
V::V() {}
|