Skip to content

Prevent view creation with underflowed size#2927

Open
m-ogita wants to merge 1 commit into
xtensor-stack:masterfrom
m-ogita:fix-underflowed-shape
Open

Prevent view creation with underflowed size#2927
m-ogita wants to merge 1 commit into
xtensor-stack:masterfrom
m-ogita:fix-underflowed-shape

Conversation

@m-ogita

@m-ogita m-ogita commented Jul 10, 2026

Copy link
Copy Markdown

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.

Description

When a slice with conditions such as start > stop && step > 0 (or vice versa) is used to create a view (sliced or strided), the resulting array view can have an enormous size. Accessing its elements then leads to undefined behavior.

Here is a minimal example:

#include <iostream>
#include <xtensor/containers/xarray.hpp>
#include <xtensor/views/xview.hpp>

int main()
{
    xt::xarray<int>::shape_type shape = {5};
    xt::xarray<int> a(shape);

    // v := a[3:0:1]
    auto slice = xt::range(3, 0, 1);
    auto v = xt::view(a, slice);

    // Print the shape of v
    for (auto e : v.shape())
    {
        std::cout << e << " ";
    }
    std::cout << std::endl;
}

The expected output is 0, but the actual output is 18446744073709551613.

The cause of this issue appears to be the size calculation in the xstepped_range constructor in xtensor/include/xtensor/views/xslice.hpp, shown below:

template <class T>
inline xstepped_range<T>::xstepped_range(size_type start_val, size_type stop_val, size_type step) noexcept
    : m_start(start_val)
    , m_size(size_type(0))
    , m_step(step)
{
    size_type n = stop_val - start_val;
    m_size = n / step + (((n < 0) ^ (step > 0)) && (n % step));
}

When start_val is greater than stop_val (or more precisely, when stop_val - start_val has the opposite sign from step), m_size can become negative. This subsequently causes the resulting size to underflow.

One simple way to fix this issue is to clamp m_size to a minimum value of 0:

template <class T>
inline xstepped_range<T>::xstepped_range(size_type start_val, size_type stop_val, size_type step) noexcept
    : m_start(start_val)
    , m_size(size_type(0))
    , m_step(step)
{
    size_type n = stop_val - start_val;
    m_size = std::max(n / step + (((n < 0) ^ (step > 0)) && (n % step)), size_type(0));
}

Note on terminology: > I used the term "underflow" to describe "integer underflow", the behavior where subtracting from a small unsigned integer results in a huge value.

@JohanMabille JohanMabille left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the fix. No need to perform the computation if stop_val is greater than start_val though.

{
size_type n = stop_val - start_val;
m_size = n / step + (((n < 0) ^ (step > 0)) && (n % step));
m_size = std::max(n / step + (((n < 0) ^ (step > 0)) && (n % step)), size_type(0));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
m_size = std::max(n / step + (((n < 0) ^ (step > 0)) && (n % step)), size_type(0));
m_size = stop_val > start_val ? n / step + (((n < 0) ^ (step > 0)) && (n % step)) : 0u;

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.

2 participants