-
Notifications
You must be signed in to change notification settings - Fork 36
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
WebAssembly.Exception is not a subclass of JS Error #183
Comments
I was under the understanding that WebAssembly.Exception was not a subclass of JS's Error class. |
@eqrion Ah, yeah you're correct. I guess then this should be retitled to should it be a subclass of JS Error? I'd expect developers would want to know the stack from which their exception originated when they get an uncaught exception. |
I believe this was intentional and discussed in some other threads, but it's hard for me to find definitive statements on it. This comment seems relevant. If that's the case and still valid, it may be good to formally state that somewhere. |
Yeah, I can see the argument for not gathering stack traces eagerly. On the other hand, I'd guess there's a significant number of developers that want those stack traces for diagnostic reasons. Perhaps we should have a way to natively create an exception with a stack trace? I suppose an alternative would be to pass a JS Error object as one of the tag's parameters but that still requires calling to JS. |
Note that in V8 there is already a way to opt-out of stack trace capture by setting the "Error.stackTraceLimit" property to 0: https://v8.dev/docs/stack-trace-api. This is not standardized though, but if other engines provide a similar functionality I think that's a very strong argument in favor of making wasm exceptions inherit from JS Error. This provides an easy and efficient way to enable/disable stack trace collection for wasm exceptions. |
There is a fundamental 'problem' with automatically collecting stack traces for wasm exceptions: the information may be useful in the case of diagnosing an unhandled exception; but many languages have very specific requirements for what stack trace information to collect and these cannot currently be handled in the EH proposal without considerable heavy lifting. (E.g., Java's stack trace is for the entire stack at the point of creating the exception (in terms of methods called), Python lets you control how much of a trace to generate, C# normally records a stack trace when the exception is thrown) |
One thing I'd like to make clear is that the current spec doesn't mandate stack traces generation now. So the current problem is more about figuring out how we are going to get stack traces and not how we make the engine faster in face of generating the stack traces. Also I don't think we can standardize the stack trace format from our JS API. By the way I just found out even |
While I don't think we should mandate stack trace generation, there are several levels to it. Higher number means less mandatory.
3-a. We make (The formatting here seems messed up, sorry, not sure how to fix) Searching the repo for previous discussions, I found #150 (comment). @takikawa @rossberg Any opinions on this? |
At a high level I really would like to be able to support both the use case of getting diagnostic info from uncaught (or not-immediately-caught) exceptions (which as @kmiller68 mentioned, includes messages as well as stack traces) and also the use case of using EH for control flow which would be harmed by taking stack traces on every throw. IMO that means there should be some way 1) to throw from wasm without any stack trace or message generation, and also 2) to have an I think that for Emscripten it would actually be OK to to call out to JS code to throw C++ exceptions (i.e. instead of using the wasm So all that is to say, tl:dr I like something like option 3-b, with a non-tracing |
I generally agree with what @dschuff said. I think whether I propose we choose one of these two options:
(These are basically some adaptation of 3-b and 3-c in my previous comment.) Both options would probably need that toolchains provide an option for stack traces, and if it's on, wasm code uses an imported host function (which throws I'd appreciate any opinions on the preference of either of these options. Also if you have another alternative not listed here I'd appreciate that too. |
I don't have a strong opinion either way, but I strongly disagree with this:
Authors should be able to rely on the same code working across browsers, whether that's with or without subclassing or stack traces. |
When the issue of stack traces was discussed long ago, I was told that a key requirement was that tooling not have to differ depending on whether stack traces were enabled or disabled, even if that difference was just a change to what import gets provided to the module (rather than the much more extensive difference suggested above). If that requirement is no longer in place, then we can consider making stack traces an explicit part of the payload. In the JS API, one can indicate that a specific In addition to giving applications a lot more control over a feature that can have a 6x impact on performance, making stack traces explicit enables us to greatly simplify the proposal. At present, the difference between rethrowing an exception and throwing an exception with the same tag and payload is just the stack trace. So by making this implicit difference explicit, we can eliminate the need for the most complicating aspects of this proposal: Those simplifications also make addressing #158 and #185 easy as there are no longer instructions whose placement is severely restricted. The impact of this change would be minimal on engines because, besides removing instructions, it only affects the JS API. There would be more substantial changes required of tooling, but it sounds such changes likely need to happen anyways. |
|
The following was a design suggestion for just the JS API:
|
It is observable in JS (e.g. through their prototypes) whether two classes are identical, in a subclass relation, or unrelated. So I agree with @Ms2ger that we cannot get away with not specifying it one way or the other. |
Regarding the subclass relationship: I agree that we should specify the browser behavior to the extent possible (modulo existing issues such as the format of the stack trace text). One thing we could do is say that the thrown objects have e.g. a [edit: I originally considered an idea where the VM could generate a call to user JS code to collect the trace or not, but all of those ideas have downsides, so I deleted that part]. So given that I think I still prefer a version of the above paragraph; specifically do the following 2 things:
|
I'm not sure my suggestion was understood. There was no automatic collection of stack traces or special imports. But I thought of a refinement of that suggestion which happens to line up with @dschuff's lazy semantics, so hopefully that's an indication that we're converging. When you define a new exception tag, in addition to specifying its type This provides a lot of flexibility. For example, a module could implement this conversion function by calling an imported function that constructs some subclass of This flexibility would be especially useful for interop. For example, in GC languages we're very likely to see exceptions like This also seems a lot more portable across hosts. Using an import in order to throw an exception (rather than using the |
What I’d like to achieve as a bottom line is
I think we can achieve these objectives in either case.
WebAssembly.Exception.prototype = Error.prototype;
var e = new WebAssembly.Exception(...);
e.name = "...";
e.message = "...";
e.stack = new Error().stack;
throw e; Something like this will make the object behave like JS This patching of the Exception object would work for Emscripten for now (given that mixing Emscripten-generated wasm with other languages is rare) but obviously isn’t ideal. I do think it would make sense to improve interoperability in a follow-up change, and the current proposal wouldn’t prevent that. |
I think this is an interesting new design problem. This may not be relevant only to exceptions; this can be a question of a general conversion interface between languages' reference types and So I think this warrants more exploration and is too complex a problem to discuss within this issue that discusses a simple yes/no question in JS API. I think a follow-on proposal is a better way to explore this issue more.
I think we are talking about JS API here, and JS has
In cases where exceptions are for error handling, it is very likely that the actual throw will be inside a language library function, along with other tasks that the language library needs to do. So the throw itself will be very rare, and optimization for it won’t matter. In cases where throws are common as a form of general control flow (where optimization would make a big difference), a toolchain would likely just use the bare throw instruction since it wouldn't want to collect a stack trace. |
This sounds reasonable.
This doesn't sound so satisfying, but is perhaps fine for an MVP, and we might do something nicer once we have better support for strings in wasm.
I realized this won't work as the jsapi spec is currently written. Consider a JS function
|
Okay, I'll get that process started.
The original exception object is not dropped by the JS API. Rather, |
Your reading is incorrect. |
Can you clarify? This was the explicit rationale I was given for having |
It's simply not what the spec says. I'm not sure how to spec anything that relies on the identity of an exception inside wasm anyway, without |
Ah, thanks for the pointer. It seems that the spec has strayed from what @aheejin had communicated before. It also seems that the formal spec cannot express @aheejin's suggested JS API for the reasons you pointed out. The approach I suggested above works with the formal spec as is. Otherwise we'll have to revise the formal spec (and engine implementations might need to change accordingly). @aheejin, what would you like? |
This pretty well sums up what I want too.
I agree, and I actually also like @RossTate's idea of a conversion callback, which would allow 2) to happen transparently; i.e. user code (JS catch or onerror handlers) could always get an But even when we do that, I do think that we still want to try to get the property that when we throw a |
I guess the most straightforward way to spec it would be to attach an |
There's another problem related to this issue. C++ has a What I expect these systems will inevitably do is what the libc++abi you're using expects you do to: convert foreign exceptions into internal exceptions (using some form of wrapping/proxies). For example, Kotlin will have some (unexposed) subclass of So the JS API that people looking for good interop with JS are going to want is one that makes it easy to do this conversion. With the JS API strategy y'all are using, they'll have to wrap every import on the JS side with a function that catches exceptions and rethrows them as the internal exception. Except they'll need to define these wrappers before the exception tag to use has been defined (since that's created during instantiation). Thus they'll have to jump through many hoops to handle what is likely the common use case (especially if they want to use EcmaScript Modules). A more broad way to convert exceptions in a custom manner would be much more usable. There are a few ways I can see that being done, but I'll table that for now. Doing so will make both |
I think this would make perfect sense actually. It matches the way we have been thinking of exceptions the entire time (i.e. exceptions have an identity, which is preserved by rethrow), and it matches the way that exceptions are implemented in engines in practice.
A foreign wasm exception can be caught in JS, just as it can in wasm. If the tag isn't exported to JS, then it will effectively be opaque, just like catching a foreign exception in wasm. (Assuming you stick to the documented interface and don't try to introspect the properties on the
As I said above, I do actually like the idea of facilitating these conversions. I think we should explore this soon. We'll have to balance making sure we think it will work well with wasm GC (predicting the future, since it's still fairly early for that spec), with getting something out soon that can be useful with linear memory programs. In the meantime I think there is still value in an "MVP" API that has the basic reflections of exception tags, identity, and payloads of non-foreign exceptions (i.e. those exported/imported tags) into JS. I don't think it prevents us from making it much nicer and more transparent in the future. |
@dschuff Let me clarify the intent of those two quotes:
To see why each of these are important to various languages compiling to the web, consider the following C++ program:
With the present tooling, exceptions thrown by With my suggestion of custom js-to-wasm exception conversion, one can add support for catching foreign exceptions (and rethrowing them) with just a few focused changes. In fact, one can even add a dynamically mutable configuration option for switching between different semantics for foreign exceptions: bypass unwinding altogether, unwind but do not catch, and unwind and catch. With the current JS API for js-to-wasm exception conversion, you have to make significant changes to the entire infrastructure. For example, you cannot simply import and use the |
OK thanks, that does make it clearer. Actually, would you mind maybe writing up something about this in a standalone issue? Given that this involves the VM calling user code implicitly (something I don't think we have anywhere in wasm/JS yet?) I would be interested in hearing what VM implementers think about that idea. [edit: I guess the |
Can do (next week 😄)! Thanks. |
As I said in the previous comment, I think reference conversion API is a whole new design issue, which would better be solved in a separate proposal. I don't think the current MVP proposal precludes potential future improvements. I also asked that we discuss different topics in different issues. This issue was discussing a very narrow JS API problem, so I'm not sure if this post is a good place for a whole new conversion API discussion. Can we discuss this topic in another issue? The new repo for the conversion API proposal will even be a better place. |
Filed #189 for preserving the JS exception identity; please file issues if there's other changes to be proposed. |
And I filed #190 so that we'd have a separate space to discuss custom conversions, as requested. |
Great! |
Got it. And, to also make sure we're all on the same page, once such an extension to the JS API for EH is made, none of the most complex instructions— |
Language developers will use whatever instructions make their lives easier. We at least don't have any plans to change C++ codegen even in the presence of this extension. |
Okay, trying to get caught up on this discussion. Here are the important details I'm seeing.
All of this being said, how would this proposal sound?
A second option is to just do nothing, leaving |
TL:DR; I'd be OK with this proposal if other folks would be.
I think this is all correct. I think V8 has a similar mechanism for displaying rich stack traces when
This is close to what I had in mind above. I think I have a mild preference for using some kind of flag in the JS-API constructor, in order to have a cross-platform way to control the stack trace behavior. I agree that it could be tricky to specify given that the underlying semantics are not specified; the language might end up being a little weasel-y like the current spec for how to spell stack frames. But it would move us a small step in the right direction toward more consistency across engines.
Yes, the advantage of this (aside from an even simpler JS spec) would be that we wouldn't end up with another kind of
If a toolchain appended a stack trace to its
This could easily be fixed by the developer, but of course doing nothing is easier than having to do something. |
Thanks @eqrion and @dschuff for the summary! So assuming we resolve the identity issue (#189), to sum up the three alternatives:
I don't have strong preferences, but if I have to pick one, I would like 2, just because with 1, in some engines, we may not be able to disable stack traces when creating an exception from JS API. Of course, we can always create a function that contains a bare wasm Also I'm not sure how much of a trouble would it be that
|
Sorry for the delay in continuing this discussion. I solicited some more opinions internally and received some more thoughts. On further thought, making On the philosophical level, there's a symmetry between JS an Wasm that would be nice to have. JS allows you to throw any value and it's not always an 'error' (represented by That all being said, I would now prefer @aheejin's (3) option (no subclassing). Thinking about stack tracing more, I think it would likely be fine to have a flag in the constructor for |
Thanks @eqrion! Sorry for the delay too. I think what you said makes sense; it can be risky to assume every If we make that option, I think the option should be false by default. If the option is on, the VM has an option of populating some JS Error properties like In case we don't make the boolean option for stack traces, it is entirely up to the toolchain to create and populate those fields when it thinks it is appropriate. For example, Emscripten, which is a C/C++ compiler, can choose to populate the fields, because in C++ exceptions mean errors. But possibly in toolchains for other languages, they choose not to do that in case a language uses Wasm exceptions simply for control flow transfers and not errors. I don't have a strong preference at this point. Please let me know if you incline towards either. |
Yes, I think a boolean argument to the [LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
interface Exception {
constructor(Tag exceptionTag, sequence<any> payload, optional boolean traceStack);
readonly attribute (DOMString or undefined) stack;
any getArg(Tag exceptionTag, unsigned long index);
boolean is(Tag exceptionTag);
};
Additionally, I think we should exclude the non-stack properties like message and name for now.
I don't think we need to worry about non-web VMs here, as this is just a JS-API feature and so I think we can assume a web VM. |
Refining the WebIDL snippet further from feedback, we should avoid a positional optional boolean parameter to prevent a future extension hazard.
|
This updates the explainer on - We add stack trace option to `Exception` class in the JS API - `Exception` can contain an optional `externref` to carry a stack trace - `Exception` is not a subclass of JS `Error` Addresses WebAssembly#183 and WebAssembly#189.
This updates the explainer on - We add stack trace option to `Exception` class in the JS API - `Exception` can contain an optional `externref` to carry a stack trace - `Exception` is not a subclass of JS `Error` The WebIDL syntax is suggested by @eqrion in WebAssembly#183 (comment) and WebAssembly#183 (comment). Addresses WebAssembly#183 and WebAssembly#189.
This updates the explainer on - We add stack trace option to `Exception` class in the JS API - `Exception` can contain an optional `externref` to carry a stack trace - `Exception` is not a subclass of JS `Error` The WebIDL syntax is suggested by @eqrion in #183 (comment) and #183 (comment). Addresses #183 and #189.
Since
WebAssembly.Exception
is a JS subclass of JS's Error class it seems like it should be possible to provide themessage
andcause
properties from the constructor. Currently, theWebAssembly.Exception
constructor only takes theWebAssembly.Tag
and the tag's parameters. Is the inability to provide amessage
orcause
intentional or an oversight?The text was updated successfully, but these errors were encountered: