diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2024-05-28 13:22:05 -0400 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2024-05-28 13:22:05 -0400 |
commit | 5ed8df22cd59680a685c4ada7daa5555bf59d4fe (patch) | |
tree | affa9679e891608f9689c780cdd00710e5ea2c95 | |
parent | 71d777ea126e7f02cb46c11bdb606094ca06f75c (diff) |
Fix up llexception.h's cross-platform SEH wrapper.
Introduce AlwaysReturn<void> specialization, which always discards any result
of calling the specified callable with specified args.
Derive new Windows_SEH_exception from LLException, not std::runtime_error.
Put the various SEH functions in LL::seh nested namespace, e.g.
LL::seh::catcher() as the primary API.
Break out more levels of Windows SEH handler to work around the restrictions on
functions containing __try/__except.
The triadic catcher() overload now does little save declare a std::string
stacktrace before forwarding the call to catcher_inner(), passing a reference
to stacktrace along with the trycode, filter and handler functions.
catcher_inner() accepts the stacktrace and the three function template
arguments. It contains the __try/__except logic. It calls a new filter_()
wrapper template, which calls fill_stacktrace() before forwarding the call to
the caller's filter function. fill_stacktrace(), in the .cpp file, contains
the logic to populate the stacktrace string -- unless the Structured Exception
is stack overflow, in which case it puts an explanatory string instead.
catcher_inner()'s __except clause passes not only the code, but also the
stacktrace string, to the caller's handler function. It wraps the caller's
handler function in always_return<rtype>(), where rtype is the type returned
by the trycode function. This allows a handler to return a value, while also
supporting the void handler case, e.g. one that throws a C++ exception. (This
is why we need AlwaysReturn<void>: some trycode() functions are themselves
void.)
For the dyadic catcher() overload, introduce common_filter() containing the
logic to distinguish a C++ exception from any other kind of Structured
Exception. The fact that the stacktrace is captured before the filter function
is called should permit capturing a stacktrace for a C++ exception as well as
for most other Structured Exceptions.
As before, the monadic catcher() overload supplies the rethrow() handler, in
the .cpp file.
Change existing calls from seh_catcher() to LL::seh::catcher().
-rw-r--r-- | indra/llcommon/always_return.h | 16 | ||||
-rw-r--r-- | indra/llcommon/llcoros.cpp | 2 | ||||
-rw-r--r-- | indra/llcommon/llexception.cpp | 24 | ||||
-rw-r--r-- | indra/llcommon/llexception.h | 104 | ||||
-rw-r--r-- | indra/llwindow/llwindowwin32.cpp | 2 | ||||
-rw-r--r-- | indra/newview/llfeaturemanager.cpp | 2 | ||||
-rw-r--r-- | indra/test/test.cpp | 2 |
7 files changed, 91 insertions, 61 deletions
diff --git a/indra/llcommon/always_return.h b/indra/llcommon/always_return.h index a206471da5..b99eb49096 100644 --- a/indra/llcommon/always_return.h +++ b/indra/llcommon/always_return.h @@ -79,6 +79,22 @@ namespace LL DESIRED mDefault; }; + // specialize for AlwaysReturn<void> + template <> + struct AlwaysReturn<void> + { + public: + AlwaysReturn() {} + + // callable returns a type not convertible to DESIRED, return default + template <typename CALLABLE, typename... ARGS> + void operator()(CALLABLE&& callable, ARGS&&... args) + { + // discard whatever callable(args) returns + std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...); + } + }; + /** * always_return<T>(some_function, some_args...) calls * some_function(some_args...). It is guaranteed to return a value of type diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index f3943b7138..7512bac88d 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -299,7 +299,7 @@ void LLCoros::toplevel(std::string name, callable_t callable) // run the code the caller actually wants in the coroutine try { - seh_catcher(callable); + LL::seh::catcher(callable); } catch (const Stop& exc) { diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp index 74b33f1e3b..107fdc2b2d 100644 --- a/indra/llcommon/llexception.cpp +++ b/indra/llcommon/llexception.cpp @@ -101,44 +101,36 @@ void annotate_exception_(boost::exception& exc) static constexpr U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific static constexpr U32 STATUS_STACK_FULL = 0xC00000FD; -U32 ll_seh_filter( - std::string& stacktrace, - std::function<U32(U32, struct _EXCEPTION_POINTERS*)> filter, - U32 code, - struct _EXCEPTION_POINTERS* exception_infop) +void LL::seh::fill_stacktrace(std::string& stacktrace, U32 code) { - // By the time the handler gets control, the stack has been unwound, - // so report the stack trace now at filter() time. - // Even though stack overflow is a problem we would very much like to - // diagnose, calling another function when the stack is already blown only - // terminates us faster. + // Sadly, despite its diagnostic importance, trying to capture a + // stacktrace when the stack is already blown only terminates us faster. if (code == STATUS_STACK_FULL) { stacktrace = "(stack overflow, no traceback)"; } else { - stacktrace = boost::stacktrace::stacktrace().to_string(); + stacktrace = to_string(boost::stacktrace::stacktrace()); } - - return filter(code, exception_infop); } -U32 seh_filter(U32 code, struct _EXCEPTION_POINTERS*) +U32 LL::seh::common_filter(U32 code, struct _EXCEPTION_POINTERS*) { if (code == STATUS_MSC_EXCEPTION) { - // C++ exception, go on + // C++ exception, don't stop at this handler return EXCEPTION_CONTINUE_SEARCH; } else { // This is a non-C++ exception, e.g. hardware check. + // Pass control into the handler block. return EXCEPTION_EXECUTE_HANDLER; } } -void seh_rethrow(U32 code, const std::string& stacktrace) +void LL::seh::rethrow(U32 code, const std::string& stacktrace) { std::ostringstream out; out << "Windows exception 0x" << std::hex << code; diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h index 3e50678b44..f58a553eb3 100644 --- a/indra/llcommon/llexception.h +++ b/indra/llcommon/llexception.h @@ -12,6 +12,7 @@ #if ! defined(LL_LLEXCEPTION_H) #define LL_LLEXCEPTION_H +#include "always_return.h" #include <stdexcept> #include <boost/exception/exception.hpp> #include <boost/throw_exception.hpp> @@ -106,90 +107,111 @@ void log_unhandled_exception_(const char*, int, const char*, const std::string&) * Structured Exception Handling *****************************************************************************/ // this is used in platform-generic code -- define outside #if LL_WINDOWS -struct Windows_SEH_exception: public std::runtime_error +struct Windows_SEH_exception: public LLException { - Windows_SEH_exception(const std::string& what): std::runtime_error(what) {} + Windows_SEH_exception(const std::string& what): LLException(what) {} }; +namespace LL +{ +namespace seh +{ + #if LL_WINDOWS //------------------------------------------------------------- -#include <functional> +void fill_stacktrace(std::string& stacktrace, U32 code); + +// wrapper around caller's U32 filter(U32 code, struct _EXCEPTION_POINTERS*) +// filter function: capture a stacktrace, if possible, before forwarding the +// call to the caller's filter() function +template <typename FILTER> +U32 filter_(std::string& stacktrace, FILTER&& filter, + U32 code, struct _EXCEPTION_POINTERS* exptrs) +{ + // By the time the handler gets control, the stack has been unwound, + // so report the stack trace now at filter() time. + fill_stacktrace(stacktrace, code); + return std::forward<FILTER>(filter)(code, exptrs); +} -// triadic variant specifies try(), filter(U32, struct _EXCEPTION_POINTERS*), -// handler(U32, const std::string& stacktrace) -// stacktrace may or may not be available template <typename TRYCODE, typename FILTER, typename HANDLER> -auto seh_catcher(TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler) +auto catcher_inner(std::string& stacktrace, + TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler) { - // don't try to construct a std::function at the moment of Structured Exception - std::function<U32(U32, struct _EXCEPTION_POINTERS*)> - filter_function(std::forward<FILTER>(filter)); - std::string stacktrace; __try { return std::forward<TRYCODE>(trycode)(); } - __except (ll_seh_filter( - stacktrace, - filter_function, - GetExceptionCode(), - GetExceptionInformation())) + __except (filter_(stacktrace, + std::forward<FILTER>(filter), + GetExceptionCode(), GetExceptionInformation())) { - return std::forward<HANDLER>(handler)(GetExceptionCode(), stacktrace); + return always_return<decltype(trycode())>( + std::forward<HANDLER>(handler), GetExceptionCode(), stacktrace); } } -// dyadic variant specifies try(), handler(U32, stacktrace), assumes default filter +// triadic variant specifies try(), filter(U32, struct _EXCEPTION_POINTERS*), +// handler(U32, const std::string& stacktrace) +// stacktrace may or may not be available +template <typename TRYCODE, typename FILTER, typename HANDLER> +auto catcher(TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler) +{ + // Construct and destroy this stacktrace string in the outer function + // because we can't do either in the function with __try/__except. + std::string stacktrace; + return catcher_inner(stacktrace, + std::forward<TRYCODE>(trycode), + std::forward<FILTER>(filter), + std::forward<HANDLER>(handler)); +} + +// common_filter() handles the typical case in which we want our handler +// clause to handle only Structured Exceptions rather than explicitly-thrown +// C++ exceptions +U32 common_filter(U32 code, struct _EXCEPTION_POINTERS*); + +// dyadic variant specifies try(), handler(U32, stacktrace), assumes common_filter() template <typename TRYCODE, typename HANDLER> -auto seh_catcher(TRYCODE&& trycode, HANDLER&& handler) +auto catcher(TRYCODE&& trycode, HANDLER&& handler) { - return seh_catcher( - std::forward<TRYCODE>(trycode), - seh_filter, - std::forward<HANDLER>(handler)); + return catcher(std::forward<TRYCODE>(trycode), + common_filter, + std::forward<HANDLER>(handler)); } // monadic variant specifies try(), assumes default filter and handler template <typename TRYCODE> -auto seh_catcher(TRYCODE&& trycode) +auto catcher(TRYCODE&& trycode) { - return seh_catcher( - std::forward<TRYCODE>(trycode), - seh_filter, - seh_rethrow); + return catcher(std::forward<TRYCODE>(trycode), rethrow); } -// SEH exception filtering for use in __try __except -// Separates C++ exceptions from C SEH exceptions -// Todo: might be good idea to do some kind of seh_to_msc_wrapper(function, ARGS&&); -U32 ll_seh_filter( - std::string& stacktrace, - std::function<U32(U32, struct _EXCEPTION_POINTERS*)> filter, - U32 code, - struct _EXCEPTION_POINTERS* exception_infop); -U32 seh_filter(U32 code, struct _EXCEPTION_POINTERS* exception_infop); -void seh_rethrow(U32 code, const std::string& stacktrace); +[[noreturn]] void rethrow(U32 code, const std::string& stacktrace); #else // not LL_WINDOWS ----------------------------------------------------- template <typename TRYCODE, typename FILTER, typename HANDLER> -auto seh_catcher(TRYCODE&& trycode, FILTER&&, HANDLER&&) +auto catcher(TRYCODE&& trycode, FILTER&&, HANDLER&&) { return std::forward<TRYCODE>(trycode)(); } template <typename TRYCODE, typename HANDLER> -auto seh_catcher(TRYCODE&& trycode, HANDLER&&) +auto catcher(TRYCODE&& trycode, HANDLER&&) { return std::forward<TRYCODE>(trycode)(); } template <typename TRYCODE> -auto seh_catcher(TRYCODE&& trycode) +auto catcher(TRYCODE&& trycode) { return std::forward<TRYCODE>(trycode)(); } #endif // not LL_WINDOWS ----------------------------------------------------- +} // namespace LL::seh +} // namespace LL + #endif /* ! defined(LL_LLEXCEPTION_H) */ diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 1535d00d50..12cd5320b8 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -163,7 +163,7 @@ HGLRC SafeCreateContext(HDC &hdc) GLuint SafeChoosePixelFormat(HDC &hdc, const PIXELFORMATDESCRIPTOR *ppfd) { - return seh_catcher(ChoosePixelFormat(hdc, ppfd)); + return LL::seh::catcher([hdc, ppfd]{ return ChoosePixelFormat(hdc, ppfd); }); } //static diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index 1f2ebd5092..765599bb82 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -387,7 +387,7 @@ bool LLFeatureManager::loadGPUClass() F32 gbps; try { - gbps = seh_catcher(gpu_benchmark); + gbps = LL::seh::catcher(gpu_benchmark); } catch (const std::exception& e) { diff --git a/indra/test/test.cpp b/indra/test/test.cpp index d1c65d6aa7..64ee702124 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -656,7 +656,7 @@ int main(int argc, char **argv) // a chained_callback subclass must be linked with previous mycallback->link(); - seh_catcher( + LL::seh::catcher( // __try [test_group] { |