From 8100fbf91fdb51ecdb170f79e931cb7a523f7904 Mon Sep 17 00:00:00 2001 From: LautaroPetaccio Date: Tue, 5 May 2026 11:54:25 -0300 Subject: [PATCH] Free wrapper Box created by wrapPointer in toUint8Array UniffiRustBufferValue.toUint8Array calls wrapPointer to hand the buffer data to restorePointer. ffi-rs's wrapPointer heap-allocates a Box that wraps the inner pointer: env.create_external( Box::into_raw(Box::new(ptr)), Some(std::mem::size_of::<*mut c_void>() as i64), ) Per ffi-rs's documented contract, the JsExternal does not free its underlying memory on garbage collection - the wrapper Box only goes away if freePointer is called on it. Nothing here calls freePointer, so every read of a Rust buffer through toUint8Array (and therefore every consumeIntoUint8Array, which is hit on every error response in uniffiCheckCallStatus) leaks a pointer-sized Box. Capture the wrapPointer result in a local, pass it into restorePointer, and reclaim it in a finally block. ffi-rs's free path for a U8Array is just Box::from_raw on the wrapper, so the underlying buffer the wrapper points at - which is owned by the Rust side and freed separately via rustbuffer_free - is left intact. --- templates/sys.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/templates/sys.ts b/templates/sys.ts index cc0ca1a..3423966 100644 --- a/templates/sys.ts +++ b/templates/sys.ts @@ -356,12 +356,22 @@ export class UniffiRustBufferValue { throw new Error(`Error converting rust buffer to uint8array - rust buffer length is ${this.struct.len}, which cannot be represented as a Number safely.`) } - const [contents] = restorePointer({ - retType: [arrayConstructor({ type: DataType.U8Array, length: Number(this.struct.len) })], - paramsValue: wrapPointer([this.struct.data]), - }); - - return new Uint8Array(contents); + const length = Number(this.struct.len); + const wrapped = wrapPointer([this.struct.data]); + try { + const [contents] = restorePointer({ + retType: [arrayConstructor({ type: DataType.U8Array, length })], + paramsValue: wrapped, + }); + + return new Uint8Array(contents); + } finally { + freePointer({ + paramsType: [arrayConstructor({ type: DataType.U8Array, length })], + paramsValue: wrapped, + pointerType: PointerType.RsPointer, + }); + } } consumeIntoUint8Array() {