From c43a9c6a74890e98ac417cbae1de8a72466264c3 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Mon, 5 Aug 2024 12:28:59 -0700 Subject: [PATCH] fix: respect input offset and bytelength when storing data views If the incoming data view does not span the entire backing array buffer, copy the data into a smaller block. Data views, like JS Typed Arrays (`Uint8Array` et al) and `DataView`, store a `byteOffset` and `byteLength` field which give them a window onto the bytes stored in an `ArrayBuffer`. We had been ignoring this when storing `Uint8Array` values -- we grabbed the backing buffer directly to store as a block. In the future we could store that windowing data on the `Block` class, but that would be a more involved change for background thread contexts. We'd have to convey that windowing information down to the backing thread. Fixes #81. --- src/call-context.ts | 6 +++++- src/mod.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/call-context.ts b/src/call-context.ts index 9d44c7c..c4dc108 100644 --- a/src/call-context.ts +++ b/src/call-context.ts @@ -496,7 +496,11 @@ export class CallContext { } if (input instanceof Uint8Array) { - if (input.buffer.constructor === this.#arrayBufferType) { + if ( + input.buffer.constructor === this.#arrayBufferType && + input.byteOffset === 0 && + input.byteLength === input.buffer.byteLength + ) { // no action necessary, wrap it up in a block const idx = this.#blocks.length; this.#blocks.push(new Block(input.buffer, true)); diff --git a/src/mod.test.ts b/src/mod.test.ts index 011bbff..76f6b1f 100644 --- a/src/mod.test.ts +++ b/src/mod.test.ts @@ -432,6 +432,31 @@ if (typeof WebAssembly === 'undefined') { } }); + test('input data respects byte offsets and lengths', async () => { + const plugin = await createPlugin({ + wasm: [ + { name: 'main', url: 'http://localhost:8124/wasm/reflect.wasm' }, + { name: 'extism:host/user', url: 'http://localhost:8124/wasm/upper.wasm' }, + ], + }); + + const arrayBuffer = new ArrayBuffer(8192) + const view = new Uint8Array(arrayBuffer, 10, "Hello world!".length) + new TextEncoder().encodeInto("Hello world!", view); + + try { + const [err, data] = await plugin.call('reflect', view).then( + (data) => [null, data], + (err) => [err, null], + ); + + assert.equal(err, null); + assert.equal(data.string(), 'HELLO WORLD!'); + } finally { + await plugin.close(); + } + }); + if (CAPABILITIES.hasWorkerCapability) { test('host functions may be async if worker is off-main-thread', async () => { const functions = {