Skip to content

Commit c1b51ad

Browse files
authored
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.
1 parent d60f96d commit c1b51ad

File tree

4 files changed

+64
-25
lines changed

4 files changed

+64
-25
lines changed

include/boost/histogram/axis/integer.hpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,14 @@ class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
153153

154154
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
155155
static constexpr bool inclusive() noexcept {
156-
return (options() & option::underflow || options() & option::overflow) ||
157-
(std::is_integral<value_type>::value &&
158-
(options() & (option::growth | option::circular)));
156+
// If axis has underflow and overflow, it is inclusive.
157+
// If axis is growing or circular:
158+
// - it is inclusive if value_type is int.
159+
// - it is not inclusive if value_type is float, because of nan and inf.
160+
constexpr bool full_flow =
161+
options() & option::underflow && options() & option::overflow;
162+
return full_flow || (std::is_integral<value_type>::value &&
163+
(options() & (option::growth | option::circular)));
159164
}
160165

161166
template <class V, class M, class O>
@@ -205,12 +210,12 @@ class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
205210
#if __cpp_deduction_guides >= 201606
206211

207212
template <class T>
208-
integer(T, T)->integer<detail::convert_integer<T, index_type>, null_type>;
213+
integer(T, T) -> integer<detail::convert_integer<T, index_type>, null_type>;
209214

210215
template <class T, class M>
211216
integer(T, T, M)
212-
->integer<detail::convert_integer<T, index_type>,
213-
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
217+
-> integer<detail::convert_integer<T, index_type>,
218+
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
214219

215220
#endif
216221

include/boost/histogram/detail/linearize.hpp

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,34 @@ namespace boost {
1919
namespace histogram {
2020
namespace detail {
2121

22-
// initial offset to out must be set
23-
template <class Index, class Opts>
24-
std::size_t linearize(Opts, Index& out, const std::size_t stride,
22+
// initial offset to out must be set;
23+
// this faster code can be used if all axes are inclusive
24+
template <class Opts>
25+
std::size_t linearize(Opts, std::size_t& out, const std::size_t stride,
2526
const axis::index_type size, const axis::index_type idx) {
2627
constexpr bool u = Opts::test(axis::option::underflow);
2728
constexpr bool o = Opts::test(axis::option::overflow);
29+
assert(idx >= (u ? -1 : 0));
30+
assert(idx < (o ? size + 1 : size));
31+
assert(idx >= 0 || static_cast<std::size_t>(-idx * stride) <= out);
32+
out += idx * stride;
33+
return size + u + o;
34+
}
2835

29-
// must be non-const to avoid if constexpr warning from msvc
30-
bool fast_track = std::is_same<Index, std::size_t>::value || (u && o);
31-
if (fast_track) {
32-
assert(idx >= (u ? -1 : 0));
33-
assert(idx < (o ? size + 1 : size));
34-
assert(idx >= 0 || static_cast<std::size_t>(-idx * stride) <= out);
36+
// initial offset to out must be set
37+
// this slower code must be used if not all axes are inclusive
38+
template <class Opts>
39+
std::size_t linearize(Opts, optional_index& out, const std::size_t stride,
40+
const axis::index_type size, const axis::index_type idx) {
41+
constexpr bool u = Opts::test(axis::option::underflow);
42+
constexpr bool o = Opts::test(axis::option::overflow);
43+
assert(idx >= -1);
44+
assert(idx < size + 1);
45+
const bool is_valid = (u || idx >= 0) && (o || idx < size);
46+
if (is_valid)
3547
out += idx * stride;
36-
} else {
37-
assert(idx >= -1);
38-
assert(idx < size + 1);
39-
// must be non-const to avoid if constexpr warning from msvc
40-
bool is_valid = (u || idx >= 0) && (o || idx < size);
41-
if (is_valid)
42-
out += idx * stride;
43-
else
44-
out = invalid_index;
45-
}
48+
else
49+
out = invalid_index;
4650
return size + u + o;
4751
}
4852

test/axis_traits_test.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,18 +140,34 @@ int main() {
140140
(traits::is_inclusive<integer<int, boost::use_default, option::growth_t>>));
141141
BOOST_TEST_TRAIT_TRUE(
142142
(traits::is_inclusive<integer<int, boost::use_default, option::circular_t>>));
143+
BOOST_TEST_TRAIT_FALSE(
144+
(traits::is_inclusive<integer<int, boost::use_default, option::underflow_t>>));
145+
BOOST_TEST_TRAIT_FALSE(
146+
(traits::is_inclusive<integer<int, boost::use_default, option::overflow_t>>));
147+
BOOST_TEST_TRAIT_TRUE(
148+
(traits::is_inclusive<integer<int, boost::use_default,
149+
decltype(option::underflow | option::overflow)>>));
143150

144151
BOOST_TEST_TRAIT_TRUE((traits::is_inclusive<integer<double>>));
145152
BOOST_TEST_TRAIT_FALSE(
146153
(traits::is_inclusive<integer<double, boost::use_default, option::growth_t>>));
147154
BOOST_TEST_TRAIT_FALSE(
148155
(traits::is_inclusive<integer<double, boost::use_default, option::circular_t>>));
156+
BOOST_TEST_TRAIT_FALSE(
157+
(traits::is_inclusive<integer<double, boost::use_default, option::underflow_t>>));
158+
BOOST_TEST_TRAIT_FALSE(
159+
(traits::is_inclusive<integer<double, boost::use_default, option::overflow_t>>));
160+
BOOST_TEST_TRAIT_TRUE(
161+
(traits::is_inclusive<integer<double, boost::use_default,
162+
decltype(option::underflow | option::overflow)>>));
149163

150164
BOOST_TEST_TRAIT_TRUE((traits::is_inclusive<category<int>>));
151165
BOOST_TEST_TRAIT_TRUE(
152166
(traits::is_inclusive<category<int, boost::use_default, option::growth_t>>));
153167
BOOST_TEST_TRAIT_FALSE(
154168
(traits::is_inclusive<category<int, boost::use_default, option::none_t>>));
169+
BOOST_TEST_TRAIT_TRUE(
170+
(traits::is_inclusive<category<int, boost::use_default, option::overflow_t>>));
155171
}
156172

157173
// is_ordered, ordered()

test/histogram_test.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,20 @@ void run_tests() {
216216
BOOST_TEST_EQ(h.at(1), 0);
217217
}
218218

219+
// 1D without underflow
220+
{
221+
using opt = axis::option::overflow_t;
222+
auto h = make(Tag(), axis::integer<int, axis::null_type, opt>{1, 3});
223+
224+
int x[] = {-1, 0, 1, 2, 3, 4, 5};
225+
for (auto&& xi : x) h(xi);
226+
227+
BOOST_TEST_EQ(algorithm::sum(h), 5);
228+
BOOST_TEST_EQ(h.at(0), 1);
229+
BOOST_TEST_EQ(h.at(1), 1);
230+
BOOST_TEST_EQ(h.at(2), 3);
231+
}
232+
219233
// 1D category axis
220234
{
221235
auto h = make(Tag(), axis::category<>({1, 2}));

0 commit comments

Comments
 (0)