Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
Exceptions.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroia_Foundation_Execution_Exceptions_h_
5#define _Stroia_Foundation_Execution_Exceptions_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <cerrno>
10#include <exception>
11#include <optional>
12#include <string>
13#include <system_error>
14
16
17#include "Activity.h"
18#include "Throw.h"
19
20/**
21 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
22 *
23 * \note \em Design Note
24 * (essentially) All exceptions thrown by Stroika (except where needed by quirks of underlying library
25 * integrated with) inherit from std::exception, or are Stroika::Foundation::Execution::SilentException.
26 *
27 * This means that any code which wishes to report an exception can catch these two types, and use
28 * the 'what()' method to report the text of the exception message.
29 *
30 * Sadly, there is no documentation (I'm aware of) or specification of the character set/code page reported
31 * back by the what () on an exception. It tends to be ascii. Stroika guarantees that all exceptions it throws
32 * will use the current SDK characters (@see SDKString). But - its best to use Characters::ToString () on the
33 * caught exception, since this uses ExceptionStringHelper to properly handle characters the SDK character set might not
34 * allow representing some unicode characters.
35 *
36 * TODO:
37 * @todo COULD MAYBE add 'suppress-activities' object you can declare and while it exists (it sets private thread local storage flag)
38 * activities are not merged into message. But this can be done other ways too and doesn't address other stages of message
39 * formation so I'm not sure that's worth while.
40 */
41
43
44 /**
45 * The type of 'errno' variable.
46 *
47 * \note POSIX and C99 just say to assume its an int and doesn't define errno_t.
48 * But I find this type usage clearer.
49 */
50#if qCompilerAndStdLib_Supports_errno_t
51 using errno_t = ::errno_t;
52#else
53 using errno_t = int;
54#endif
55
56 /**
57 * This is a base class for Execution::Exception<> template, which gets mixed with the std c++ exception class, to mix
58 * in Stroika string support.
59 *
60 * This probably should NOT be used directly.
61 */
63 public:
64 /**
65 * If the current activities are NOT provided explicitly, they are copied from Execution::CaptureCurrentActivities ().
66 */
69 ExceptionStringHelper (const Characters::String& reasonForError);
70 ExceptionStringHelper (const Characters::String& reasonForError, const Containers::Stack<Activity<>>& activities);
71
72 public:
73 /**
74 * Return error message without added 'activities'.
75 */
76 nonvirtual Characters::String GetBasicErrorMessage () const;
77
78 public:
79 /**
80 * Return error message with added 'activities'
81 */
82 nonvirtual Characters::String GetFullErrorMessage () const;
83
84 public:
85 /**
86 * Return the activity stack from when the exception was thrown. NOTE - see @Activity<>. This has little
87 * todo with the thread runtime stack. It refers to a logical stack of declared Activity<> objects.
88 */
90
91 public:
92 /**
93 * Only implemented for
94 * o wstring
95 * o String
96 *
97 * This returns the message callers should display to represent the error (e.g. in exception::c_str ()).
98 * For now, it returns GetFullErrorMessage () - but may someday change.
99 */
100 template <typename T>
101 nonvirtual T As () const;
102
103 protected:
104 /**
105 * In order for subclasses to support the c++ exception::c_str () API, we need to convert
106 * the string/message to an SDKChar string, and have that lifetime be very long. So we store it
107 * in a std::string. And this function returns the pointer to that string. This object is
108 * immutable, so that the lifetime of the underlying return const char* is as long as this object.
109 */
110 nonvirtual const char* _PeekAtNarrowSDKString_ () const;
111
112 private:
113 Containers::Stack<Activity<>> fActivities_;
114 Characters::String fRawErrorMessage_;
115 Characters::String fFullErrorMessage_;
116 string fSDKCharString_; // important declared after others cuz mem-initializer refers back
117 };
118 template <>
119 wstring ExceptionStringHelper::As () const;
120 template <>
122
123 /**
124 * \brief Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::exception'),
125 * which adds UNICODE String support.
126 *
127 * Stroika's Exception<> class is fully interoperable with the normal C++ exception classes, but its use offers two
128 * benefits:
129 * o It guarantees that UNICODE messages (including things like filenames) are properly preserved in the exception
130 * message, even if the system default code page (locale) does not allow representing those characters.
131 *
132 * o It integrates neatly with the Stroika 'Activity' mechanism, whereby you declare current 'activities' on the stack
133 * and these are automatically integrated into exceptions to provide clearer messages (e.g. instead of
134 * getting the error message
135 * o "(errno: 13)" OR
136 * o "Permission denied"
137 * Stroika returns something like:
138 * o "Permission denied {errno: 13} while binding to INADDR_ANY:80, while constructing static content webserver."
139 *
140 * @see Activity<>
141 * @see DeclareActivity
142 *
143 * \par Example Usage
144 * \code
145 * static constexpr Activity kBuildingThingy_ {"building thingy"sv };
146 * try {
147 * DeclareActivity declareActivity { &kBuildingThingy_ };
148 * doBuildThing (); // throw any exception (that inherits from Exception<>)
149 * }
150 * catch (...) {
151 * String exceptionMsg = Characters::ToString (current_exception ());
152 * Assert (exceptionMsg.Contains (kBuildingThingy_.AsString ()); // exception e while building thingy...
153 * }
154 * \endcode
155 */
156 template <typename BASE_EXCEPTION = exception>
157 class Exception : public ExceptionStringHelper, public BASE_EXCEPTION {
158 private:
159 static_assert (derived_from<BASE_EXCEPTION, exception>);
160
161 private:
162 using inherited = BASE_EXCEPTION;
163
164 public:
165 /**
166 */
167 Exception () = delete;
168 Exception (const Exception&) = default;
169 Exception (const Characters::String& reasonForError);
170
171 protected:
172 /**
173 * For BASE_EXCEPTION classes with constructors OTHER than just 'message' - you cannot use a 'using X = Exception(x)' but a subclass
174 * which uses this delegating method.
175 */
176 template <typename... BASE_EXCEPTION_ARGS>
177 explicit Exception (const Characters::String& reasonForError, BASE_EXCEPTION_ARGS... baseExceptionArgs);
178
179 public:
180 /**
181 * Provide a 'c string' variant of the exception message. Convert the UNICODE
182 * string argument to a narrow-string (multibyte) in the current locale encoding.
183 */
184 virtual const char* what () const noexcept override;
185 };
186
187 /**
188 * A wrapper on std::runtime_error, which adds Stroika UNICODE string support.
189 *
190 * According to https://en.cppreference.com/w/cpp/error/runtime_error
191 * It reports errors that are due to events beyond the scope of the
192 * program and can not be easily predicted.
193 *
194 * This isn't super clear, but Stroika interprets this to mean external conditions - files, networks, memory etc - essentially
195 * ALL the things Stroika treats as exceptions.
196 */
197 template <typename BASE_EXCEPTION = runtime_error>
198 class RuntimeErrorException : public Exception<BASE_EXCEPTION> {
199 public:
200 /**
201 */
203 };
204
205 /**
206 * \brief NestedException contains a new higher level error message (typically based on argument basedOnException)
207 * and preserves the original exception (which you can use to get its message, with Characters::ToString (fBasedOnException)
208 *
209 * This also can be used to wrap third-party libraries exceptions, which don't know about 'DeclareActivity' - and incorporating
210 * that into an error message.
211 */
213 public:
214 /**
215 */
216 NestedException () = delete;
217 NestedException (const NestedException&) = default;
218 NestedException (const exception_ptr& basedOnException);
219 NestedException (const Characters::String& msg, const exception_ptr& basedOnException);
220
221 public:
222 const exception_ptr fBasedOnException;
223 };
224
225 /**
226 * Simple wrapper on std::system_error, but adding support for Stroika String, and other utility methods.
227 *
228 * \note see https://en.cppreference.com/w/cpp/error/errc for a mapping of errc conditions and ERRNO values.
229 *
230 * \note It's best to not catch (const SystemErrorException&) - and instead catch (const system_error&), since you can
231 * still extract the UNICODE message with Characters::ToString () - and caching const SystemErrorException& risks
232 * missing exceptions from non-Stroika sources (which will just throw system_error) or subclasses of system_error such as
233 * filesystem_error.
234 *
235 * \par Example Usage
236 * \code
237 * try {
238 * ThrowPOSIXErrNo (make_error_code (errc::bad_address).value ());
239 * }
240 * catch (const std::system_error& e) {
241 * EXPECT_TRUE (e.code ().value () == make_error_code (errc::bad_address).value ());
242 * EXPECT_TRUE (e.code ().category () == system_category () or e.code ().category () == generic_category ());
243 * Assert (Characters::ToString (e).Contains ("bad address {errno: 14}"));
244 * }
245 * \endcode
246 *
247 * Note this preserves UNICODE characters in messages, even if not using UNICODE code page/locale
248 * \par Example Usage
249 * \code
250 * try {
251 * const Characters::String kMsgWithUnicode_ = L"zß水𝄋"; // this works even if using a code page / locale which doesn't support UNICODE/Chinese
252 * try {
253 * Execution::Throw (SystemErrorException<> (make_error_code (errc::bad_address), kMsgWithUnicode_));
254 * }
255 * catch (const std::system_error& e) {
256 * Assert (Characters::ToString (e).Contains (kMsgWithUnicode_)); // message also includes the number for bad_address
257 * }
258 * \endcode
259 *
260 * \par Example Usage
261 * \code
262 * try {
263 * s.JoinMulticastGroup (UPnP::SSDP::V4::kSocketAddress.GetInternetAddress ());
264 * }
265 * catch (const std::system_error& e) {
266 * if (e.code () == errc::no_such_device) {
267 * // This can happen on Linux when you start before you have a network connection - no problem - just keep trying
268 * DbgTrace ("Got exception (errno: ENODEV) - while joining multicast group, so try again");
269 * Execution::Sleep (1);
270 * goto Again;
271 * }
272 * else {
273 * Execution::ReThrow ();
274 * }
275 * }
276 * \endcode
277 *
278 * @see also GetAssociatedErrorCode ()
279 */
280 template <typename BASE_EXCEPTION = system_error>
281 class SystemErrorException : public Exception<BASE_EXCEPTION> {
282 private:
283 static_assert (derived_from<BASE_EXCEPTION, system_error>);
284
285 private:
287
288 public:
289 /**
290 */
291 SystemErrorException (error_code errCode);
292 SystemErrorException (error_code errCode, const Characters::String& message);
293 SystemErrorException (int ev, const error_category& ecat);
294 SystemErrorException (int ev, const error_category& ecat, const Characters::String& message);
295
296 protected:
297 /**
298 * For BASE_EXCEPTION classes with constructors OTHER than just 'message' - you cannot use a 'using X = Exception(x)' but a subclass
299 * which uses this delegating method.
300 *
301 * \note - _PeekAtSDKString_ () will probably have to be among the baseExceptionArgs.
302 */
303 template <typename... BASE_EXCEPTION_ARGS>
304 explicit SystemErrorException (const Characters::String& reasonForError, BASE_EXCEPTION_ARGS... baseExceptionArgs);
305 };
306
307 /**
308 * \brief treats errNo as a `POSIX errno` value, and throws a SystemError (subclass of @std::system_error) exception with it.
309 *
310 * \pre errNo != 0
311 *
312 * \note Translates some throws to subclass of SystemErrorException like TimedException or other classes like bad_alloc.
313 *
314 * \note On a POSIX system, this amounts to a call to ThrowSystemErrNo.
315 * But even on a non-POSIX system, many APIs map their error numbers to POSIX error numbers so this can make sense to use.
316 * Also, on POSIX systems, its legal to call this with POSIX compatible extended (and therefore not POSIX) erorr nubmbers.
317 * In other words, you can call this with anything (except 0) you read out of errno on a POSIX system.
318 *
319 * \note From http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf -
320 * "If the argument ev corresponds to a POSIX errno value posv, the function
321 * shall return error_- condition(posv, generic_category()). Otherwise, the function"
322 *
323 * \note this function takes errno as a default value because you almost always want to call it with the value from errno.
324 *
325 * See:
326 * @see ThrowSystemErrNo ();
327 */
328 [[noreturn]] void ThrowPOSIXErrNo (errno_t errNo = errno);
329
330 /**
331 * Look at the argument value and if < 0,ThrowPOSIXErrNo (), and otherwise return it.
332 *
333 * \note Many POSIX - APIs - return a number which is zero if good, or -1 (or sometimes defined negative) if errno is set and there is an error.
334 * This function is useful for wrapping calls to those style functions. It checks if the argument result is negative (so -1 covers that) and
335 * throws and a POSIX (generic_error) SystemErrorException.
336 */
337 template <typename INT_TYPE>
338 INT_TYPE ThrowPOSIXErrNoIfNegative (INT_TYPE returnCode);
339
340 /**
341 * \brief treats sysErr as a platform-defined error number, and throws a SystemErrorException (subclass of @std::system_error) exception with it.
342 *
343 * \pre sysErr != 0
344 *
345 * \note stdc++ uses 'int' for the type of this error number, but Windows generally defines the type to be
346 * DWORD.
347 *
348 * \note Translates some throws to subclass of SystemErrorException like TimedException or other classes like bad_alloc.
349 *
350 * \note From http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf -
351 * "That object’s category() member shall return std::system_category() for errors originating
352 * from the operating system, or a reference to an implementation"
353 *
354 * \par Example Usage
355 * \code
356 * #if qStroika_Foundation_Common_Platform_POSIX
357 * ThrowSystemErrNo (errno);
358 * #elif qStroika_Foundation_Common_Platform_Windows
359 * ThrowSystemErrNo (::GetLastError ()); // works with this type of error # - GetLastError () is default if no arg provided
360 * ThrowSystemErrNo (::WSAGetLastError ()); // or this
361 * #endif
362 * \endcode
363 *
364 * See:
365 * @see ThrowPOSIXErrNo ();
366 *
367 * \note zero arg versions only defined for POSIX and Windows platforms, and there the default is the obvious value for each
368 * platform - errno and GetLastError(). It is still an assertion (require) error to call these when errno / GetLastError () would return 0.
369 */
370 [[noreturn]] void ThrowSystemErrNo (int sysErr);
371#if qStroika_Foundation_Common_Platform_POSIX or qStroika_Foundation_Common_Platform_Windows
372 [[noreturn]] void ThrowSystemErrNo ();
373#endif
374
375 /**
376 * \brief Handle UNIX EINTR system call behavior - fairly transparently - just effectively removes them from the set of errors that can be returned
377 *
378 * Run the given (argument) call. After each call, invoke Thread::CheckForInterruption ().
379 * If the call returns < 0 and errno == EINTR, repeat the call.
380 * If the result was < 0, but errno != EINTR, then ThrowErrNoIfNegative ();
381 * Then return the result.
382 *
383 * \note The only HITCH with respect to automatically handling interruptability is that that its handled by 'restarting' the argument 'call'
384 * That means if it was partially completed, the provider of 'call' must accommodate that fact (use mutable lambda).
385 *
386 * This behavior is meant to work with the frequent POSIX API semantics of a return value of < 0
387 * implying an error, and < 0 but errno == EINTR means retry the call. This API also provides a
388 * cancelation point - so it makes otherwise blocking calls (like select, or read) work well with thread
389 * interruption.
390 *
391 * \note ***Cancelation Point***
392 */
393 template <typename CALL>
394 auto Handle_ErrNoResultInterruption (CALL call) -> decltype (call ());
395
396 /**
397 * Check the argument 'return value' from some function, and if its null, throw a SystemError exception with
398 * the current errno value.
399 *
400 * \note rarely useful, but some POSIX APIs such as getcwd() do return null on error.
401 */
402 void ThrowPOSIXErrNoIfNull (void* returnValue);
403
404 /**
405 * This checks if the given exception_ptr is of a type that contains an error code, and if so
406 * it extracts the error code, and returns it (else nullopt).
407 *
408 * \par Example Usage
409 * \code
410 * try {
411 * /// do something that throws
412 * }
413 * catch (...) {
414 * if (auto err = GetAssociatedErrorCode (current_exception ())) {
415 * if (*err == errc::no_such_device) {
416 * // This can happen on Linux when you start before you have a network connection - no problem - just keep trying
417 * DbgTrace ("Got exception (errno: ENODEV) - while joining multicast group, so try again");
418 * Execution::Sleep (1);
419 * goto Again;
420 * }
421 * else {
422 * Execution::ReThrow ();
423 * }
424 * }
425 * }
426 * \endcode
427 */
428 optional<error_code> GetAssociatedErrorCode (const exception_ptr& e) noexcept;
429
430 /**
431 * Wrap the the argument function (typically a lambda) in an OPTIONAL of the argument type, and return nullopt - dropping the exception
432 * on the floor.
433 *
434 * @todo When we support std c++23, do likewise for expected!!!
435 */
436 template <typename F>
437 inline auto TranslateExceptionToOptional (F&& f) -> optional<remove_cvref_t<invoke_result_t<F>>>;
438
439}
440
441/*
442 ********************************************************************************
443 ***************************** Implementation Details ***************************
444 ********************************************************************************
445 */
446#include "Exceptions.inl"
447
448#endif /*_Stroia_Foundation_Execution_Exceptions_h_*/
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
virtual const char * what() const noexcept override
nonvirtual Containers::Stack< Activity<> > GetActivities() const
nonvirtual Characters::String GetBasicErrorMessage() const
nonvirtual const char * _PeekAtNarrowSDKString_() const
nonvirtual Characters::String GetFullErrorMessage() const
NestedException contains a new higher level error message (typically based on argument basedOnExcepti...
Definition Exceptions.h:212
void ThrowPOSIXErrNo(errno_t errNo=errno)
treats errNo as a POSIX errno value, and throws a SystemError (subclass of @std::system_error) except...
auto Handle_ErrNoResultInterruption(CALL call) -> decltype(call())
Handle UNIX EINTR system call behavior - fairly transparently - just effectively removes them from th...
void ThrowPOSIXErrNoIfNull(void *returnValue)
auto TranslateExceptionToOptional(F &&f) -> optional< remove_cvref_t< invoke_result_t< F > > >
optional< error_code > GetAssociatedErrorCode(const exception_ptr &e) noexcept
INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode)