Latest Results
Fix xview::element when a newaxis precedes an integral slice
make_index maps each dimension i of the underlying expression to its
slice k, then to the position of that slice in the view index:
k minus the number of integral slices before it. It counted the
integral slices before i instead of before k. The two differ when a
newaxis comes before an integral slice, so the wrong index was read:
xt::xarray<int> a = {{0, 1, 2}, {3, 4, 5}};
auto v = xt::view(a, xt::newaxis(), 1, xt::all());
v[{0, 2}]; // returned 3 instead of 5
operator() already used the slice position. element(), and the
operator[] and at-style accesses built on it, now do too.gouarin:fix-xview-element-newaxis Validate the arguments of pad and tile
pad and tile take one value, or one pair of values, per axis of the
expression and index their argument by axis. The sizes were only
checked by XTENSOR_ASSERT, which is off in release builds, and the
limits of each pad mode were not checked at all in release:
- pad with fewer pairs than axes, or pairs with fewer than two values,
read past the end of pad_width (heap-buffer-overflow);
- pad with more pairs than axes ignored the extra ones;
- wrap, periodic and symmetric with a width larger than the axis, or
reflect with a width of at least its size, and edge on an empty
axis, returned uninitialized values;
- tile with more repetitions than axes wrote past the std::array shape
of an xtensor (stack-buffer-overflow), and with fewer it built a
garbage shape;
- tile(e, n) on a 0-D expression wrote to an empty std::vector;
- negative widths or repetitions wrapped around to huge sizes.
These cases now throw std::runtime_error before the result is
allocated. The loops of tile use the dimension of the expression for
every buffer they index, which also removes the GCC 13 -O3
-Wstringop-overflow warning on its slice vector.gouarin:fix-pad-tile-validation Simplify svector::grow
Both branches of grow() allocated the new buffer and copied the
elements into it, and only the heap branch released the old buffer.
grow() now does the allocation and the copy once, then releases the old
buffer when it is on the heap. The copy uses current_size, the value
that sizes the new buffer and sets the new end. The XTENSOR_ASSERT on
the result of allocate(), which came after the buffer had been written
to, is removed: allocate() throws on failure.
The branch taken when the elements are inline was also where GCC 15 at
-O3 replaced m_begin with the inline array and, unable to bound the
size, warned that the copy reads past it (-Warray-bounds). The size is
at most N there, so the read was never out of bounds; the warning goes
away with the duplicated branch. Copying current_size elements, rather
than recomputing the count from m_end, also keeps GCC 13 from pairing a
one-element buffer with a non-empty copy when grow() is inlined into
push_back().gouarin:simplify-svector-grow Check the dimension of runtime shapes given to xtensor
The shape of an xtensor<T, N> is a std::array<std::size_t, N>. resize()
checks that a runtime shape has N values, but from_shape() only had an
XTENSOR_ASSERT, off in release builds, and reshape() had no check. A
shape of the wrong length was copied past the std::array, a
stack-buffer-overflow, as in
xtensor<double, 1>::from_shape(std::vector<std::size_t>{2, 3}).
interp() builds its xtensor<T, 1> result with from_shape(x.shape()), so
a 2-D x overflowed the same way, and a 0-D x gave a result with an
uninitialized shape. It also read a 2-D xp or a short fp out of bounds.
from_shape() and both reshape() overloads now use the same
XTENSOR_PRECONDITION as resize(). interp() checks up front that x, xp
and fp are 1-D, that xp is not empty and that fp has the length of xp.
This removes the GCC 13 and 15 -Warray-bounds and -Wmaybe-uninitialized
warnings on the copy into the shape in from_shape(), reached from
interp() in test_extended_xmath_interp.gouarin:fix-xtensor-rank-checks Fix xview_stepper on broadcast and reverse traversals
xview_stepper is the stepper of views that are not strided, such as
views with keep or drop slices. It had three bugs.
step() and reset() received a dimension of the iterated shape but used
it as a view dimension. When the view is broadcast to a higher rank,
the first m_offset dimensions of that shape are not view dimensions,
so the stepper moved along the wrong axes. reset() did not even skip
them. With a = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}},
zeros({2, 2, 2}) + view(a, keep(0, 2), keep(1, 3)) gave
{{1, 1}, {3, 3}} twice instead of {{1, 3}, {9, 11}}, and other shapes
read the strides of the underlying container out of bounds.
m_index_keeper is indexed like the slices in the constructor and in
to_end(), but step() and reset() indexed it by view dimension. The two
differ when an integral slice comes first, so view(a, 1, keep(0, 2, 3))
iterated backward from an uninitialized entry and crashed.
to_end() stepped back from the end of the underlying expression, which
leaves the stepper one past the last element along the last underlying
axis. That is not the innermost axis of the view when the last slice is
integral, when it is a newaxis or when the view has fewer slices than
dimensions, so rbegin() started at the wrong element or past the end.
The stepper now subtracts m_offset like xstepper, indexes
m_index_keeper by slice, delegates the dimensions without a slice to
the underlying stepper, and builds the end position from the begin
position, the way increment_stepper reaches it. to_begin() also moves
to the first element of the view, not of the underlying expression.
The reverse column major check in test_view_iter, disabled because it
failed for keep slices, now passes and is enabled again. The new
xview tests fail on master and pass with the fix, also under
AddressSanitizer and UBSan. This also removes the GCC -Warray-bounds
warnings reported in common_step_forward and common_reset.gouarin:fix-xview-stepper Latest Branches
0%
gouarin:fix-xview-element-newaxis 0%
gouarin:fix-pad-tile-validation 0%
gouarin:fix-xfixed-test-uninit © 2026 CodSpeed Technology