Skip to content

Commit 87817dc

Browse files
BridgeJS: Add tests for memory management of SwiftHeapObject
1 parent 6bb09b6 commit 87817dc

File tree

4 files changed

+180
-1
lines changed

4 files changed

+180
-1
lines changed

Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9190,6 +9190,42 @@ fileprivate func _bjs_Container_wrap_extern(_ pointer: UnsafeMutableRawPointer)
91909190
return _bjs_Container_wrap_extern(pointer)
91919191
}
91929192

9193+
@_expose(wasm, "bjs_LeakCheck_init")
9194+
@_cdecl("bjs_LeakCheck_init")
9195+
public func _bjs_LeakCheck_init() -> UnsafeMutableRawPointer {
9196+
#if arch(wasm32)
9197+
let ret = LeakCheck()
9198+
return ret.bridgeJSLowerReturn()
9199+
#else
9200+
fatalError("Only available on WebAssembly")
9201+
#endif
9202+
}
9203+
9204+
@_expose(wasm, "bjs_LeakCheck_deinit")
9205+
@_cdecl("bjs_LeakCheck_deinit")
9206+
public func _bjs_LeakCheck_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {
9207+
#if arch(wasm32)
9208+
Unmanaged<LeakCheck>.fromOpaque(pointer).release()
9209+
#else
9210+
fatalError("Only available on WebAssembly")
9211+
#endif
9212+
}
9213+
9214+
extension LeakCheck: ConvertibleToJSValue, _BridgedSwiftHeapObject {
9215+
public var jsValue: JSValue {
9216+
return .object(JSObject(id: UInt32(bitPattern: _bjs_LeakCheck_wrap(Unmanaged.passRetained(self).toOpaque()))))
9217+
}
9218+
}
9219+
9220+
#if arch(wasm32)
9221+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_LeakCheck_wrap")
9222+
fileprivate func _bjs_LeakCheck_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32
9223+
#else
9224+
fileprivate func _bjs_LeakCheck_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 {
9225+
fatalError("Only available on WebAssembly")
9226+
}
9227+
#endif
9228+
91939229
#if arch(wasm32)
91949230
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ClosureSupportImports_jsApplyVoid_static")
91959231
fileprivate func bjs_ClosureSupportImports_jsApplyVoid_static_extern(_ callback: Int32) -> Void
@@ -11252,6 +11288,24 @@ fileprivate func bjs_SwiftClassSupportImports_jsRoundTripOptionalGreeter_static_
1125211288
return bjs_SwiftClassSupportImports_jsRoundTripOptionalGreeter_static_extern(greeterIsSome, greeterPointer)
1125311289
}
1125411290

11291+
#if arch(wasm32)
11292+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_SwiftClassSupportImports_jsConsumeLeakCheck_static")
11293+
fileprivate func bjs_SwiftClassSupportImports_jsConsumeLeakCheck_static(_ value: UnsafeMutableRawPointer) -> Void
11294+
#else
11295+
fileprivate func bjs_SwiftClassSupportImports_jsConsumeLeakCheck_static(_ value: UnsafeMutableRawPointer) -> Void {
11296+
fatalError("Only available on WebAssembly")
11297+
}
11298+
#endif
11299+
11300+
#if arch(wasm32)
11301+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_SwiftClassSupportImports_jsConsumeOptionalLeakCheck_static")
11302+
fileprivate func bjs_SwiftClassSupportImports_jsConsumeOptionalLeakCheck_static(_ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void
11303+
#else
11304+
fileprivate func bjs_SwiftClassSupportImports_jsConsumeOptionalLeakCheck_static(_ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void {
11305+
fatalError("Only available on WebAssembly")
11306+
}
11307+
#endif
11308+
1125511309
func _$SwiftClassSupportImports_jsRoundTripGreeter(_ greeter: Greeter) throws(JSException) -> Greeter {
1125611310
let greeterPointer = greeter.bridgeJSLowerParameter()
1125711311
let ret = bjs_SwiftClassSupportImports_jsRoundTripGreeter_static(greeterPointer)
@@ -11268,4 +11322,20 @@ func _$SwiftClassSupportImports_jsRoundTripOptionalGreeter(_ greeter: Optional<G
1126811322
throw error
1126911323
}
1127011324
return Optional<Greeter>.bridgeJSLiftReturn(ret)
11325+
}
11326+
11327+
func _$SwiftClassSupportImports_jsConsumeLeakCheck(_ value: LeakCheck) throws(JSException) -> Void {
11328+
let valuePointer = value.bridgeJSLowerParameter()
11329+
bjs_SwiftClassSupportImports_jsConsumeLeakCheck_static(valuePointer)
11330+
if let error = _swift_js_take_exception() {
11331+
throw error
11332+
}
11333+
}
11334+
11335+
func _$SwiftClassSupportImports_jsConsumeOptionalLeakCheck(_ value: Optional<LeakCheck>) throws(JSException) -> Void {
11336+
let (valueIsSome, valuePointer) = value.bridgeJSLowerParameter()
11337+
bjs_SwiftClassSupportImports_jsConsumeOptionalLeakCheck_static(valueIsSome, valuePointer)
11338+
if let error = _swift_js_take_exception() {
11339+
throw error
11340+
}
1127111341
}

Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3626,6 +3626,28 @@
36263626
}
36273627
],
36283628
"swiftCallName" : "Container"
3629+
},
3630+
{
3631+
"constructor" : {
3632+
"abiName" : "bjs_LeakCheck_init",
3633+
"effects" : {
3634+
"isAsync" : false,
3635+
"isStatic" : false,
3636+
"isThrows" : false
3637+
},
3638+
"parameters" : [
3639+
3640+
]
3641+
},
3642+
"explicitAccessControl" : "public",
3643+
"methods" : [
3644+
3645+
],
3646+
"name" : "LeakCheck",
3647+
"properties" : [
3648+
3649+
],
3650+
"swiftCallName" : "LeakCheck"
36293651
}
36303652
],
36313653
"enums" : [
@@ -15643,6 +15665,47 @@
1564315665
"_1" : "null"
1564415666
}
1564515667
}
15668+
},
15669+
{
15670+
"name" : "jsConsumeLeakCheck",
15671+
"parameters" : [
15672+
{
15673+
"name" : "value",
15674+
"type" : {
15675+
"swiftHeapObject" : {
15676+
"_0" : "LeakCheck"
15677+
}
15678+
}
15679+
}
15680+
],
15681+
"returnType" : {
15682+
"void" : {
15683+
15684+
}
15685+
}
15686+
},
15687+
{
15688+
"name" : "jsConsumeOptionalLeakCheck",
15689+
"parameters" : [
15690+
{
15691+
"name" : "value",
15692+
"type" : {
15693+
"nullable" : {
15694+
"_0" : {
15695+
"swiftHeapObject" : {
15696+
"_0" : "LeakCheck"
15697+
}
15698+
},
15699+
"_1" : "null"
15700+
}
15701+
}
15702+
}
15703+
],
15704+
"returnType" : {
15705+
"void" : {
15706+
15707+
}
15708+
}
1564615709
}
1564715710
]
1564815711
}

Tests/BridgeJSRuntimeTests/JavaScript/SwiftClassSupportTests.mjs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,14 @@ export function getImports(importsContext) {
99
jsRoundTripOptionalGreeter: (greeter) => {
1010
return greeter;
1111
},
12+
jsConsumeLeakCheck: (value) => {
13+
// Explicitly release on JS side to mimic user cleanup.
14+
value.release();
15+
},
16+
jsConsumeOptionalLeakCheck: (value) => {
17+
if (value) {
18+
value.release();
19+
}
20+
},
1221
};
13-
}
22+
}

Tests/BridgeJSRuntimeTests/SwiftClassSupportTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import JavaScriptKit
44
@JSClass struct SwiftClassSupportImports {
55
@JSFunction static func jsRoundTripGreeter(_ greeter: Greeter) throws(JSException) -> Greeter
66
@JSFunction static func jsRoundTripOptionalGreeter(_ greeter: Greeter?) throws(JSException) -> Greeter?
7+
@JSFunction static func jsConsumeLeakCheck(_ value: LeakCheck) throws(JSException) -> Void
8+
@JSFunction static func jsConsumeOptionalLeakCheck(_ value: LeakCheck?) throws(JSException) -> Void
79
}
810

911
@JSFunction(from: .global) func gc() throws(JSException) -> Void
@@ -57,4 +59,39 @@ final class SwiftClassSupportTests: XCTestCase {
5759
// Here, the greeter should be deallocated
5860
XCTAssertNil(weakGreeter)
5961
}
62+
63+
func testJSReleaseDoesNotOverReleaseHeapObject() throws {
64+
LeakCheck.deinits = 0
65+
var obj: LeakCheck? = LeakCheck()
66+
67+
try SwiftClassSupportImports.jsConsumeLeakCheck(obj!)
68+
XCTAssertEqual(LeakCheck.deinits, 0)
69+
70+
obj = nil
71+
XCTAssertEqual(LeakCheck.deinits, 1)
72+
}
73+
74+
func testJSReleaseOptionalDoesNotOverReleaseHeapObject() throws {
75+
LeakCheck.deinits = 0
76+
77+
try SwiftClassSupportImports.jsConsumeOptionalLeakCheck(nil)
78+
XCTAssertEqual(LeakCheck.deinits, 0)
79+
80+
var obj: LeakCheck? = LeakCheck()
81+
try SwiftClassSupportImports.jsConsumeOptionalLeakCheck(obj)
82+
XCTAssertEqual(LeakCheck.deinits, 0)
83+
84+
obj = nil
85+
XCTAssertEqual(LeakCheck.deinits, 1)
86+
}
87+
}
88+
89+
@JS public class LeakCheck {
90+
nonisolated(unsafe) public static var deinits: Int = 0
91+
92+
@JS public init() {}
93+
94+
deinit {
95+
Self.deinits += 1
96+
}
6097
}

0 commit comments

Comments
 (0)