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.
Browser preview2-shim:
Descriptor.readthrows on canonical BigInt argumentsSummary
In
@bytecodealliance/preview2-shim, the browser implementation ofwasi:filesystem.types.Descriptor.readpasses itslengthandoffsetarguments straight to
Uint8Array.prototype.slice. The WIT signaturedeclares both as
filesize(u64), which the canonical-ABI bindingdelivers as
BigInt, soslicealways throws:Any component that performs
readagainst a file in the browser shimfails on the first call. The Node-side filesystem implementation in
this same package is unaffected because its
readgoes throughfs.read(which accepts BigInt position).Affected version / location
@bytecodealliance/preview2-shim(verified on0.18.0)packages/preview2-shim/lib/browser/filesystem.jsDescriptor.read(length, offset)— currently lines 189-192on
main(commit6c585ac1b0b252e5ec8987a0d7d8f3ffda0cd076)Minimal reproducer
repro.mjs:(The relative
./node_modules/...import is used so the file resolvesto the browser implementation under Node; the package's
exportsfield otherwise routes specifier imports to the Node implementation.)
Steps:
Before the fix
After the fix
Root cause
Descriptor.readin the browser implementation:Uint8Array.prototype.slicerequiresNumberarguments and throwson
BigInt. The wasi-filesystem WIT declares the arguments asfilesize = u64, so the canonical-ABI binding passes them asBigInt. The call therefore always throws.Proposed fix
Coerce both arguments to
Numberonce at entry:This:
BigIntarguments without throwing,Number(typeofguard makes the coercion idempotent),source.byteLengthand the returned EOF boolean asNumber-typed (matching the existing function's already-Numberresult tuple).
Safe up to
Number.MAX_SAFE_INTEGERbytes (~9 PB), well above anypractical 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+ thepreview2-shimbrowserbuilds. The component reads a ~15 MB resource file on startup
through
wasi-filesystem.types.read; every browser run failedimmediately on the first
read. The same artifact runs withoutmodification under
jco run(which uses the Node-side filesystemimplementation, where this code path is not exercised).
Related code
packages/preview2-shim/lib/browser/filesystem.jsDescriptor.writeat line ~194 also takes a
filesizeoffsetbut currently onlychecks
offset !== 0and stores the buffer; it doesn't index withthe value, so the same BigInt issue doesn't manifest there. No
change proposed.