vercel-labs / vercel-labs/scriptc

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

Open
#31 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
4.9k
Forks
125
Avg merge
2h 14m
Merged PRs (30d)
95

Description

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

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the three TypeScript reproductions in the issue and compare their native output with Node: destructuring and indexed reads should produce undefined, while an indexed write should grow the array. Trace the compiler and runtime handling of array indexing, then verify all three cases no longer abort and preserve the expected length and values.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.