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,9 +153,14 @@ public:
/// Whether the axis is inclusive (see axis::traits::is_inclusive). /// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept { static constexpr bool inclusive() noexcept {
return (options() & option::underflow || options() & option::overflow) || // If axis has underflow and overflow, it is inclusive.
(std::is_integral<value_type>::value && // If axis is growing or circular:
(options() & (option::growth | option::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)));
} }
template <class V, class M, class O> template <class V, class M, class O>
@ -205,12 +210,12 @@ private:
#if __cpp_deduction_guides >= 201606 #if __cpp_deduction_guides >= 201606
template <class T> template <class T>
integer(T, T)->integer<detail::convert_integer<T, index_type>, null_type>; integer(T, T) -> integer<detail::convert_integer<T, index_type>, null_type>;
template <class T, class M> template <class T, class M>
integer(T, T, M) integer(T, T, M)
->integer<detail::convert_integer<T, index_type>, -> integer<detail::convert_integer<T, index_type>,
detail::replace_type<std::decay_t<M>, const char*, std::string>>; detail::replace_type<std::decay_t<M>, const char*, std::string>>;
#endif #endif

View File

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

View File

@ -140,18 +140,34 @@ int main() {
(traits::is_inclusive<integer<int, boost::use_default, option::growth_t>>)); (traits::is_inclusive<integer<int, boost::use_default, option::growth_t>>));
BOOST_TEST_TRAIT_TRUE( BOOST_TEST_TRAIT_TRUE(
(traits::is_inclusive<integer<int, boost::use_default, option::circular_t>>)); (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_TRUE((traits::is_inclusive<integer<double>>));
BOOST_TEST_TRAIT_FALSE( BOOST_TEST_TRAIT_FALSE(
(traits::is_inclusive<integer<double, boost::use_default, option::growth_t>>)); (traits::is_inclusive<integer<double, boost::use_default, option::growth_t>>));
BOOST_TEST_TRAIT_FALSE( BOOST_TEST_TRAIT_FALSE(
(traits::is_inclusive<integer<double, boost::use_default, option::circular_t>>)); (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_TEST_TRAIT_TRUE( BOOST_TEST_TRAIT_TRUE(
(traits::is_inclusive<category<int, boost::use_default, option::growth_t>>)); (traits::is_inclusive<category<int, boost::use_default, option::growth_t>>));
BOOST_TEST_TRAIT_FALSE( BOOST_TEST_TRAIT_FALSE(
(traits::is_inclusive<category<int, boost::use_default, option::none_t>>)); (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() // is_ordered, ordered()

View File

@ -216,6 +216,20 @@ void run_tests() {
BOOST_TEST_EQ(h.at(1), 0); 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 // 1D category axis
{ {
auto h = make(Tag(), axis::category<>({1, 2})); auto h = make(Tag(), axis::category<>({1, 2}));