Skip to content

Commit 11ae34d

Browse files
BridgeJS: Fix SwiftHeapObject finalization registry leaks and double release
1 parent 6df18bb commit 11ae34d

27 files changed

+500
-140
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: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -355,23 +355,36 @@ 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;
363372
obj.hasReleased = false;
364373
obj.deinit = deinit;
365-
obj.registry = new FinalizationRegistry((pointer) => {
366-
deinit(pointer);
367-
});
368-
obj.registry.register(this, obj.pointer);
374+
obj.__swiftHeapObjectState = state;
375+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
369376
return obj;
370377
}
371378

372379
release() {
373-
this.registry.unregister(this);
374-
this.deinit(this.pointer);
380+
const state = this.__swiftHeapObjectState;
381+
if (state.hasReleased) {
382+
return;
383+
}
384+
state.hasReleased = true;
385+
this.hasReleased = true;
386+
swiftHeapObjectFinalizationRegistry.unregister(state);
387+
state.deinit(state.pointer);
375388
}
376389
}
377390
class Item extends SwiftHeapObject {

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -295,23 +295,36 @@ 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;
303312
obj.hasReleased = false;
304313
obj.deinit = deinit;
305-
obj.registry = new FinalizationRegistry((pointer) => {
306-
deinit(pointer);
307-
});
308-
obj.registry.register(this, obj.pointer);
314+
obj.__swiftHeapObjectState = state;
315+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
309316
return obj;
310317
}
311318

312319
release() {
313-
this.registry.unregister(this);
314-
this.deinit(this.pointer);
320+
const state = this.__swiftHeapObjectState;
321+
if (state.hasReleased) {
322+
return;
323+
}
324+
state.hasReleased = true;
325+
this.hasReleased = true;
326+
swiftHeapObjectFinalizationRegistry.unregister(state);
327+
state.deinit(state.pointer);
315328
}
316329
}
317330
class DefaultGreeter extends SwiftHeapObject {

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -242,23 +242,36 @@ 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;
250259
obj.hasReleased = false;
251260
obj.deinit = deinit;
252-
obj.registry = new FinalizationRegistry((pointer) => {
253-
deinit(pointer);
254-
});
255-
obj.registry.register(this, obj.pointer);
261+
obj.__swiftHeapObjectState = state;
262+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
256263
return obj;
257264
}
258265

259266
release() {
260-
this.registry.unregister(this);
261-
this.deinit(this.pointer);
267+
const state = this.__swiftHeapObjectState;
268+
if (state.hasReleased) {
269+
return;
270+
}
271+
state.hasReleased = true;
272+
this.hasReleased = true;
273+
swiftHeapObjectFinalizationRegistry.unregister(state);
274+
state.deinit(state.pointer);
262275
}
263276
}
264277
class Box extends SwiftHeapObject {

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,23 +1042,36 @@ 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;
10501059
obj.hasReleased = false;
10511060
obj.deinit = deinit;
1052-
obj.registry = new FinalizationRegistry((pointer) => {
1053-
deinit(pointer);
1054-
});
1055-
obj.registry.register(this, obj.pointer);
1061+
obj.__swiftHeapObjectState = state;
1062+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
10561063
return obj;
10571064
}
10581065

10591066
release() {
1060-
this.registry.unregister(this);
1061-
this.deinit(this.pointer);
1067+
const state = this.__swiftHeapObjectState;
1068+
if (state.hasReleased) {
1069+
return;
1070+
}
1071+
state.hasReleased = true;
1072+
this.hasReleased = true;
1073+
swiftHeapObjectFinalizationRegistry.unregister(state);
1074+
state.deinit(state.pointer);
10621075
}
10631076
}
10641077
class User extends SwiftHeapObject {

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,23 +267,36 @@ 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;
275284
obj.hasReleased = false;
276285
obj.deinit = deinit;
277-
obj.registry = new FinalizationRegistry((pointer) => {
278-
deinit(pointer);
279-
});
280-
obj.registry.register(this, obj.pointer);
286+
obj.__swiftHeapObjectState = state;
287+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
281288
return obj;
282289
}
283290

284291
release() {
285-
this.registry.unregister(this);
286-
this.deinit(this.pointer);
292+
const state = this.__swiftHeapObjectState;
293+
if (state.hasReleased) {
294+
return;
295+
}
296+
state.hasReleased = true;
297+
this.hasReleased = true;
298+
swiftHeapObjectFinalizationRegistry.unregister(state);
299+
state.deinit(state.pointer);
287300
}
288301
}
289302
class Converter extends SwiftHeapObject {

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,23 +248,36 @@ 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;
256265
obj.hasReleased = false;
257266
obj.deinit = deinit;
258-
obj.registry = new FinalizationRegistry((pointer) => {
259-
deinit(pointer);
260-
});
261-
obj.registry.register(this, obj.pointer);
267+
obj.__swiftHeapObjectState = state;
268+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
262269
return obj;
263270
}
264271

265272
release() {
266-
this.registry.unregister(this);
267-
this.deinit(this.pointer);
273+
const state = this.__swiftHeapObjectState;
274+
if (state.hasReleased) {
275+
return;
276+
}
277+
state.hasReleased = true;
278+
this.hasReleased = true;
279+
swiftHeapObjectFinalizationRegistry.unregister(state);
280+
state.deinit(state.pointer);
268281
}
269282
}
270283
class Converter extends SwiftHeapObject {

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -343,23 +343,36 @@ 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;
351360
obj.hasReleased = false;
352361
obj.deinit = deinit;
353-
obj.registry = new FinalizationRegistry((pointer) => {
354-
deinit(pointer);
355-
});
356-
obj.registry.register(this, obj.pointer);
362+
obj.__swiftHeapObjectState = state;
363+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
357364
return obj;
358365
}
359366

360367
release() {
361-
this.registry.unregister(this);
362-
this.deinit(this.pointer);
368+
const state = this.__swiftHeapObjectState;
369+
if (state.hasReleased) {
370+
return;
371+
}
372+
state.hasReleased = true;
373+
this.hasReleased = true;
374+
swiftHeapObjectFinalizationRegistry.unregister(state);
375+
state.deinit(state.pointer);
363376
}
364377
}
365378
class JSValueHolder extends SwiftHeapObject {

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,23 +215,36 @@ 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;
223232
obj.hasReleased = false;
224233
obj.deinit = deinit;
225-
obj.registry = new FinalizationRegistry((pointer) => {
226-
deinit(pointer);
227-
});
228-
obj.registry.register(this, obj.pointer);
234+
obj.__swiftHeapObjectState = state;
235+
swiftHeapObjectFinalizationRegistry.register(obj, state, state);
229236
return obj;
230237
}
231238

232239
release() {
233-
this.registry.unregister(this);
234-
this.deinit(this.pointer);
240+
const state = this.__swiftHeapObjectState;
241+
if (state.hasReleased) {
242+
return;
243+
}
244+
state.hasReleased = true;
245+
this.hasReleased = true;
246+
swiftHeapObjectFinalizationRegistry.unregister(state);
247+
state.deinit(state.pointer);
235248
}
236249
}
237250
class GlobalClass extends SwiftHeapObject {

0 commit comments

Comments
 (0)