Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal]: Enable full C++ Exception support via "Dynamically Registering C++ Exception Handlers" proposal #24

Open
NHDaly opened this issue Nov 4, 2019 · 2 comments
Labels
enhancement New feature or request

Comments

@NHDaly
Copy link

NHDaly commented Nov 4, 2019

Hello! I heard about Boden on Cppcast (https://cppcast.com/marcus-tobias/), and it sounds really cool! :)

I love the idea of a pure C++ library that embraces modern C++, and provides native UIs for the target environments. To me, this is the right solution for building UIs in C++, so all my best wishes and support! :)

This issue is a proposal to support a key feature of C++ that has, as far as I know, never been well supported in a widely used C++ GUI framework: native C++ exception handling.

TL;DR: My 2013 (😅) undergraduate honors thesis proposes a mechanism to properly support user code throwing arbitrary exceptions in a C++ application framework, and I believe this work could be easily added to Boden! The idea is essentially to have user code dynamically register exception handlers by exception type, and have the framework implement a central handler function that matches the caught exception to the correct handler. This solution places minimal burden on the app developer.

Boden is attempting to be a modern C++17 GUI framework, and good, modern C++ code often throws exceptions, so I think having a great story around exception handling would be really neat.

The paper can be found here:
"Dynamically Registering C++ Exception Handlers: Centralized Handling of C++ Exceptions in Application Framework Libraries"
https://deepblue.lib.umich.edu/handle/2027.42/102769

Unfortunately, this was my first academic paper, so it is quite rambly. Sorry about that! You might enjoy simply scanning the code in the Appendix, which contains a simple example implementation of this idea.


I wasn't able to find any information on exception handling in your documentation, so I might be mistaken, but glancing through the source code I think these ideas could be relevant. Boden is slightly less of an easy candidate for this than other C++ gui frameworks could be, because the main application run-loop (a convenient place to put the exception handling) isn't in your code; it's of course instead in either Apple's or Android's code. However, by placing try/catch wrappers around a few key callback points in the Boden code, I think this could still be valuable.

The goal of the work in the paper is to prevent users from having to repeat their exception-handling boiler-plate across every one of their callbacks. For example, a purely custom-written terminal UI application might have code that looks like this:

int main() {
  while (true) { // loop forever until the program exits
    try {
      auto cmd = read_user_input_cmd();

      if (cmd == ACTION__X) {
        do_x_action(read_x_input());
      } else if (/* ... */) { /* ... */ }

      // ...

    } catch (const BadInputException& e) {
      cout << "Error: Got bad input `" << e.got << "`, expected `" << e.expected << "`." << endl;
    } catch (/*...*/) { /*...*/ }
  }
  return 0;
}

But for a GUI program, where the developer is implementing callbacks to hook-up their functionality to GUI actions, there is no central place for the developer to put the exception handlers, so they may have to repeat that handling logic across many different callbacks, which is especially annoying if there are several different types that the developer wants to handle.

The paper essentially proposes having developers dynamically register exception handlers per-exception-type with the GUI framework, which the framework can then match exceptions against whenever an exception escapes user code. The crux of this approach relies on a somewhat weird trick where the GUI framework's handler repeatedly rethrows the exception in a new try-block, where it can be caught by the user's registered dynamic handlers.

It's essentially taking advantage of this (somewhat surprising) behavior:

try {
  throw /* ...Something... */; // 0. exception object is allocated here.
}
catch(...) {
  // 1. From here, the exception type is unknown.
  try { // 2. so we enter a new try-block to try again to find a good handler.
    throw; // 3. rethrow the exception (the runtime still know's the exceptions type even though code in this `...` block doesn't have access to it.)
  }
  catch(A &e) {
    // 4. the original exception was of type A.
  }
  catch(B &e) {
    // 5. the original exception was of type B.
  }
  catch(...) {
    // 6. we still do not know the type of the exception.
  }
} // 7. the exception object is deallocated here.

The only addition is that you repeat this try/re-throw/catch sequence in a loop, once for each dynamically registered handler, calling their try_handle_exception function, which is templated for the correct exception type.


AAAAAANNNNNYYYYYYYWAY, sorry for the long post! I just got excited when hearing about Boden in the podcast, and wanted to share the ideas in that paper because it's rare to find a relevant audience! 😊

Unfortunately, I'm not working on C++ much anymore these days (instead I'm working almost exclusively in julia, and I love it!) but i'm happy to chat with you about these ideas and/or maybe help you implement them in my spare time if it's something you're interested in!

Cheers! All the best,
~Nathan Daly

@gitoby gitoby added the enhancement New feature or request label Nov 4, 2019
@gitoby
Copy link
Contributor

gitoby commented Nov 4, 2019

Hi Nathan,

Wow, thanks a lot for your praise and the comprehensive proposal! We are honored to hear that you like Boden and even consider contributing.

You are right that we don't have any information on exception handling in our documentation yet. This is something that should definitely be added.

Currently, Boden does in fact not do any special C++ exception handling and it does not offer a mechanism to handle generic exceptions thrown in user code as part of the framework. Such a mechanism could be easily added to platformEntryWrapper(), which is used in all entry points (specialized for each platform). (See framework/foundation/platforms/ios/src/entry.cpp as an example.)

With regard to your proposal, wouldn't it be sufficient to provide a hook method that is called when an unhandled exception is being thrown that users could then implement? I am unsure as to whether a full-blown mechanism for registering different exception handlers is really needed in this case, but maybe I am missing something.

Tobias

@NHDaly
Copy link
Author

NHDaly commented Nov 11, 2019

Hi Tobias! Thanks for your super-quick and thoughtful response!! :) Sorry my reply was so delayed.

Wow, thanks a lot for your praise and the comprehensive proposal! We are honored to hear that you like Boden and even consider contributing.

:)

You are right that we don't have any information on exception handling in our documentation yet. This is something that should definitely be added.

👍

[...] Such a mechanism could be easily added to platformEntryWrapper(), which is used in all entry points (specialized for each platform). (See framework/foundation/platforms/ios/src/entry.cpp as an example.)

Ah, yeah, perfect, that's exactly the kind of place i had in mind. :) Agreed!

With regard to your proposal, wouldn't it be sufficient to provide a hook method that is called when an unhandled exception is being thrown that users could then implement? I am unsure as to whether a full-blown mechanism for registering different exception handlers is really needed in this case, but maybe I am missing something.

Yeah, that's a good suggestion! I think that would be sufficient, and in fact it's what's wxWidgets provides (or provided in 2013, when I wrote the paper 😅 [I discuss it in section 2.2 on page 11]).

The trouble with an approach like that is that it requires users to know how to re-acquire the current exception from inside their callback, essentially requiring them to know about the trick I describe in the OP, which as far as I could tell in 2013 wasn't well documented anywhere. The standard implicitly makes it permissible, but I never saw mention of this explicitly.

To make the above point concrete, if you provide a callback like this:

    void platformEntryWrapper(const std::function<void()> &function, bool canKeepRunningAfterException,
                              void * /*unused*/)
    {
        try {
            function();
        } catch (...) {
            userReference->handle_unknown_exception();  // virtual function or callback or whatever
        }
    }

Then the users would have to know about the try-rethrow-catch trick to handle specific exception types:

    void MyUserCode::handle_unknown_exception() override {
        try {  throw;  }  // Rethrow the exception to get its type
        catch (MyExceptionType &e) {
            cout << e.msg() << endl;  // handle myExceptionType ...
        }
    }

So I think it's reasonable to go with this approach, but if you do, I think you should also include explanation in your documentation about how to use it to capture the type of the exception via the above "trick," since it doesn't seem to be widely known (at least, back when I last checked.) I was surprised that wxWidgets gave no such examples, and as such, the callback seems rather useless. You can know that some exception triggered somewhere, I guess, but not much else.

So my recommendation would be to either go with an "unhandled exception" callback as you suggest, and fully document this try-rethrow-catch mechanism along with it, or to encapsulate this mechanism in your system somehow and provide a "cuter" user-facing API. It could be an API similar to what I proposed in the paper, or maybe there are better alternatives.

(One neat aspect of registration, is that it's dynamic -- it allows run-time registration and deregistration of handlers -- but I'm not actually sure if that's good or bad... 😛 (I could imagine a use-case where at first your handler writes your exceptions to the console, but once some UI has loaded, it registers a new handler that can display the error in the UI to the user?) But I dunno, registering callbacks is always messy, with ordering concerns, and etc, so it might be an overly complicated solution, yeah.)


Hope that's helpful! :)
Best,
~Nathan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants