• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Proposed SG14 status_code
2 (C) 2018-2020 Niall Douglas <http://www.nedproductions.biz/> (5 commits)
3 File Created: Feb 2018
4 
5 
6 Boost Software License - Version 1.0 - August 17th, 2003
7 
8 Permission is hereby granted, free of charge, to any person or organization
9 obtaining a copy of the software and accompanying documentation covered by
10 this license (the "Software") to use, reproduce, display, distribute,
11 execute, and transmit the Software, and to prepare derivative works of the
12 Software, and to permit third-parties to whom the Software is furnished to
13 do so, all subject to the following:
14 
15 The copyright notices in the Software and this entire statement, including
16 the above license grant, this restriction and the following disclaimer,
17 must be included in all copies of the Software, in whole or in part, and
18 all derivative works of the Software, unless such copies or derivative
19 works are solely in the form of machine-executable object code generated by
20 a source language processor.
21 
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
25 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
26 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
27 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 DEALINGS IN THE SOFTWARE.
29 */
30 
31 #ifndef BOOST_OUTCOME_SYSTEM_ERROR2_STATUS_CODE_DOMAIN_HPP
32 #define BOOST_OUTCOME_SYSTEM_ERROR2_STATUS_CODE_DOMAIN_HPP
33 
34 #include "config.hpp"
35 
36 #include <cstring>  // for strchr
37 
38 BOOST_OUTCOME_SYSTEM_ERROR2_NAMESPACE_BEGIN
39 
40 /*! The main workhorse of the system_error2 library, can be typed (`status_code<DomainType>`), erased-immutable (`status_code<void>`) or erased-mutable (`status_code<erased<T>>`).
41 
42 Be careful of placing these into containers! Equality and inequality operators are
43 *semantic* not exact. Therefore two distinct items will test true! To help prevent
44 surprise on this, `operator<` and `std::hash<>` are NOT implemented in order to
45 trap potential incorrectness. Define your own custom comparison functions for your
46 container which perform exact comparisons.
47 */
48 template <class DomainType> class status_code;
49 class _generic_code_domain;
50 //! The generic code is a status code with the generic code domain, which is that of `errc` (POSIX).
51 using generic_code = status_code<_generic_code_domain>;
52 
53 namespace detail
54 {
55   template <class StatusCode> class indirecting_domain;
56   template <class T> struct status_code_sizer
57   {
58     void *a;
59     T b;
60   };
61   template <class To, class From> struct type_erasure_is_safe
62   {
63     static constexpr bool value = traits::is_move_bitcopying<From>::value  //
64                                   && (sizeof(status_code_sizer<From>) <= sizeof(status_code_sizer<To>));
65   };
66   /* We are severely limited by needing to retain C++ 11 compatibility when doing
67   constexpr string parsing. MSVC lets you throw exceptions within a constexpr
68   evaluation context when exceptions are globally disabled, but won't let you
69   divide by zero, even if never evaluated, ever in constexpr. GCC and clang won't
70   let you throw exceptions, ever, if exceptions are globally disabled. So let's
71   use the trick of divide by zero in constexpr on GCC and clang if and only if
72   exceptions are globally disabled.
73   */
74 #ifdef __GNUC__
75 #pragma GCC diagnostic push
76 #pragma GCC diagnostic ignored "-Wdiv-by-zero"
77 #endif
78 #if defined(__cpp_exceptions) || (defined(_MSC_VER) && !defined(__clang__))
79 #define BOOST_OUTCOME_SYSTEM_ERROR2_FAIL_CONSTEXPR(msg) throw msg
80 #else
81 #define BOOST_OUTCOME_SYSTEM_ERROR2_FAIL_CONSTEXPR(msg) ((void) msg, 1 / 0)
82 #endif
parse_hex_byte(char c)83   constexpr inline unsigned long long parse_hex_byte(char c) { return ('0' <= c && c <= '9') ? (c - '0') : ('a' <= c && c <= 'f') ? (10 + c - 'a') : ('A' <= c && c <= 'F') ? (10 + c - 'A') : BOOST_OUTCOME_SYSTEM_ERROR2_FAIL_CONSTEXPR("Invalid character in UUID"); }
parse_uuid2(const char * s)84   constexpr inline unsigned long long parse_uuid2(const char *s)
85   {
86     return ((parse_hex_byte(s[0]) << 0) | (parse_hex_byte(s[1]) << 4) | (parse_hex_byte(s[2]) << 8) | (parse_hex_byte(s[3]) << 12) | (parse_hex_byte(s[4]) << 16) | (parse_hex_byte(s[5]) << 20) | (parse_hex_byte(s[6]) << 24) | (parse_hex_byte(s[7]) << 28) | (parse_hex_byte(s[9]) << 32) | (parse_hex_byte(s[10]) << 36) |
87             (parse_hex_byte(s[11]) << 40) | (parse_hex_byte(s[12]) << 44) | (parse_hex_byte(s[14]) << 48) | (parse_hex_byte(s[15]) << 52) | (parse_hex_byte(s[16]) << 56) | (parse_hex_byte(s[17]) << 60))  //
88            ^                                                                                                                                                                                                //
89            ((parse_hex_byte(s[19]) << 0) | (parse_hex_byte(s[20]) << 4) | (parse_hex_byte(s[21]) << 8) | (parse_hex_byte(s[22]) << 12) | (parse_hex_byte(s[24]) << 16) | (parse_hex_byte(s[25]) << 20) | (parse_hex_byte(s[26]) << 24) | (parse_hex_byte(s[27]) << 28) | (parse_hex_byte(s[28]) << 32) |
90             (parse_hex_byte(s[29]) << 36) | (parse_hex_byte(s[30]) << 40) | (parse_hex_byte(s[31]) << 44) | (parse_hex_byte(s[32]) << 48) | (parse_hex_byte(s[33]) << 52) | (parse_hex_byte(s[34]) << 56) | (parse_hex_byte(s[35]) << 60));
91   }
parse_uuid_from_array(const char (& uuid)[N])92   template <size_t N> constexpr inline unsigned long long parse_uuid_from_array(const char (&uuid)[N]) { return (N == 37) ? parse_uuid2(uuid) : ((N == 39) ? parse_uuid2(uuid + 1) : BOOST_OUTCOME_SYSTEM_ERROR2_FAIL_CONSTEXPR("UUID does not have correct length")); }
parse_uuid_from_pointer(const char * uuid)93   template <size_t N> constexpr inline unsigned long long parse_uuid_from_pointer(const char *uuid) { return (N == 36) ? parse_uuid2(uuid) : ((N == 38) ? parse_uuid2(uuid + 1) : BOOST_OUTCOME_SYSTEM_ERROR2_FAIL_CONSTEXPR("UUID does not have correct length")); }
94 #ifdef __GNUC__
95 #pragma GCC diagnostic pop
96 #endif
97   static constexpr unsigned long long test_uuid_parse = parse_uuid_from_array("430f1201-94fc-06c7-430f-120194fc06c7");
98   //static constexpr unsigned long long test_uuid_parse2 = parse_uuid_from_array("x30f1201-94fc-06c7-430f-120194fc06c7");
99 }  // namespace detail
100 
101 /*! Abstract base class for a coding domain of a status code.
102  */
103 class status_code_domain
104 {
105   template <class DomainType> friend class status_code;
106   template <class StatusCode> friend class indirecting_domain;
107 
108 public:
109   //! Type of the unique id for this domain.
110   using unique_id_type = unsigned long long;
111   /*! (Potentially thread safe) Reference to a message string.
112 
113   Be aware that you cannot add payload to implementations of this class.
114   You get exactly the `void *[3]` array to keep state, this is usually
115   sufficient for a `std::shared_ptr<>` or a `std::string`.
116 
117   You can install a handler to be called when this object is copied,
118   moved and destructed. This takes the form of a C function pointer.
119   */
120   class string_ref
121   {
122   public:
123     //! The value type
124     using value_type = const char;
125     //! The size type
126     using size_type = size_t;
127     //! The pointer type
128     using pointer = const char *;
129     //! The const pointer type
130     using const_pointer = const char *;
131     //! The iterator type
132     using iterator = const char *;
133     //! The const iterator type
134     using const_iterator = const char *;
135 
136   protected:
137     //! The operation occurring
138     enum class _thunk_op
139     {
140       copy,
141       move,
142       destruct
143     };
144     //! The prototype of the handler function. Copies can throw, moves and destructs cannot.
145     using _thunk_spec = void (*)(string_ref *dest, const string_ref *src, _thunk_op op);
146 #ifndef NDEBUG
147   private:
_checking_string_thunk(string_ref * dest,const string_ref * src,_thunk_op)148     static void _checking_string_thunk(string_ref *dest, const string_ref *src, _thunk_op /*unused*/) noexcept
149     {
150       (void) dest;
151       (void) src;
152       assert(dest->_thunk == _checking_string_thunk);                   // NOLINT
153       assert(src == nullptr || src->_thunk == _checking_string_thunk);  // NOLINT
154       // do nothing
155     }
156 
157   protected:
158 #endif
159     //! Pointers to beginning and end of character range
160     pointer _begin{}, _end{};
161     //! Three `void*` of state
162     void *_state[3]{};  // at least the size of a shared_ptr
163     //! Handler for when operations occur
164     const _thunk_spec _thunk{nullptr};
165 
string_ref(_thunk_spec thunk)166     constexpr explicit string_ref(_thunk_spec thunk) noexcept
167         : _thunk(thunk)
168     {
169     }
170 
171   public:
172     //! Construct from a C string literal
string_ref(const char * str,size_type len=static_cast<size_type> (-1),void * state0=nullptr,void * state1=nullptr,void * state2=nullptr,_thunk_spec thunk=_checking_string_thunk)173     BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR14 explicit string_ref(const char *str, size_type len = static_cast<size_type>(-1), void *state0 = nullptr, void *state1 = nullptr, void *state2 = nullptr,
174 #ifndef NDEBUG
175                                                   _thunk_spec thunk = _checking_string_thunk
176 #else
177                                                   _thunk_spec thunk = nullptr
178 #endif
179                                                   ) noexcept
180         : _begin(str)
181         , _end((len == static_cast<size_type>(-1)) ? (str + detail::cstrlen(str)) : (str + len))
182         ,  // NOLINT
183         _state{state0, state1, state2}
184         , _thunk(thunk)
185     {
186     }
187     //! Copy construct the derived implementation.
string_ref(const string_ref & o)188     string_ref(const string_ref &o)
189         : _begin(o._begin)
190         , _end(o._end)
191         , _state{o._state[0], o._state[1], o._state[2]}
192         , _thunk(o._thunk)
193     {
194       if(_thunk != nullptr)
195       {
196         _thunk(this, &o, _thunk_op::copy);
197       }
198     }
199     //! Move construct the derived implementation.
string_ref(string_ref && o)200     string_ref(string_ref &&o) noexcept
201         : _begin(o._begin)
202         , _end(o._end)
203         , _state{o._state[0], o._state[1], o._state[2]}
204         , _thunk(o._thunk)
205     {
206       if(_thunk != nullptr)
207       {
208         _thunk(this, &o, _thunk_op::move);
209       }
210     }
211     //! Copy assignment
operator =(const string_ref & o)212     string_ref &operator=(const string_ref &o)
213     {
214       if(this != &o)
215       {
216 #if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
217         string_ref temp(static_cast<string_ref &&>(*this));
218         this->~string_ref();
219         try
220         {
221           new(this) string_ref(o);  // may throw
222         }
223         catch(...)
224         {
225           new(this) string_ref(static_cast<string_ref &&>(temp));
226           throw;
227         }
228 #else
229         this->~string_ref();
230         new(this) string_ref(o);
231 #endif
232       }
233       return *this;
234     }
235     //! Move assignment
operator =(string_ref && o)236     string_ref &operator=(string_ref &&o) noexcept
237     {
238       if(this != &o)
239       {
240         this->~string_ref();
241         new(this) string_ref(static_cast<string_ref &&>(o));
242       }
243       return *this;
244     }
245     //! Destruction
~string_ref()246     ~string_ref()
247     {
248       if(_thunk != nullptr)
249       {
250         _thunk(this, nullptr, _thunk_op::destruct);
251       }
252       _begin = _end = nullptr;
253     }
254 
255     //! Returns whether the reference is empty or not
empty() const256     BOOST_OUTCOME_SYSTEM_ERROR2_NODISCARD bool empty() const noexcept { return _begin == _end; }
257     //! Returns the size of the string
size() const258     size_type size() const noexcept { return _end - _begin; }
259     //! Returns a null terminated C string
c_str() const260     const_pointer c_str() const noexcept { return _begin; }
261     //! Returns a null terminated C string
data() const262     const_pointer data() const noexcept { return _begin; }
263     //! Returns the beginning of the string
begin()264     iterator begin() noexcept { return _begin; }
265     //! Returns the beginning of the string
begin() const266     const_iterator begin() const noexcept { return _begin; }
267     //! Returns the beginning of the string
cbegin() const268     const_iterator cbegin() const noexcept { return _begin; }
269     //! Returns the end of the string
end()270     iterator end() noexcept { return _end; }
271     //! Returns the end of the string
end() const272     const_iterator end() const noexcept { return _end; }
273     //! Returns the end of the string
cend() const274     const_iterator cend() const noexcept { return _end; }
275   };
276 
277   /*! A reference counted, threadsafe reference to a message string.
278    */
279   class atomic_refcounted_string_ref : public string_ref
280   {
281     struct _allocated_msg
282     {
283       mutable std::atomic<unsigned> count{1};
284     };
_msg()285     _allocated_msg *&_msg() noexcept { return reinterpret_cast<_allocated_msg *&>(this->_state[0]); }                  // NOLINT
_msg() const286     const _allocated_msg *_msg() const noexcept { return reinterpret_cast<const _allocated_msg *>(this->_state[0]); }  // NOLINT
287 
_refcounted_string_thunk(string_ref * _dest,const string_ref * _src,_thunk_op op)288     static void _refcounted_string_thunk(string_ref *_dest, const string_ref *_src, _thunk_op op) noexcept
289     {
290       auto dest = static_cast<atomic_refcounted_string_ref *>(_dest);      // NOLINT
291       auto src = static_cast<const atomic_refcounted_string_ref *>(_src);  // NOLINT
292       (void) src;
293       assert(dest->_thunk == _refcounted_string_thunk);                   // NOLINT
294       assert(src == nullptr || src->_thunk == _refcounted_string_thunk);  // NOLINT
295       switch(op)
296       {
297       case _thunk_op::copy:
298       {
299         if(dest->_msg() != nullptr)
300         {
301           auto count = dest->_msg()->count.fetch_add(1, std::memory_order_relaxed);
302           (void) count;
303           assert(count != 0);  // NOLINT
304         }
305         return;
306       }
307       case _thunk_op::move:
308       {
309         assert(src);                                                  // NOLINT
310         auto msrc = const_cast<atomic_refcounted_string_ref *>(src);  // NOLINT
311         msrc->_begin = msrc->_end = nullptr;
312         msrc->_state[0] = msrc->_state[1] = msrc->_state[2] = nullptr;
313         return;
314       }
315       case _thunk_op::destruct:
316       {
317         if(dest->_msg() != nullptr)
318         {
319           auto count = dest->_msg()->count.fetch_sub(1, std::memory_order_release);
320           if(count == 1)
321           {
322             std::atomic_thread_fence(std::memory_order_acquire);
323             free((void *) dest->_begin);  // NOLINT
324             delete dest->_msg();          // NOLINT
325           }
326         }
327       }
328       }
329     }
330 
331   public:
332     //! Construct from a C string literal allocated using `malloc()`.
atomic_refcounted_string_ref(const char * str,size_type len=static_cast<size_type> (-1),void * state1=nullptr,void * state2=nullptr)333     explicit atomic_refcounted_string_ref(const char *str, size_type len = static_cast<size_type>(-1), void *state1 = nullptr, void *state2 = nullptr) noexcept
334         : string_ref(str, len, new(std::nothrow) _allocated_msg, state1, state2, _refcounted_string_thunk)
335     {
336       if(_msg() == nullptr)
337       {
338         free((void *) this->_begin);  // NOLINT
339         _msg() = nullptr;             // disabled
340         this->_begin = "failed to get message from system";
341         this->_end = strchr(this->_begin, 0);
342         return;
343       }
344     }
345   };
346 
347 private:
348   unique_id_type _id;
349 
350 protected:
351   /*! Use [https://www.random.org/cgi-bin/randbyte?nbytes=8&format=h](https://www.random.org/cgi-bin/randbyte?nbytes=8&format=h) to get a random 64 bit id.
352 
353   Do NOT make up your own value. Do NOT use zero.
354   */
status_code_domain(unique_id_type id)355   constexpr explicit status_code_domain(unique_id_type id) noexcept
356       : _id(id)
357   {
358   }
359   /*! UUID constructor, where input is constexpr parsed into a `unique_id_type`.
360    */
361   template <size_t N>
status_code_domain(const char (& uuid)[N])362   constexpr explicit status_code_domain(const char (&uuid)[N]) noexcept
363       : _id(detail::parse_uuid_from_array<N>(uuid))
364   {
365   }
366   template <size_t N> struct _uuid_size
367   {
368   };
369   //! Alternative UUID constructor
370   template <size_t N>
status_code_domain(const char * uuid,_uuid_size<N>)371   constexpr explicit status_code_domain(const char *uuid, _uuid_size<N> /*unused*/) noexcept
372       : _id(detail::parse_uuid_from_pointer<N>(uuid))
373   {
374   }
375   //! No public copying at type erased level
376   status_code_domain(const status_code_domain &) = default;
377   //! No public moving at type erased level
378   status_code_domain(status_code_domain &&) = default;
379   //! No public assignment at type erased level
380   status_code_domain &operator=(const status_code_domain &) = default;
381   //! No public assignment at type erased level
382   status_code_domain &operator=(status_code_domain &&) = default;
383   //! No public destruction at type erased level
384   ~status_code_domain() = default;
385 
386 public:
387   //! True if the unique ids match.
operator ==(const status_code_domain & o) const388   constexpr bool operator==(const status_code_domain &o) const noexcept { return _id == o._id; }
389   //! True if the unique ids do not match.
operator !=(const status_code_domain & o) const390   constexpr bool operator!=(const status_code_domain &o) const noexcept { return _id != o._id; }
391   //! True if this unique is lower than the other's unique id.
operator <(const status_code_domain & o) const392   constexpr bool operator<(const status_code_domain &o) const noexcept { return _id < o._id; }
393 
394   //! Returns the unique id used to identify identical category instances.
id() const395   constexpr unique_id_type id() const noexcept { return _id; }
396   //! Name of this category.
397   virtual string_ref name() const noexcept = 0;
398 
399 protected:
400   //! True if code means failure.
401   virtual bool _do_failure(const status_code<void> &code) const noexcept = 0;
402   //! True if code is (potentially non-transitively) equivalent to another code in another domain.
403   virtual bool _do_equivalent(const status_code<void> &code1, const status_code<void> &code2) const noexcept = 0;
404   //! Returns the generic code closest to this code, if any.
405   virtual generic_code _generic_code(const status_code<void> &code) const noexcept = 0;
406   //! Return a reference to a string textually representing a code.
407   virtual string_ref _do_message(const status_code<void> &code) const noexcept = 0;
408 #if defined(_CPPUNWIND) || defined(__EXCEPTIONS) || defined(BOOST_OUTCOME_STANDARDESE_IS_IN_THE_HOUSE)
409   //! Throw a code as a C++ exception.
410   BOOST_OUTCOME_SYSTEM_ERROR2_NORETURN virtual void _do_throw_exception(const status_code<void> &code) const = 0;
411 #else
412   // Keep a vtable slot for binary compatibility
_do_throw_exception(const status_code<void> &) const413   BOOST_OUTCOME_SYSTEM_ERROR2_NORETURN virtual void _do_throw_exception(const status_code<void> & /*code*/) const { abort(); }
414 #endif
415   // For a `status_code<erased<T>>` only, copy from `src` to `dst`. Default implementation uses `memcpy()`.
_do_erased_copy(status_code<void> & dst,const status_code<void> & src,size_t bytes) const416   virtual void _do_erased_copy(status_code<void> &dst, const status_code<void> &src, size_t bytes) const { memcpy(&dst, &src, bytes); }  // NOLINT
417   // For a `status_code<erased<T>>` only, destroy the erased value type. Default implementation does nothing.
_do_erased_destroy(status_code<void> & code,size_t bytes) const418   virtual void _do_erased_destroy(status_code<void> &code, size_t bytes) const noexcept  // NOLINT
419   {
420     (void) code;
421     (void) bytes;
422   }
423 };
424 
425 BOOST_OUTCOME_SYSTEM_ERROR2_NAMESPACE_END
426 
427 #endif
428