Skip to content

Out-of-bounds array read/write throws RangeError instead of undefined (clean build, runtime abort) #31

Description

@ryoppippi

Out-of-bounds array access throws RangeError where Node evaluates to undefined. The build is clean; the divergence only shows up at runtime, and it hits the most ordinary JS idioms there are — destructuring a short array, probing arr[i] before checking length, growing an array by index.

This one cost me the most time while porting a real CLI: six separate sites, each compiling without a diagnostic and then aborting on the first run.

Repro

1. Destructuring past the end

export {}
function main() {
  const a = ['x']
  const [first, second] = a
  console.log(`first=${first} second=${second}`)
}
main()
$ node --experimental-strip-types oob.ts
first=x second=undefined

$ scriptc build oob.ts -o oob && ./oob
scriptc: RangeError: array index 1 out of bounds (length 1)
# exit 134

2. Indexed read past the end

export {}
function main() {
  const a: string[] = ['x']
  console.log(`read=${a[1]}`)
}
main()

Node: read=undefined — scriptc: RangeError: array index 1 out of bounds (length 1).

3. Write past the end (array growth)

export {}
function main() {
  const a: string[] = []
  a[2] = 'z'
  console.log(`len=${a.length} v=${a[2]}`)
}
main()

Node: len=3 v=z — scriptc: RangeError: array index 2 out of bounds (length 0).

Expected

Per the README's "What compiles behaves byte-for-byte like Node", an out-of-range read should evaluate to undefined and an out-of-range write should extend the array.

Why it is easy to hit

These are the shapes real code uses, and none of them look risky:

const [src, selector] = positionals          // optional second CLI argument
const peek = () => toks[pos]                 // recursive-descent parser at EOF
if (bytes[0] === 0xef && bytes[1] === 0xbb)  // BOM sniff on an empty body
while (grid[r][c] !== undefined) c++         // HTML table grid fill
argv[index - 1] === flag                     // look-behind at index 0

Under noUncheckedIndexedAccess the checker already types these as T | undefined, so the source is honest about the undefined case — the runtime just never produces it.

Suggestions

Ideally the read lowers to a bounds test yielding undefined, and the write extends. If matching Node here is genuinely out of scope for the value representation, a compile-time diagnostic would still be a large improvement over a runtime abort — the alternative today is discovering each site by running the binary.

Environment

  • scriptc 0.0.17, @scriptc/compiler 0.0.17
  • macOS 26.5.2, arm64, Node v24.18.0
  • Static build (no --dynamic), default backend

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions