Skip to content

[BUG] Division by zero panic in ResizablePanelGroup #894

Open

Description

@rajpreetcodes

Bug Report

Platform

All platforms (Linux, macOS, Windows, WSL2)

Current Behavior

In �pps/desktop/src/components/resizable.rs:71-79, the drag fraction calculation divides by bounds width/height without checking for zero:

ust let fraction = match self.orientation { Orientation::Horizontal => { (event.event.position.x - bounds.left()) / (bounds.right() - bounds.left()) // Can be 0 } Orientation::Vertical => { (event.event.position.y - bounds.top()) / (bounds.bottom() - bounds.top()) // Can be 0 } };

If the panel group has zero width or height (e.g., during window initialization, minimized state, or layout edge cases), this causes a division by zero panic.

Expected Behavior

The calculation should guard against zero-size bounds and return a safe default fraction.

Steps To Reproduce

  1. Run the desktop app
  2. Resize window to minimum size or trigger a layout where panel bounds collapse to zero
  3. Attempt to drag the resizable handle

Recurrence Probability

Sometimes - depends on window state and layout timing

Additional Context

Critical bug - causes immediate panic/crash. Should be fixed before any PRs are accepted.

Activity

  1. killer1panda commented on Sep 8, 2026

    @killer1panda

    I've investigated this issue, verified the root cause, and put together a fix with unit tests on my fork:

    Root Cause

    When the resizable panel group collapses or has zero width/height during window initialization or layout edge cases, (offset) / (bounds.right() - bounds.left()) results in 0.0 / 0.0 = NaN or +/-inf. Passing this to self.clamp_fraction(fraction) triggers f32::clamp's internal assertion !self.is_nan(), causing an immediate panic crash.

    Changes

    1. Guard against non-positive span (span <= px(0.0)) in ResizablePanelGroup::resize, early-returning without modifying fraction or notifying listeners.
    2. Ensure fraction.is_finite() before updating self.fraction.
    3. Harden clamp_fraction and minimum_fraction with .is_finite() checks so non-finite floats safely resolve to boundaries instead of panicking.
    4. Added unit test panel_fraction_handles_nan_and_infinities covering NaN, +/-inf, and invalid minimums.

    All existing and new tests pass cleanly (cargo test --manifest-path apps/desktop/Cargo.toml).

    Given that direct PRs are currently closed during the rewrite, happy to open a PR or let maintainers cherry-pick this commit whenever appropriate!

  2. rollroyces commented on Oct 23, 2026

    @rollroyces

    Correction on the failure mode — and an offer

    I dug into this one while deciding whether to fix it, and the "division by zero panic" framing doesn't quite hold. Worth recording before someone writes a PR against the wrong theory.

    px is float-backed, so this is floating-point division, not integer division. Rust does not panic for f32 / 0.0; the quotient is inf, or NaN when the numerator is also zero. I reproduced both paths by extracting clamp_fraction and the resize arithmetic into a standalone program (rustc 1.98.1):

    numerator=50  denominator=0  -> raw fraction = inf
      after clamp_fraction(_, 0.1) = 0.9
    numerator=0   denominator=0  -> raw fraction = NaN
      after clamp_fraction(_, 0.1) = NaN   (NaN survives clamp)
    

    So the real consequences are:

    • inf clamps to 0.9, so the panel silently snaps to 90%.
    • NaN survives f32::clamp (which propagates NaN rather than clamping it), gets stored in self.fraction, and reaches .w(relative(NaN)) in render, corrupting the layout.
    • A third case the issue doesn't mention: inverted bounds, where end < start gives a negative span and the fraction clamps to the minimum_fraction floor.

    So it's a genuine bug and the fix is worth having, but it presents as layout corruption plus a panel snapping open, not as a crash. That matters for triage: "immediate panic/crash" would reasonably jump the queue, and this isn't that.

    Patch

    Guarding on the span and ignoring the drag when the group is collapsed:

    let (position, start, end) = match self.orientation {
        Orientation::Horizontal => (event.event.position.x, bounds.left(), bounds.right()),
        Orientation::Vertical => (event.event.position.y, bounds.top(), bounds.bottom()),
    };
    
    // A collapsed panel group (zero width or height, e.g. before the window
    // is laid out) divides by zero. `px` is float-backed, so this yields inf
    // or NaN rather than panicking, but both then reach `relative(..)` and
    // corrupt the layout. Ignore the drag instead of storing a bad fraction.
    let span = end - start;
    if span <= 0.0 {
        return;
    }
    
    self.fraction = self.clamp_fraction((position - start) / span);

    span <= 0.0 covers the zero and inverted cases together. rustfmt --check is clean, and I asserted the fraction stays finite and inside the clamp range across zero-width, zero-height, normal, and inverted spans.

    Full branch — this also includes the apps/desktop/README.md Windows/MSVC correction reported in #892, since that was a one-line doc fix and fit naturally on the same branch:

    https://github.com/rollroyces/OpenCut/tree/fix/resizable-zero-span

    On submitting

    I wasn't able to open a PR: GitHub returns rollroyces does not have the correct permissions to execute CreatePullRequest, and REST returns 404, so PR creation appears gated to collaborators. I know from the README and #811 that the architecture is still being designed and outside contributions aren't open yet, so I'm not going to force it. Happy to land it however is easiest — direct commit, cherry-pick, or opening the PR myself once the gate lifts.

    One caveat stated plainly: ResizablePanelGroup isn't instantiated anywhere yet. It's exported from components/mod.rs, but shell.rs doesn't mount it, so this is groundwork hardening rather than a crash someone can hit today.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions