fix inclusive() in integer axis (#320)

It was possible to trigger this bug, which triggered an assertion in debug mode. In release mode, it would have probably caused a segfault.
This commit is contained in:
Hans Dembinski 2021-04-24 12:24:47 +02:00 committed by GitHub
parent d60f96ded6
commit c1b51ad640
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 25 deletions

View File

@ -153,8 +153,13 @@ public:
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept {
return (options() & option::underflow || options() & option::overflow) ||
(std::is_integral<value_type>::value &&
// If axis has underflow and overflow, it is inclusive.
// If axis is growing or circular:
// - it is inclusive if value_type is int.
// - it is not inclusive if value_type is float, because of nan and inf.
constexpr bool full_flow =
options() & option::underflow && options() & option::overflow;
return full_flow || (std::is_integral<value_type>::value &&
(options() & (option::growth | option::circular)));
}

View File

@ -19,30 +19,34 @@ namespace boost {
namespace histogram {
namespace detail {
// initial offset to out must be set
template <class Index, class Opts>
std::size_t linearize(Opts, Index& out, const std::size_t stride,
// initial offset to out must be set;
// this faster code can be used if all axes are inclusive
template <class Opts>
std::size_t linearize(Opts, std::size_t& out, const std::size_t stride,
const axis::index_type size, const axis::index_type idx) {
constexpr bool u = Opts::test(axis::option::underflow);
constexpr bool o = Opts::test(axis::option::overflow);
// must be non-const to avoid if constexpr warning from msvc
bool fast_track = std::is_same<Index, std::size_t>::value || (u && o);
if (fast_track) {
assert(idx >= (u ? -1 : 0));
assert(idx < (o ? size + 1 : size));
assert(idx >= 0 || static_cast<std::size_t>(-idx * stride) <= out);
out += idx * stride;
} else {
return size + u + o;
}
// initial offset to out must be set
// this slower code must be used if not all axes are inclusive
template <class Opts>
std::size_t linearize(Opts, optional_index& out, const std::size_t stride,
const axis::index_type size, const axis::index_type idx) {
constexpr bool u = Opts::test(axis::option::underflow);
constexpr bool o = Opts::test(axis::option::overflow);
assert(idx >= -1);
assert(idx < size + 1);
// must be non-const to avoid if constexpr warning from msvc
bool is_valid = (u || idx >= 0) && (o || idx < size);
const bool is_valid = (u || idx >= 0) && (o || idx < size);
if (is_valid)
out += idx * stride;
else
out = invalid_index;
}
return size + u + o;
}

View File

@ -140,18 +140,34 @@ int main() {
(traits::is_inclusive<integer<int, boost::use_default, option::growth_t>>));
BOOST_TEST_TRAIT_TRUE(
(traits::is_inclusive<integer<int, boost::use_default, option::circular_t>>));
BOOST_TEST_TRAIT_FALSE(
(traits::is_inclusive<integer<int, boost::use_default, option::underflow_t>>));
BOOST_TEST_TRAIT_FALSE(
(traits::is_inclusive<integer<int, boost::use_default, option::overflow_t>>));
BOOST_TEST_TRAIT_TRUE(
(traits::is_inclusive<integer<int, boost::use_default,
decltype(option::underflow | option::overflow)>>));
BOOST_TEST_TRAIT_TRUE((traits::is_inclusive<integer<double>>));
BOOST_TEST_TRAIT_FALSE(
(traits::is_inclusive<integer<double, boost::use_default, option::growth_t>>));
BOOST_TEST_TRAIT_FALSE(
(traits::is_inclusive<integer<double, boost::use_default, option::circular_t>>));
BOOST_TEST_TRAIT_FALSE(
(traits::is_inclusive<integer<double, boost::use_default, option::underflow_t>>));
BOOST_TEST_TRAIT_FALSE(
(traits::is_inclusive<integer<double, boost::use_default, option::overflow_t>>));
BOOST_TEST_TRAIT_TRUE(
(traits::is_inclusive<integer<double, boost::use_default,
decltype(option::underflow | option::overflow)>>));
BOOST_TEST_TRAIT_TRUE((traits::is_inclusive<category<int>>));
BOOST_TEST_TRAIT_TRUE(
(traits::is_inclusive<category<int, boost::use_default, option::growth_t>>));
BOOST_TEST_TRAIT_FALSE(
(traits::is_inclusive<category<int, boost::use_default, option::none_t>>));
BOOST_TEST_TRAIT_TRUE(
(traits::is_inclusive<category<int, boost::use_default, option::overflow_t>>));
}
// is_ordered, ordered()

View File

@ -216,6 +216,20 @@ void run_tests() {
BOOST_TEST_EQ(h.at(1), 0);
}
// 1D without underflow
{
using opt = axis::option::overflow_t;
auto h = make(Tag(), axis::integer<int, axis::null_type, opt>{1, 3});
int x[] = {-1, 0, 1, 2, 3, 4, 5};
for (auto&& xi : x) h(xi);
BOOST_TEST_EQ(algorithm::sum(h), 5);
BOOST_TEST_EQ(h.at(0), 1);
BOOST_TEST_EQ(h.at(1), 1);
BOOST_TEST_EQ(h.at(2), 3);
}
// 1D category axis
{
auto h = make(Tag(), axis::category<>({1, 2}));