Skip to content

Array length is unchecked: a negative or huge length= crashes or gives a wrong length, and failed allocations are not detected #483

Closed

Description

@ASDAlexander77

Array sizes are never checked. A negative or huge length = crashes or silently gives the wrong length, and a failed allocation is not detected.

function shrink(a: number[], n: number) {
    a.length = n;
}

function main() {
    const a: number[] = [1, 2, 3];
    shrink(a, -1);
    print("length: ", a.length);
    print("done.");
}
function main() {
    const a: number[] = [1, 2, 3];
    a.length = 4294967296 * 1024;
    print("length: ", a.length);
    print("done.");
}

Expected, as in TypeScript: a RangeError for both, since a length must be an integer in [0, 2^32 - 1]. Short of exceptions, a clear runtime error; never a crash or a wrong length.

Actual, tslang --emit=jit --no-default-lib:

program model main f013674 (before #453) #482's head (c7ff7fc)
length = -1 gc prints length: 3 (ignored) access violation
length = -1 rc access violation access violation
length = 2^42 gc prints length: 4398046511104 (no such block) access violation
length = 2^42 rc access violation access violation

Cause: the lowerings never check the size.

  • SetLengthOf converts the new length straight to an index. A negative value becomes a huge unsigned size.
  • ArrayLayout::ensureCapacity computes max(4, 2 * capacity, needed) and capacity * sizeof(element) without an overflow check (out of reach on x64 except through a bad length =, closer on x86).
  • MemoryRealloc/MemoryAlloc results are not compared with null before the header's data pointer is replaced and the new slots are zeroed.

Before #482, gc's GC_realloc returned null for the huge request and the length was stored anyway. After #482, the zeroing of the new range writes through that null.

Suggested:

  • length =: a negative, non-integer or > 2^32 - 1 value is a runtime error (an exception once available) before anything is touched.
  • Growth: check the multiplication for overflow and the allocator's result for null, and fail the same way.
  • push/unshift/splice through ensureCapacity get the same allocation check.

Found in the final review of #453 (PR #482).

🤖 Generated with Claude Code

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions