diff options
| -rw-r--r-- | .github/workflows/build.yaml | 35 | ||||
| -rw-r--r-- | indra/llcommon/always_return.h | 16 | ||||
| -rw-r--r-- | indra/llcommon/llcoros.cpp | 55 | ||||
| -rw-r--r-- | indra/llcommon/llexception.cpp | 44 | ||||
| -rw-r--r-- | indra/llcommon/llexception.h | 114 | ||||
| -rw-r--r-- | indra/llcommon/tests/llleap_test.cpp | 2 | ||||
| -rw-r--r-- | indra/llwindow/llwindowwin32.cpp | 13 | ||||
| -rw-r--r-- | indra/newview/llappviewerwin32.cpp | 36 | ||||
| -rw-r--r-- | indra/newview/llfeaturemanager.cpp | 36 | ||||
| -rw-r--r-- | indra/test/test.cpp | 75 | 
10 files changed, 246 insertions, 180 deletions
| diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 903d54210e..d5b4fab380 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,13 +21,19 @@ jobs:      runs-on: ubuntu-latest      outputs:        release_run: ${{ steps.setvar.outputs.release_run }} +      branch:   ${{ steps.which-branch.outputs.branch }} +      relnotes: ${{ steps.which-branch.outputs.relnotes }}      env:        # Build with a tag like "Second_Life#abcdef0" to generate a release page        # (used for builds we are planning to deploy). +      # Even though inputs.release_run is specified with type boolean, which +      # correctly presents a checkbox, its *value* is a GH workflow string +      # 'true' or 'false'. If you simply test github.event.inputs.release_run, +      # it always evaluates as true because it's a non-empty string either way.        # When you want to use a string variable as a workflow YAML boolean, it's        # important to ensure it's the empty string when false. If you omit || '', -      # its value when false is "false", which is interpreted as true. -      RELEASE_RUN: ${{ (github.event.inputs.release_run || github.ref_type == 'tag' && startsWith(github.ref_name, 'Second_Life')) && 'Y' || '' }} +      # its value when false is "false", which (again) is interpreted as true. +      RELEASE_RUN: ${{ (github.event.inputs.release_run != 'false' || (github.ref_type == 'tag' && startsWith(github.ref_name, 'Second_Life'))) && 'Y' || '' }}      steps:        - name: Set Variable          id: setvar @@ -35,6 +41,12 @@ jobs:          run: |            echo "release_run=$RELEASE_RUN" >> "$GITHUB_OUTPUT" +      - name: Determine source branch +        id: which-branch +        uses: secondlife/viewer-build-util/which-branch@v2 +        with: +          token: ${{ github.token }} +    build:      needs: setvar      strategy: @@ -56,8 +68,6 @@ jobs:      outputs:        viewer_channel: ${{ steps.build.outputs.viewer_channel }}        viewer_version: ${{ steps.build.outputs.viewer_version }} -      viewer_branch:  ${{ steps.which-branch.outputs.branch }} -      relnotes:       ${{ steps.which-branch.outputs.relnotes }}        imagename: ${{ steps.build.outputs.imagename }}      env:        AUTOBUILD_ADDRSIZE: 64 @@ -151,19 +161,12 @@ jobs:          if: env.BUILD && runner.os == 'Windows'          run: choco install nsis-unicode -      - name: Determine source branch -        id: which-branch -        if: env.BUILD -        uses: secondlife/viewer-build-util/which-branch@v2 -        with: -          token: ${{ github.token }} -        - name: Build          id: build          if: env.BUILD          shell: bash          env: -          AUTOBUILD_VCS_BRANCH: ${{ steps.which-branch.outputs.branch }} +          AUTOBUILD_VCS_BRANCH: ${{ needs.setvar.outputs.branch }}            RUNNER_OS: ${{ runner.os }}          run: |            # set up things the viewer's build.sh script expects @@ -426,7 +429,11 @@ jobs:    release:      needs: [setvar, build, sign-and-package-windows, sign-and-package-mac]      runs-on: ubuntu-latest -    if: needs.setvar.outputs.release_run +    # action-gh-release requires a tag (presumably for automatic generation of +    # release notes). Possible TODO: if we arrive here but do not have a +    # suitable tag for github.sha, create one? If we do that, of course remove +    # this == 'tag' condition. +    if: needs.setvar.outputs.release_run && github.ref_type == 'tag'      steps:        - uses: actions/download-artifact@v4          with: @@ -460,7 +467,7 @@ jobs:              Build ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}              ${{ needs.build.outputs.viewer_channel }}              ${{ needs.build.outputs.viewer_version }} -            ${{ needs.build.outputs.relnotes }} +            ${{ needs.setvar.outputs.relnotes }}            prerelease: true            generate_release_notes: true            target_commitish: ${{ github.sha }} 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 aa8eca7d90..7512bac88d 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -123,7 +123,7 @@ LLCoros::LLCoros():      // Previously we used      // boost::context::guarded_stack_allocator::default_stacksize();      // empirically this is insufficient. -    mStackSize(1024*1024), +    mStackSize(512*1024),      // mCurrent does NOT own the current CoroData instance -- it simply      // points to it. So initialize it with a no-op deleter.      mCurrent{ [](CoroData*){} } @@ -155,7 +155,7 @@ void LLCoros::cleanupSingleton()          // don't use llcoro::suspend() because that module depends          // on this one          // This will yield current(main) thread and will let active -        // corutines run once +        // coroutines run once          boost::this_fiber::yield();      }      printActiveCoroutines("after pumping"); @@ -286,55 +286,6 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl      return name;  } -namespace -{ - -#if LL_WINDOWS - -static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific - -U32 exception_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop) -{ -    if (code == STATUS_MSC_EXCEPTION) -    { -        // C++ exception, go on -        return EXCEPTION_CONTINUE_SEARCH; -    } -    else -    { -        // handle it -        return EXCEPTION_EXECUTE_HANDLER; -    } -} - -void sehandle(const LLCoros::callable_t& callable) -{ -    __try -    { -        callable(); -    } -    __except (exception_filter(GetExceptionCode(), GetExceptionInformation())) -    { -        // convert to C++ styled exception -        // Note: it might be better to use _se_set_translator -        // if you want exception to inherit full callstack -        char integer_string[512]; -        sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); -        throw std::exception(integer_string); -    } -} - -#else  // ! LL_WINDOWS - -inline void sehandle(const LLCoros::callable_t& callable) -{ -    callable(); -} - -#endif // ! LL_WINDOWS - -} // anonymous namespace -  // Top-level wrapper around caller's coroutine callable.  // Normally we like to pass strings and such by const reference -- but in this  // case, we WANT to copy both the name and the callable to our local stack! @@ -348,7 +299,7 @@ void LLCoros::toplevel(std::string name, callable_t callable)      // run the code the caller actually wants in the coroutine      try      { -        sehandle(callable); +        LL::seh::catcher(callable);      }      catch (const Stop& exc)      { diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp index c0154a569f..107fdc2b2d 100644 --- a/indra/llcommon/llexception.cpp +++ b/indra/llcommon/llexception.cpp @@ -15,7 +15,12 @@  #include "llexception.h"  // STL headers  // std headers +#include <iomanip> +#include <sstream>  #include <typeinfo> +#if LL_WINDOWS +#include <excpt.h> +#endif // LL_WINDOWS  // external library headers  #include <boost/exception/diagnostic_information.hpp>  #include <boost/exception/error_info.hpp> @@ -29,7 +34,6 @@  // On Windows, header-only implementation causes macro collisions -- use  // prebuilt library  #define BOOST_STACKTRACE_LINK -#include <excpt.h>  #endif // LL_WINDOWS  #include <boost/stacktrace.hpp> @@ -94,25 +98,47 @@ void annotate_exception_(boost::exception& exc)  // For windows SEH exception handling we sometimes need a filter that will  // separate C++ exceptions from C SEH exceptions -static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific +static constexpr U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific +static constexpr U32 STATUS_STACK_FULL    = 0xC00000FD; -U32 msc_exception_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop) +void LL::seh::fill_stacktrace(std::string& stacktrace, U32 code)  { -    const auto stack = to_string(boost::stacktrace::stacktrace()); -    LL_WARNS() << "SEH Exception handled (that probably shouldn't be): Code " << code -        << "\n Stack trace: \n" -        << stack << LL_ENDL; +    // 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 = to_string(boost::stacktrace::stacktrace()); +    } +} +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      { -        // handle it +        // This is a non-C++ exception, e.g. hardware check. +        // Pass control into the handler block.          return EXCEPTION_EXECUTE_HANDLER;      }  } +void LL::seh::rethrow(U32 code, const std::string& stacktrace) +{ +    std::ostringstream out; +    out << "Windows exception 0x" << std::hex << code; +    if (! stacktrace.empty()) +    { +        out << '\n' << stacktrace; +    } +    LLTHROW(Windows_SEH_exception(out.str())); +} +  #endif //LL_WINDOWS diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h index 68e609444e..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> @@ -102,14 +103,115 @@ void crash_on_unhandled_exception_(const char*, int, const char*, const std::str       log_unhandled_exception_(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, CONTEXT)  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 LLException +{ +    Windows_SEH_exception(const std::string& what): LLException(what) {} +}; + +namespace LL +{ +namespace seh +{ + +#if LL_WINDOWS //------------------------------------------------------------- + +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); +} + +template <typename TRYCODE, typename FILTER, typename HANDLER> +auto catcher_inner(std::string& stacktrace, +                   TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler) +{ +    __try +    { +        return std::forward<TRYCODE>(trycode)(); +    } +    __except (filter_(stacktrace, +                      std::forward<FILTER>(filter), +                      GetExceptionCode(), GetExceptionInformation())) +    { +        return always_return<decltype(trycode())>( +            std::forward<HANDLER>(handler), GetExceptionCode(), stacktrace); +    } +} + +// 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)); +} -#if LL_WINDOWS +// 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 catcher(TRYCODE&& trycode, 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 catcher(TRYCODE&& trycode) +{ +    return catcher(std::forward<TRYCODE>(trycode), rethrow); +} + +[[noreturn]] void rethrow(U32 code, const std::string& stacktrace); + +#else  // not LL_WINDOWS ----------------------------------------------------- + +template <typename TRYCODE, typename FILTER, typename HANDLER> +auto catcher(TRYCODE&& trycode, FILTER&&, HANDLER&&) +{ +    return std::forward<TRYCODE>(trycode)(); +} + +template <typename TRYCODE, typename HANDLER> +auto catcher(TRYCODE&& trycode, HANDLER&&) +{ +    return std::forward<TRYCODE>(trycode)(); +} + +template <typename TRYCODE> +auto catcher(TRYCODE&& trycode) +{ +    return std::forward<TRYCODE>(trycode)(); +} -// 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 msc_exception_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop); +#endif // not LL_WINDOWS ----------------------------------------------------- -#endif //LL_WINDOWS +} // namespace LL::seh +} // namespace LL  #endif /* ! defined(LL_LLEXCEPTION_H) */ diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index fa48bcdefd..3fb25b4cef 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -35,7 +35,7 @@  // causes Windows abdominal pain such that it later fails code-signing in some  // mysterious way. Entirely suppressing these LLLeap tests pushes the failure  // rate MUCH lower. Can we re-enable them with a smaller data size on Windows? -const size_t BUFFERED_LENGTH =  100*1024; +const size_t BUFFERED_LENGTH = 1023*1024;  #else // not Windows  const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte of data diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index d6b93b93d9..12cd5320b8 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -163,18 +163,7 @@ HGLRC SafeCreateContext(HDC &hdc)  GLuint SafeChoosePixelFormat(HDC &hdc, const PIXELFORMATDESCRIPTOR *ppfd)  { -    __try -    { -        return ChoosePixelFormat(hdc, ppfd); -    } -    __except (EXCEPTION_EXECUTE_HANDLER) -    { -        // convert to C++ styled exception -        // C exception don't allow classes, so it's a regular char array -        char integer_string[32]; -        sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); -        throw std::exception(integer_string); -    } +    return LL::seh::catcher([hdc, ppfd]{ return ChoosePixelFormat(hdc, ppfd); });  }  //static diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 4c90a82fcb..a13e4de308 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -400,17 +400,10 @@ void ll_nvapi_init(NvDRSSessionHandle hSession)      }  } -//#define DEBUGGING_SEH_FILTER 1 -#if DEBUGGING_SEH_FILTER -#   define WINMAIN DebuggingWinMain -#else -#   define WINMAIN wWinMain -#endif - -int APIENTRY WINMAIN(HINSTANCE hInstance, -                     HINSTANCE hPrevInstance, -                     PWSTR     pCmdLine, -                     int       nCmdShow) +int APIENTRY wWinMain(HINSTANCE hInstance, +                      HINSTANCE hPrevInstance, +                      PWSTR     pCmdLine, +                      int       nCmdShow)  {      // Call Tracy first thing to have it allocate memory      // https://github.com/wolfpld/tracy/issues/196 @@ -559,27 +552,6 @@ int APIENTRY WINMAIN(HINSTANCE hInstance,      return 0;  } -#if DEBUGGING_SEH_FILTER -// The compiler doesn't like it when you use __try/__except blocks -// in a method that uses object destructors. Go figure. -// This winmain just calls the real winmain inside __try. -// The __except calls our exception filter function. For debugging purposes. -int APIENTRY wWinMain(HINSTANCE hInstance, -                     HINSTANCE hPrevInstance, -                     PWSTR     lpCmdLine, -                     int       nCmdShow) -{ -    __try -    { -        WINMAIN(hInstance, hPrevInstance, lpCmdLine, nCmdShow); -    } -    __except( viewer_windows_exception_handler( GetExceptionInformation() ) ) -    { -        _tprintf( _T("Exception handled.\n") ); -    } -} -#endif -  void LLAppViewerWin32::disableWinErrorReporting()  {      std::string executable_name = gDirUtilp->getExecutableFilename(); diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index 99139e6528..765599bb82 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -40,6 +40,7 @@  #include "llappviewer.h"  #include "llbufferstream.h" +#include "llexception.h"  #include "llnotificationsutil.h"  #include "llviewercontrol.h"  #include "llworld.h" @@ -377,33 +378,6 @@ bool LLFeatureManager::parseFeatureTable(std::string filename)  F32 gpu_benchmark(); -#if LL_WINDOWS - -F32 logExceptionBenchmark() -{ -    // FIXME: gpu_benchmark uses many C++ classes on the stack to control state. -    //  SEH exceptions with our current exception handling options do not call -    //  destructors for these classes, resulting in an undefined state should -    //  this handler be invoked. -    F32 gbps = -1; -    __try -    { -        gbps = gpu_benchmark(); -    } -    __except (msc_exception_filter(GetExceptionCode(), GetExceptionInformation())) -    { -        // HACK - ensure that profiling is disabled -        LLGLSLShader::finishProfile(false); - -        // convert to C++ styled exception -        char integer_string[32]; -        sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); -        throw std::exception(integer_string); -    } -    return gbps; -} -#endif -  bool LLFeatureManager::loadGPUClass()  {      if (!gSavedSettings.getBOOL("SkipBenchmark")) @@ -413,14 +387,12 @@ bool LLFeatureManager::loadGPUClass()          F32 gbps;          try          { -#if LL_WINDOWS -            gbps = logExceptionBenchmark(); -#else -            gbps = gpu_benchmark(); -#endif +            gbps = LL::seh::catcher(gpu_benchmark);          }          catch (const std::exception& e)          { +            // HACK - ensure that profiling is disabled +            LLGLSLShader::finishProfile(false);              gbps = -1.f;              LL_WARNS("RenderInit") << "GPU benchmark failed: " << e.what() << LL_ENDL;          } diff --git a/indra/test/test.cpp b/indra/test/test.cpp index cbd1077306..cc2c130072 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -36,6 +36,7 @@  #include "linden_common.h"  #include "llerrorcontrol.h" +#include "llexception.h"  #include "lltut.h"  #include "chained_callback.h"  #include "stringize.h" @@ -63,11 +64,6 @@  #pragma warning (pop)  #endif -#include <boost/scoped_ptr.hpp> -#include <boost/shared_ptr.hpp> -#include <boost/make_shared.hpp> -#include <boost/foreach.hpp> -  #include <fstream>  void wouldHaveCrashed(const std::string& message); @@ -176,10 +172,6 @@ public:      LLTestCallback(bool verbose_mode, std::ostream *stream,                     std::shared_ptr<LLReplayLog> replayer) :          mVerboseMode(verbose_mode), -        mTotalTests(0), -        mPassedTests(0), -        mFailedTests(0), -        mSkippedTests(0),          // By default, capture a shared_ptr to std::cout, with a no-op "deleter"          // so that destroying the shared_ptr makes no attempt to delete std::cout.          mStream(std::shared_ptr<std::ostream>(&std::cout, [](std::ostream*){})), @@ -215,6 +207,8 @@ public:      virtual void group_started(const std::string& name) {          LL_INFOS("TestRunner")<<"Unit test group_started name=" << name << LL_ENDL;          *mStream << "Unit test group_started name=" << name << std::endl; +        mGroup = name; +        mGroupTests = 0;          super::group_started(name);      } @@ -227,6 +221,7 @@ public:      virtual void test_completed(const tut::test_result& tr)      {          ++mTotalTests; +        ++mGroupTests;          // If this test failed, dump requested log messages BEFORE stating the          // test result. @@ -314,12 +309,15 @@ public:          super::run_completed();      } +    std::string mGroup; +    int mGroupTests{ 0 }; +  protected: -    bool mVerboseMode; -    int mTotalTests; -    int mPassedTests; -    int mFailedTests; -    int mSkippedTests; +    bool mVerboseMode{ false }; +    int mTotalTests{ 0 }; +    int mPassedTests{ 0 }; +    int mFailedTests{ 0 }; +    int mSkippedTests{ 0 };      std::shared_ptr<std::ostream> mStream;      std::shared_ptr<LLReplayLog> mReplayer;  }; @@ -647,14 +645,47 @@ int main(int argc, char **argv)      // a chained_callback subclass must be linked with previous      mycallback->link(); -    if(test_group.empty()) -    { -        tut::runner.get().run_tests(); -    } -    else -    { -        tut::runner.get().run_tests(test_group); -    } +    LL::seh::catcher( +        // __try +        [test_group] +        { +            if(test_group.empty()) +            { +                tut::runner.get().run_tests(); +            } +            else +            { +                tut::runner.get().run_tests(test_group); +            } +        }, +        // __except +        [mycallback](U32 code, const std::string& /*stacktrace*/) +        { +            static std::map<U32, const char*> codes = { +                { 0xC0000005, "Access Violation" }, +                { 0xC00000FD, "Stack Overflow" }, +                // ... continue filling in as desired +            }; + +            auto found{ codes.find(code) }; +            const char* name = ((found == codes.end())? "unknown" : found->second); +            auto msg{ stringize("test threw ", std::hex, code, " (", name, ")") }; + +            // Instead of bombing the whole test run, report this as a test +            // failure. Arguably, catching structured exceptions should be +            // hacked into TUT itself. +            mycallback->test_completed(tut::test_result( +                mycallback->mGroup, +                mycallback->mGroupTests+1, // test within group +                "unknown",                 // test name +                tut::test_result::ex,      // result: exception +                // we don't have to throw this exception subclass to use it to +                // populate the test_result struct +                Windows_SEH_exception(msg))); +            // we've left the TUT framework -- finish up by hand +            mycallback->group_completed(mycallback->mGroup); +            mycallback->run_completed(); +        });      bool success = (mycallback->getFailedTests() == 0); | 
