Skip to content

Commit

Permalink
fix: respect input offset and bytelength when storing data views
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
chrisdickinson committed Aug 5, 2024
1 parent 943db4c commit c43a9c6
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 1 deletion.
6 changes: 5 additions & 1 deletion src/call-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
25 changes: 25 additions & 0 deletions src/mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down

0 comments on commit c43a9c6

Please sign in to comment.