Skip to content

Fix infinite loop in xt::roll on arrays with a zero-length dimension#2922

Open
f14XuanLv wants to merge 1 commit into
xtensor-stack:masterfrom
f14XuanLv:fix/roll-empty-array
Open

Fix infinite loop in xt::roll on arrays with a zero-length dimension#2922
f14XuanLv wants to merge 1 commit into
xtensor-stack:masterfrom
f14XuanLv:fix/roll-empty-array

Conversation

@f14XuanLv
Copy link
Copy Markdown
Contributor

Checklist

  • The title and commit message(s) are descriptive.
  • Small commits made to fix your PR have been squashed to avoid history pollution.
  • Tests have been added for new features or bug fixes.
  • API of new functions and classes are documented. (N/A — bug fix only, no API change)

Description

xt::roll enters an infinite loop (and then divides by zero) when the input
has a zero-length axis:

  • No-axis overload roll(e, shift) when e.size() == 0:
    while (shift < 0) shift += flat_size; loops forever with flat_size == 0,
    then shift %= 0 is undefined behaviour.
  • Axis-aware overload roll(e, shift, axis) when the rolled axis has length 0:
    same issue on axis_dim == 0.

Fix

For a shape $s = (s_0, \dots, s_{n-1})$,
if $\exists k: s_k = 0$, then $\mathrm{roll}(a, \mathrm{shift}, \mathrm{axis}) = a$,
i.e. roll is the identity.

In both overloads, after constructing cpy = empty_like(e), return cpy
early when cpy.size() == 0. The no-axis overload's hand-rolled
std::accumulate(shape, 1L, multiplies<size_t>()) is replaced by cpy.size()
(numerically equivalent, and O(1) on contiguous layouts).

When the rolled axis is non-empty but another axis has length 0
(e.g. shape={3,0,4} rolled along axis 0), the original code didn't loop —
detail::roll's inner loops became no-ops because the stride product
collapses to 0 — but the recursion was wasted work. The widened guard
cpy.size() == 0 skips it cleanly. Observable output is unchanged.

Tests

Three regression cases in test_xmanipulation.cpp, one per code path:

Shape Axis Path exercised
{0} (ravel) no-axis overload's empty-input guard
{3, 0} 1 axis-aware overload, rolled axis itself is 0
{3, 0, 4} 0 axis-aware overload, other axis is 0

All test_xmanipulation tests pass: 28 cases, 188 assertions. Verified
against numpy.roll, which returns a same-shape empty array for these inputs.

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 28, 2026

Merging this PR will not alter performance

✅ 255 untouched benchmarks


Comparing f14XuanLv:fix/roll-empty-array (10a24af) with master (9575b39)

Open in CodSpeed

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