Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
3
 * It is copyright by its individual contributors, as recorded in the
4
 * project's Git history.  See COPYING.txt at the top level for license
5
 * terms and a link to the Git history.
6
 */
7
#pragma once
8
#include <array>
9
#include <stdexcept>
10
#include <iterator>
11
#include <cstdio>
12
#include <string>
13
#include <type_traits>
14
#include "fwd-partial_range.h"
15
#include <memory>
16
#include "dxxsconf.h"
17
 
18
/* If no value was specified for DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE,
19
 * then define it to true for NDEBUG builds and false for debug builds.
20
 *
21
 * When DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE is true, the partial_range
22
 * exception is a global scope structure.
23
 *
24
 * When DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE is false, the
25
 * partial_range exception is nested in partial_range_t<I>, so that the
26
 * exception type encodes the range type.
27
 *
28
 * Encoding the range type in the exception produces a more descriptive
29
 * message when the program aborts for an uncaught exception, but
30
 * produces larger debug information and many nearly-redundant
31
 * instantiations of the report function.  Using the global scope
32
 * structure avoids those redundant instances, but makes the exception
33
 * description less descriptive.
34
 *
35
 * Choose false if you expect to debug partial_range exceptions or
36
 * report them to someone who will debug them for you.  Choose true if
37
 * you want a smaller program.
38
 */
39
#ifndef DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE
40
#ifdef NDEBUG
41
#define DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE   1
42
#else
43
#define DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE   0
44
#endif
45
#endif
46
 
47
namespace partial_range_detail
48
{
49
 
50
namespace {
51
 
52
#define REPORT_FORMAT_STRING     "%s:%u: %s %lu past %p end %lu \"%s\""
53
/* Round reporting into large buckets.  Code size is more
54
 * important than stack space on a cold path.
55
 */
56
template <std::size_t NF, std::size_t NE>
57
constexpr std::size_t required_buffer_size = ((sizeof(REPORT_FORMAT_STRING) + sizeof("65535") + (sizeof("18446744073709551615") * 2) + sizeof("0x0000000000000000") + (NF + NE + sizeof("begin"))) | 0xff) + 1;
58
 
59
template <std::size_t N>
60
__attribute_cold
61
void prepare_error_string(std::array<char, N> &buf, unsigned long d, const char *estr, const char *file, unsigned line, const char *desc, unsigned long expr, const void *t)
62
{
63
        std::snprintf(buf.data(), buf.size(), REPORT_FORMAT_STRING, file, line, desc, expr, t, d, estr);
64
}
65
#undef REPORT_FORMAT_STRING
66
 
67
/*
68
 * These are placed out of line from the class and used as an
69
 * indirection, so that the compiler can pick std::begin/std::end or ADL
70
 * appropriate alternatives, if they exist.
71
 */
72
template <typename T>
73
inline auto adl_begin(T &t)
74
{
75
        using std::begin;
76
        return begin(t);
77
}
78
 
79
template <typename T>
80
inline auto adl_end(T &t)
81
{
82
        using std::end;
83
        return end(t);
84
}
85
 
86
}
87
 
88
}
89
 
90
#if DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE
91
struct partial_range_error;
92
#endif
93
 
94
template <typename I>
95
class partial_range_t
96
{
97
public:
98
        using range_owns_iterated_storage = std::false_type;
99
        typedef I iterator;
100
        /* When using the unminimized type, forward declare a structure.
101
         *
102
         * When using the minimized type, add a typedef here so that later
103
         * code can unconditionally use the qualified type name.  Using a
104
         * typedef here instead of a preprocessor macro at the usage sites
105
         * causes the debug information to be very slightly bigger, but has
106
         * no effect on the size of the generated code and is easier to
107
         * maintain.
108
         */
109
#if DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE
110
        using partial_range_error =
111
#endif
112
        struct partial_range_error;
113
        iterator m_begin, m_end;
114
        partial_range_t(iterator b, iterator e) :
115
                m_begin(b), m_end(e)
116
        {
117
        }
118
        partial_range_t(const partial_range_t &) = default;
119
        partial_range_t(partial_range_t &&) = default;
120
        partial_range_t &operator=(const partial_range_t &) = default;
121
        template <typename T>
122
                partial_range_t(T &t) :
123
                        m_begin(partial_range_detail::adl_begin(t)), m_end(partial_range_detail::adl_end(t))
124
        {
125
        }
126
        template <typename T>
127
                partial_range_t(partial_range_t<T> &&t) :
128
                        m_begin(t.begin()), m_end(t.end())
129
        {
130
        }
131
        __attribute_warn_unused_result
132
        iterator begin() const { return m_begin; }
133
        __attribute_warn_unused_result
134
        iterator end() const { return m_end; }
135
        bool empty() const __attribute_warn_unused_result
136
        {
137
                return m_begin == m_end;
138
        }
139
        __attribute_warn_unused_result
140
        std::size_t size() const { return std::distance(m_begin, m_end); }
141
        std::reverse_iterator<iterator> rbegin() const __attribute_warn_unused_result { return std::reverse_iterator<iterator>{m_end}; }
142
        std::reverse_iterator<iterator> rend() const __attribute_warn_unused_result { return std::reverse_iterator<iterator>{m_begin}; }
143
        partial_range_t<std::reverse_iterator<iterator>> reversed() const __attribute_warn_unused_result
144
        {
145
                return {rbegin(), rend()};
146
        }
147
};
148
 
149
#if DXX_PARTIAL_RANGE_MINIMIZE_ERROR_TYPE
150
struct partial_range_error
151
#else
152
template <typename I>
153
struct partial_range_t<I>::partial_range_error
154
#endif
155
        final : std::out_of_range
156
{
157
        DXX_INHERIT_CONSTRUCTORS(partial_range_error, out_of_range);
158
        template <std::size_t N>
159
                __attribute_cold
160
                __attribute_noreturn
161
        static void report(const char *file, unsigned line, const char *estr, const char *desc, unsigned long expr, const void *t, unsigned long d)
162
        {
163
                std::array<char, N> buf;
164
                partial_range_detail::prepare_error_string(buf, d, estr, file, line, desc, expr, t);
165
                throw partial_range_error(buf.data());
166
        }
167
};
168
 
169
namespace partial_range_detail
170
{
171
 
172
namespace {
173
 
174
template <typename I, std::size_t required_buffer_size>
175
inline void check_range_bounds(const char *file, unsigned line, const char *estr, const void *t, const std::size_t o, const std::size_t l, const std::size_t d)
176
{
177
#ifdef DXX_CONSTANT_TRUE
178
        /*
179
         * If EXPR and d are compile-time constant, and the (EXPR > d)
180
         * branch is optimized out, then the expansion of
181
         * PARTIAL_RANGE_COMPILE_CHECK_BOUND is optimized out, preventing
182
         * the compile error.
183
         *
184
         * If EXPR and d are compile-time constant, and the (EXPR > d)
185
         * branch is not optimized out, then this function is guaranteed to
186
         * throw if it is ever called.  In that case, the compile fails,
187
         * since the program is guaranteed not to work as the programmer
188
         * intends.
189
         *
190
         * If they are not compile-time constant, but the compiler can
191
         * optimize based on constants, then it will optimize out the
192
         * expansion of PARTIAL_RANGE_COMPILE_CHECK_BOUND, preventing the
193
         * compile error.  The function might throw on invalid inputs,
194
         * including constant inputs that the compiler failed to recognize
195
         * as compile-time constant.
196
         *
197
         * If the compiler cannot optimize based on the result of
198
         * __builtin_constant_p (such as at -O0), then configure tests do
199
         * not define DXX_CONSTANT_TRUE and the macro expands to nothing.
200
         */
201
#define PARTIAL_RANGE_COMPILE_CHECK_BOUND(EXPR,S)       \
202
        (DXX_CONSTANT_TRUE(EXPR > d) && (DXX_ALWAYS_ERROR_FUNCTION(partial_range_will_always_throw_##S, #S " will always throw"), 0))
203
#else
204
#define PARTIAL_RANGE_COMPILE_CHECK_BOUND(EXPR,S)       static_cast<void>(0)
205
#endif
206
#define PARTIAL_RANGE_CHECK_BOUND(EXPR,S)       \
207
        PARTIAL_RANGE_COMPILE_CHECK_BOUND(EXPR,S),      \
208
        ((EXPR > d) && (I::template report<required_buffer_size>(file, line, estr, #S, EXPR, t, d), 0))
209
        PARTIAL_RANGE_CHECK_BOUND(o, begin);
210
        PARTIAL_RANGE_CHECK_BOUND(l, end);
211
#undef PARTIAL_RANGE_CHECK_BOUND
212
#undef PARTIAL_RANGE_COMPILE_CHECK_BOUND
213
}
214
 
215
template <typename I, std::size_t N, typename T>
216
inline void check_partial_range(const char *file, unsigned line, const char *estr, const T &t, const std::size_t o, const std::size_t l)
217
{
218
        check_range_bounds<I, N>(file, line, estr, std::addressof(t), o, l, std::size(t));
219
}
220
 
221
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
222
template <typename I, std::size_t required_buffer_size, typename P>
223
inline void check_range_object_size(const char *file, unsigned line, const char *estr, P &ref, const std::size_t o, const std::size_t l)
224
{
225
        const auto ptr = std::addressof(ref);
226
        const std::size_t bos = __builtin_object_size(ptr, 1);
227
        if (bos != static_cast<std::size_t>(-1))
228
                check_range_bounds<I, required_buffer_size>(file, line, estr, ptr, o, l, bos / sizeof(P));
229
}
230
 
231
/* When P refers to a temporary, this overload is picked.  Temporaries
232
 * have no useful address, so they cannot be checked.  A temporary would
233
 * be present if iterator.operator*() returns a proxy object, rather
234
 * than a reference to an element in the container.
235
 */
236
template <typename I, std::size_t required_buffer_size, typename P>
237
void check_range_object_size(const char *, unsigned, const char *, const P &&, std::size_t, std::size_t) {}
238
#endif
239
 
240
}
241
 
242
}
243
 
244
namespace {
245
 
246
template <typename I, std::size_t required_buffer_size>
247
__attribute_warn_unused_result
248
inline partial_range_t<I> (unchecked_partial_range)(const char *const file, const unsigned line, const char *const estr, I range_begin, const std::size_t o, const std::size_t l)
249
{
250
#ifdef DXX_CONSTANT_TRUE
251
        /* Compile-time only check.  Runtime handles (o > l) correctly, and
252
         * it can happen in a correct program.  If it is guaranteed to
253
         * happen, then the range is always empty, which likely indicates a
254
         * bug.
255
         */
256
        if (DXX_CONSTANT_TRUE(!(o < l)))
257
                DXX_ALWAYS_ERROR_FUNCTION(partial_range_is_always_empty, "offset never less than length");
258
#endif
259
#ifdef DXX_HAVE_BUILTIN_OBJECT_SIZE
260
        /* Avoid iterator dereference if range is empty */
261
        if (l)
262
        {
263
                partial_range_detail::check_range_object_size<typename partial_range_t<I>::partial_range_error, required_buffer_size>(file, line, estr, *range_begin, o, l);
264
        }
265
#else
266
        (void)file;
267
        (void)line;
268
        (void)estr;
269
#endif
270
        auto range_end = range_begin;
271
        /* Use <= so that (o == 0) makes the expression always-true, so the
272
         * compiler will optimize out the test.
273
         */
274
        if (o <= l)
275
        {
276
                using std::advance;
277
                advance(range_begin, o);
278
                advance(range_end, l);
279
        }
280
        return {range_begin, range_end};
281
}
282
 
283
template <typename I, typename UO, typename UL, std::size_t NF, std::size_t NE>
284
__attribute_warn_unused_result
285
inline partial_range_t<I> (unchecked_partial_range)(const char (&file)[NF], unsigned line, const char (&estr)[NE], I range_begin, const UO &o, const UL &l)
286
{
287
        /* Require unsigned length */
288
        //static_assert(std::is_unsigned<UO>::value, "offset to partial_range must be unsigned");
289
        static_assert(std::is_unsigned<UL>::value, "length to partial_range must be unsigned");
290
        return unchecked_partial_range<I, partial_range_detail::required_buffer_size<NF, NE>>(
291
                file, line, estr, range_begin, o, l
292
        );
293
}
294
 
295
template <typename I, typename UL, std::size_t NF, std::size_t NE>
296
__attribute_warn_unused_result
297
inline partial_range_t<I> (unchecked_partial_range)(const char (&file)[NF], unsigned line, const char (&estr)[NE], I range_begin, const UL &l)
298
{
299
        return unchecked_partial_range<I, UL, UL>(file, line, estr, range_begin, 0, l);
300
}
301
 
302
template <typename T, typename UO, typename UL, std::size_t NF, std::size_t NE, typename I = decltype(std::begin(std::declval<T &>()))>
303
__attribute_warn_unused_result
304
inline partial_range_t<I> (partial_range)(const char (&file)[NF], unsigned line, const char (&estr)[NE], T &t, const UO &o, const UL &l)
305
{
306
        partial_range_detail::check_partial_range<typename partial_range_t<I>::partial_range_error, partial_range_detail::required_buffer_size<NF, NE>, T>(file, line, estr, t, o, l);
307
        return unchecked_partial_range<I, UO, UL>(file, line, estr, std::begin(t), o, l);
308
}
309
 
310
template <typename T, typename UL, std::size_t NF, std::size_t NE>
311
__attribute_warn_unused_result
312
inline auto (partial_range)(const char (&file)[NF], const unsigned line, const char (&estr)[NE], T &t, const UL &l)
313
{
314
        return partial_range<T, UL, UL>(file, line, estr, t, 0, l);
315
}
316
 
317
template <typename T, typename UO, typename UL, std::size_t NF, std::size_t NE>
318
__attribute_warn_unused_result
319
inline auto (partial_const_range)(const char (&file)[NF], unsigned line, const char (&estr)[NE], const T &t, const UO &o, const UL &l)
320
{
321
        return partial_range<const T, UO, UL>(file, line, estr, t, o, l);
322
}
323
 
324
template <typename T, typename UL, std::size_t NF, std::size_t NE>
325
__attribute_warn_unused_result
326
inline auto (partial_const_range)(const char (&file)[NF], unsigned line, const char (&estr)[NE], const T &t, const UL &l)
327
{
328
        return partial_range<const T, UL>(file, line, estr, t, l);
329
}
330
 
331
template <typename T, typename I = decltype(std::begin(std::declval<T &>()))>
332
__attribute_warn_unused_result
333
inline partial_range_t<I> (make_range)(T &t)
334
{
335
        return t;
336
}
337
 
338
}
339
 
340
/* Explicitly block use on rvalue t because returned partial_range_t<I>
341
 * will outlive the rvalue.
342
 */
343
template <typename T, typename UO, typename UL, std::size_t NF, std::size_t NE, typename I = decltype(begin(std::declval<T &&>()))>
344
partial_range_t<I> (partial_const_range)(const char (&file)[NF], unsigned line, const char (&estr)[NE], const T &&t, const UO &o, const UL &l) = delete;
345
 
346
template <typename T, typename UL, std::size_t NF, std::size_t NE, typename I = decltype(begin(std::declval<T &&>()))>
347
partial_range_t<I> (partial_const_range)(const char (&file)[NF], unsigned line, const char (&estr)[NE], const T &&t, const UL &l) = delete;
348
 
349
#define unchecked_partial_range(T,...)  unchecked_partial_range(__FILE__, __LINE__, #T, T, ##__VA_ARGS__)
350
#define partial_range(T,...)    partial_range(__FILE__, __LINE__, #T, T, ##__VA_ARGS__)
351
#define partial_const_range(T,...)      partial_const_range(__FILE__, __LINE__, #T, T, ##__VA_ARGS__)