Skip to content

splice: a negative start, and a left-out or negative delete count - #385

Merged
ASDAlexander77 merged 1 commit into
mainfrom
native-splice-arguments
Sep 28, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
native-splice-arguments

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

The native array splice handled only the plain case. Found while adding splice to DefaultLib's Array<T> class (ASDAlexander77/TypeScriptCompilerDefaultLib#13, for #231):

call before now (as in JavaScript)
a.splice(-1, 1) access violation removes the last element
a.splice(-10, 1) access violation removes the first
a.splice(2) read an operand that was not there; nothing printed removes everything from index 2
a.splice(1, -3) removed everything after 1 removes nothing
a.splice(10, 1) out of range removes nothing

Cause

  • The start's sign was lost. Start and delete count were cast straight to index. From an s32 literal that cast is a zero extension (index is not a signed type), so -1 arrived as 4294967295. The lowering then used the value as an unsigned offset.
  • A left-out delete count was never checked for. mlirGenArraySplice(operands) read operands[2] whether or not it was there.

Fix

  • MLIRCodeLogic.h:

    • start and delete count are cast to index through a signed 64-bit integer, so the sign survives;
    • a left-out delete count becomes INT32_MAX, which is positive in a 32-bit index as well; the lowering's existing clamp brings it down to what is there;
    • a call with no start at all reports "splice needs the index to start at".
  • ArraySpliceOpLowering: start and delete count are read as signed:

    • a negative start counts from the end and stops at 0;
    • a start past the end becomes the end;
    • a negative delete count becomes 0.

    This happens before the existing clamp and before the rc release of the removed elements, so both see the corrected values.

Tests

  • 00array_splice.ts gains six cases: a negative start, a start before 0, a start past the end, a left-out delete count, a negative delete count, and a number start. It already runs in compile, jit, jit -mm=rc and the corpus. The old build faults on it.
  • Full release suite: 2987/2987 passed. test-compile-none-corpus-00for-await failed once in the full run and passed three times on rerun; that is the known flaky async test.
  • TypeScriptCompilerDefaultLib tests, release jit and compile: 156/156 each.

Not in this PR

  • a.splice(1, 1, ...x) drops the spread items silently, and a.push(...x) fails verification. A spread argument never reaches the array builtins as its elements.
  • Native splice returns the new length, not the removed elements as in JavaScript. DefaultLib's Array<T>.splice (DefaultLib#13) returns the removed elements.

🤖 Generated with Claude Code

`a.splice(-1, 1)` faulted: start went to index by zero extension (s32) or
as-is, and the lowering took -1 as the largest unsigned index. `a.splice(2)`
read an operand that was not there. JavaScript's rules now apply: start and
delete count go to index through a signed 64-bit integer, and the lowering
reads them signed - a negative start counts from the end and stops at 0, a
start past the end is the end, a negative delete count deletes nothing, and a
left-out delete count removes everything from start on (the existing clamp
takes it down to what is there).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 59e93c3 into main Sep 28, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the native-splice-arguments branch September 28, 2026 08:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant