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

Feature suggestion: C++ exception formatting #16326

Closed
hoodmane opened this issue Feb 17, 2022 · 10 comments · Fixed by #16343
Closed

Feature suggestion: C++ exception formatting #16326

hoodmane opened this issue Feb 17, 2022 · 10 comments · Fixed by #16343

Comments

@hoodmane
Copy link
Contributor

hoodmane commented Feb 17, 2022

If there is an uncaught C++ exception, it prints a message like Uncaught 13777136, which isn't super useful. I want a function to convert the pointer into a more user friendly message like:
CppException myexception: My exception happened
or:
CppException int: An object of type int at address 15738976 was thrown as a C++ exception

This seems like something that could be added to Emscripten. WDYT? Also, I have no clue how wasm-exceptions work and whether the code would need to be different for that case, but if you are interested in adding a feature like this, I would be willing to figure it out.

I wrote the following code for this:

Details

C++

#include <exception>
#include <typeinfo>
using namespace std;

extern "C"
{

  const char* exc_what(exception& e) { return e.what(); }

  const std::type_info* exc_type() { return &typeid(exception); }

  const char* exc_typename(std::type_info* type) { return type->name(); }
}

Typescript

function cppExceptionInfo(ptr: number): [string, boolean, number]{
  const base_exception_type = Module._exc_type();
  const ei = new Module.ExceptionInfo(ptr);
  const caught_exception_type = ei.get_type();
  const stackTop = Module.stackSave();
  const exceptionThrowBuf = Module.stackAlloc(4);
  Module.HEAP32[exceptionThrowBuf / 4] = ptr;
  const exc_type_name = Module.demangle(
    Module.UTF8ToString(Module._exc_typename(caught_exception_type))
  );
  const is_exception_subclass = !!Module.___cxa_can_catch(
    base_exception_type,
    caught_exception_type,
    exceptionThrowBuf
  );
  const adjusted_ptr = Module.HEAP32[exceptionThrowBuf / 4];
  Module.stackRestore(stackTop);
  return [exc_type_name, is_exception_subclass, adjusted_ptr];
}

function convertCppException(ptr: number): CppException {
  const [exc_type_name, is_exception_subclass, adjusted_ptr] = cppExceptionInfo(ptr);
  let msg;
  if (is_exception_subclass) {
    // If the ptr inherits from exception, we can use exception.what() to
    // generate a message
    const msgPtr = Module._exc_what(adjusted_ptr);
    msg = Module.UTF8ToString(msgPtr);
  } else {
    msg = `The exception is an object of type ${exc_type_name} at address ${ptr}  was thrown as a C++ exception`;
  }
  return new CppException(exc_type_name, msg);
}
@hoodmane hoodmane changed the title Feature suggestion / question: C++ exception formatting Feature suggestion: C++ exception formatting Feb 17, 2022
@sbc100
Copy link
Collaborator

sbc100 commented Feb 18, 2022

Seems like a good idea yes.

One problem we have is that we don't normally control the exception handler on a given web-page so it would still be the responsibility of the page author use these helpers to format the exception (I think).

But having the helpers available certainly makes sense to me.

@hoodmane
Copy link
Contributor Author

it would still be the responsibility of the page author use these helpers to format the exception

Yes this makes sense to me too.

I can open a PR then. Would the C++ code for this go under system/lib somewhere? And the javascript code can go in src/library_exceptions.js? How is it controlled which system libraries are compiled in?

@sbc100
Copy link
Collaborator

sbc100 commented Feb 18, 2022

We always prefer native code where possible. Rule of thumb is to only call out the JS if it can't be done in native.

Somewhere in system/lib makes sense yes..

@hoodmane
Copy link
Contributor Author

hoodmane commented Feb 18, 2022

Well this functionality is meant to be called from Javascript I think, so the outermost layer has to be in Javascript. We could do most of the stuff in a function like:

extern "C" char *format_exception(void *exc);

and then we would still need a Javascript wrapper to convert the return value:

function formatException(ptr){
    var msg_ptr = _format_exception(ptr);
    var msg = UTF8ToString(msg_ptr);
    _free(msg_ptr);
    return msg;
}

@sbc100
Copy link
Collaborator

sbc100 commented Feb 18, 2022

Sounds like that sounds reasonable yes.

(If you use stackAlloc and pass in the buffer size you can avoid pulling in a malloc/free dependency).

@hoodmane
Copy link
Contributor Author

I think I can't avoid malloc altogether without also avoiding __cxa_demangle:
Demangling routine. ABI-mandated entry point in the C++ runtime library for demangling.

See the __cxa_demangle docs:

Parameters:
__mangled_name – A NUL-terminated character string containing the name to be demangled.
__output_buffer – A region of memory, allocated with malloc, of *__length bytes, into which the demangled name is stored. If __output_buffer is not long enough, it is expanded using realloc. __output_buffer may instead be NULL; in that case, the demangled name is placed in a region of memory allocated with malloc.
__length – If __length is non-null, the length of the buffer containing the demangled name is placed in *__length.
__status – If __status is non-null, *__status is set to one of the following values: 0: The demangling operation succeeded. -1: A memory allocation failure occurred. -2: mangled_name is not a valid name under the C++ ABI mangling rules. -3: One of the arguments is invalid.

Returns:
A pointer to the start of the NUL-terminated demangled name, or NULL if the demangling fails. The caller is responsible for deallocating this memory using free. The demangling is performed using the C++ ABI mangling rules, with GNU extensions. For example, this function is used in __gnu_cxx::__verbose_terminate_handler. See https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html for other examples of use.

@sbc100
Copy link
Collaborator

sbc100 commented Feb 18, 2022

Fair enough. BTW, why do you need to call __cxa_demangle?

@sbc100
Copy link
Collaborator

sbc100 commented Feb 18, 2022

IIRC, __cxa_demangle is a fairly costly function in terms of code size, so big that we don't include it by default and you need to opt in with -sDEMANGLE_SUPPORT.

@hoodmane
Copy link
Contributor Author

Well I think the nicest thing would be to have throw std::runtime_error("some_message") result in a message like:

Uncaught C++ exception std::runtime error: some_message

To get std::runtime error I can use std::type_info.name() but it gives the mangled name, so it would say:

Uncaught C++ exception _ZSt13runtime_error: some_message

@sbc100
Copy link
Collaborator

sbc100 commented Feb 18, 2022

Well I think the nicest thing would be to have throw std::runtime_error("some_message") result in a message like:

Uncaught C++ exception std::runtime error: some_message

To get std::runtime error I can use std::type_info.name() but it gives the mangled name, so it would say:

Uncaught C++ exception _ZSt13runtime_error: some_message

Sounds good!

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

Successfully merging a pull request may close this issue.

2 participants