Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Exceptions.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <cstdio>
7
8#if qStroika_Platform_Windows
9#include <Windows.h>
10#include <wininet.h> // for error codes
11#endif
12
16#include "Stroika/Foundation/Execution/Throw.h"
17#include "Stroika/Foundation/Linguistics/MessageUtilities.h"
18
19#include "Exceptions.h"
20
21using namespace Stroika::Foundation;
22using namespace Characters;
23using namespace Execution;
24
26
27// Comment this in to turn on aggressive noisy DbgTrace in this module
28//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
29
30namespace {
31 // @todo this message needs lots of linguistic cleanup (punctuation, capitalization etc) - started but quite incomplete
32 String mkMessage_ (const Characters::String& reasonForError, const Containers::Stack<Activity<>>& activities)
33 {
34 //
35 // @todo rewrite this using Linguistics::CurrentLocaleMessageUtilities so we capture the 'while' crap there too
36 //
37 if (activities.empty ()) {
38 return reasonForError;
39 }
41 auto tmp = Linguistics::MessageUtilities::Manager::sThe.RemoveTrailingSentencePunctuation (reasonForError);
42 sb << tmp.first;
43 sb << " while "sv;
44 for (auto i = activities.begin (); i != activities.end ();) {
45 sb << i->AsString ();
46 ++i;
47 if (i == activities.end ()) {
48 sb << tmp.second.value_or ("."sv);
49 }
50 else {
51 // not clear yet what message will work here
52 sb << ", while "sv;
53 }
54 }
55 return sb;
56 }
57}
58
59/*
60 ********************************************************************************
61 ****************************** ExceptionStringHelper ***************************
62 ********************************************************************************
63 */
64ExceptionStringHelper::ExceptionStringHelper (const Characters::String& reasonForError, const optional<Containers::Stack<Activity<>>>& activities)
65 : fActivities_{activities}
66 , fRawErrorMessage_{reasonForError}
67{
68}
69
71 : fActivities_{src.fActivities_}
72 , fRawErrorMessage_{src.fRawErrorMessage_}
73{
74 lock_guard lk{src.fBuildMutex_};
75 fBuilt_ = src.fBuilt_;
76 fFullErrorMessage_ = src.fFullErrorMessage_;
77 fSDKCharString_ = src.fSDKCharString_;
78}
79
80bool Execution::Private_::ShouldImbueActivities_ (const ExceptionStringHelper& e) noexcept
81{
82 // cheap first: an exception which specified activities is never overwritten. AnyCurrentActivities () is
83 // inexpensive, and is only consulted when there is actually a decision to make.
84 return e.GetActivities () == nullopt and AnyCurrentActivities ();
85}
86
87void Execution::Private_::ImbueCurrentActivities_ (ExceptionStringHelper* e)
88{
91}
92
94{
95 lock_guard lk{fBuildMutex_};
96 fActivities_ = activities;
97 fBuilt_ = false; // the message merges these in, so anything already built is stale and gets rebuilt
98}
99
100void ExceptionStringHelper::EnsureBuilt_ () const noexcept
101{
102 try {
103 lock_guard lk{fBuildMutex_};
104 if (not fBuilt_) {
105 fFullErrorMessage_ = mkMessage_ (fRawErrorMessage_, fActivities_.value_or (Containers::Stack<Activity<>>{}));
106 fSDKCharString_ = fFullErrorMessage_.AsNarrowSDKString (eIgnoreErrors);
107 fBuilt_ = true;
108 }
109 }
110 catch (...) {
111 // Only reachable if formatting an error message itself fails (allocation). Leave the cache empty rather
112 // than propagate - callers are noexcept, and an empty what () beats std::terminate.
113 }
114}
115
116/*
117 ********************************************************************************
118 ********************************* NestedException ******************************
119 ********************************************************************************
120 */
121NestedException::NestedException (const exception_ptr& basedOnException)
122 : NestedException{Characters::ToString (basedOnException), basedOnException}
123{
124}
125
126/*
127 ********************************************************************************
128 ***************** Private_::SystemErrorExceptionPrivate_ ***********************
129 ********************************************************************************
130 */
131namespace {
132 // the category the OS's own error numbers arrive in: on Windows Stroika tags them with Win32_error_category (), but the
133 // standard library (and others) still use system_category ()
134 bool IsNativeSystemCategory_ (const error_category& c) noexcept
135 {
136#if qStroika_Platform_Windows
137 if (c == Execution::Platform::Windows::Win32_error_category ()) {
138 return true;
139 }
140#endif
141 return c == system_category ();
142 }
143}
144
145#if qStroika_Platform_Windows
146
147// for InternetGetConnectedState
148#if _MSC_VER
149#pragma comment(lib, "Wininet.lib")
150#endif
151
152optional<String> TryToOverrideDefaultWindowsSystemCategoryMessage_ (error_code errCode)
153{
154 if (IsNativeSystemCategory_ (errCode.category ())) {
155 switch (errCode.value ()) {
156 case ERROR_NOT_ENOUGH_MEMORY:
157 return "Not enough memory to complete that operation (ERROR_NOT_ENOUGH_MEMORY)"sv;
158 case ERROR_OUTOFMEMORY:
159 return "Not enough memory to complete that operation (ERROR_OUTOFMEMORY)"sv;
160 case WSAEADDRNOTAVAIL:
161 return "Socket address not available (WSAEADDRNOTAVAIL)"sv;
162 case ERROR_INTERNET_INVALID_URL:
163 return "ERROR_INTERNET_INVALID_URL"sv;
164 case ERROR_INTERNET_CANNOT_CONNECT:
165 return "Failed to connect to internet URL (ERROR_INTERNET_CANNOT_CONNECT)"sv;
166 case ERROR_INTERNET_NAME_NOT_RESOLVED:
167 return "ERROR_INTERNET_NAME_NOT_RESOLVED"sv;
168 case ERROR_INTERNET_INCORRECT_HANDLE_STATE:
169 return "ERROR_INTERNET_INCORRECT_HANDLE_STATE"sv;
170 case ERROR_INTERNET_TIMEOUT:
171 return "Operation timed out (ERROR_INTERNET_TIMEOUT)"sv;
172 case ERROR_INTERNET_CONNECTION_ABORTED:
173 return "ERROR_INTERNET_CONNECTION_ABORTED"sv;
174 case ERROR_INTERNET_CONNECTION_RESET:
175 return "ERROR_INTERNET_CONNECTION_RESET"sv;
176 case ERROR_HTTP_INVALID_SERVER_RESPONSE:
177 return "Invalid Server Response (ERROR_HTTP_INVALID_SERVER_RESPONSE)"sv;
178 case ERROR_INTERNET_PROTOCOL_NOT_FOUND: {
179 DWORD r = 0;
180 if (::InternetGetConnectedState (&r, 0) and (r & INTERNET_CONNECTION_OFFLINE) == 0) {
181 return "ERROR_INTERNET_PROTOCOL_NOT_FOUND"sv;
182 }
183 else {
184 return "ERROR_INTERNET_PROTOCOL_NOT_FOUND (offline mode)"sv;
185 }
186 }
187 }
188 }
189 return nullopt;
190}
191#endif
192Characters::String Execution::Private_::SystemErrorExceptionPrivate_::mkMsg_ (error_code errCode)
193{
194 /*
195 * errc::timed_out is ETIMEDOUT, which POSIX inherited from BSD sockets, where it really did mean a
196 * connect () timeout - so glibc's strerror () says "Connection timed out". C++ then reused the same value
197 * for timeouts generally, and Stroika reuses it again for lock/thread/event waits which have nothing to do
198 * with connections. Left alone, an ordinary Synchronized<> lock timeout reports "Connection timed out" on
199 * Linux and "timed out" on Windows - confusing on one platform and inconsistent across both.
200 *
201 * Only generic_category () is overridden here, and that is the whole point: make_error_code (errc::X)
202 * always produces generic_category (), so this catches exactly the case where STROIKA decided something
203 * timed out. A genuine socket timeout reported by the OS arrives via ThrowPOSIXErrNo (), which tags it
204 * with system_category () on POSIX - that keeps "Connection timed out", where it is accurate.
205 */
206 if (errCode.category () == generic_category () and errCode == errc::timed_out) {
207 return "Operation timed out"sv; // phrasing matches the ERROR_INTERNET_TIMEOUT override below
208 }
209#if qStroika_Platform_Windows
210 // for some messages, the default windows implementation does poorly generating messages
211 if (optional<String> o = TryToOverrideDefaultWindowsSystemCategoryMessage_ (errCode)) {
212 return *o;
213 }
214#endif
215 // Let the standard C++ library generate the default error message for the given error code - from the category object
216 return Characters::String::FromNarrowSDKString (errCode.message ());
217}
218
219Characters::String Execution::Private_::SystemErrorExceptionPrivate_::mkCombinedMsg_ (error_code errCode, const Characters::String& message)
220{
221 StringBuilder sb{message};
222 sb += " ";
223 if (errCode.category () == generic_category ()) {
224 sb += "{{errno: {}}}"_f(errCode.value ());
225 }
226 else if (IsNativeSystemCategory_ (errCode.category ())) {
227#if qStroika_Platform_POSIX
228 sb += "{{errno: {}}}"_f(errCode.value ());
229#elif qStroika_Platform_Windows
230 sb += "{{Windows error: {}}}"_f(errCode.value ());
231#else
232 sb += "{{system error: {}}}"_f(errCode.value ());
233#endif
234 }
235 else {
236 sb += "{{{}: {}}}"_f(Characters::String::FromNarrowSDKString (errCode.category ().name ()), errCode.value ());
237 }
238 return sb;
239}
240
241void Execution::Private_::SystemErrorExceptionPrivate_::ThrowTranslatedExceptionIfNeeded_ (error_code errCode, [[maybe_unused]] const String* message)
242{
243 // Windows codes are NOT rewritten here (as WAIT_TIMEOUT / ERROR_INTERNET_TIMEOUT once were): the conditions Microsoft's
244 // system_category leaves out come from Win32_error_category (), so code () keeps the raw value.
245 // @see https://github.com/SophistSolutions/Stroika/issues/1192
246 if (errCode == errc::not_enough_memory) {
247 // Deliberately lossy - 'message' and the Activity stack are both dropped. @see ThrowError () for why
248 // enriching this one is a bad trade.
249 Throw (bad_alloc{});
250 }
251 // double check the compare-with-conditions code working the way I think its supposed to... matching multiple error codes -- LGP 2019-02-04
252#if qStroika_Platform_Windows && qStroika_Foundation_Debug_AssertionsChecked
253 if (IsNativeSystemCategory_ (errCode.category ())) {
254 switch (errCode.value ()) {
255 case ERROR_NOT_ENOUGH_MEMORY: // errc::not_enough_memory
256 case ERROR_OUTOFMEMORY: // ""
257 AssertNotReached (); // should have been caught above in if (ec == errc::... checks) - so thats not working - maybe need to add this switch or debug
258 break;
259 }
260 }
261#endif
262}
263
264/*
265 ********************************************************************************
266 *********************** Execution::GetAssociatedErrorCode **********************
267 ********************************************************************************
268 */
269optional<error_code> Execution::GetAssociatedErrorCode (const exception_ptr& e) noexcept
270{
271 try {
272 rethrow_exception (e);
273 }
274 catch (const system_error& se) {
275 return se.code ();
276 }
277 catch (...) {
278 return nullopt;
279 }
280}
281
282/*
283 ********************************************************************************
284 ******************************** Execution::IsA ********************************
285 ********************************************************************************
286 */
287bool Execution::IsA (const error_code& ec, error_condition cond) noexcept
288{
289#if qStroika_Platform_Windows
290 // A Win32 code in std::system_category () - from the standard library, or another library - is asked as Stroika's
291 // Win32_error_category (), so it gets the conditions Microsoft's leaves out. @see Execution::Platform::Windows::Win32_error_category
292 if (ec.category () == system_category ()) {
293 return error_code{ec.value (), Execution::Platform::Windows::Win32_error_category ()} == cond;
294 }
295#endif
296 return ec == cond;
297}
298
299bool Execution::IsA (const system_error& e, error_condition cond) noexcept
300{
301 return IsA (e.code (), cond);
302}
303
304bool Execution::IsA (const exception& e, error_condition cond) noexcept
305{
306 if (const system_error* se = dynamic_cast<const system_error*> (&e)) {
307 return IsA (se->code (), cond);
308 }
309 return false;
310}
311
312bool Execution::IsA (const exception_ptr& e, error_condition cond) noexcept
313{
314 // No way to ask an exception_ptr anything without rethrowing it - @see GetAssociatedErrorCode,
315 // which is where that (comparatively costly) dance lives, so it exists once rather than at each
316 // call site.
317 if (e == nullptr) {
318 return false;
319 }
320 optional<error_code> ec = GetAssociatedErrorCode (e);
321 return ec.has_value () and IsA (*ec, cond);
322}
323
324#if qStroika_Platform_Windows
325/*
326 ********************************************************************************
327 *************** Execution::Platform::Windows::Win32_error_category *************
328 ********************************************************************************
329 */
330namespace {
331 // Win32 codes std::system_category () maps onto no condition (or not onto all the ones they mean). A code may be listed more
332 // than once: its FIRST row is its default_error_condition (). @see Win32_error_category and
333 // https://github.com/SophistSolutions/Stroika/issues/1192 (and the earlier report to Microsoft about their _Winerror_map,
334 // https://developercommunity.visualstudio.com/content/problem/484206/const-int-posv-winerror-map-errval-should-probably.html)
335 struct Win32ConditionMapping_ {
336 int fWin32;
337 errc fCondition;
338 };
339 constexpr Win32ConditionMapping_ kWin32ConditionMappings_[] = {
340 // winerror.h's own network errors - std maps only their Winsock (WSAE*) twins
341 {ERROR_NETNAME_DELETED, errc::connection_reset},
342 {ERROR_NETNAME_DELETED, errc::connection_aborted},
343 {ERROR_CANCELLED, errc::operation_canceled},
344 {ERROR_CONNECTION_REFUSED, errc::connection_refused},
345 {ERROR_PORT_UNREACHABLE, errc::connection_refused}, // as Boost.Asio maps it (UDP: the peer's port is closed)
346 {ERROR_NETWORK_UNREACHABLE, errc::network_unreachable},
347 {ERROR_HOST_UNREACHABLE, errc::host_unreachable},
348 {ERROR_CONNECTION_ABORTED, errc::connection_aborted},
349
350 // WinINet - WinHTTP's ERROR_WINHTTP_* share these numbers (ERROR_WINHTTP_CONNECTION_ERROR is "reset or terminated")
351 {ERROR_INTERNET_TIMEOUT, errc::timed_out},
352 {ERROR_INTERNET_NAME_NOT_RESOLVED, errc::no_such_device}, // as DNS maps EAI_NONAME - there is no 'name not found' errc
353 {ERROR_INTERNET_CANNOT_CONNECT, errc::connection_refused},
354 {ERROR_INTERNET_CONNECTION_ABORTED, errc::connection_aborted},
355 {ERROR_INTERNET_CONNECTION_ABORTED, errc::connection_reset},
356 {ERROR_INTERNET_CONNECTION_RESET, errc::connection_reset},
357 {ERROR_HTTP_INVALID_SERVER_RESPONSE, errc::protocol_error},
358 };
359
360 // WinINet / WinHTTP keep their message text in their own module, so FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM) - all
361 // std's system_category ().message () uses - says "unknown error" for them
362 optional<string> GetModuleMessage_ (int ev)
363 {
364 static const HMODULE kModules_[] = {
365 ::LoadLibraryExW (L"winhttp.dll", nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_SEARCH_SYSTEM32),
366 ::LoadLibraryExW (L"wininet.dll", nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_SEARCH_SYSTEM32),
367 }; // loaded once (as data), and never freed - like the category itself
368 for (HMODULE m : kModules_) {
369 if (m == nullptr) {
370 continue;
371 }
372 char* buf = nullptr;
373 DWORD n = ::FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, m,
374 static_cast<DWORD> (ev), 0, reinterpret_cast<char*> (&buf), 0, nullptr);
375 string result{buf == nullptr ? string{} : string{buf, n}};
376 ::LocalFree (buf);
377 while (not result.empty () and (result.back () == '\n' or result.back () == '\r' or result.back () == ' ')) {
378 result.pop_back ();
379 }
380 if (not result.empty ()) {
381 return result;
382 }
383 }
384 return nullopt;
385 }
386
387 class Win32_error_category_ : public error_category {
388 public:
389 virtual const char* name () const noexcept override
390 {
391 return system_category ().name (); // same name as Microsoft's - @see Win32_error_category
392 }
393 virtual string message (int ev) const override
394 {
395 if (INTERNET_ERROR_BASE <= ev and ev < INTERNET_ERROR_BASE + 1000) { // the WinINet / WinHTTP block (WinHTTP runs past INTERNET_ERROR_LAST)
396 if (optional<string> m = GetModuleMessage_ (ev)) {
397 return *m;
398 }
399 }
400 return system_category ().message (ev);
401 }
402 virtual error_condition default_error_condition (int ev) const noexcept override
403 {
404 for (const Win32ConditionMapping_& i : kWin32ConditionMappings_) {
405 if (i.fWin32 == ev and ev != 0) {
406 return make_error_condition (i.fCondition);
407 }
408 }
409 return system_category ().default_error_condition (ev);
410 }
411 virtual bool equivalent (int ev, const error_condition& cond) const noexcept override
412 {
413 for (const Win32ConditionMapping_& i : kWin32ConditionMappings_) {
414 if (i.fWin32 == ev and ev != 0 and make_error_condition (i.fCondition) == cond) {
415 return true;
416 }
417 }
418 return system_category ().equivalent (ev, cond);
419 }
420 };
421}
422
423const error_category& Execution::Platform::Windows::Win32_error_category () noexcept
424{
425 return Common::Immortalize<Win32_error_category_> ();
426}
427
428bool Execution::Platform::Windows::IsWin32Error (const error_code& ec, int win32Err) noexcept
429{
430 return ec.value () == win32Err and (ec.category () == Win32_error_category () or ec.category () == system_category ());
431}
432#endif
#define RequireNotNull(p)
Definition Assertions.h:350
#define AssertNotReached()
Definition Assertions.h:358
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual string AsNarrowSDKString() const
Definition String.inl:832
static String FromNarrowSDKString(const char *from)
Definition String.inl:472
nonvirtual void ImbueActivities(const optional< Containers::Stack< Activity<> > > &activities)
Set, replace, or clear the activity stack. Any already-computed message text is discarded,...
ExceptionStringHelper()=delete
activities default to NULLOPT - meaning "not specified" - which is different from "specified,...
NestedException contains a new higher level error message (typically based on argument basedOnExcepti...
Definition Exceptions.h:284
String ToString(T &&t, ARGS... args)
Return a debug-friendly, display version of the argument: not guaranteed parsable or usable except fo...
Definition ToString.inl:424
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
Definition Throw.inl:54
optional< error_code > GetAssociatedErrorCode(const exception_ptr &e) noexcept
Containers::Stack< Activity<> > CaptureCurrentActivities()
Returns a copyable preservable version of the current activities stack.
Definition Activity.cpp:19
bool IsA(const error_code &ec, error_condition cond) noexcept
Does this error MEAN the given condition - regardless of how it happens to be represented?
bool AnyCurrentActivities()
Checks if CaptureCurrentActivities() would produce a non-empty stack (but faster)
Definition Activity.cpp:39