tinygo icon indicating copy to clipboard operation
tinygo copied to clipboard

wasm_exec.js unsigned int problem

Open vladKrk opened this issue 2 months ago • 0 comments

RangeError: Start offset is negative when processing large data in WebAssembly

Description

When working with large data in the browser, the following errors occurs:

RangeError: Start offset -2147452000 is outside the bounds of the buffer
at new DataView
at loadString
at syscall/js.stringVal
RangeError: Start offset -2018731584 is outside the bounds of the buffer
at new Uint8Array
at syscall/js.copyBytesToJS 

Root Cause

The issue occurs because WebAssembly memory pointers are unsigned 32-bit integers, but JavaScript interprets them as signed integers in certain operations. When pointers fall into the upper half of the 32-bit address space (starting from bit 2³¹), JavaScript treats them as negative numbers.

For example:

  • Unsigned representation: 2147515296 (valid address)
  • Signed representation: -2147452000 (after overflow)

This causes DataView and typed array constructors to throw a RangeError since they don't accept negative offsets.

Affected Code

The issue affects the loadString, loadValue, storeValue, loadSlice function and other places where trying to set or get uint value:

const loadString = (ptr, len) => {  
    return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len)); // <- ptr could be negative
};
const storeValue = (addr, v) => {
    let v_ref = boxValue(v);
    mem().setBigUint64(addr, v_ref, true); // <- this addr could be negavtive
}

Proposed Solution

Use the unsigned right shift operator >>> 0 to convert values to unsigned 32-bit integers:

const loadString = (ptr, len) => {  
    // Convert to unsigned 32-bit integers to handle potential negative values from WASM  
    return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr >>> 0, len >>> 0));
};

The same fix should be applied to similar functions that create typed arrays or DataViews from WASM memory:

const loadSlice = (array, len, cap) => {  
    // Convert to unsigned 32-bit integers to handle potential negative values from WASM  
    return new Uint8Array(this._inst.exports.memory.buffer, array >>> 0, len >>> 0);
};

Reproduction

  1. Load a TinyGo-compiled WebAssembly module
  2. Process data that causes memory allocation in the upper half of the 32-bit address space
  3. Call any syscall/js function that uses loadString or similar memory access functions
  4. Observe the RangeError

Environment

  • Browser: Any (Chrome, Firefox, Safari)
  • WebAssembly: TinyGo-compiled modules
  • File: wasm_exec.js (TinyGo runtime)

Related Issues

Similar issue but fix was done only for one place https://github.com/tinygo-org/tinygo/issues/4763

vladKrk avatar Nov 20 '25 10:11 vladKrk