llvm-project/clang/test/CodeGenCXX/microsoft-abi-structors.cpp
Mariya Podchishchaeva 842b57b775
Reland [MS][clang] Add support for vector deleting destructors (#133451)
Whereas it is UB in terms of the standard to delete an array of objects
via pointer whose static type doesn't match its dynamic type, MSVC
supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case,
currently not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts scalar deleting destructor to the vftable. This PR
aims to resolve these problems.

It was reverted due to link time errors in chromium with sanitizer
coverage enabled,
which is fixed by https://github.com/llvm/llvm-project/pull/131929 .

The second commit of this PR also contains a fix for a runtime failure
in chromium reported
in
https://github.com/llvm/llvm-project/pull/126240#issuecomment-2730216384
.

Fixes https://github.com/llvm/llvm-project/issues/19772
2025-03-31 10:03:39 +02:00

462 lines
16 KiB
C++

// RUN: %clang_cc1 -no-enable-noundef-analysis -emit-llvm -fno-rtti %s -std=c++11 -o - -mconstructor-aliases -triple=i386-pc-win32 -fno-rtti > %t
// RUN: FileCheck %s < %t
// vftables are emitted very late, so do another pass to try to keep the checks
// in source order.
// RUN: FileCheck --check-prefix DTORS %s < %t
// RUN: FileCheck --check-prefix DTORS2 %s < %t
// RUN: FileCheck --check-prefix DTORS3 %s < %t
// RUN: FileCheck --check-prefix DTORS4 %s < %t
//
// RUN: %clang_cc1 -emit-llvm %s -o - -mconstructor-aliases -triple=x86_64-pc-win32 -fno-rtti -std=c++11 | FileCheck --check-prefix DTORS-X64 %s
namespace basic {
class A {
public:
A() { }
~A();
};
void no_constructor_destructor_infinite_recursion() {
A a;
// CHECK: define linkonce_odr dso_local x86_thiscallcc ptr @"??0A@basic@@QAE@XZ"(ptr {{[^,]*}} returned {{[^,]*}} %this) {{.*}} comdat {{.*}} {
// CHECK: [[THIS_ADDR:%[.0-9A-Z_a-z]+]] = alloca ptr, align 4
// CHECK-NEXT: store ptr %this, ptr [[THIS_ADDR]], align 4
// CHECK-NEXT: [[T1:%[.0-9A-Z_a-z]+]] = load ptr, ptr [[THIS_ADDR]]
// CHECK-NEXT: ret ptr [[T1]]
// CHECK-NEXT: }
}
A::~A() {
// Make sure that the destructor doesn't call itself:
// CHECK: define {{.*}} @"??1A@basic@@QAE@XZ"
// CHECK-NOT: call void @"??1A@basic@@QAE@XZ"
// CHECK: ret
}
struct B {
B();
};
// Tests that we can define constructors outside the class (PR12784).
B::B() {
// CHECK: define dso_local x86_thiscallcc ptr @"??0B@basic@@QAE@XZ"(ptr {{[^,]*}} returned {{[^,]*}} %this)
// CHECK: ret
}
struct C {
virtual ~C() {
// DTORS: define linkonce_odr dso_local x86_thiscallcc ptr @"??_GC@basic@@UAEPAXI@Z"(ptr {{[^,]*}} %this, i32 %should_call_delete) {{.*}} comdat {{.*}} {
// DTORS: store i32 %should_call_delete, ptr %[[SHOULD_DELETE_VAR:[0-9a-z._]+]], align 4
// DTORS: store ptr %{{.*}}, ptr %[[RETVAL:retval]]
// DTORS: %[[SHOULD_DELETE_VALUE:[0-9a-z._]+]] = load i32, ptr %[[SHOULD_DELETE_VAR]]
// DTORS: call x86_thiscallcc void @"??1C@basic@@UAE@XZ"(ptr {{[^,]*}} %[[THIS:[0-9a-z]+]])
// DTORS-NEXT: %[[AND:[0-9]+]] = and i32 %[[SHOULD_DELETE_VALUE]], 1
// DTORS-NEXT: %[[CONDITION:[0-9]+]] = icmp eq i32 %[[AND]], 0
// DTORS-NEXT: br i1 %[[CONDITION]], label %[[CONTINUE_LABEL:[0-9a-z._]+]], label %[[CALL_DELETE_LABEL:[0-9a-z._]+]]
//
// DTORS: [[CALL_DELETE_LABEL]]
// DTORS-NEXT: call void @"??3@YAXPAX@Z"(ptr %[[THIS]])
// DTORS-NEXT: br label %[[CONTINUE_LABEL]]
//
// DTORS: [[CONTINUE_LABEL]]
// DTORS-NEXT: %[[RET:.*]] = load ptr, ptr %[[RETVAL]]
// DTORS-NEXT: ret ptr %[[RET]]
// Check that we do the mangling correctly on x64.
// DTORS-X64: @"??_GC@basic@@UEAAPEAXI@Z"
}
virtual void foo();
};
// Emits the vftable in the output.
void C::foo() {}
void check_vftable_offset() {
C c;
// The vftable pointer should point at the beginning of the vftable.
// CHECK: store ptr @"??_7C@basic@@6B@", ptr {{.*}}
}
void call_complete_dtor(C *obj_ptr) {
// CHECK: define dso_local void @"?call_complete_dtor@basic@@YAXPAUC@1@@Z"(ptr %obj_ptr)
obj_ptr->~C();
// CHECK: %[[OBJ_PTR_VALUE:.*]] = load ptr, ptr %{{.*}}, align 4
// CHECK-NEXT: %[[VTABLE:.*]] = load ptr, ptr %[[OBJ_PTR_VALUE]]
// CHECK-NEXT: %[[PVDTOR:.*]] = getelementptr inbounds ptr, ptr %[[VTABLE]], i64 0
// CHECK-NEXT: %[[VDTOR:.*]] = load ptr, ptr %[[PVDTOR]]
// CHECK-NEXT: call x86_thiscallcc ptr %[[VDTOR]](ptr {{[^,]*}} %[[OBJ_PTR_VALUE]], i32 0)
// CHECK-NEXT: ret void
}
void call_deleting_dtor(C *obj_ptr) {
// CHECK: define dso_local void @"?call_deleting_dtor@basic@@YAXPAUC@1@@Z"(ptr %obj_ptr)
delete obj_ptr;
// CHECK: %[[OBJ_PTR_VALUE:.*]] = load ptr, ptr %{{.*}}, align 4
// CHECK: br i1 {{.*}}, label %[[DELETE_NULL:.*]], label %[[DELETE_NOTNULL:.*]]
// CHECK: [[DELETE_NOTNULL]]
// CHECK-NEXT: %[[VTABLE:.*]] = load ptr, ptr %[[OBJ_PTR_VALUE]]
// CHECK-NEXT: %[[PVDTOR:.*]] = getelementptr inbounds ptr, ptr %[[VTABLE]], i64 0
// CHECK-NEXT: %[[VDTOR:.*]] = load ptr, ptr %[[PVDTOR]]
// CHECK-NEXT: call x86_thiscallcc ptr %[[VDTOR]](ptr {{[^,]*}} %[[OBJ_PTR_VALUE]], i32 1)
// CHECK: ret void
}
void call_deleting_dtor_and_global_delete(C *obj_ptr) {
// CHECK: define dso_local void @"?call_deleting_dtor_and_global_delete@basic@@YAXPAUC@1@@Z"(ptr %obj_ptr)
::delete obj_ptr;
// CHECK: %[[OBJ_PTR_VALUE:.*]] = load ptr, ptr %{{.*}}, align 4
// CHECK: br i1 {{.*}}, label %[[DELETE_NULL:.*]], label %[[DELETE_NOTNULL:.*]]
// CHECK: [[DELETE_NOTNULL]]
// CHECK-NEXT: %[[VTABLE:.*]] = load ptr, ptr %[[OBJ_PTR_VALUE]]
// CHECK-NEXT: %[[PVDTOR:.*]] = getelementptr inbounds ptr, ptr %[[VTABLE]], i64 0
// CHECK-NEXT: %[[VDTOR:.*]] = load ptr, ptr %[[PVDTOR]]
// CHECK-NEXT: %[[CALL:.*]] = call x86_thiscallcc ptr %[[VDTOR]](ptr {{[^,]*}} %[[OBJ_PTR_VALUE]], i32 0)
// CHECK-NEXT: call void @"??3@YAXPAX@Z"(ptr %[[CALL]])
// CHECK: ret void
}
struct D {
static int foo();
D() {
static int ctor_static = foo();
// CHECK that the static in the ctor gets mangled correctly:
// CHECK: @"?ctor_static@?1???0D@basic@@QAE@XZ@4HA"
}
~D() {
static int dtor_static = foo();
// CHECK that the static in the dtor gets mangled correctly:
// CHECK: @"?dtor_static@?1???1D@basic@@QAE@XZ@4HA"
}
};
void use_D() { D c; }
} // end namespace basic
namespace dtor_in_second_nvbase {
struct A {
virtual void f(); // A needs vftable to be primary.
};
struct B {
virtual ~B();
};
struct C : A, B {
virtual ~C();
};
C::~C() {
// CHECK-LABEL: define dso_local x86_thiscallcc void @"??1C@dtor_in_second_nvbase@@UAE@XZ"(ptr{{[^,]*}} %this)
// No this adjustment!
// CHECK-NOT: getelementptr
// CHECK: load ptr, ptr %{{.*}}
// Now we this-adjust before calling ~B.
// CHECK: getelementptr inbounds i8, ptr %{{.*}}, i32 4
// CHECK: call x86_thiscallcc void @"??1B@dtor_in_second_nvbase@@UAE@XZ"(ptr{{[^,]*}} %{{.*}})
// CHECK: ret void
}
void foo() {
C c;
}
// DTORS2-LABEL: define linkonce_odr dso_local x86_thiscallcc ptr @"??_EC@dtor_in_second_nvbase@@W3AEPAXI@Z"(ptr %this, i32 %should_call_delete)
// Do an adjustment from B* to C*.
// DTORS2: getelementptr i8, ptr %{{.*}}, i32 -4
// DTORS2: %[[CALL:.*]] = tail call x86_thiscallcc ptr @"??_EC@dtor_in_second_nvbase@@UAEPAXI@Z"
// DTORS2: ret ptr %[[CALL]]
}
namespace test2 {
// Just like dtor_in_second_nvbase, except put that in a vbase of a diamond.
// C's dtor is in the non-primary base.
struct A { virtual void f(); };
struct B { virtual ~B(); };
struct C : A, B { virtual ~C(); int c; };
// Diamond hierarchy, with C as the shared vbase.
struct D : virtual C { int d; };
struct E : virtual C { int e; };
struct F : D, E { ~F(); int f; };
F::~F() {
// CHECK-LABEL: define dso_local x86_thiscallcc void @"??1F@test2@@UAE@XZ"(ptr{{[^,]*}})
// Do an adjustment from C vbase subobject to F as though F was the
// complete type.
// CHECK: getelementptr inbounds i8, ptr %{{.*}}, i32 -20
// CHECK: store ptr
}
void foo() {
F f;
}
// DTORS3-LABEL: define linkonce_odr dso_local x86_thiscallcc void @"??_DF@test2@@QAEXXZ"({{.*}} {{.*}} comdat
// Do an adjustment from C* to F*.
// DTORS3: getelementptr i8, ptr %{{.*}}, i32 20
// DTORS3: call x86_thiscallcc void @"??1F@test2@@UAE@XZ"
// DTORS3: ret void
}
namespace constructors {
struct A {
A() {}
};
struct B : A {
B();
~B();
};
B::B() {
// CHECK: define dso_local x86_thiscallcc ptr @"??0B@constructors@@QAE@XZ"(ptr {{[^,]*}} returned {{[^,]*}} %this)
// CHECK: call x86_thiscallcc ptr @"??0A@constructors@@QAE@XZ"(ptr {{[^,]*}} %{{.*}})
// CHECK: ret
}
struct C : virtual A {
C();
};
C::C() {
// CHECK: define dso_local x86_thiscallcc ptr @"??0C@constructors@@QAE@XZ"(ptr {{[^,]*}} returned {{[^,]*}} %this, i32 %is_most_derived)
// TODO: make sure this works in the Release build too;
// CHECK: store i32 %is_most_derived, ptr %[[IS_MOST_DERIVED_VAR:.*]], align 4
// CHECK: %[[IS_MOST_DERIVED_VAL:.*]] = load i32, ptr %[[IS_MOST_DERIVED_VAR]]
// CHECK: %[[SHOULD_CALL_VBASE_CTORS:.*]] = icmp ne i32 %[[IS_MOST_DERIVED_VAL]], 0
// CHECK: br i1 %[[SHOULD_CALL_VBASE_CTORS]], label %[[INIT_VBASES:.*]], label %[[SKIP_VBASES:.*]]
//
// CHECK: [[INIT_VBASES]]
// CHECK-NEXT: %[[vbptr_off:.*]] = getelementptr inbounds i8, ptr %{{.*}}, i32 0
// CHECK-NEXT: store ptr @"??_8C@constructors@@7B@", ptr %[[vbptr_off]]
// CHECK-NEXT: getelementptr inbounds i8, ptr %{{.*}}, i32 4
// CHECK-NEXT: call x86_thiscallcc ptr @"??0A@constructors@@QAE@XZ"(ptr {{[^,]*}} %{{.*}})
// CHECK-NEXT: br label %[[SKIP_VBASES]]
//
// CHECK: [[SKIP_VBASES]]
// Class C does not define or override methods, so shouldn't change the vfptr.
// CHECK-NOT: @"??_7C@constructors@@6B@"
// CHECK: ret
}
void create_C() {
C c;
// CHECK: define dso_local void @"?create_C@constructors@@YAXXZ"()
// CHECK: call x86_thiscallcc ptr @"??0C@constructors@@QAE@XZ"(ptr {{[^,]*}} %c, i32 1)
// CHECK: ret
}
struct D : C {
D();
};
D::D() {
// CHECK: define dso_local x86_thiscallcc ptr @"??0D@constructors@@QAE@XZ"(ptr {{[^,]*}} returned {{[^,]*}} %this, i32 %is_most_derived) unnamed_addr
// CHECK: store i32 %is_most_derived, ptr %[[IS_MOST_DERIVED_VAR:.*]], align 4
// CHECK: %[[IS_MOST_DERIVED_VAL:.*]] = load i32, ptr %[[IS_MOST_DERIVED_VAR]]
// CHECK: %[[SHOULD_CALL_VBASE_CTORS:.*]] = icmp ne i32 %[[IS_MOST_DERIVED_VAL]], 0
// CHECK: br i1 %[[SHOULD_CALL_VBASE_CTORS]], label %[[INIT_VBASES:.*]], label %[[SKIP_VBASES:.*]]
//
// CHECK: [[INIT_VBASES]]
// CHECK-NEXT: %[[vbptr_off:.*]] = getelementptr inbounds i8, ptr %{{.*}}, i32 0
// CHECK-NEXT: store ptr @"??_8D@constructors@@7B@", ptr %[[vbptr_off]]
// CHECK-NEXT: getelementptr inbounds i8, ptr %{{.*}}, i32 4
// CHECK-NEXT: call x86_thiscallcc ptr @"??0A@constructors@@QAE@XZ"(ptr {{[^,]*}} %{{.*}})
// CHECK-NEXT: br label %[[SKIP_VBASES]]
//
// CHECK: [[SKIP_VBASES]]
// CHECK: call x86_thiscallcc ptr @"??0C@constructors@@QAE@XZ"(ptr {{[^,]*}} %{{.*}}, i32 0)
// CHECK: ret
}
struct E : virtual C {
E();
};
E::E() {
// CHECK: define dso_local x86_thiscallcc ptr @"??0E@constructors@@QAE@XZ"(ptr {{[^,]*}} returned {{[^,]*}} %this, i32 %is_most_derived) unnamed_addr
// CHECK: store i32 %is_most_derived, ptr %[[IS_MOST_DERIVED_VAR:.*]], align 4
// CHECK: %[[IS_MOST_DERIVED_VAL:.*]] = load i32, ptr %[[IS_MOST_DERIVED_VAR]]
// CHECK: %[[SHOULD_CALL_VBASE_CTORS:.*]] = icmp ne i32 %[[IS_MOST_DERIVED_VAL]], 0
// CHECK: br i1 %[[SHOULD_CALL_VBASE_CTORS]], label %[[INIT_VBASES:.*]], label %[[SKIP_VBASES:.*]]
//
// CHECK: [[INIT_VBASES]]
// CHECK-NEXT: %[[offs:.*]] = getelementptr inbounds i8, ptr %{{.*}}, i32 0
// CHECK-NEXT: store ptr @"??_8E@constructors@@7B01@@", ptr %[[offs]]
// CHECK-NEXT: %[[offs:.*]] = getelementptr inbounds i8, ptr %{{.*}}, i32 4
// CHECK-NEXT: store ptr @"??_8E@constructors@@7BC@1@@", ptr %[[offs]]
// CHECK-NEXT: getelementptr inbounds i8, ptr %{{.*}}, i32 4
// CHECK-NEXT: call x86_thiscallcc ptr @"??0A@constructors@@QAE@XZ"(ptr {{[^,]*}} %{{.*}})
// CHECK: call x86_thiscallcc ptr @"??0C@constructors@@QAE@XZ"(ptr {{[^,]*}} %{{.*}}, i32 0)
// CHECK-NEXT: br label %[[SKIP_VBASES]]
//
// CHECK: [[SKIP_VBASES]]
// CHECK: ret
}
// PR16735 - even abstract classes should have a constructor emitted.
struct F {
F();
virtual void f() = 0;
};
F::F() {}
// CHECK: define dso_local x86_thiscallcc ptr @"??0F@constructors@@QAE@XZ"
} // end namespace constructors
namespace dtors {
struct A {
~A();
};
void call_nv_complete(A *a) {
a->~A();
// CHECK: define dso_local void @"?call_nv_complete@dtors@@YAXPAUA@1@@Z"
// CHECK: call x86_thiscallcc void @"??1A@dtors@@QAE@XZ"
// CHECK: ret
}
// CHECK: declare dso_local x86_thiscallcc void @"??1A@dtors@@QAE@XZ"
// Now try some virtual bases, where we need the complete dtor.
struct B : virtual A { ~B(); };
struct C : virtual A { ~C(); };
struct D : B, C { ~D(); };
void call_vbase_complete(D *d) {
d->~D();
// CHECK: define dso_local void @"?call_vbase_complete@dtors@@YAXPAUD@1@@Z"
// CHECK: call x86_thiscallcc void @"??_DD@dtors@@QAEXXZ"(ptr {{[^,]*}} %{{[^,]+}})
// CHECK: ret
}
// The complete dtor should call the base dtors for D and the vbase A (once).
// CHECK: define linkonce_odr dso_local x86_thiscallcc void @"??_DD@dtors@@QAEXXZ"({{.*}}) {{.*}} comdat
// CHECK-NOT: call
// CHECK: call x86_thiscallcc void @"??1D@dtors@@QAE@XZ"
// CHECK-NOT: call
// CHECK: call x86_thiscallcc void @"??1A@dtors@@QAE@XZ"
// CHECK-NOT: call
// CHECK: ret
void destroy_d_complete() {
D d;
// CHECK: define dso_local void @"?destroy_d_complete@dtors@@YAXXZ"
// CHECK: call x86_thiscallcc void @"??_DD@dtors@@QAEXXZ"(ptr {{[^,]*}} %{{[^,]+}})
// CHECK: ret
}
// FIXME: Clang manually inlines the deletion, so we don't get a call to the
// deleting dtor (_G). The only way to call deleting dtors currently is through
// a vftable.
void call_nv_deleting_dtor(D *d) {
delete d;
// CHECK: define dso_local void @"?call_nv_deleting_dtor@dtors@@YAXPAUD@1@@Z"
// CHECK: call x86_thiscallcc void @"??_DD@dtors@@QAEXXZ"(ptr {{[^,]*}} %{{[^,]+}})
// CHECK: call void @"??3@YAXPAX@Z"
// CHECK: ret
}
}
namespace test1 {
struct A { };
struct B : virtual A {
B(int *a);
B(const char *a, ...);
__cdecl B(short *a);
};
B::B(int *a) {}
B::B(const char *a, ...) {}
B::B(short *a) {}
// CHECK: define dso_local x86_thiscallcc ptr @"??0B@test1@@QAE@PAH@Z"
// CHECK: (ptr {{[^,]*}} returned {{[^,]*}} %this, ptr %a, i32 %is_most_derived)
// CHECK: define dso_local ptr @"??0B@test1@@QAA@PBDZZ"
// CHECK: (ptr {{[^,]*}} returned {{[^,]*}} %this, i32 %is_most_derived, ptr %a, ...)
// CHECK: define dso_local x86_thiscallcc ptr @"??0B@test1@@QAE@PAF@Z"
// CHECK: (ptr {{[^,]*}} returned {{[^,]*}} %this, ptr %a, i32 %is_most_derived)
void construct_b() {
int a;
B b1(&a);
B b2("%d %d", 1, 2);
}
// CHECK-LABEL: define dso_local void @"?construct_b@test1@@YAXXZ"()
// CHECK: call x86_thiscallcc ptr @"??0B@test1@@QAE@PAH@Z"
// CHECK: (ptr {{.*}}, ptr {{.*}}, i32 1)
// CHECK: call ptr (ptr, i32, ptr, ...) @"??0B@test1@@QAA@PBDZZ"
// CHECK: (ptr {{.*}}, i32 1, ptr {{.*}}, i32 1, i32 2)
}
namespace implicit_copy_vtable {
// This was a crash that only reproduced in ABIs without key functions.
struct ImplicitCopy {
// implicit copy ctor
virtual ~ImplicitCopy();
};
void CreateCopy(ImplicitCopy *a) {
new ImplicitCopy(*a);
}
// CHECK: store {{.*}} @"??_7ImplicitCopy@implicit_copy_vtable@@6B@"
struct MoveOnly {
MoveOnly(MoveOnly &&o) = default;
virtual ~MoveOnly();
};
MoveOnly &&f();
void g() { new MoveOnly(f()); }
// CHECK: store {{.*}} @"??_7MoveOnly@implicit_copy_vtable@@6B@"
}
namespace delegating_ctor {
struct Y {};
struct X : virtual Y {
X(int);
X();
};
X::X(int) : X() {}
}
// CHECK: define dso_local x86_thiscallcc ptr @"??0X@delegating_ctor@@QAE@H@Z"(
// CHECK: %[[is_most_derived_addr:.*]] = alloca i32, align 4
// CHECK: store i32 %is_most_derived, ptr %[[is_most_derived_addr]]
// CHECK: %[[is_most_derived:.*]] = load i32, ptr %[[is_most_derived_addr]]
// CHECK: call x86_thiscallcc ptr @"??0X@delegating_ctor@@QAE@XZ"({{.*}} i32 %[[is_most_derived]])
// Dtor thunks for classes in anonymous namespaces should be internal, not
// linkonce_odr.
namespace {
struct A {
virtual ~A() { }
};
}
void *getA() {
return (void*)new A();
}
// CHECK: define internal x86_thiscallcc ptr @"??_GA@?A0x{{[^@]*}}@@UAEPAXI@Z"
// CHECK: (ptr {{[^,]*}} %this, i32 %should_call_delete)
// CHECK: define internal x86_thiscallcc void @"??1A@?A0x{{[^@]*}}@@UAE@XZ"
// CHECK: (ptr {{[^,]*}} %this)
// Check that we correctly transform __stdcall to __thiscall for ctors and
// dtors.
class G {
public:
__stdcall G() {};
// DTORS4: define linkonce_odr dso_local x86_thiscallcc ptr @"??0G@@QAE@XZ"
__stdcall ~G() {};
// DTORS4: define linkonce_odr dso_local x86_thiscallcc void @"??1G@@QAE@XZ"
};
extern void testG() {
G g;
}