Stroika Library 3.0d16
 
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 */
210 public:
211 /**
212 */
213 NestedException () = delete;
214 NestedException (const NestedException&) = default;
215 NestedException (const Characters::String& msg, const exception_ptr& basedOnException);
216
217 public:
218 const exception_ptr fBasedOnException;
219 };
220
221 /**
222 * Simple wrapper on std::system_error, but adding support for Stroika String, and other utility methods.
223 *
224 * \note see https://en.cppreference.com/w/cpp/error/errc for a mapping of errc conditions and ERRNO values.
225 *
226 * \note It's best to not catch (const SystemErrorException&) - and instead catch (const system_error&), since you can
227 * still extract the UNICODE message with Characters::ToString () - and caching const SystemErrorException& risks
228 * missing exceptions from non-Stroika sources (which will just throw system_error) or subclasses of system_error such as
229 * filesystem_error.
230 *
231 * \par Example Usage
232 * \code
233 * try {
234 * ThrowPOSIXErrNo (make_error_code (errc::bad_address).value ());
235 * }
236 * catch (const std::system_error& e) {
237 * EXPECT_TRUE (e.code ().value () == make_error_code (errc::bad_address).value ());
238 * EXPECT_TRUE (e.code ().category () == system_category () or e.code ().category () == generic_category ());
239 * Assert (Characters::ToString (e).Contains ("bad address {errno: 14}"));
240 * }
241 * \endcode
242 *
243 * Note this preserves UNICODE characters in messages, even if not using UNICODE code page/locale
244 * \par Example Usage
245 * \code
246 * try {
247 * const Characters::String kMsgWithUnicode_ = L"zß水𝄋"; // this works even if using a code page / locale which doesn't support UNICODE/Chinese
248 * try {
249 * Execution::Throw (SystemErrorException<> (make_error_code (errc::bad_address), kMsgWithUnicode_));
250 * }
251 * catch (const std::system_error& e) {
252 * Assert (Characters::ToString (e).Contains (kMsgWithUnicode_)); // message also includes the number for bad_address
253 * }
254 * \endcode
255 *
256 * \par Example Usage
257 * \code
258 * try {
259 * s.JoinMulticastGroup (UPnP::SSDP::V4::kSocketAddress.GetInternetAddress ());
260 * }
261 * catch (const std::system_error& e) {
262 * if (e.code () == errc::no_such_device) {
263 * // This can happen on Linux when you start before you have a network connection - no problem - just keep trying
264 * DbgTrace ("Got exception (errno: ENODEV) - while joining multicast group, so try again");
265 * Execution::Sleep (1);
266 * goto Again;
267 * }
268 * else {
269 * Execution::ReThrow ();
270 * }
271 * }
272 * \endcode
273 *
274 * @see also GetAssociatedErrorCode ()
275 */
276 template <typename BASE_EXCEPTION = system_error>
277 class SystemErrorException : public Exception<BASE_EXCEPTION> {
278 private:
279 static_assert (derived_from<BASE_EXCEPTION, system_error>);
280
281 private:
283
284 public:
285 /**
286 */
287 SystemErrorException (error_code errCode);
288 SystemErrorException (error_code errCode, const Characters::String& message);
289 SystemErrorException (int ev, const error_category& ecat);
290 SystemErrorException (int ev, const error_category& ecat, const Characters::String& message);
291
292 protected:
293 /**
294 * For BASE_EXCEPTION classes with constructors OTHER than just 'message' - you cannot use a 'using X = Exception(x)' but a subclass
295 * which uses this delegating method.
296 *
297 * \note - _PeekAtSDKString_ () will probably have to be among the baseExceptionArgs.
298 */
299 template <typename... BASE_EXCEPTION_ARGS>
300 explicit SystemErrorException (const Characters::String& reasonForError, BASE_EXCEPTION_ARGS... baseExceptionArgs);
301 };
302
303 /**
304 * \brief treats errNo as a `POSIX errno` value, and throws a SystemError (subclass of @std::system_error) exception with it.
305 *
306 * \pre errNo != 0
307 *
308 * \note Translates some throws to subclass of SystemErrorException like TimedException or other classes like bad_alloc.
309 *
310 * \note On a POSIX system, this amounts to a call to ThrowSystemErrNo.
311 * But even on a non-POSIX system, many APIs map their error numbers to POSIX error numbers so this can make sense to use.
312 * Also, on POSIX systems, its legal to call this with POSIX compatible extended (and therefore not POSIX) erorr nubmbers.
313 * In other words, you can call this with anything (except 0) you read out of errno on a POSIX system.
314 *
315 * \note From http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf -
316 * "If the argument ev corresponds to a POSIX errno value posv, the function
317 * shall return error_- condition(posv, generic_category()). Otherwise, the function"
318 *
319 * \note this function takes errno as a default value because you almost always want to call it with the value from errno.
320 *
321 * See:
322 * @see ThrowSystemErrNo ();
323 */
324 [[noreturn]] void ThrowPOSIXErrNo (errno_t errNo = errno);
325
326 /**
327 * Look at the argument value and if < 0,ThrowPOSIXErrNo (), and otherwise return it.
328 *
329 * \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.
330 * This function is useful for wrapping calls to those style functions. It checks if the argument result is negative (so -1 covers that) and
331 * throws and a POSIX (generic_error) SystemErrorException.
332 */
333 template <typename INT_TYPE>
334 INT_TYPE ThrowPOSIXErrNoIfNegative (INT_TYPE returnCode);
335
336 /**
337 * \brief treats sysErr as a platform-defined error number, and throws a SystemErrorException (subclass of @std::system_error) exception with it.
338 *
339 * \pre sysErr != 0
340 *
341 * \note stdc++ uses 'int' for the type of this error number, but Windows generally defines the type to be
342 * DWORD.
343 *
344 * \note Translates some throws to subclass of SystemErrorException like TimedException or other classes like bad_alloc.
345 *
346 * \note From http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf -
347 * "That object’s category() member shall return std::system_category() for errors originating
348 * from the operating system, or a reference to an implementation"
349 *
350 * \par Example Usage
351 * \code
352 * #if qStroika_Foundation_Common_Platform_POSIX
353 * ThrowSystemErrNo (errno);
354 * #elif qStroika_Foundation_Common_Platform_Windows
355 * ThrowSystemErrNo (::GetLastError ()); // works with this type of error # - GetLastError () is default if no arg provided
356 * ThrowSystemErrNo (::WSAGetLastError ()); // or this
357 * #endif
358 * \endcode
359 *
360 * See:
361 * @see ThrowPOSIXErrNo ();
362 *
363 * \note zero arg versions only defined for POSIX and Windows platforms, and there the default is the obvious value for each
364 * platform - errno and GetLastError(). It is still an assertion (require) error to call these when errno / GetLastError () would return 0.
365 */
366 [[noreturn]] void ThrowSystemErrNo (int sysErr);
367#if qStroika_Foundation_Common_Platform_POSIX or qStroika_Foundation_Common_Platform_Windows
368 [[noreturn]] void ThrowSystemErrNo ();
369#endif
370
371 /**
372 * \brief Handle UNIX EINTR system call behavior - fairly transparently - just effectively removes them from the set of errors that can be returned
373 *
374 * Run the given (argument) call. After each call, invoke Thread::CheckForInterruption ().
375 * If the call returns < 0 and errno == EINTR, repeat the call.
376 * If the result was < 0, but errno != EINTR, then ThrowErrNoIfNegative ();
377 * Then return the result.
378 *
379 * \note The only HITCH with respect to automatically handling interruptability is that that its handled by 'restarting' the argument 'call'
380 * That means if it was partially completed, the provider of 'call' must accommodate that fact (use mutable lambda).
381 *
382 * This behavior is meant to work with the frequent POSIX API semantics of a return value of < 0
383 * implying an error, and < 0 but errno == EINTR means retry the call. This API also provides a
384 * cancelation point - so it makes otherwise blocking calls (like select, or read) work well with thread
385 * interruption.
386 *
387 * \note ***Cancelation Point***
388 */
389 template <typename CALL>
390 auto Handle_ErrNoResultInterruption (CALL call) -> decltype (call ());
391
392 /**
393 * Check the argument 'return value' from some function, and if its null, throw a SystemError exception with
394 * the current errno value.
395 *
396 * \note rarely useful, but some POSIX APIs such as getcwd() do return null on error.
397 */
398 void ThrowPOSIXErrNoIfNull (void* returnValue);
399
400 /**
401 * This checks if the given exception_ptr is of a type that contains an error code, and if so
402 * it extracts the error code, and returns it (else nullopt).
403 *
404 * \par Example Usage
405 * \code
406 * try {
407 * /// do something that throws
408 * }
409 * catch (...) {
410 * if (auto err = GetAssociatedErrorCode (current_exception ())) {
411 * if (*err == errc::no_such_device) {
412 * // This can happen on Linux when you start before you have a network connection - no problem - just keep trying
413 * DbgTrace ("Got exception (errno: ENODEV) - while joining multicast group, so try again");
414 * Execution::Sleep (1);
415 * goto Again;
416 * }
417 * else {
418 * Execution::ReThrow ();
419 * }
420 * }
421 * }
422 * \endcode
423 */
424 optional<error_code> GetAssociatedErrorCode (const exception_ptr& e) noexcept;
425
426 /**
427 * Wrap the the argument function (typically a lambda) in an OPTIONAL of the argument type, and return nullopt - dropping the exception
428 * on the floor.
429 *
430 * @todo When we support std c++23, do likewise for expected!!!
431 */
432 template <typename F>
433 inline auto TranslateExceptionToOptional (F&& f) -> optional<remove_cvref_t<invoke_result_t<F>>>;
434
435}
436
437/*
438 ********************************************************************************
439 ***************************** Implementation Details ***************************
440 ********************************************************************************
441 */
442#include "Exceptions.inl"
443
444#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:209
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)