diff --git a/common/include/partial_range.h b/common/include/partial_range.h index 35d84456c..23bee8d9f 100644 --- a/common/include/partial_range.h +++ b/common/include/partial_range.h @@ -23,7 +23,9 @@ struct partial_range_t m_begin(b), m_end(e) { } + __attribute_warn_unused_result iterator begin() const { return m_begin; } + __attribute_warn_unused_result iterator end() const { return m_end; } bool empty() const __attribute_warn_unused_result { @@ -74,6 +76,9 @@ struct base_partial_range_error_t : std::out_of_range __attribute_warn_unused_result static inline std::string prepare(const char (&file)[NF], unsigned line, const char (&estr)[NE], const char (&desc)[ND], unsigned long expr, const void *t, unsigned long d) { + /* Round reporting into large buckets. Code size is more + * important than stack space. + */ return prepare<((NF + NE + ND) | 0xff) + 1>(file + 0, line, estr, desc, expr, t, d); } }; @@ -90,11 +95,36 @@ struct partial_range_error_t : base_partial_range_error_t } }; -template -static void check_partial_range(const char (&file)[NF], unsigned line, const char (&estr)[NE], T &t, I range_begin, I container_end, const std::size_t o, const std::size_t l) +namespace partial_range_detail +{ + +template +static inline void check_range_bounds(const char (&file)[NF], unsigned line, const char (&estr)[NE], T &t, const std::size_t o, const std::size_t l, const std::size_t d) { - using std::distance; #ifdef DXX_HAVE_BUILTIN_CONSTANT_P + /* + * If EXPR and d are compile-time constant, and the (EXPR > d) + * branch is optimized out, then the expansion of + * PARTIAL_RANGE_COMPILE_CHECK_BOUND is optimized out, preventing + * the compile error. + * + * If EXPR and d are compile-time constant, and the (EXPR > d) + * branch is not optimized out, then this function is guaranteed to + * throw if it is ever called. In that case, the compile fails, + * since the program is guaranteed not to work as the programmer + * intends. + * + * If they are not compile-time constant, but the compiler can + * optimize based on constants, then it will optimize out the + * expansion of PARTIAL_RANGE_COMPILE_CHECK_BOUND, preventing the + * compile error. The function might throw on invalid inputs, + * including constant inputs that the compiler failed to recognize + * as compile-time constant. + * + * If the compiler cannot optimize based on the result of + * __builtin_constant_p (such as at -O0), then configure tests set + * !DXX_HAVE_BUILTIN_CONSTANT_P and the macro expands to nothing. + */ #define PARTIAL_RANGE_COMPILE_CHECK_BOUND(EXPR,S) \ (__builtin_constant_p(EXPR) && __builtin_constant_p(d) && (DXX_ALWAYS_ERROR_FUNCTION(partial_range_will_always_throw, S " will always throw"), 0)) #else @@ -104,22 +134,44 @@ static void check_partial_range(const char (&file)[NF], unsigned line, const cha if (EXPR > d) \ ((void)(PARTIAL_RANGE_COMPILE_CHECK_BOUND(EXPR,S))), \ partial_range_error_t::report(file, line, estr, S, EXPR, t, d) - size_t d = distance(range_begin, container_end); PARTIAL_RANGE_CHECK_BOUND(o, "begin"); PARTIAL_RANGE_CHECK_BOUND(l, "end"); #undef PARTIAL_RANGE_CHECK_BOUND #undef PARTIAL_RANGE_COMPILE_CHECK_BOUND } +/* C arrays lack a size method, but have a constant size */ +template +static inline void check_partial_range(const char (&file)[NF], unsigned line, const char (&estr)[NE], T (&t)[d], const std::size_t o, const std::size_t l) +{ + check_range_bounds(file, line, estr, t, o, l, d); +} + +template +static inline void check_partial_range(const char (&file)[NF], unsigned line, const char (&estr)[NE], T &t, const std::size_t o, const std::size_t l) +{ + check_range_bounds(file, line, estr, t, o, l, t.size()); +} + +} + template __attribute_warn_unused_result static inline partial_range_t unchecked_partial_range(I range_begin, const std::size_t o, const std::size_t l, tt::true_type) { #ifdef DXX_HAVE_BUILTIN_CONSTANT_P + /* Compile-time only check. Runtime handles (o > l) correctly, and + * it can happen in a correct program. If it is guaranteed to + * happen, then the range is always empty, which likely indicates a + * bug. + */ if (__builtin_constant_p(!(o < l)) && !(o < l)) DXX_ALWAYS_ERROR_FUNCTION(partial_range_is_always_empty, "offset never less than length"); #endif auto range_end = range_begin; + /* Use <= so that (o == 0) makes the expression always-true, so the + * compiler will optimize out the test. + */ if (o <= l) { using std::advance; @@ -152,8 +204,8 @@ template partial_range(const char (&file)[NF], unsigned line, const char (&estr)[NE], T &t, const UO &o, const UL &l) { + partial_range_detail::check_partial_range(file, line, estr, t, o, l); auto range_begin = begin(t); - check_partial_range(file, line, estr, t, range_begin, end(t), o, l); return unchecked_partial_range(range_begin, o, l); }