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

throw exception with message in cpp #6330

Closed
robfors opened this issue Mar 12, 2018 · 67 comments · Fixed by #18003
Closed

throw exception with message in cpp #6330

robfors opened this issue Mar 12, 2018 · 67 comments · Fixed by #18003

Comments

@robfors
Copy link
Contributor

robfors commented Mar 12, 2018

I have a cpp file test.cpp:

#include <stdio.h>
#include <iostream>
#include <emscripten/bind.h>
#include <emscripten/val.h>

int main()
{
  throw std::invalid_argument("test error");
  
  printf("\ndone\n");
  return 0;
}

Compiled with emcc --bind -fexceptions -std=c++11 test.cpp -o test.js

I would like the code to print out the exception message in some way so the user has a description of the error.
When I run it I get exception thrown: 5246984 and Uncaught 5246984.
It does not appear as though emscripten will print any distinguishable message.
Does emscripten have this feature?

If not, I was thinking of adding a thow method to val that would accept a message and possibly a javascript exception class.

@robfors robfors changed the title throw exception in cpp throw exception with message in cpp Mar 12, 2018
@kripken
Copy link
Member

kripken commented Mar 13, 2018

I don't think we have such a feature. In general I think people just add a try-catch at the highest level and print it from C++. If there's a way to add that feature it would be nice, but I'm not sure how easy it would be.

@jleni
Copy link

jleni commented Apr 16, 2018

@kripken so there is no way of propagating the exception to js? Is there any way in which it could be wrapped or something?

@kripken
Copy link
Member

kripken commented Apr 16, 2018

I can't think of an easy way. But the thrown exception is a pointer, so in theory you could call from JS into C++ code like this, and give it that pointer:

// gets an exception object, and prints it out.
void print_exception(void* exception) {
  try {
    throw exception;
  } catch (...) {
    std::cout << exception.what() << '\n';
  }
}

I think that might work, but I'm not sure.

@jleni
Copy link

jleni commented Apr 16, 2018

ok, I didnt know that was an actual pointer!
I will play a bit with the idea and write back, thanks!

kripken pushed a commit that referenced this issue Oct 30, 2018
As mentioned in #6330.
Something like this will now work:

val::global("TypeError").new_("wrong type").throw_();

In the tests found in test_val.cpp it can be seen that an error can be thrown in CPP and successfully caught in JS.
Beuc pushed a commit to Beuc/emscripten that referenced this issue Nov 17, 2018
As mentioned in emscripten-core#6330.
Something like this will now work:

val::global("TypeError").new_("wrong type").throw_();

In the tests found in test_val.cpp it can be seen that an error can be thrown in CPP and successfully caught in JS.
@tuananh
Copy link

tuananh commented Aug 14, 2019

is it still open? i would like to know how to throw exception from c++ to js as well.

@Shachlan
Copy link
Contributor

Shachlan commented Dec 22, 2019

Hi, this is the current solution I use:

In C++:

namespace foo {
std::string getExceptionMessage(int exceptionPtr) {
  return std::string(reinterpret_cast<std::exception *>(exceptionPtr)->what());
}
}

EMSCRIPTEN_BINDINGS(fooBinding) {
emscripten::function("getExceptionMessage", &foo::getExceptionMessage);
};

In JS:

try {
  ... // some code that calls webassembly
} catch (exception) {
  console.error(Module.getExceptionMessage(exception));
} finally {
  ...
}

I guess you can also throw an exception from an EM_ASM block, if you wanted to rethrow c++ to JS.

@artemjackson
Copy link

Any updates?

@kripken
Copy link
Member

kripken commented Apr 29, 2020

I think adding an option like @Shachlan 's from the previous comment might be a good idea, if @Shachlan or someone else wants to submit a PR.

@Shachlan
Copy link
Contributor

@kripken, sure. I'm not well versed in the Emscripten project organization - where would you prefer something like this to be added?

@kripken
Copy link
Member

kripken commented May 1, 2020

@Shachlan Good question, I'm not sure. Perhaps just adding an entry in the docs is enough, like in the debugging docs at ./site/source/docs/porting/Debugging.rst?

@Shachlan
Copy link
Contributor

Shachlan commented May 3, 2020

#11073

@ChrisChiasson
Copy link

Printing exception.what() seems to be covered in this answer without needing to enable exception catching inside C++ and without using embind:
https://stackoverflow.com/a/33743704/581848
This post explains why exception catching at the C++ level kills performance under wasm:
https://brionv.com/log/2019/10/24/exception-handling-in-emscripten-how-it-works-and-why-its-disabled-by-default/

@ChrisChiasson
Copy link

ChrisChiasson commented May 21, 2020

For the purposes of unit testing C++ compiled with emscripten and run under node.js, which does not have window.onerror like the browser solutions above, I was attempting to use something like this:

struct Exception{
 static std::string what(intptr_t p){
  return reinterpret_cast<std::runtime_error*>(p)->what();
 }
 /**ctor*/
 Exception(){
#ifdef __EMSCRIPTEN__
 EM_ASM(({
  console.log("This gets called and gives false:",
   process.hasUncaughtExceptionCaptureCallback()
   );
  //window.addEventListener('error',
  //process['on']('unhandledRejection',
  //process['on']('uncaughtExceptionMonitor',
  process['on']('uncaughtException',
   ()=>{console.log("This never gets called.");
    console.error(Module.Exception.what(e.error));
   });
 }),0);
#endif
 };
};

#ifdef __EMSCRIPTEN__
EMSCRIPTEN_BINDINGS(CRANE){
 emscripten::class_<Exception>("Exception")
  .class_function("what",&Exception::what);
}
#endif

I instantiate it as a C++ static variable inside the ctor body of another class I wrote for setting up and running tests, so that Exception() only gets called once, and thus only after main. However, in node.s, the console.log inside uncaughtException will never get printed. I always get something like what the original poster received: exception thrown: 5246984

I believe the reason why is as follows. In parseTools.js, the function makeThrow(what) returns a string 'throw' + what + ';', where what seems to come from one of three spots in library_exceptions.js and is the address of the exception (i.e. the value of the pointer). So, I created a simple one-line node.js script where I threw a number to replicate this behavior, and its output was completely different:

C:\msys64\home\chris.chiasson\wCRANE>node tmp/tmp.js

C:\msys64\home\chris.chiasson\wCRANE\tmp\tmp.js:1
throw 593485;
^
593485

I am also able to catch that throw via process.on('uncaughtException',...). So, knowing the weird behavior was not coming from node itself, I grepped all of the emsdk folder for exception thrown, and three spots stood out, along with two commits.

Essentially, these code structures in Emscripten are preventing the global error handlers in node from ever firing. In my opinion, one way to solve it would be to actually register Emscripten's top level error handlers using the node.js process.on mechanism (or window.addEventListener in-brower). That way, Emscripten's handlers would still fire, but we would also be free to add our own straight from within our c++ unit tests and makefiles without any other special steps or source files required (other than prefixing Emscripten wasm/javascript unit tests with the name of the node executable, whereas in straight C++, we'd call the executable directly). This would also prevent the added complexity of integrating emrun and the browser into standard unit tests. What do y'all think?

@stale
Copy link

stale bot commented Jun 2, 2021

This issue has been automatically marked as stale because there has been no activity in the past year. It will be closed automatically if no further activity occurs in the next 30 days. Feel free to re-open at any time if this issue is still relevant.

@stale stale bot added the wontfix label Jun 2, 2021
@ChrisChiasson
Copy link

ChrisChiasson commented Jun 2, 2021

The reason why this has been an issue is that emscripten does not always allow exceptions to reach the global error handlers of the environments in which it runs (specifically node.js), as mentioned in the last paragraph of my previous post (and cited within #11073). This is why it is so hard to get unit testing working with plain node.js and why people end up with so many workarounds (like #11073). Good job "stale bot" (sarcasm).

@stale stale bot removed the wontfix label Jun 2, 2021
@sbc100
Copy link
Collaborator

sbc100 commented Jun 3, 2021

@ChrisChiasson let me see if understand what you are doing and the behaviour you are asking for.

Here is my understand if your setup.

  1. You are running tests where each test is its own executable (rather than a single executable which runs a sequence of unit tests)
  2. Each test executable can fail by thrown an uncaught C++ exception.
  3. Under emscripten + node.js today when this happens the process will (correctly) exit with a non-zero status (so the failure os registered) but you would a better error message than the default one which is exception thrown: 5246984.

Is that correct?

I think we can figure out a way to make that work.

@ChrisChiasson
Copy link

If I remember correctly, these lines are key:

  //window.addEventListener('error',
  //process['on']('unhandledRejection',
  //process['on']('uncaughtExceptionMonitor',
  process['on']('uncaughtException',

Yes, and if the tests are run in the browser and the window.addEventListener line is uncommented (and the process[on] lines are commented), then the rest of the inline javascript in the EM_ASM block will print the what of the exception. However, for the equivalent facility under Node.js, emscripten seems to intercept the exception before it ever gets to the javscript engine. My post was an attempt to explain that if the current code would just stop intercepting everything before the top level handler (such as uncaughException) got ahold of it, then emscripten would still be able to handle those events normally... because it could leverage uncaughtException to properly register its own handler via process['on',...], which would have the side effect of allowing developers to register their handlers normally via process['on',...]... as I was trying to do here.

@ChrisChiasson
Copy link

  1. You are running tests where each test is its own executable (rather than a single executable which runs a sequence of unit tests)

Yes, a test suite would be its own executable, usually testing an entire class (see this self-test cpp file @ bottom of this gist for a non-emscripten version of what I normally use... The only difference with emscripten is that I would add a static member to the hpp to instantiate my Exception struct from this issue #6330 once, so no js files are needed). In the case of an uncaught (and therefore probably unexpected) exception during the test suite, a normal C++ program would just die and print the "what" of the exception... Although it is possible for some kind of non-standard exception to be thrown that may not even have a "what" (and actually, I think the base C++ exception class doesn't have it), so we need to allow developers to specify custom behavior.

@sbc100
Copy link
Collaborator

sbc100 commented Jun 9, 2021

Regarding the what method: It does look like all C++ exceptions have that what method: https://www.cplusplus.com/reference/exception/exception/what/.

It sounds like the problem is the difference between native and wasm here when an unhandled C++ exceptions reaches the outer scope:

Native: Prints the what of the exception.
Emscripten: Prints the pointer value of the exception.

If we could make emscripten print the what value in the same way that native does would that solve your issue?

@ChrisChiasson
Copy link

Speifically, emscripten intercepts the exception before it gets to the official error handler for node process['on']('uncaughtException',...). It would make more sense if emscripten would hook into the node error handler, so that devs could also hook into the same handler (similar to how in browsers devs are able to hook into window.onerror / window.addEventListener("error",...) ). For the tests I was working on, sure, printing the "what" would have been ok, but other devs will probably still need access to the exception itself in order to handle it appropriately, and may not want the "what" printed at all. On the std::exception base class, you are right that the "what" is present (on gcc for the base class it "helpfully" just prints "std::exception", which I only half remembered).

@RReverser
Copy link
Collaborator

I believe that with wasm EH proposal the stack trace is automatically tracked

Hmm, that's actually exactly what I'm trying to understand. When you say it's "automatically tracked" do you mean it's captured as part of C++ object? Or is the exception represented as anyref?

If it's the last one and the exception already contains the correct stacktrace, then things are even simpler - we can unconditionally (regardless of settings) do

catch (err) {
  err.message = ...;
  throw err;
}

from JS side at such boundaries, and then it would be completely seamless - the original stacktrace would be preserved, and only the message would be updated to human-readable variant.

@RReverser
Copy link
Collaborator

Okay I just tried and I see that it does preserve the stack at the original throw point in C++:

#include <emscripten.h>

#include <iostream>

EMSCRIPTEN_KEEPALIVE
int foo() {
  try {
    throw std::runtime_error("hello");
  } catch (...) {
    std::cout << "caught and rethrowing" << std::endl;
    throw;
  }
}
> Module.__Z3foov()
temp:1237 caught and rethrowing
​ Uncaught WebAssembly.Exception: wasm exception
    at _Unwind_RaiseException (http://localhost:5000/temp.wasm:wasm-function[1585]:0x21fd5)
    at __cxa_rethrow (http://localhost:5000/temp.wasm:wasm-function[1583]:0x21f88)
    at foo() (http://localhost:5000/temp.wasm:wasm-function[13]:0xef2)
    at Object.__Z3foov (http://localhost:5000/temp.js:1519:22)
    at <anonymous>:1:8

So this looks pretty promising, the only missing bits are 1) wrapping of each export at least for main and Embind and 2) actually propagating the .what() and setting the .message.

@sbc100
Copy link
Collaborator

sbc100 commented Sep 14, 2021

Right that makes sense. I was just checking that throw err does indeed preserve the stack trace.

I was previously confused because we were loosing stacktraces, but it turns out we were holding it wrong here: #15042

@RReverser
Copy link
Collaborator

RReverser commented Sep 14, 2021

Yeah it does. E.g. if I quickly try something like this:

> try { Module.__Z3foov() } catch (e) { e.message = 'custom updated message'; throw e }
caught and rethrowing
VM719:1 Uncaught WebAssembly.Exception: custom updated message
    at _Unwind_RaiseException (http://localhost:5000/temp.wasm:wasm-function[1585]:0x21fd5)
    at __cxa_rethrow (http://localhost:5000/temp.wasm:wasm-function[1583]:0x21f88)
    at foo() (http://localhost:5000/temp.wasm:wasm-function[13]:0xef2)
    at Object.__Z3foov (http://localhost:5000/temp.js:1519:22)
    at <anonymous>:1:14

You can see the message got updated but original stacktrace is preserved.

@aheejin
Copy link
Member

aheejin commented Sep 15, 2021

I'm still not sure why we need to wrap every export site. We can make a library function, such as $print_exception_info as I suggested in #6330 (comment) and add it in libc++abi to be called form JS, and if the calling JS function wants to print some messages, can't it just call the function, like

try {
  some_wasm_function();
} catch (e) {
  print_exception_info(e);
}

Not sure why we do a similar thing for every single export entry by default.

And about the stack traces, the Wasm EH does not mandate embedding stack traces when throwing exceptions, but all web VMs that currently implement the proposal (V8 and FF) do. The relevant explainer's paragraph is here. We didn't make it mandatory at the proposal level because it was considered the area of embedders and there were concerns that some non-web VMs that use exceptions to implement something lightweight may not want to embed them.

Note that the embedded stack trace is not the same thing as the whole stack is preserved; we include the stack traces using string, and the stack has been already unwound at that point.

@RReverser
Copy link
Collaborator

Not sure why we do a similar thing for every single export entry by default.

Because it provides much better development experience - both during debugging and in production - than "exception thrown: 1234". Development experience is something that we are currently actively trying to improve in Emscripten, as bad DX usually discourages developers from using a toolchain in the first place.

@RReverser
Copy link
Collaborator

Also note that last two examples you posted are with print_exception_info, but this proposal / issue is not about printing exceptions, but augmenting them with useful human-readable messages.

The exception flow itself doesn't change for the consumer - from their standpoint, it's still an exception thrown from Wasm, and they can choose whether to print it, to ignore it, to report to a 3rd-party service like Sentry or something else.

@aheejin
Copy link
Member

aheejin commented Sep 15, 2021

Not sure why we do a similar thing for every single export entry by default.

Because it provides much better development experience - both during debugging and in production - than "exception thrown: 1234". Development experience is something that we are currently actively trying to improve in Emscripten, as bad DX usually discourages developers from using a toolchain in the first place.

I don't think I was suggesting to print "exception thrown: 1234" instead of some helpful message?

I was saying, without wrapping every export call site, I think we can give the JS caller the capability to print the helpful error message. I said two things: One is we can wrap main with try-catch and print the info. The other is, for other export entries, we can provide a library function that prints what() of the exception so that JS caller can call it if it wants.

Also note that last two examples you posted are with print_exception_info, but this proposal / issue is not about printing exceptions, but augmenting them with useful human-readable messages.

The function doesn't have to be print, but it can be get. To pass strings I guess we need some conversion between JS and C, but it's doable.

@RReverser
Copy link
Collaborator

I was saying, without wrapping every export call site, I think we can give the JS caller the capability to print the helpful error message.

Ah okay, and what I'm saying is that the best way to expose that message is via standard Error.message mechanism. I just don't want us to hide it under another Emscripten-specific setting or API, but rather make Emscripten better "citizen" on regular Web and expose those things in a way that matches expectations of existing callers in JS land.

That is, I want those exceptions have Error.message that resolves to the result of C++ .what() where possible.

The only way to achieve without significant perf overhead that that I could come up with is to wrap each Embind export + main in such try-catch as above. But then, I don't know much about Wasm EH, which is why I'm asking all those questions - if there's a better way to achieve same smooth integration, I'm all for it.

@aheejin
Copy link
Member

aheejin commented Sep 16, 2021

I see, and I think that makes sense. Is there a way to do that automatically in every export site? I'm not familiar with embind myself..

@sbc100
Copy link
Collaborator

sbc100 commented Sep 16, 2021

I see, and I think that makes sense. Is there a way to do that automatically in every export site? I'm not familiar with embind myself..

Yes, we have way in emscripten of adding wrappers around exports which we already do in certain circumstances.

@kripken
Copy link
Member

kripken commented Sep 17, 2021

Here is where asyncify adds wrappers for all exports:

instrumentWasmExports: function(exports) {

@RReverser
Copy link
Collaborator

RReverser commented Sep 17, 2021

Here is where asyncify adds wrappers for all exports

I was thinking that we could limit it to Embind exports, since that would 1) limit wrappers to C++ code and not C where we know exceptions don't exist so we don't need to catch them and 2) limit it to explicitly Embind-exposed methods where users already expect value conversions and some associated overhead.

Do you think we should wrap every exported function instead?

@sbc100
Copy link
Collaborator

sbc100 commented Sep 17, 2021

If the wrapping is cheap I don't see why we wouldn't just wrap all explicitly exported functions. I don't see why embind should be special here.

@RReverser
Copy link
Collaborator

I don't see why embind should be special here.

My motivation is mostly because currently it's the method for exposing C++ code, and C++ code is the only place where C++ exceptions can originate, so it feels like a natural boundary / annotation for the wrapping.

I guess it's possible that some C++ code is exposed via C FFI, but in those scenarios exceptions are usually exposed via C style get_last_error functions too instead of propagating across FFI boundary as-is.

@sbc100
Copy link
Collaborator

sbc100 commented Sep 17, 2021

I guess that makes sense yes. I guess it would be easy to limit the wrapping to just embind exports.

OTOH, if exceptions are enabled there is no reason they can't be thrown when normal (non-embind) exports are called directly and it might be confusing for users if uncaught exceptions only sometimes have useful messages on them.

@RReverser
Copy link
Collaborator

RReverser commented Sep 17, 2021

there is no reason they can't be thrown when normal (non-embind) exports are called directly

Right, that's what I meant by C FFI for C++ codebase.

Or are you saying there are users who call C++ methods directly from JS via their mangled names with no binding layer in between, like Module.__Z6squarei(10)?

@aheejin
Copy link
Member

aheejin commented Sep 17, 2021

@RReverser I'm not familiar with embind, but does that also take care of exceptions escaping main? Or that should be handled separately?

The other thing is, if we use SjLj support using Wasm EH (#14976), longjmps will surface as exceptions as well. Longjmps don't have a helpful message, but it'd be good to just at last print "longjmp".. But I just checked in native Linux and unmatched longjmps surface as a segmentation fault there, so it's not great there either.

@RReverser
Copy link
Collaborator

but does that also take care of exceptions escaping main? Or that should be handled separately?

Yeah no, main does need to be handled separately.

The other thing is, if we use SjLj support using Wasm EH (#14976), longjmps will surface as exceptions as well. Longjmps don't have a helpful message, but it'd be good to just at last print "longjmp"..

Ah that's an interesting point, makes sense. I'm not sure how to distinguish longjmps from other exceptions though? Will they have a special class like std::exception does?

@RReverser
Copy link
Collaborator

RReverser commented Sep 17, 2021

To be clear - I'm not opposed to wrapping all exports either, it just seemed wasteful to wrap C functions (including somewhat frequently called things like _malloc and _free) when all we are interested in are C++ exceptions and methods.

Plus, it's usually easier to start off with a limited surface and expand later as necessary.

However, if there are cases where we want to wrap C functions, that makes sense too.

@aheejin
Copy link
Member

aheejin commented Sep 17, 2021

Ah that's an interesting point, makes sense. I'm not sure how to distinguish longjmps from other exceptions though? Will they have a special class like std::exception does?

No we give that a separate tag. So if we want to distinguish it it would be something like

try
  main code
catch __cpp_exception
  Augment object with C++ exception info string
catch __c_longjmp
  Augment object with "longjmp"
end

@kripken
Copy link
Member

kripken commented Sep 17, 2021

I think it might be ok to wrap all exports. That's simpler, and it would help users that do not use embind. A common case might be a problem with no bindings, just exported main and callbacks to render a frame etc.

@RReverser
Copy link
Collaborator

just exported main and callbacks to render a frame etc.

Right, but if it's callbacks, those exceptions would propagate through main anyway, and would be wrapped too. (btw, probably something we need to account for - make sure we don't try to double-wrap an exception in such scenarios)

aheejin added a commit to aheejin/emscripten that referenced this issue Oct 6, 2022
After emscripten-core#17979, when `ASSERTIONS` is set, uncaught exceptions carry stack
traces that are printed to the screen for debugging help. This adds
an exception message to the exception objects in addition to that.

The message it adds is produced by `__get_exception_message` function:
https://github.com/emscripten-core/emscripten/blob/f6c46570e3780e52050bf822a07b342ec4bdddbe/system/lib/libcxxabi/src/cxa_exception_emscripten.cpp#L75-L111

If an exception is a subclass of `std::exception`, it returns
`std::exception::what()`. If not, it just prints its type.

If an exception is uncaught and its type is a subclass of
`std::exception`, now we print the `what()` message along with the stack
trace when `ASSERTION` is set. In case our exception is
`std::runtime_error` and the message is "my exception", when uncaught,
this prints:
```
exiting due to exception: [object WebAssembly.Exception],Error:
std::runtime_error,my exception
    at __cxa_throw (wasm://wasm/009a7c9a:wasm-function[1551]:0x24367)
    ...
```

Fixes emscripten-core#6330.
aheejin added a commit that referenced this issue Oct 10, 2022
After #17979, when `ASSERTIONS` is set, uncaught exceptions carry stack
traces that are printed to the screen for debugging help. This adds
an exception message to the exception objects in addition to that.

The message it adds is produced by `__get_exception_message` function:
https://github.com/emscripten-core/emscripten/blob/f6c46570e3780e52050bf822a07b342ec4bdddbe/system/lib/libcxxabi/src/cxa_exception_emscripten.cpp#L75-L111

If an exception is a subclass of `std::exception`, it returns
`std::exception::what()`. If not, it just prints its type.

If an exception is uncaught and its type is a subclass of
`std::exception`, now we print the `what()` message along with the stack
trace when `ASSERTIONS` is set. In case our exception is
`std::runtime_error` and the message is "my exception", when uncaught,
this prints:
```
exiting due to exception: [object WebAssembly.Exception],Error:
std::runtime_error,my exception
    at __cxa_throw (wasm://wasm/009a7c9a:wasm-function[1551]:0x24367)
    ...
```

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

Successfully merging a pull request may close this issue.