diff --git a/lib/IRGen/GenStruct.cpp b/lib/IRGen/GenStruct.cpp index 3f1961f80664b..de2f8ebc3cda2 100644 --- a/lib/IRGen/GenStruct.cpp +++ b/lib/IRGen/GenStruct.cpp @@ -16,20 +16,25 @@ #include "GenStruct.h" -#include "swift/AST/Types.h" +#include "swift/AST/ClangModuleLoader.h" #include "swift/AST/Decl.h" #include "swift/AST/IRGenOptions.h" #include "swift/AST/Pattern.h" #include "swift/AST/SubstitutionMap.h" +#include "swift/AST/Types.h" #include "swift/IRGen/Linking.h" #include "swift/SIL/SILModule.h" -#include "llvm/IR/DerivedTypes.h" -#include "llvm/IR/Function.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/Mangle.h" #include "clang/AST/RecordLayout.h" +#include "clang/CodeGen/CodeGenABITypes.h" #include "clang/CodeGen/SwiftCallingConv.h" +#include "clang/Sema/Sema.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" #include "GenMeta.h" #include "GenRecord.h" @@ -62,6 +67,24 @@ static StructTypeInfoKind getStructTypeInfoKind(const TypeInfo &type) { return (StructTypeInfoKind) type.getSubclassKind(); } +/// If this type has a CXXDestructorDecl, find it and return it. Otherwise, +/// return nullptr. +static clang::CXXDestructorDecl *getCXXDestructor(SILType type) { + auto *structDecl = type.getStructOrBoundGenericStruct(); + if (!structDecl || !structDecl->getClangDecl()) + return nullptr; + const clang::CXXRecordDecl *cxxRecordDecl = + dyn_cast(structDecl->getClangDecl()); + if (!cxxRecordDecl) + return nullptr; + for (auto member : cxxRecordDecl->methods()) { + if (auto dest = dyn_cast(member)) { + return dest; + } + } + return nullptr; +} + namespace { class StructFieldInfo : public RecordField { public: @@ -362,11 +385,60 @@ namespace { // with user-defined special member functions. SpareBitVector(llvm::Optional{ llvm::APInt(size.getValueInBits(), 0)}), - align, IsPOD, IsNotBitwiseTakable, IsFixedSize), + align, IsNotPOD, IsNotBitwiseTakable, IsFixedSize), ClangDecl(clangDecl) { (void)ClangDecl; } + void destroy(IRGenFunction &IGF, Address address, SILType T, + bool isOutlined) const override { + auto *destructor = getCXXDestructor(T); + // If the destructor is trivial, clang will assert when we call + // `emitCXXDestructorCall` so, just let Swift handle this destructor. + if (!destructor || destructor->isTrivial()) { + // If we didn't find a destructor to call, bail out to the parent + // implementation. + StructTypeInfoBase::destroy(IGF, address, T, + isOutlined); + return; + } + + if (!destructor->isUserProvided() && + !destructor->doesThisDeclarationHaveABody()) { + assert(!destructor->isDeleted() && + "Swift cannot handle a type with no known destructor."); + // Make sure we define the destructor so we have something to call. + auto &sema = IGF.IGM.Context.getClangModuleLoader()->getClangSema(); + sema.DefineImplicitDestructor(clang::SourceLocation(), destructor); + } + + clang::GlobalDecl destructorGlobalDecl(destructor, clang::Dtor_Complete); + auto *destructorFnAddr = + cast(IGF.IGM.getAddrOfClangGlobalDecl( + destructorGlobalDecl, NotForDefinition)); + + SmallVector args; + auto *thisArg = IGF.coerceValue(address.getAddress(), + destructorFnAddr->getArg(0)->getType(), + IGF.IGM.DataLayout); + args.push_back(thisArg); + llvm::Value *implicitParam = + clang::CodeGen::getCXXDestructorImplicitParam( + IGF.IGM.getClangCGM(), IGF.Builder.GetInsertBlock(), + IGF.Builder.GetInsertPoint(), destructor, clang::Dtor_Complete, + false, false); + if (implicitParam) { + implicitParam = IGF.coerceValue(implicitParam, + destructorFnAddr->getArg(1)->getType(), + IGF.IGM.DataLayout); + args.push_back(implicitParam); + } + + IGF.Builder.CreateCall(destructorFnAddr->getFunctionType(), + destructorFnAddr, args); + } + TypeLayoutEntry *buildTypeLayoutEntry(IRGenModule &IGM, SILType T) const override { return IGM.typeLayoutCache.getOrCreateScalarEntry(*this, T); diff --git a/lib/IRGen/IRGenModule.h b/lib/IRGen/IRGenModule.h index ab13c39280125..3ccbc3ea5dd9c 100644 --- a/lib/IRGen/IRGenModule.h +++ b/lib/IRGen/IRGenModule.h @@ -1062,10 +1062,10 @@ class IRGenModule { SILType objectType, const TypeInfo &objectTI, const OutliningMetadataCollector &collector); -private: llvm::Constant *getAddrOfClangGlobalDecl(clang::GlobalDecl global, ForDefinition_t forDefinition); +private: using CopyAddrHelperGenerator = llvm::function_ref; diff --git a/test/Interop/Cxx/class/Inputs/destructors.h b/test/Interop/Cxx/class/Inputs/destructors.h new file mode 100644 index 0000000000000..3804991edeb03 --- /dev/null +++ b/test/Interop/Cxx/class/Inputs/destructors.h @@ -0,0 +1,20 @@ +#ifndef TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H +#define TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H + +struct DummyStruct {}; + +struct HasUserProvidedDestructorAndDummy { + DummyStruct dummy; + ~HasUserProvidedDestructorAndDummy() {} +}; + +struct HasUserProvidedDestructor { + int *value; + ~HasUserProvidedDestructor() { *value = 42; } +}; + +struct HasNonTrivialImplicitDestructor { + HasUserProvidedDestructor member; +}; + +#endif // TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H diff --git a/test/Interop/Cxx/class/Inputs/module.modulemap b/test/Interop/Cxx/class/Inputs/module.modulemap index 2ab3295eb2715..4ac709036f6f6 100644 --- a/test/Interop/Cxx/class/Inputs/module.modulemap +++ b/test/Interop/Cxx/class/Inputs/module.modulemap @@ -18,6 +18,11 @@ module ConstructorsObjC { requires cplusplus } +module Destructors { + header "destructors.h" + requires cplusplus +} + module LoadableTypes { header "loadable-types.h" requires cplusplus diff --git a/test/Interop/Cxx/class/destructors-correct-abi-irgen.swift b/test/Interop/Cxx/class/destructors-correct-abi-irgen.swift new file mode 100644 index 0000000000000..2d355bccb95e7 --- /dev/null +++ b/test/Interop/Cxx/class/destructors-correct-abi-irgen.swift @@ -0,0 +1,13 @@ +// RUN: %swift -I %S/Inputs -enable-cxx-interop -emit-ir %s | %FileCheck %s + +import Destructors + +// CHECK-LABEL: define {{.*}}void @"$s4main4testyyF" +// CHECK: [[H:%.*]] = alloca %TSo33HasUserProvidedDestructorAndDummyV +// CHECK: [[CXX_THIS:%.*]] = bitcast %TSo33HasUserProvidedDestructorAndDummyV* [[H]] to %struct.HasUserProvidedDestructorAndDummy* +// CHECK: call {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}}(%struct.HasUserProvidedDestructorAndDummy* [[CXX_THIS]]) +// CHECK: ret void +public func test() { + let d = DummyStruct() + let h = HasUserProvidedDestructorAndDummy(dummy: d) +} diff --git a/test/Interop/Cxx/class/destructors-non-trivial-implicit-irgen.swift b/test/Interop/Cxx/class/destructors-non-trivial-implicit-irgen.swift new file mode 100644 index 0000000000000..39ed1424d41c3 --- /dev/null +++ b/test/Interop/Cxx/class/destructors-non-trivial-implicit-irgen.swift @@ -0,0 +1,18 @@ +// RUN: %target-swift-frontend -enable-cxx-interop -I %S/Inputs %s -emit-ir | %FileCheck %s + +import Destructors + +// CHECK-LABEL: define {{.*}}void @"$s4main35testHasNonTrivialImplicitDestructoryyF" +// CHECK: call {{.*}}@{{_ZN31HasNonTrivialImplicitDestructorD(1|2)Ev|"\?\?1HasNonTrivialImplicitDestructor@@QEAA@XZ"}}(%struct.HasNonTrivialImplicitDestructor* +// CHECK: ret void + +// TODO: Somehow check that _ZN31HasNonTrivialImplicitDestructorD1Ev (if present) calls _ZN25HasUserProvidedDestructorD2Ev. + +public func testHasNonTrivialImplicitDestructor() { + _ = HasNonTrivialImplicitDestructor() +} + +// Check that we call the base destructor. +// CHECK-LABEL: define {{.*}}@{{_ZN31HasNonTrivialImplicitDestructorD2Ev|"\?\?1HasNonTrivialImplicitDestructor@@QEAA@XZ"}}(%struct.HasNonTrivialImplicitDestructor* +// CHECK: call {{.*}}@{{_ZN25HasUserProvidedDestructorD(1|2)Ev|"\?\?1HasUserProvidedDestructor@@QEAA@XZ"}} +// CHECK: ret diff --git a/test/Interop/Cxx/value-witness-table/Inputs/custom-destructors.h b/test/Interop/Cxx/value-witness-table/Inputs/custom-destructors.h new file mode 100644 index 0000000000000..a65e5e3276871 --- /dev/null +++ b/test/Interop/Cxx/value-witness-table/Inputs/custom-destructors.h @@ -0,0 +1,76 @@ +#ifndef TEST_INTEROP_CXX_VALUE_WITNESS_TABLE_INPUTS_CUSTOM_DESTRUCTORS_H +#define TEST_INTEROP_CXX_VALUE_WITNESS_TABLE_INPUTS_CUSTOM_DESTRUCTORS_H + +struct HasUserProvidedDestructor { + int *value; + ~HasUserProvidedDestructor() { *value = 42; } +}; + +struct HasEmptyDestructorAndMemberWithUserDefinedConstructor { + HasUserProvidedDestructor member; + ~HasEmptyDestructorAndMemberWithUserDefinedConstructor() { /* empty */ + } +}; + +struct HasNonTrivialImplicitDestructor { + HasUserProvidedDestructor member; +}; + +struct HasNonTrivialDefaultedDestructor { + HasUserProvidedDestructor member; + ~HasNonTrivialDefaultedDestructor() = default; +}; + +struct HasDefaultedDestructor { + ~HasDefaultedDestructor() = default; +}; + +// For the following objects with virtual bases / destructors, make sure that +// any exectuable user of these objects disable rtti and exceptions. Otherwise, +// the linker will error because of undefined vtables. +// FIXME: Once we can link with libc++ we can enable RTTI. + +struct HasVirtualBaseAndDestructor : virtual HasDefaultedDestructor { + int *value; + HasVirtualBaseAndDestructor(int *value) : value(value) {} + ~HasVirtualBaseAndDestructor() { *value = 42; } +}; + +struct HasVirtualDestructor { + // An object with a virtual destructor requires a delete operator in case + // we try to delete the base object. Until we can link against libc++, use + // this dummy implementation. + static void operator delete(void *p) { __builtin_unreachable(); } + virtual ~HasVirtualDestructor(){}; +}; + +struct HasVirtualDefaultedDestructor { + static void operator delete(void *p) { __builtin_unreachable(); } + virtual ~HasVirtualDefaultedDestructor() = default; +}; + +struct HasBaseWithVirtualDestructor : HasVirtualDestructor { + int *value; + HasBaseWithVirtualDestructor(int *value) : value(value) {} + ~HasBaseWithVirtualDestructor() { *value = 42; } +}; + +struct HasVirtualBaseWithVirtualDestructor : virtual HasVirtualDestructor { + int *value; + HasVirtualBaseWithVirtualDestructor(int *value) : value(value) {} + ~HasVirtualBaseWithVirtualDestructor() { *value = 42; } +}; + +struct DummyStruct {}; + +struct HasUserProvidedDestructorAndDummy { + DummyStruct dummy; + ~HasUserProvidedDestructorAndDummy() {} +}; + +// Make sure that we don't crash on struct templates with destructors. +template struct S { + ~S() {} +}; + +#endif // TEST_INTEROP_CXX_VALUE_WITNESS_TABLE_INPUTS_CUSTOM_DESTRUCTORS_H diff --git a/test/Interop/Cxx/value-witness-table/Inputs/module.modulemap b/test/Interop/Cxx/value-witness-table/Inputs/module.modulemap new file mode 100644 index 0000000000000..976bafed06cf3 --- /dev/null +++ b/test/Interop/Cxx/value-witness-table/Inputs/module.modulemap @@ -0,0 +1,4 @@ +module CustomDestructor { + header "custom-destructors.h" + requires cplusplus +} diff --git a/test/Interop/Cxx/value-witness-table/custom-destructors-non-virtual-irgen.swift b/test/Interop/Cxx/value-witness-table/custom-destructors-non-virtual-irgen.swift new file mode 100644 index 0000000000000..96f6d2ab33f18 --- /dev/null +++ b/test/Interop/Cxx/value-witness-table/custom-destructors-non-virtual-irgen.swift @@ -0,0 +1,41 @@ +// RUN: %target-swift-frontend -enable-cxx-interop -I %S/Inputs %s -emit-ir | %FileCheck %s + +import CustomDestructor + +protocol InitWithDummy { + init(dummy: DummyStruct) +} + +extension HasUserProvidedDestructorAndDummy : InitWithDummy { } + +// Make sure the destructor is added as a witness. +// CHECK: @"$sSo33HasUserProvidedDestructorAndDummyVWV" = linkonce_odr hidden constant %swift.vwtable +// CHECK-SAME: i8* bitcast (void (%swift.opaque*, %swift.type*)* @"$sSo33HasUserProvidedDestructorAndDummyVwxx" to i8*) + +// CHECK-LABEL: define {{.*}}void @"$s4main37testHasUserProvidedDestructorAndDummyyyF" +// CHECK: [[OBJ:%.*]] = alloca %TSo33HasUserProvidedDestructorAndDummyV +// CHECK: [[CXX_OBJ:%.*]] = bitcast %TSo33HasUserProvidedDestructorAndDummyV* [[OBJ]] to %struct.HasUserProvidedDestructorAndDummy* +// CHECK: call {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}}(%struct.HasUserProvidedDestructorAndDummy* [[CXX_OBJ]]) +// CHECK: ret void + +// Make sure we not only declare but define the destructor. +// CHECK-LABEL: define {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}} +// CHECK: ret +public func testHasUserProvidedDestructorAndDummy() { + _ = HasUserProvidedDestructorAndDummy(dummy: DummyStruct()) +} + +// CHECK-LABEL: define {{.*}}void @"$s4main26testHasDefaultedDestructoryyF" +// CHECK: call {{.*}}@{{_ZN22HasDefaultedDestructorC(1|2)Ev|"\?\?0HasDefaultedDestructor@@QEAA@XZ"}}(%struct.HasDefaultedDestructor* +// CHECK: ret void + +// CHECK-LABEL: define {{.*}}@{{_ZN22HasDefaultedDestructorC(1|2)Ev|"\?\?0HasDefaultedDestructor@@QEAA@XZ"}}(%struct.HasDefaultedDestructor* +// CHECK: ret +public func testHasDefaultedDestructor() { + _ = HasDefaultedDestructor() +} + +// Make sure the destroy value witness calls the destructor. +// CHECK-LABEL: define {{.*}}void @"$sSo33HasUserProvidedDestructorAndDummyVwxx" +// CHECK: call {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}}(%struct.HasUserProvidedDestructorAndDummy* +// CHECK: ret diff --git a/test/Interop/Cxx/value-witness-table/custom-destructors-non-virtual.swift b/test/Interop/Cxx/value-witness-table/custom-destructors-non-virtual.swift new file mode 100644 index 0000000000000..2235e10353b8a --- /dev/null +++ b/test/Interop/Cxx/value-witness-table/custom-destructors-non-virtual.swift @@ -0,0 +1,128 @@ +// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-cxx-interop) +// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-cxx-interop -O) +// +// REQUIRES: executable_test + +import CustomDestructor +import StdlibUnittest + +var CXXDestructorTestSuite = TestSuite("CXXDestructor") + +protocol InitWithPtr { + init(value: UnsafeMutablePointer!) +} + +extension HasUserProvidedDestructor : InitWithPtr { } + +protocol InitWithMember { + init(member: HasUserProvidedDestructor) +} + +extension HasEmptyDestructorAndMemberWithUserDefinedConstructor + : InitWithMember { } + +@inline(never) +@_optimize(none) +func withCxxDestructorSideEffects(_ _: inout T) { } + +func createTypeWithUserProvidedDestructor(_ ptr: UnsafeMutablePointer) { + var obj = HasUserProvidedDestructor(value: ptr) + withCxxDestructorSideEffects(&obj) +} + +func createTypeWithEmptyDestructorAndMemberWithUserDefinedConstructor( + _ ptr: UnsafeMutablePointer +) { + let member = HasUserProvidedDestructor(value: ptr) + var obj = HasEmptyDestructorAndMemberWithUserDefinedConstructor(member: member) + withCxxDestructorSideEffects(&obj) +} + +func createTypeWithNonTrivialImplicitDestructor( + _ ptr: UnsafeMutablePointer +) { + let member = HasUserProvidedDestructor(value: ptr) + var obj = HasNonTrivialImplicitDestructor(member: member) + withCxxDestructorSideEffects(&obj) +} + +func createTypeWithNonTrivialDefaultDestructor( + _ ptr: UnsafeMutablePointer +) { + let member = HasUserProvidedDestructor(value: ptr) + var obj = HasNonTrivialDefaultedDestructor(member: member) + withCxxDestructorSideEffects(&obj) +} + +func createTypeWithGeneric( + _ ptr: UnsafeMutablePointer, + type: T.Type +) { + var obj = T(value: ptr) + withCxxDestructorSideEffects(&obj) +} + +func createTypeWithProtocol( + _ ptr: UnsafeMutablePointer, + type: InitWithPtr.Type +) { + var obj = type.self.init(value: ptr) + withCxxDestructorSideEffects(&obj) +} + +func createTypeWithProtocol( + _ ptr: UnsafeMutablePointer, + type: InitWithPtr.Type, + holder: InitWithMember.Type +) { + let member = type.self.init(value: ptr) + var obj = holder.self.init(member: member as! HasUserProvidedDestructor) + withCxxDestructorSideEffects(&obj) +} + +CXXDestructorTestSuite.test("Basic object with destructor") { + var value: Int32 = 0 + createTypeWithUserProvidedDestructor(&value) + expectEqual(value, 42) +} + +CXXDestructorTestSuite.test("Nested objects with destructors") { + var value: Int32 = 0 + createTypeWithEmptyDestructorAndMemberWithUserDefinedConstructor(&value) + expectEqual(value, 42) +} + +CXXDestructorTestSuite.test("Implicit destructor, member with user-defined destructor") { + var value: Int32 = 0 + createTypeWithNonTrivialImplicitDestructor(&value) + expectEqual(value, 42) +} + +CXXDestructorTestSuite.test("Default destructor, member with user-defined destructor") { + var value: Int32 = 0 + createTypeWithNonTrivialDefaultDestructor(&value) + expectEqual(value, 42) +} + +CXXDestructorTestSuite.test("Generic with destructor") { + var value: Int32 = 0 + createTypeWithGeneric(&value, type: HasUserProvidedDestructor.self) + expectEqual(value, 42) +} + +CXXDestructorTestSuite.test("Protocol with destructor") { + var value: Int32 = 0 + createTypeWithProtocol(&value, type: HasUserProvidedDestructor.self) + expectEqual(value, 42) +} + +CXXDestructorTestSuite.test("Protocol with member with destructor") { + var value: Int32 = 0 + createTypeWithProtocol( + &value, + type: HasUserProvidedDestructor.self, + holder: HasEmptyDestructorAndMemberWithUserDefinedConstructor.self) + expectEqual(value, 42) +} + +runAllTests() diff --git a/test/Interop/Cxx/value-witness-table/custom-destructors-typechecker.swift b/test/Interop/Cxx/value-witness-table/custom-destructors-typechecker.swift new file mode 100644 index 0000000000000..26b962bb9b3f9 --- /dev/null +++ b/test/Interop/Cxx/value-witness-table/custom-destructors-typechecker.swift @@ -0,0 +1,8 @@ +// RUN: %target-typecheck-verify-swift -I %S/Inputs -enable-cxx-interop + +import CustomDestructor + +_ = HasUserProvidedDestructor() +_ = HasEmptyDestructorAndMemberWithUserDefinedConstructor() +_ = HasNonTrivialImplicitDestructor() +_ = HasNonTrivialDefaultedDestructor() diff --git a/test/Interop/Cxx/value-witness-table/custom-destructors-virtual-irgen.swift b/test/Interop/Cxx/value-witness-table/custom-destructors-virtual-irgen.swift new file mode 100644 index 0000000000000..e3dd0e44925b3 --- /dev/null +++ b/test/Interop/Cxx/value-witness-table/custom-destructors-virtual-irgen.swift @@ -0,0 +1,19 @@ +// With RTTI some of the objects with virtual bases / destructors in this test +// will cause linker errors because of undefined vtables. +// FIXME: Once we can link with libc++ we can start using RTTI. +// +// RUN: %target-swift-frontend -enable-cxx-interop -I %S/Inputs %s -emit-ir -Xcc -fno-rtti | %FileCheck %s +// +// Windows doesn't support -fno-rtti. +// UNSUPPORTED: OS=windows-msvc + +import CustomDestructor + +// CHECK-LABEL: define {{.*}}void @"$s4main022testHasVirtualBaseWithD10DestructoryySpys5Int32VGF" +// CHECK: call {{.*}}@{{_ZN28HasBaseWithVirtualDestructorD(1|2)Ev|"\?\?1HasBaseWithVirtualDestructor@@UEAA@XZ"}}(%struct.HasBaseWithVirtualDestructor* +// CHECK: ret +public func testHasVirtualBaseWithVirtualDestructor( + _ ptr: UnsafeMutablePointer +) { + _ = HasBaseWithVirtualDestructor(ptr) +} diff --git a/test/Interop/Cxx/value-witness-table/custom-destructors-virtual.swift b/test/Interop/Cxx/value-witness-table/custom-destructors-virtual.swift new file mode 100644 index 0000000000000..bbab6c8b481f0 --- /dev/null +++ b/test/Interop/Cxx/value-witness-table/custom-destructors-virtual.swift @@ -0,0 +1,57 @@ +// With RTTI some of the objects with virtual bases / destructors in this test +// will cause linker errors because of undefined vtables. +// FIXME: Once we can link with libc++ we can start using RTTI. +// +// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-cxx-interop -Xcc -fno-rtti) +// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-cxx-interop -Xcc -fno-rtti -O) +// +// REQUIRES: executable_test +// Windows doesn't support -fno-rtti. +// UNSUPPORTED: OS=windows-msvc + +import CustomDestructor +import StdlibUnittest + +var CXXDestructorTestSuite = TestSuite("CXXDestructor") + +func createTypeWithVirtualBaseAndDestructor( + _ ptr: UnsafeMutablePointer +) { + _ = HasVirtualBaseAndDestructor(ptr) +} + +func createTypeWithBaseWithVirtualDestructor( + _ ptr: UnsafeMutablePointer +) { + _ = HasBaseWithVirtualDestructor(ptr) +} + +func createTypeWithVirtualBaseWithVirtualDestructor( + _ ptr: UnsafeMutablePointer +) { + _ = HasVirtualBaseWithVirtualDestructor(ptr) +} + +CXXDestructorTestSuite.test("Virtual base and destructor") { + var value: Int32 = 0 + createTypeWithVirtualBaseAndDestructor(&value) + expectEqual(value, 42) +} + +CXXDestructorTestSuite.test("Base with virtual destructor") { + var value: Int32 = 0 + createTypeWithBaseWithVirtualDestructor(&value) + expectEqual(value, 42) +} + +CXXDestructorTestSuite.test("Virtual base with virtual destructor") { + var value: Int32 = 0 + createTypeWithVirtualBaseWithVirtualDestructor(&value) + expectEqual(value, 42) +} + +CXXDestructorTestSuite.test("Type with virtual defaulted destructor") { + _ = HasVirtualDefaultedDestructor() +} + +runAllTests()