Skip to content

fix(view): checked shape products and dense strides in StridedArray constructors (#205) - #346

Merged
shinaoka merged 1 commit into
mainfrom
fix/205-checked-shapes
Oct 30, 2026
Merged

shinaoka merged 1 commit into
mainfrom
fix/205-checked-shapes

Conversation

@shinaoka

Copy link
Copy Markdown
Member

Closes #205.

Problem

StridedArray's allocating constructors computed dims.iter().product() without overflow checks. col_major_strides / row_major_strides multiplied without checks and cast extents with as isize. In a release build, [1 << 62, 4] wrapped to 0 elements: the array held an empty Vec behind a [1 << 62, 4] layout, and view() / get read past it. The len() accessors (StridedView, StridedViewMut, StridedArray) used the same unchecked product.

Change (strided-view only, no changed signatures, so this can ship as a patch)

  • New try_* constructors. try_col_major, try_row_major, try_from_fn_col_major, try_from_fn_row_major, try_col_major_from_buffer, plus the unsafe try_col_major_uninit and try_col_major_from_buffer_uninit. They return StridedError::OffsetOverflow when the element count or a stride does not fit isize. f is not called on error. I reused the existing OffsetOverflow variant because StridedError is not #[non_exhaustive], so a new variant would be a breaking change.
  • Existing constructors. They delegate to the try_* versions. They now panic with a message instead of wrapping, documented under # Panics. Under REPOSITORY_RULES "Public Boundary Safety" they should really return Result, but that changes their signatures. I suggest deprecating them, or switching them to Result, in the next minor release.
  • col_major_strides / row_major_strides. Built with checked multiplication and isize::try_from. They panic only when a non-empty shape overflows. A shape with a zero extent saturates instead of panicking, because none of its elements are ever accessed. For example, [1 << 62, 4, 0] stays constructible as an empty array. Values for every shape that does not overflow are unchanged.
  • len(). A zero extent now short-circuits the product, so [usize::MAX, 2, 0] returns 0; before, debug builds overflowed. It panics only when the element count of a broadcast (stride-0) layout exceeds usize.

Tests

  • Rejection. Every try_* constructor is checked against five overflowing shapes: [1<<62, 4] wraps to 0; [1<<62, 2] overflows only isize; [2, 1<<62]; [isize::MAX+2, 1] overflows the as isize conversion; [usize::MAX, usize::MAX].
  • Acceptance. Valid shapes and empty shapes are accepted, including [1<<62, 4, 0] and [0, usize::MAX, usize::MAX].
  • Messages. The panic messages of col_major and col_major_strides.
  • Empty-shape strides. [3, 0, 5] keeps its existing values, and the saturating cases are checked.
  • len(). Of a zero-sized broadcast view, and of an overflowing one.

The five tests that do not use the new API fail on origin/main and pass with this change.

Verification

All builds ran under nice -n 19 taskset -c 56-63 with -j 8. broccoli has only CPUs 0-63.

  • cargo fmt --all -- --check: OK
  • cargo test --workspace: 1106 passed, 0 failed
  • cargo clippy -p strided-view --all-targets: no new warnings in the changed code (the existing manual_contains warnings are untouched)
  • Repository rules review, dry run: pass

🤖 Generated with Claude Code

…onstructors (#205)

The allocating StridedArray constructors computed dims.iter().product()
unchecked and col_major_strides/row_major_strides multiplied unchecked (and
cast extents to isize with as). In a release build [1 << 62, 4] wrapped to
0 elements, so the array kept an empty Vec behind a [1 << 62, 4] layout and
view()/get dereferenced past it. The len() accessors had the same product.

- New try_col_major, try_row_major, try_from_fn_col_major,
  try_from_fn_row_major, try_col_major_from_buffer, try_col_major_uninit and
  try_col_major_from_buffer_uninit return StridedError::OffsetOverflow when
  the element count or a stride does not fit isize. f is not called on error.
- The existing constructors keep their signatures (0.x patch-compatible) and
  delegate to them, panicking with a message instead of wrapping; documented
  under # Panics.
- col_major_strides/row_major_strides panic on overflow of a non-empty shape;
  a shape with a zero extent saturates instead, since it is never accessed.
- len() counts a zero extent first and panics instead of wrapping when only a
  broadcast layout's count overflows usize.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shinaoka shinaoka self-assigned this Oct 30, 2026
@github-actions

Copy link
Copy Markdown

Repository rules review

Repository rules review (7053f9d1a35906a0c5593d0802851f63b5f779ef...77bf594871f67e5251ac47ab65b49caae21fcc8b)
Verdict: pass
Findings:
- [warn] llm-skipped (External LLM Review) <unknown>: External LLM review was skipped
  External LLM review is permanently disabled; deterministic checks only.

@shinaoka
shinaoka merged commit 8dc0366 into main Oct 30, 2026
11 checks passed
@shinaoka
shinaoka deleted the fix/205-checked-shapes branch October 30, 2026 06:16
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.

strided-view: checked arithmetic for shape products and stride construction in StridedArray constructors

1 participant