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