Skip to content

bug(gc): arrays of ~9M+ elements holding heap strings read back corrupted values (silent use-after-free) #10215

Description

@proggeramlug

What happened

A plain TypeScript array of about 9 million or more elements that mixes heap strings and numbers reads back wrong values after it is filled with push. Millions of slots hold unrelated strings or empty strings. No error is raised: the program keeps running with corrupted data. Node returns the correct values for the same program at every size.

The wrong values include strings allocated after the array was built (for example "churn-string-that-is-long-2385" appears in slots that held "a-long-heap-string-number-…"). That suggests element slots whose strings were collected and whose memory was reused. This is a memory-safety bug in the runtime; no regex code is involved.

Measured on perry 0.5.1557 (main at 64f5249ac), release build from source, Linux x86_64 (perrymaster). Not bisected: it is not yet known when it started.

Reproduction

// bigarray.ts — pass "churn" as the last argument to add unrelated allocations
const churnArg = process.argv[process.argv.length - 1] === "churn";
for (const n of [9_000_000, 10_000_000]) {
  const a: any[] = [];
  for (let i = 0; i < n; i++) a.push(i % 3 === 0 ? "a-long-heap-string-number-" + (i % 1000) : i);
  if (churnArg) { let churn = 0; for (let j = 0; j < 3000000; j++) churn += ("churn-string-that-is-long-" + j).length; }
  let bad = 0, firstBad = -1, sample = "";
  for (let i = 0; i < a.length; i++) {
    const v = a[i];
    const ok = i % 3 === 0 ? v === "a-long-heap-string-number-" + (i % 1000) : v === i;
    if (!ok) { bad++; if (firstBad < 0) { firstBad = i; sample = String(typeof v) + ":" + String(v).slice(0, 30); } }
  }
  console.log(`n=${n} churn=${churnArg} bad=${bad} firstBad=${firstBad} sample=${sample}`);
}

perry compile bigarray.ts --no-auto-optimize -o bigarray && ./bigarray nochurn && ./bigarray churn

Results

elements extra churn Perry bad slots first bad index first bad value Node bad slots
8,000,000 yes 0 0
8,500,000 yes 0 0
9,000,000 no 2,843,963 418,350 "a-long-heap-string-number-541" (wrong slot) 0
9,000,000 yes 2,976,690 20,166 "churn-string-that-is-long-2385" 0
10,000,000 no 2,177,942 3,366,645 "" 0
10,000,000 yes 3,202,154 0 "" 0

Controls that are fine on the same build: arrays of 8–12 million elements holding only numbers and short strings ("s" + (i % 1000), which are stored inline, not as heap strings).

How it surfaced

String.prototype.replace over large inputs goes through a runtime list backed by a GC array of heap-string references and offsets. With the list's fixed entry cap removed by #10207 (part of #10164), a callback replace over 'ab12 cd345;'.repeat(n) completes at n = 600,000. At n = 800,000 it fails with an internal invalid-span error, surfaced as RangeError: Regular expression execution failed. At n = 1,000,000 it fails with an encoding error in the string it reads back, surfaced as RangeError: Invalid regular expression input storage. The failures start where that list passes about 2^23 entries. Before #10207, the cap made the same calls throw "Regular expression memory limit exceeded" before reaching this size.

Hypothesis (unconfirmed)

2^23 slots × 8 bytes = 64 MiB exactly, and the failures begin between 8.5 and 9 million elements. Element storage growing past a 64 MiB boundary may move onto a different path whose slots are not fully traced or relocated: large-object or malloc-tracked storage, card or remembered-set coverage, or a scan range limit. This has not been verified.

Expected

Every element reads back as written, as in Node.

Acceptance

Related: #10164, #10207.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regressiontype:bugBug fix

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions