Skip to content

fix(cornerstone): guard against non-finite slice counts crashing the viewer - #6187

Open
awatson1978 wants to merge 1 commit into
OHIF:masterfrom
awatson1978:fix/6174-guard-infinite-slice-count
Open

fix(cornerstone): guard against non-finite slice counts crashing the viewer#6187
awatson1978 wants to merge 1 commit into
OHIF:masterfrom
awatson1978:fix/6174-guard-infinite-slice-count

Conversation

@awatson1978

@awatson1978 awatson1978 commented Jul 26, 2026

Copy link
Copy Markdown

Context

Fixes #6174

Open the segmentation mode with the study from the issue (TCIA …285242291560760827564488897577), load or create a segmentation while the default 2×2 layout is active, and the whole viewer goes blank with:

RangeError: Invalid typed array length: Infinity
    at new Uint8Array (<anonymous>)
    ...
    at isFullMode

The isFullMode frame in the minified stack pointed at the slice progress scrollbar, and that's exactly where it is — this turns out to be an OHIF-side crash, not a cornerstone3D one.

Changes & Results

Why this happens: cornerstone's viewport.getNumberOfSlices() computes the count as (max − min) / spacingInNormalDirection + 1. For some SEG-derived volumes in the segmentation mode's multi-viewport layouts, the spacing in the camera's normal direction comes back as 0, so the division yields Infinity. The ViewportSliceProgressScrollbar feeds that number straight into new Uint8Array(size) (via the useByteArray hook that tracks per-slice loaded/viewed state), which throws, and the route error boundary blanks the entire app.

The fix — guard at three layers so no path can feed a non-finite count into an allocation:

  1. getViewportSliceCount (extensions/cornerstone/src/utils/viewportDataShape.ts) clamps non-finite or negative counts to 0
  2. The scrollbar's slice-event handler (hooks.ts) now routes through that same guarded helper instead of calling getNumberOfSlices() raw
  3. useByteArray (platform/ui-next) defensively sanitizes its size so no other consumer can crash the Uint8Array allocation either

With the guard, the degenerate viewport simply renders without a slice scrollbar instead of taking down the viewer. The underlying "why is the spacing 0 for this volume" question belongs upstream in cornerstone3D — getNumberOfSlices() arguably should never return Infinity — and I'm happy to file that issue with the captured details as a follow-up.

Before (blank app after the RangeError) / After (SEG loads in the 2×2 layout, segments hydrate normally):

Testing

  1. pnpm dev → open /segmentation?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.3098.5025.285242291560760827564488897577
  2. Keep the default multi-viewport layout
  3. Load the "SEG AIMI lung and FDG tumor AI segmentation" series (or click Add segmentation)
  4. Before: RangeError + blank viewer. After: segmentation loads, both segments ("Lung", "FDG-Avid Tumor") appear in the panel, all viewports stay interactive with zero page errors

Reproduced the crash on unpatched master and verified the fix in Chromium via Playwright (page-error listener attached — no errors after the fix).

Screenshots

6174-before-crash-blank-app
6174-before-crash-blank-app
6174-after-no-crash
6174-after-no-crash
6174-after-seg-loaded-no-crash
6174-after-seg-loaded-no-crash

Checklist

PR

  • My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • The documentation page has been updated as necessary. (no doc changes needed)

Tested Environment

  • "OS: macOS 25.5.0
  • "Node version: 24.x
  • "Browser: Chromium (Playwright)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved scrollbar stability for edge-case and degenerate volumes.
    • Prevented invalid slice counts from causing viewport synchronization issues.
    • Fixed potential crashes caused by invalid scrollbar sizes, including infinite values.
    • Ensured slice and scrollbar calculations remain consistent when data is unavailable or invalid.

…viewer

viewport.getNumberOfSlices() returns Infinity when the volume's spacing
in the camera's normal direction degenerates to 0, which happens with
some SEG-derived volumes in the segmentation mode's multi-viewport
layouts. The slice progress scrollbar fed that count straight into
new Uint8Array(size), throwing 'RangeError: Invalid typed array
length: Infinity' and blanking the whole viewer behind the route error
boundary.

Clamp the count to a finite non-negative value in
getViewportSliceCount, route the slice-event path through the same
guard, and defensively sanitize the size in useByteArray so no caller
can crash the allocation.

Fixes OHIF#6174

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 26, 2026

Copy link
Copy Markdown

Deploy Preview for ohif-dev ready!

Name Link
🔨 Latest commit 42d3e13
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a6576c114c9290008907f73
😎 Deploy Preview https://deploy-preview-6187--ohif-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Viewport slice counts are sanitized for invalid values, navigation consumes the sanitized count, and SmartScrollbar byte-array sizes are normalized before allocation.

Changes

Slice and byte-array size safety

Layer / File(s) Summary
Sanitized viewport slice-count flow
extensions/cornerstone/src/utils/viewportDataShape.ts, extensions/cornerstone/src/Viewport/Overlays/ViewportSliceProgressScrollbar/hooks.ts
getViewportSliceCount returns 0 for invalid counts, and viewport navigation uses this helper when publishing slice data.
Sanitized SmartScrollbar allocation size
platform/ui-next/src/components/SmartScrollbar/useByteArray.ts
useByteArray converts invalid sizes to 0 and floors positive finite sizes before array use.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • OHIF/Viewers#6034: Introduces the related viewport slice progress scrollbar and underlying slice-count logic.

Suggested reviewers: sedghi

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, semantic-release styled, and accurately summarizes the crash guard fix.
Description check ✅ Passed The description includes Context, Changes & Results, Testing, and a completed checklist in the required template structure.
Linked Issues check ✅ Passed The changes address #6174 by preventing non-finite slice counts from reaching Uint8Array allocation, allowing segmentation to load.
Out of Scope Changes check ✅ Passed The extra sanitization in useByteArray is still directly related to the reported crash and not out of scope.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@platform/ui-next/src/components/SmartScrollbar/useByteArray.ts`:
- Around line 25-28: Update the shared getViewportSliceCount flow to clamp or
reject finite positive slice counts above the supported allocation limit before
useByteArray receives them. Preserve the existing handling for non-finite and
non-positive counts, and ensure useByteArray’s Uint8Array allocation only
receives bounded values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 34fb40c9-310f-474f-a454-bb07f83247b1

📥 Commits

Reviewing files that changed from the base of the PR and between 1628ddb and 42d3e13.

📒 Files selected for processing (3)
  • extensions/cornerstone/src/Viewport/Overlays/ViewportSliceProgressScrollbar/hooks.ts
  • extensions/cornerstone/src/utils/viewportDataShape.ts
  • platform/ui-next/src/components/SmartScrollbar/useByteArray.ts

Comment on lines +25 to 28
// A non-finite or negative size (e.g. a degenerate volume reporting
// Infinity slices) must not crash the Uint8Array allocation.
size = Number.isFinite(size) && size > 0 ? Math.floor(size) : 0;
const bytesRef = useRef(new Uint8Array(size));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

node - <<'NODE'
for (const size of [2 ** 32, Number.MAX_SAFE_INTEGER]) {
  try {
    new Uint8Array(size);
    console.log(size, 'accepted');
  } catch (error) {
    console.log(size, error.name);
  }
}
NODE

Repository: OHIF/Viewers

Length of output: 198


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate file and related symbols =="
fd -a 'useByteArray\.ts$|SmartScrollbar' . | sed 's#^\./##' | head -50

echo
echo "== useByteArray.ts outline =="
ast-grep outline platform/ui-next/src/components/SmartScrollbar/useByteArray.ts --view compact || true

echo
echo "== useByteArray.ts relevant contents =="
cat -n platform/ui-next/src/components/SmartScrollbar/useByteArray.ts

echo
echo "== Search getViewportSliceCount usages =="
rg -n "getViewportSliceCount|useByteArray|Uint8Array" platform/ui-next/src -S

echo
echo "== Locate getViewportSliceCount definition =="
rg -n "function getViewportSliceCount|const [A-Za-z0-9_]+ getViewportSliceCount|getViewportSliceCount =|getViewportSliceCount\\(" -S .

Repository: OHIF/Viewers

Length of output: 10267


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== viewportDataShape.ts relevant contents =="
sed -n '1,130p' extensions/cornerstone/src/utils/viewportDataShape.ts | cat -n

echo
echo "== hook call sites and surrounding logic =="
sed -n '1,220p' extensions/cornerstone/src/Viewport/Overlays/ViewportSliceProgressScrollbar/hooks.ts | cat -n

echo
echo "== ImageScrollbar call site =="
sed -n '1,110p' extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx | cat -n

echo
echo "== getViewportSliceCount call sites =="
rg -n -C 4 "getViewportSliceCount\\(" .

Repository: OHIF/Viewers

Length of output: 22594


Cap slice counts before allocating the byte array.

useByteArray only floors the value before calling Uint8Array, while getViewportSliceCount rejects only non-finite/non-positive counts and otherwise returns any finite positive value. A huge slice count can still cause allocation failure; clamp/reject oversized slice counts at the shared source instead of relying on Uint8Array allocation to fail later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platform/ui-next/src/components/SmartScrollbar/useByteArray.ts` around lines
25 - 28, Update the shared getViewportSliceCount flow to clamp or reject finite
positive slice counts above the supported allocation limit before useByteArray
receives them. Preserve the existing handling for non-finite and non-positive
counts, and ensure useByteArray’s Uint8Array allocation only receives bounded
values.

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.

[Bug] [Segmentation]Application crashes with “Invalid typed array length: Infinity” when calculating full mode

1 participant