From 4836e24cfef1262097c15645fa735acdb259043f Mon Sep 17 00:00:00 2001 From: Loic Gouarin Date: Thu, 29 Oct 2026 18:54:45 +0100 Subject: [PATCH] Check the dimension of runtime shapes given to xtensor The shape of an xtensor is a std::array. 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::from_shape(std::vector{2, 3}). interp() builds its xtensor 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. --- include/xtensor/containers/xcontainer.hpp | 8 ++++++++ include/xtensor/containers/xtensor.hpp | 2 +- include/xtensor/core/xmath.hpp | 24 ++++++++++++++++++++++- test/test_xmath.cpp | 21 ++++++++++++++++++++ test/test_xtensor.cpp | 17 ++++++++++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/include/xtensor/containers/xcontainer.hpp b/include/xtensor/containers/xcontainer.hpp index 7f17ffe01..df4162e0b 100644 --- a/include/xtensor/containers/xcontainer.hpp +++ b/include/xtensor/containers/xcontainer.hpp @@ -1091,6 +1091,10 @@ namespace xt template inline auto& xstrided_container::reshape(S&& shape, layout_type layout) & { + XTENSOR_PRECONDITION( + detail::check_resize_dimension(m_shape, shape), + "cannot change the number of dimensions of xtensor" + ); reshape_impl( std::forward(shape), xtl::is_signed::value_type>>(), @@ -1103,6 +1107,10 @@ namespace xt template inline auto& xstrided_container::reshape(std::initializer_list shape, layout_type layout) & { + XTENSOR_PRECONDITION( + detail::check_resize_dimension(m_shape, shape), + "cannot change the number of dimensions of xtensor" + ); using sh_type = rebind_container_t; sh_type sh = xtl::make_sequence(shape.size()); std::copy(shape.begin(), shape.end(), sh.begin()); diff --git a/include/xtensor/containers/xtensor.hpp b/include/xtensor/containers/xtensor.hpp index db8d92385..8531b0972 100644 --- a/include/xtensor/containers/xtensor.hpp +++ b/include/xtensor/containers/xtensor.hpp @@ -543,7 +543,7 @@ namespace xt template inline xtensor_container xtensor_container::from_shape(S&& s) { - XTENSOR_ASSERT_MSG(s.size() == N, "Cannot change dimension of xtensor."); + XTENSOR_PRECONDITION(s.size() == N, "cannot change the number of dimensions of xtensor"); shape_type shape = xtl::forward_sequence(s); return self_type(shape); } diff --git a/include/xtensor/core/xmath.hpp b/include/xtensor/core/xmath.hpp index 6b86c4b74..4c6cfe118 100644 --- a/include/xtensor/core/xmath.hpp +++ b/include/xtensor/core/xmath.hpp @@ -2966,6 +2966,27 @@ namespace xt return eval(sum(trap, {saxis})); } + namespace detail + { + // The result is a one-dimensional xtensor built from the shape of x, and + // xp and fp are read at the indices found in xp. + template + inline void check_interp_arguments(const E1& x, const E2& xp, const E3& fp) + { + if (x.dimension() != 1 || xp.dimension() != 1 || fp.dimension() != 1) + { + XTENSOR_THROW(std::runtime_error, "interp: x, xp and fp must be one-dimensional."); + } + if (xp.size() == 0 || fp.size() != xp.size()) + { + XTENSOR_THROW( + std::runtime_error, + "interp: xp must not be empty and fp must have the length of xp." + ); + } + } + } + /** * @ingroup basic_functions * @brief Returns the one-dimensional piecewise linear interpolant to a function with given discrete data @@ -2985,7 +3006,7 @@ namespace xt using value_type = typename E3::value_type; // basic checks - XTENSOR_ASSERT(xp.dimension() == 1); + detail::check_interp_arguments(x, xp, fp); XTENSOR_ASSERT(std::is_sorted(x.cbegin(), x.cend())); XTENSOR_ASSERT(std::is_sorted(xp.cbegin(), xp.cend())); @@ -3146,6 +3167,7 @@ namespace xt template inline auto interp(const E1& x, const E2& xp, const E3& fp) { + detail::check_interp_arguments(x, xp, fp); return interp(x, xp, fp, fp[0], fp[fp.size() - 1]); } diff --git a/test/test_xmath.cpp b/test/test_xmath.cpp index 57771859b..d51199ed6 100644 --- a/test/test_xmath.cpp +++ b/test/test_xmath.cpp @@ -903,6 +903,27 @@ namespace xt } } + TEST(xmath, interp_invalid_arguments) + { + xt::xarray xp = {0.0, 1.0, 3.0}; + xt::xarray fp = {0.0, 1.0, 3.0}; + xt::xarray x = {0.5, 2.0}; + xt::xarray x2 = {{0.5, 1.5}, {2.0, 2.5}}; + xt::xarray empty = xt::xarray::from_shape({0}); + xt::xarray fp_short = {0.0, 1.0}; + + XT_EXPECT_THROW(xt::interp(x2, xp, fp), std::runtime_error); + XT_EXPECT_THROW(xt::interp(x, x2, fp), std::runtime_error); + XT_EXPECT_THROW(xt::interp(x, xp, x2), std::runtime_error); + XT_EXPECT_THROW(xt::interp(x, empty, empty), std::runtime_error); + XT_EXPECT_THROW(xt::interp(x, xp, fp_short), std::runtime_error); + XT_EXPECT_THROW(xt::interp(x, xp, fp_short, 0.0, 1.0), std::runtime_error); + + auto f = xt::interp(x, xp, fp); + EXPECT_EQ(f(0), 0.5); + EXPECT_EQ(f(1), 2.0); + } + TEST(xmath, cov) { xt::xarray x = {0.0, 1.0, 2.0}; diff --git a/test/test_xtensor.cpp b/test/test_xtensor.cpp index f7f81c75d..e20ef2430 100644 --- a/test/test_xtensor.cpp +++ b/test/test_xtensor.cpp @@ -233,6 +233,23 @@ namespace xt test_throwing_reshape(b); } + TEST(xtensor, change_dimension) + { + // The shape of an xtensor holds exactly N values. + xtensor a = xt::arange(6); + XT_EXPECT_THROW(a.reshape({2, 3}), std::runtime_error); + XT_EXPECT_THROW(a.reshape(std::vector{2, 3}), std::runtime_error); + XT_EXPECT_THROW(a.reshape(std::vector{2, -1}), std::runtime_error); + XT_EXPECT_THROW(a.resize(std::vector{2, 3}), std::runtime_error); + EXPECT_EQ(a.shape()[0], 6u); + + using shape = std::vector; + XT_EXPECT_THROW((xtensor::from_shape(shape{2, 3})), std::runtime_error); + XT_EXPECT_THROW((xtensor::from_shape(shape{6})), std::runtime_error); + XT_EXPECT_THROW((xtensor::from_shape(shape{})), std::runtime_error); + EXPECT_EQ((xtensor::from_shape(shape{2, 3}).size()), 6u); + } + TEST(xtensor, transpose) { xtensor_dynamic a;