Skip to content

Commit ef3634d

Browse files
Merge pull request #648 from swiftwasm/katei/956f-bridgejs-fix-swi
BridgeJS: Fix SwiftHeapObject finalization registry leaks and double release
2 parents 6df18bb + 012fb6b commit ef3634d

27 files changed

+478
-184
lines changed

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,33 @@ public struct BridgeJSLink {
5151
"""
5252

5353
let swiftHeapObjectClassJs = """
54+
const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => {
55+
if (state.hasReleased) {
56+
return;
57+
}
58+
state.hasReleased = true;
59+
state.deinit(state.pointer);
60+
});
61+
5462
/// Represents a Swift heap object like a class instance or an actor instance.
5563
class SwiftHeapObject {
5664
static __wrap(pointer, deinit, prototype) {
5765
const obj = Object.create(prototype);
66+
const state = { pointer, deinit, hasReleased: false };
5867
obj.pointer = pointer;
59-
obj.hasReleased = false;
60-
obj.deinit = deinit;
61-
obj.registry = new FinalizationRegistry((pointer) => {
62-
deinit(pointer);
63-
});
64-
obj.registry.register(this, obj.pointer);
68+
obj.__swiftHeapObjectState = state;
69+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
6570
return obj;
6671
}
6772
6873
release() {
69-
this.registry.unregister(this);
70-
this.deinit(this.pointer);
74+
const state = this.__swiftHeapObjectState;
75+
if (state.hasReleased) {
76+
return;
77+
}
78+
state.hasReleased = true;
79+
swiftHeapObjectFinalizationRegistry.unregister(state);
80+
state.deinit(state.pointer);
7181
}
7282
}
7383
"""

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -355,23 +355,33 @@ export async function createInstantiator(options, swift) {
355355
/** @param {WebAssembly.Instance} instance */
356356
createExports: (instance) => {
357357
const js = swift.memory.heap;
358+
const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => {
359+
if (state.hasReleased) {
360+
return;
361+
}
362+
state.hasReleased = true;
363+
state.deinit(state.pointer);
364+
});
365+
358366
/// Represents a Swift heap object like a class instance or an actor instance.
359367
class SwiftHeapObject {
360368
static __wrap(pointer, deinit, prototype) {
361369
const obj = Object.create(prototype);
370+
const state = { pointer, deinit, hasReleased: false };
362371
obj.pointer = pointer;
363-
obj.hasReleased = false;
364-
obj.deinit = deinit;
365-
obj.registry = new FinalizationRegistry((pointer) => {
366-
deinit(pointer);
367-
});
368-
obj.registry.register(this, obj.pointer);
372+
obj.__swiftHeapObjectState = state;
373+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
369374
return obj;
370375
}
371376

372377
release() {
373-
this.registry.unregister(this);
374-
this.deinit(this.pointer);
378+
const state = this.__swiftHeapObjectState;
379+
if (state.hasReleased) {
380+
return;
381+
}
382+
state.hasReleased = true;
383+
swiftHeapObjectFinalizationRegistry.unregister(state);
384+
state.deinit(state.pointer);
375385
}
376386
}
377387
class Item extends SwiftHeapObject {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -295,23 +295,33 @@ export async function createInstantiator(options, swift) {
295295
/** @param {WebAssembly.Instance} instance */
296296
createExports: (instance) => {
297297
const js = swift.memory.heap;
298+
const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => {
299+
if (state.hasReleased) {
300+
return;
301+
}
302+
state.hasReleased = true;
303+
state.deinit(state.pointer);
304+
});
305+
298306
/// Represents a Swift heap object like a class instance or an actor instance.
299307
class SwiftHeapObject {
300308
static __wrap(pointer, deinit, prototype) {
301309
const obj = Object.create(prototype);
310+
const state = { pointer, deinit, hasReleased: false };
302311
obj.pointer = pointer;
303-
obj.hasReleased = false;
304-
obj.deinit = deinit;
305-
obj.registry = new FinalizationRegistry((pointer) => {
306-
deinit(pointer);
307-
});
308-
obj.registry.register(this, obj.pointer);
312+
obj.__swiftHeapObjectState = state;
313+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
309314
return obj;
310315
}
311316

312317
release() {
313-
this.registry.unregister(this);
314-
this.deinit(this.pointer);
318+
const state = this.__swiftHeapObjectState;
319+
if (state.hasReleased) {
320+
return;
321+
}
322+
state.hasReleased = true;
323+
swiftHeapObjectFinalizationRegistry.unregister(state);
324+
state.deinit(state.pointer);
315325
}
316326
}
317327
class DefaultGreeter extends SwiftHeapObject {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -242,23 +242,33 @@ export async function createInstantiator(options, swift) {
242242
/** @param {WebAssembly.Instance} instance */
243243
createExports: (instance) => {
244244
const js = swift.memory.heap;
245+
const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => {
246+
if (state.hasReleased) {
247+
return;
248+
}
249+
state.hasReleased = true;
250+
state.deinit(state.pointer);
251+
});
252+
245253
/// Represents a Swift heap object like a class instance or an actor instance.
246254
class SwiftHeapObject {
247255
static __wrap(pointer, deinit, prototype) {
248256
const obj = Object.create(prototype);
257+
const state = { pointer, deinit, hasReleased: false };
249258
obj.pointer = pointer;
250-
obj.hasReleased = false;
251-
obj.deinit = deinit;
252-
obj.registry = new FinalizationRegistry((pointer) => {
253-
deinit(pointer);
254-
});
255-
obj.registry.register(this, obj.pointer);
259+
obj.__swiftHeapObjectState = state;
260+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
256261
return obj;
257262
}
258263

259264
release() {
260-
this.registry.unregister(this);
261-
this.deinit(this.pointer);
265+
const state = this.__swiftHeapObjectState;
266+
if (state.hasReleased) {
267+
return;
268+
}
269+
state.hasReleased = true;
270+
swiftHeapObjectFinalizationRegistry.unregister(state);
271+
state.deinit(state.pointer);
262272
}
263273
}
264274
class Box extends SwiftHeapObject {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,23 +1042,33 @@ export async function createInstantiator(options, swift) {
10421042
/** @param {WebAssembly.Instance} instance */
10431043
createExports: (instance) => {
10441044
const js = swift.memory.heap;
1045+
const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => {
1046+
if (state.hasReleased) {
1047+
return;
1048+
}
1049+
state.hasReleased = true;
1050+
state.deinit(state.pointer);
1051+
});
1052+
10451053
/// Represents a Swift heap object like a class instance or an actor instance.
10461054
class SwiftHeapObject {
10471055
static __wrap(pointer, deinit, prototype) {
10481056
const obj = Object.create(prototype);
1057+
const state = { pointer, deinit, hasReleased: false };
10491058
obj.pointer = pointer;
1050-
obj.hasReleased = false;
1051-
obj.deinit = deinit;
1052-
obj.registry = new FinalizationRegistry((pointer) => {
1053-
deinit(pointer);
1054-
});
1055-
obj.registry.register(this, obj.pointer);
1059+
obj.__swiftHeapObjectState = state;
1060+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
10561061
return obj;
10571062
}
10581063

10591064
release() {
1060-
this.registry.unregister(this);
1061-
this.deinit(this.pointer);
1065+
const state = this.__swiftHeapObjectState;
1066+
if (state.hasReleased) {
1067+
return;
1068+
}
1069+
state.hasReleased = true;
1070+
swiftHeapObjectFinalizationRegistry.unregister(state);
1071+
state.deinit(state.pointer);
10621072
}
10631073
}
10641074
class User extends SwiftHeapObject {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -267,23 +267,33 @@ export async function createInstantiator(options, swift) {
267267
/** @param {WebAssembly.Instance} instance */
268268
createExports: (instance) => {
269269
const js = swift.memory.heap;
270+
const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => {
271+
if (state.hasReleased) {
272+
return;
273+
}
274+
state.hasReleased = true;
275+
state.deinit(state.pointer);
276+
});
277+
270278
/// Represents a Swift heap object like a class instance or an actor instance.
271279
class SwiftHeapObject {
272280
static __wrap(pointer, deinit, prototype) {
273281
const obj = Object.create(prototype);
282+
const state = { pointer, deinit, hasReleased: false };
274283
obj.pointer = pointer;
275-
obj.hasReleased = false;
276-
obj.deinit = deinit;
277-
obj.registry = new FinalizationRegistry((pointer) => {
278-
deinit(pointer);
279-
});
280-
obj.registry.register(this, obj.pointer);
284+
obj.__swiftHeapObjectState = state;
285+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
281286
return obj;
282287
}
283288

284289
release() {
285-
this.registry.unregister(this);
286-
this.deinit(this.pointer);
290+
const state = this.__swiftHeapObjectState;
291+
if (state.hasReleased) {
292+
return;
293+
}
294+
state.hasReleased = true;
295+
swiftHeapObjectFinalizationRegistry.unregister(state);
296+
state.deinit(state.pointer);
287297
}
288298
}
289299
class Converter extends SwiftHeapObject {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,23 +248,33 @@ export async function createInstantiator(options, swift) {
248248
/** @param {WebAssembly.Instance} instance */
249249
createExports: (instance) => {
250250
const js = swift.memory.heap;
251+
const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => {
252+
if (state.hasReleased) {
253+
return;
254+
}
255+
state.hasReleased = true;
256+
state.deinit(state.pointer);
257+
});
258+
251259
/// Represents a Swift heap object like a class instance or an actor instance.
252260
class SwiftHeapObject {
253261
static __wrap(pointer, deinit, prototype) {
254262
const obj = Object.create(prototype);
263+
const state = { pointer, deinit, hasReleased: false };
255264
obj.pointer = pointer;
256-
obj.hasReleased = false;
257-
obj.deinit = deinit;
258-
obj.registry = new FinalizationRegistry((pointer) => {
259-
deinit(pointer);
260-
});
261-
obj.registry.register(this, obj.pointer);
265+
obj.__swiftHeapObjectState = state;
266+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
262267
return obj;
263268
}
264269

265270
release() {
266-
this.registry.unregister(this);
267-
this.deinit(this.pointer);
271+
const state = this.__swiftHeapObjectState;
272+
if (state.hasReleased) {
273+
return;
274+
}
275+
state.hasReleased = true;
276+
swiftHeapObjectFinalizationRegistry.unregister(state);
277+
state.deinit(state.pointer);
268278
}
269279
}
270280
class Converter extends SwiftHeapObject {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -343,23 +343,33 @@ export async function createInstantiator(options, swift) {
343343
/** @param {WebAssembly.Instance} instance */
344344
createExports: (instance) => {
345345
const js = swift.memory.heap;
346+
const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => {
347+
if (state.hasReleased) {
348+
return;
349+
}
350+
state.hasReleased = true;
351+
state.deinit(state.pointer);
352+
});
353+
346354
/// Represents a Swift heap object like a class instance or an actor instance.
347355
class SwiftHeapObject {
348356
static __wrap(pointer, deinit, prototype) {
349357
const obj = Object.create(prototype);
358+
const state = { pointer, deinit, hasReleased: false };
350359
obj.pointer = pointer;
351-
obj.hasReleased = false;
352-
obj.deinit = deinit;
353-
obj.registry = new FinalizationRegistry((pointer) => {
354-
deinit(pointer);
355-
});
356-
obj.registry.register(this, obj.pointer);
360+
obj.__swiftHeapObjectState = state;
361+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
357362
return obj;
358363
}
359364

360365
release() {
361-
this.registry.unregister(this);
362-
this.deinit(this.pointer);
366+
const state = this.__swiftHeapObjectState;
367+
if (state.hasReleased) {
368+
return;
369+
}
370+
state.hasReleased = true;
371+
swiftHeapObjectFinalizationRegistry.unregister(state);
372+
state.deinit(state.pointer);
363373
}
364374
}
365375
class JSValueHolder extends SwiftHeapObject {

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -215,23 +215,33 @@ export async function createInstantiator(options, swift) {
215215
/** @param {WebAssembly.Instance} instance */
216216
createExports: (instance) => {
217217
const js = swift.memory.heap;
218+
const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => {
219+
if (state.hasReleased) {
220+
return;
221+
}
222+
state.hasReleased = true;
223+
state.deinit(state.pointer);
224+
});
225+
218226
/// Represents a Swift heap object like a class instance or an actor instance.
219227
class SwiftHeapObject {
220228
static __wrap(pointer, deinit, prototype) {
221229
const obj = Object.create(prototype);
230+
const state = { pointer, deinit, hasReleased: false };
222231
obj.pointer = pointer;
223-
obj.hasReleased = false;
224-
obj.deinit = deinit;
225-
obj.registry = new FinalizationRegistry((pointer) => {
226-
deinit(pointer);
227-
});
228-
obj.registry.register(this, obj.pointer);
232+
obj.__swiftHeapObjectState = state;
233+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
229234
return obj;
230235
}
231236

232237
release() {
233-
this.registry.unregister(this);
234-
this.deinit(this.pointer);
238+
const state = this.__swiftHeapObjectState;
239+
if (state.hasReleased) {
240+
return;
241+
}
242+
state.hasReleased = true;
243+
swiftHeapObjectFinalizationRegistry.unregister(state);
244+
state.deinit(state.pointer);
235245
}
236246
}
237247
class GlobalClass extends SwiftHeapObject {

0 commit comments

Comments
 (0)