Skip to content

Browser preview2-shim: Descriptor.read throws on canonical BigInt arguments #1573

@zacharywhitley

Description

@zacharywhitley

Browser preview2-shim: Descriptor.read throws on canonical BigInt arguments

Summary

In @bytecodealliance/preview2-shim, the browser implementation of
wasi:filesystem.types.Descriptor.read passes its length and offset
arguments straight to Uint8Array.prototype.slice. The WIT signature
declares both as filesize (u64), which the canonical-ABI binding
delivers as BigInt, so slice always throws:

TypeError: Cannot convert a BigInt value to a number
    at Uint8Array.slice (<anonymous>)
    at Descriptor.read (preview2-shim/lib/browser/filesystem.js:191)

Any component that performs read against a file in the browser shim
fails on the first call. The Node-side filesystem implementation in
this same package is unaffected because its read goes through
fs.read (which accepts BigInt position).

Affected version / location

  • Package: @bytecodealliance/preview2-shim (verified on 0.18.0)
  • File: packages/preview2-shim/lib/browser/filesystem.js
  • Function: Descriptor.read(length, offset) — currently lines 189-192
    on main (commit 6c585ac1b0b252e5ec8987a0d7d8f3ffda0cd076)

Minimal reproducer

repro.mjs:

import { _setFileData, preopens } from
  './node_modules/@bytecodealliance/preview2-shim/lib/browser/filesystem.js';

_setFileData({
  dir: {
    'test.txt': { source: new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]) },
  },
});

const [[root]] = [preopens.getDirectories()];
const fd = root.openAt({}, 'test.txt', {}, {});

try {
  const [bytes, eof] = fd.read(5n, 0n);
  console.log('OK', new TextDecoder().decode(bytes), 'eof=' + eof);
} catch (e) {
  console.error('THROWS', String(e));
}

(The relative ./node_modules/... import is used so the file resolves
to the browser implementation under Node; the package's exports
field otherwise routes specifier imports to the Node implementation.)

Steps:

mkdir repro && cd repro
npm init -y
npm install @bytecodealliance/preview2-shim@0.18.0
# save repro.mjs alongside package.json
node repro.mjs

Before the fix

$ node repro.mjs
THROWS TypeError: Cannot convert a BigInt value to a number
exit 1

After the fix

$ node repro.mjs
OK Hello eof=true
exit 0

Root cause

Descriptor.read in the browser implementation:

read(length, offset) {
  const source = getSource(this.#entry);
  return [source.slice(offset, offset + length),
          offset + length >= source.byteLength];
}

Uint8Array.prototype.slice requires Number arguments and throws
on BigInt. The wasi-filesystem WIT declares the arguments as
filesize = u64, so the canonical-ABI binding passes them as
BigInt. The call therefore always throws.

Proposed fix

Coerce both arguments to Number once at entry:

read(length, offset) {
  const source = getSource(this.#entry);
  const off = typeof offset === "bigint" ? Number(offset) : offset;
  const len = typeof length === "bigint" ? Number(length) : length;
  return [source.slice(off, off + len),
          off + len >= source.byteLength];
}

This:

  • accepts the canonical-ABI BigInt arguments without throwing,
  • preserves behavior for any caller that still happens to pass
    Number (typeof guard makes the coercion idempotent),
  • keeps source.byteLength and the returned EOF boolean as
    Number-typed (matching the existing function's already-Number
    result tuple).

Safe up to Number.MAX_SAFE_INTEGER bytes (~9 PB), well above any
practical browser use.

Patch attached as patch.diff.

Discovery context

Found while embedding a WASI component (a WebAssembly-native JVM)
in a browser via jco transpile + the preview2-shim browser
builds. The component reads a ~15 MB resource file on startup
through wasi-filesystem.types.read; every browser run failed
immediately on the first read. The same artifact runs without
modification under jco run (which uses the Node-side filesystem
implementation, where this code path is not exercised).

Related code

packages/preview2-shim/lib/browser/filesystem.js Descriptor.write
at line ~194 also takes a filesize offset but currently only
checks offset !== 0 and stores the buffer; it doesn't index with
the value, so the same BigInt issue doesn't manifest there. No
change proposed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions