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

Creating wasm-uncatchable exceptions in Javascript #101

Open
Macil opened this issue Feb 12, 2020 · 19 comments
Open

Creating wasm-uncatchable exceptions in Javascript #101

Macil opened this issue Feb 12, 2020 · 19 comments

Comments

@Macil
Copy link

Macil commented Feb 12, 2020

If a WebAssembly function calls a Javascript function, is it possible for the Javascript function to throw an error that's uncatchable to the WASM function (and may be catchable to the Javascript function that called the WASM function)?

I'm interested in being able to abort sandboxed/untrusted WebAssembly code when it calls into a Javascript function. One case where this is useful is when using https://github.com/ewasm/wasm-metering, which rewrites a WASM binary to keep track of instruction counts and to call an imported function to abort once a limit is hit. It's important that the exception thrown by the called abort function isn't catchable by the WASM. This can be used to deterministically limit the execution time of a WASM function.

(Since wasm-metering already relies on rewriting WASM binaries, I guess it would be possible to make it rewrite them to not catch the "out of gas, stop execution" error, but it would be convenient if there was a simpler option.)


I tried to figure out whether this proposal already had a way to do this, but I had trouble understanding this part:

Filtering these exceptions should be based on a predicate that is not observable by JavaScript. Traps currently generate instances of WebAssembly.RuntimeError, but this detail is not used to decide type.

I think it's saying that traps create special WebAssembly.RuntimeError instances that are uncatchable, and that normally-created WebAssembly.RuntimeError instances are catchable like regular errors. If it were the case that JS-created instances of WebAssembly.RuntimeError (or another class) were uncatchable to WASM, then that would solve my use-case.


It seems like one workaround to accomplish this with the spec as-is would be to have the Javascript function call another WASM function which does nothing but trap, and then let that error bubble up uncatchably through the WASM stack frames. It seems a little weird that only WASM could directly make an uncatchable error like this though, which makes me think I might be missing something.

@aheejin
Copy link
Member

aheejin commented Feb 17, 2020

Your interpretation is correct. Currently the only reliable way to throw an exception that is uncatchable by wasm is to call into a wasm function that does nothing but trap. It is possible to extend our current JS API so that we can generate trap in JS. This should be a different API from WebAssembly.RuntimeError, because that API should create RuntimeError that has a special predicate internally that makes it uncatchable by wasm. This JS API extension, which allows creating/throwing exception and trapping in JS, is one thing I'm currently thinking of, and it might be proposed sometime soon.

@RossTate
Copy link
Contributor

I worry that this is just the first of many issues that will arise from having a catch-all in a low-trust setting...

@aheejin
Copy link
Member

aheejin commented Feb 19, 2020

@RossTate Could you elaborate? Catch-all was basically adopted to make wasm catch foreign exceptions, having the multi-language environments in mind.

@RossTate
Copy link
Contributor

I don't think @Macil's concern is specific to multi-language environments. He wants to be able to call into untrusted code and know that it can't catch his exceptions. His concern is a security application, but there is also a concern about composability. This paper talks about the issues that arise from accidental exception handling, i.e. accidentally handling exceptions that weren't meant for you, and it proposes ideas for addressing it. (To be clear, I'm not proposing those ideas for wasm—I do not even know yet if they make sense for a low-level language—but the discussion of accidental handling seems pertinent regardless.)

Having looked through the discussion (thanks for posting and linking to those minutes!), I got the impression that the reason for having catch-all was that it enabled code reuse and finally-clauses/using-clauses/destructors. I didn't see any discussion of this potential downside, though, which is why I noted it here. Is that an accurate impression? Or did I miss an application or a discussion about the tradeoff with this concern? Sorry to bug you; I'm trying to catch up as quickly as I can.

@aheejin
Copy link
Member

aheejin commented Feb 20, 2020

@RossTate
A bit of history - We had a catch_all from the very first version of the proposals. The rationale behind that is 1. we want capability to catch foreign exceptions 2. sometimes we don't really care what kind of exception it is, because we need to run cleanups (destructors for C++) whenever we catch an exception.

The difference between the first and the current versions is, in the first version, we had both catch [tag] and catch_all, and the exception object was implicit. In the current version, we now have a first-class explicit exnref object and catch_all became the only catch instruction, after which we check for tags using br_on_exn. The reason for this switch was, that we were able to rethrow exception objects only within catch clause was a too much of a restriction to toolchains that generate / transform / reuse code blocks.

And the reason we wanted catch_all from the beginning is, as I mentioned, we want to make sure that the module A's frame and resources are not leaked by an exception that bypasses it without doing any necessary cleanup actions. Allowing a separate class of 'uncatchable' exception means a module cannot call another module that does not trust ever, because that module can throw an uncatchable exception and corrupt the module's frame.

@tlively
Copy link
Member

tlively commented Feb 20, 2020

Without uncatchable exceptions, C++ cannot guarantee that it will be able to handle the exceptions it throws through untrusted frames, so it cannot guarantee that exception objects and their payloads will be cleaned up. But with uncatchable exceptions, no one can throw them through a C++ frame without forcing the C++ module to leak resources because the block that runs the destructors will have been popped off the control flow stack. The solution is for the uncatchable exception to be resumable, so the thrower can return to the throw point and throw a new exception that C++ can catch to run its destructors.

Without resumable exceptions, using uncatchable exceptions would force resource leakage, even if all modules were benevolent. Without uncatchable exceptions, malicious modules could force resource leakage, but benevolent modules would be able to clean up all resources correctly.

Since we cannot have a well-encapsulated, non-leaky world without both resumable and uncatchable exceptions, and because resumable exceptions are out of scope for this proposal, I suggest that we defer uncatchable exceptions to be included in the future resumable exceptions proposal as well. Until both are implemented, WebAssembly in untrusted contexts will not be able to safely throw exceptions across untrusted frames.

@Macil
Copy link
Author

Macil commented Feb 20, 2020

But with uncatchable exceptions, no one can throw them through a C++ frame without forcing the C++ module to leak resources because the block that runs the destructors will have been popped off the control flow stack.
I suggest that we defer uncatchable exceptions to be included in the future resumable exceptions proposal as well.

I'm interested in uncatchable exceptions specifically for the ability to halt everything immediately without the module running cleanup or any other code. I fully expect the module's memory to be left in a bad/leaky state after that, but I don't mind because I'm trying to do the closest thing possible to an in-process SIGKILL on it and then get back to my original JS calling code.

I'm not sure my use-case alone would warrant uncatchable exceptions to exist, but this is already the behavior of WASM trap exceptions, which I assume also had the goal of halting things immediately without any cleanup happening. If this concept of uncatchable exceptions is going to exist, then it might be nice to make this more explicit and make some JS APIs around that, maybe even so the trap error can have a custom message. But if it's likely that WASM trap exceptions will become catchable in this proposal or a future one, and WASM-uncatchable exceptions will stop being a thing, then I could instead do binary rewriting to solve my use-case.

(If WASM moves in the direction of being able to catch all exceptions/traps, then it seems like I would have to make wasm-metering rewrite WASM binaries so that after every catch op, it was checked with br_on_exn to see if the exception was the abort exception, and then rethrow if it was.)

@RossTate
Copy link
Contributor

I'm a little confused because I mentioned that y'all had raised finally/using/destructors as motivators for catch-all, but y'all seem to have countered with resource releasing as the motivator, i.e. the primary application of finally/using/destructors. So I suspect this is just a case of describing the same thing with different, but if that's not the case then please help me understand the distinction you are making.

What's important to note is that none of finally/using/destructors catch all exceptions; in fact all of them catch no exceptions—they clean up resources when an exception is thrown but do not catch the exception itself. So what I'm pointing out is that catch-all is much stronger than necessary to achieve the desired use case and is even perhaps harmfully powerful.

Really what it seems you want is two separate features: exception handling (i.e. catching specific exceptions), and some sort of "on exit" feature (the most general form of which I believe is Scheme/Racket's dynamic-wind, which even works for continuations and algebraic effects).

@tlively
Copy link
Member

tlively commented Feb 20, 2020

@Macil We decided that traps would not be catchable. Does that cover your use case?

@RossTate we’re not countering, we’re agreeing with you and filling in some details! We currently implement landing pads by catching exceptions, running destructors, then rethrowing. I don’t know that we’ve considered a construct that runs when an exception passes through without catching it. Wouldn’t such a construct still break encapsulation by observing an exception passing through? Or does it not matter because the exceptions can’t be inspected or tampered with?

@aheejin
Copy link
Member

aheejin commented Feb 20, 2020

@Macil

I'm interested in uncatchable exceptions specifically for the ability to halt everything immediately without the module running cleanup or any other code. I fully expect the module's memory to be left in a bad/leaky state after that, but I don't mind because I'm trying to do the closest thing possible to an in-process SIGKILL on it and then get back to my original JS calling code.

As @tlively pointed out above, traps are exactly for this use case. Sorry that we don't currently have a convenient way of trapping in JS; this can be solved by adding a corresponding JS API later. But in the meantime, you can call into a wasm function that does nothing but trap, as you first suggested.

@RossTate

What's important to note is that none of finally/using/destructors catch all exceptions; in fact all of them catch no exceptions—they clean up resources when an exception is thrown but do not catch the exception itself. So what I'm pointing out is that catch-all is much stronger than necessary to achieve the desired use case and is even perhaps harmfully powerful.

What you described is composed of multiple steps: catching (or at least detecting?) an exception, running destructors, and rethrowing the exception. And the current proposal provides these low-level constructs. I guess what you meant was in some way similar to the first proposal, with an implicit rethrow attached to the end, like:

catch [tag]
 ...
catch_all
 ...
 (implicit rethrow)
end

This has the same set of problems with our first proposal that we cannot rethrow outside catch_all clause. In practice, the code we need to run before rethrowing may exist outside of these catch or catch_all clauses in case cleanup code is shared, and we can even rethrow within a function called from the current function, which actually is how libc++abi is implemented. This model does not adapt well to all those situations, which was the reason why we switched to the current proposal, in which we can take exnref outside of catch clauses.

@RossTate
Copy link
Contributor

@RossTate we’re not countering, we’re agreeing with you and filling in some details!

Ah, thanks for the clarification (and the additional details)!

Wouldn’t such a construct still break encapsulation by observing an exception passing through? Or does it not matter because the exceptions can’t be inspected or tampered with?

So you're right that information is still being leaked, i.e. that some exception was thrown, and that's important for people to be aware of (though I doubt that they'd be surprised), but it's overall fine because as you say the exception itself would not be tampered with.

I guess what you meant was in some way similar to the first proposal, with an implicit rethrow attached to the end, like:

This seems to be a finally clause.

we can even rethrow within a function called from the current function

I want to table this scenario for now, not because I don't think it needs to be addressed, but because I want to first get everyone on the same page for the "easy" case before adding more complications. My suspicion is that this is actually an orthogonal issue, but we should revisit that suspicion later.

in case cleanup code is shared

Ah, so this is the second motivation that I had noted. I'm hoping you can give me a better understanding of the specifics here. In particular, suppose we had a (familiar) try/catch(tag)... (without catch-all) construct and an (additional) begin/finally construct where the body of the finally clause is always executed, regardless of whether the after clauses finishes normally or whether an exception is thrown in the after clause (in which case the exception is "paused", finally is executed, and the exception is "resumed"). Then would the following pattern handle the code-sharing issue?

after
    (acquire first resources)
    try
        after
            (acquire second resources)
            (main code)
        finally
            (release second resources)
    catch (exc1)
        (handle exception 1, possibly rethrowing it)
    catch (exc2)
        (handle exception 2, possibly rethtrowing it)
finally
    (release first resources)

@tlively
Copy link
Member

tlively commented Feb 21, 2020

@RossTate We've been discussing this at length here in the office, and one of the missing pieces of this discussion is a shared understanding of what security properties are under consideration. The higher level question here is "Do we want to design WebAssembly features so that they can be used across trust boundaries and still preserve X properties?" What is X and why do we care about those properties in particular? I assume X is something like full abstraction or contextual equivalence that has been rigorously defined and justified in the literature.

I hope having an answer to those questions will imply an answer to the following questions: Why should we prefer having the ability to guarantee that thrown exceptions will not be caught by modules in a separate trust domain over having the ability to catch all exceptions that propagate through a particular trust domain? Why privilege the thrower over the catcher?

Yes, a catch-all construct can be created by importing all exception tags from all trust domains in an application, but I don't think that can be represented at the source level in today's languages, so practically there is a real tradeoff of functionality here for applications with a privileged trust domain.

@aheejin
Copy link
Member

aheejin commented Feb 21, 2020

I don't think we have a definition of trust domain in the first place. @RossTate, in your scenario, a module may not be able to drop an exception that does not know the tag for, but it can still drop exceptions they can match with known tags. And tags don't imply anything about trust domains; they were invented primarily for differentiating languages. So I'm not sure if making a module incapable of dropping some exceptions but not others is particularly safer than the current situation.

And I don't think we have a good threat model or definition of what's safe and not safe in the first place. One can argue it is safer if a module is be able to catch all exceptions as it wants and run a handler for that and continue execution, and it should not be disrupted by an untrusted module's exception unwinding through itself. (I'm not necessarily endorsing this definition; what I'm saying is we don't have a definition of what's safe in the first place.)

@jgravelle-google
Copy link
Contributor

I continue to think Interface Types solves everything 🤷‍♂️

In particular, consider that IT has to do something in the share-nothing linking case. It would be very strange to say "I don't trust this module one bit and don't want it to interfere with me whatsoever, but also if it ever traps I should abort instantly." So IT needs to be able to reason about exceptions across module boundaries. (I consider this a good thing: it should be possible to turn a C++ exception into a Rust Result<T> and carry on with that adapted interface)

So to my (obviously biased) view, any non-IT inter-module linking is going to be shared-something linking. And I think it makes sense to have less trust at that layer of wasm. If two modules share memory, one can corrupt the other if they aren't coordinating. If two modules share an exception stack, ditto.

@RossTate
Copy link
Contributor

What is $X$ and why do we care about those properties in particular?

I don't think we have a definition of trust domain in the first place.

These are great questions that admittedly I don't have the answer to. I do know the paper I linked to above has abstraction theorems for exceptions (and algebraic effects) akin to relational parametricity, which has known security benefits, but I do not yet know whether its design is appropriate for or translates well to WebAssembly.

That said, just because we do not know the goal precisely doesn't mean we should throw it away entirely. All this uncertainty makes me all the more compelled to take a conservative approach if we can find one that sufficiently addresses the current pressing considerations in increasing order of importance (as I understand it).

  1. "Top-level" module that needs to prevent "lower-level" modules from bypassing its control flow.

This is addressed fairly obviously by catch-all. But even without a wasm-level catch all, it can be addressed by the host providing a wasm module the capability to do this, since the host can provide a module with a callback to use in order to place the host's catch-all abilities on the stack at the appropriate location. This would be more in line with the principle of least privilege. Furthermore, since we don't really understand this use case well, we can always add functionality that better fits the intended purpose, like a catch-uncaught construct that catches only exceptions that are not caught by someone else further up the stack.

So altogether, while catch-all achieves this consideration, it's not clear to me it does so in a particularly good manner, and it's not even necessary for this consideration.

  1. Code sharing

Y'all never answered whether my pattern above addresses code sharing, but I'm going to operate under the assumption that it does. It occurs to me that there might be code to share across exception handlers (besides releasing of resources). If so, a slightly different design would have a(/the?) catch clause take a list of tags. If not though, then note that we no longer need exnref (and reference counting or garbage collection) for basic catching functionality—you just put the exception arguments on the stack.

Now suppose the handler itself throws an exception (whether explicitly or by some called function). Then the resource releasing still needs to be done. But with catch-all you can't reuse the same resource-releasing code that would be used if no exception were thrown because that code won't redirect to the throw. So now you have to duplicate that code. And since any function called in the handler might throw an exception, you have to have code to catch that potential exception and duplicate that resource-releasing code yet again. On the other hand, the finally design ensures the same body of resource-releasing code will be executed regardless of how control flow exits the main body, making for better code reuse. This seems like a nightmare to generate. It was also a nightmare to write as a programmer, which is why languages with catch-all functionality always eventually add finally.

So altogether, it seems like finally might address this consideration better than catch-all, with furthermore potentially having the added benefit that it doesn't require adding memory management for basic functionality (though advanced functionality still needs discussing).

  1. Releasing of resources

Now for the most important consideration. Unfortunately catch-all bundles exception-handling code with resource-freeing code, making it only work for exceptions (and likely more slowly because the stack-unwinder has to keep putting that (updated?) exnref on the stack even though it will be rethrown many times but handled only once). But exceptions aren't the only situation where resource-freeing code needs to be run—they're just the most familiar situation.

Since Andreas just spoke about it, a less familiar situation is continuations. The basic idea of continuations is to make stacks be just another construct on the heap. In Andreas's lightweight-threads example, he had a stack/continuation for each thread. Now suppose a thread gets discarded because the lightweight system discovered that its work had already been completed. That discarded (i.e. to-be-garbage-collected) thread might have acquired resources that need to be released, but the code for doing so is buried in the stack. Before garbage-collecting that stack, we need to free those resources. In other words, stacks-as-heap-objects, i.e. continuations, have finalizers. The finally clause nicely separates the finalizing code from the exception-handling code, so we can still free all those resources even though no exception was thrown to be handled. (You could try to emulate this by throwing a "stack finalizer" exception to trigger all that resource-releasing code, but what should happen if the exception is caught and not rethrown by a catch-all?)

So altogether, it seems like finally addresses this consideration more efficiently and more generally.

That was a lot, so let me summarize. catch-all seems to be dangerously powerful, and finally seems to better achieve at least the primary considerations while being less (potentially) dangerous. Furthermore, finally is more in line with designs in major languages (including C++) and seems to have faired well. And while many aspects of high-level programming languages don't translate well to low-level languages like WebAssembly, the arguments above do seem to apply just as well to WebAssembly.

@aheejin
Copy link
Member

aheejin commented Feb 21, 2020

@RossTate

What is $X$ and why do we care about those properties in particular?
I don't think we have a definition of trust domain in the first place.

These are great questions that admittedly I don't have the answer to. I do know the paper I linked to above has abstraction theorems for exceptions (and algebraic effects) akin to relational parametricity, which has known security benefits, but I do not yet know whether its design is appropriate for or translates well to WebAssembly.

That said, just because we do not know the goal precisely doesn't mean we should throw it away entirely. All this uncertainty makes me all the more compelled to take a conservative approach if we can find one that sufficiently addresses the current pressing considerations in increasing order of importance (as I understand it).

I feel arguing about what's more conservative without defining security goals does not have much meaning. As I said above, one could very much argue that the ability to catch and handle all exceptions is more conservative and helpful to guard a module from any malicious module out there, especially in case one module does not have full knowledge about all other participants, which can be a probable scenario in the world where wasm becomes popular and people start to use some kind of package managers to download modules.

After all, as @jgravelle-google pointed out, I think each module should resort to Interface Types to guard itself from malicious modules out there.

  1. "Top-level" module that needs to prevent "lower-level" modules from bypassing its control flow.

This is addressed fairly obviously by catch-all. But even without a wasm-level catch all, it can be addressed by the host providing a wasm module the capability to do this, since the host can provide a module with a callback to use in order to place the host's catch-all abilities on the stack at the appropriate location.

The scenario you described about a callback seems rather contrived. Why a module has to rely on the host callback for such a basic capability? And I'll repeat my question above; if catch-and-drop is such a threat, why is making a module incapable of dropping some exceptions but not others is any safer than the current situation? A module can still catch and drop exceptions with a tag it understands.

This would be more in line with the principle of least privilege.

Was this principle agreed on at some point? Isn't Interface Types all about making a module not trust anyone else and giving it capability to provide its own adapter to transform, or coerce, other modules' activity upon it?

Furthermore, since we don't really understand this use case well, we can always add functionality that better fits the intended purpose, like a catch-uncaught construct that catches only exceptions that are not caught by someone else further up the stack.

I'm not very sure if we should change the proposal in a significant way over a use case we don't really understand.

  1. Code sharing

Y'all never answered whether my pattern above addresses code sharing, but I'm going to operate under the assumption that it does. It occurs to me that there might be code to share across exception handlers (besides releasing of resources). If so, a slightly different design would have a(/the?) catch clause take a list of tags. If not though, then note that we no longer need exnref (and reference counting or garbage collection) for basic catching functionality—you just put the exception arguments on the stack.

As I mentioned above, we need explicit exnrefs, which was the whole point of switching to the second proposal. That we only should rethrow within a catch clause is a very severe restriction on code generation, and as I said, there are cases we rethrow not within the current function but within a callee. (__cxa_rethrow in libc++abi is implemented exactly in this way.) You said it's an orthogonal issue that we should revisit later, but I, the toolchain developer, have no later. It's an important concern for the toolchain now.

Now suppose the handler itself throws an exception (whether explicitly or by some called function). Then the resource releasing still needs to be done. But with catch-all you can't reuse the same resource-releasing code that would be used if no exception were thrown because that code won't redirect to the throw. So now you have to duplicate that code. And since any function called in the handler might throw an exception, you have to have code to catch that potential exception and duplicate that resource-releasing code yet again. On the other hand, the finally design ensures the same body of resource-releasing code will be executed regardless of how control flow exits the main body, making for better code reuse. This seems like a nightmare to generate. It was also a nightmare to write as a programmer, which is why languages with catch-all functionality always eventually add finally.

Clang, and I think other compiler frontends too, generates cleanup (destructors) code for normal path and exception path separately, for a reason.

  • In an exception path we need to rethrow the exception but in the normal path we don't.
  • In case one of destructors also throws, the unwind destination for that can be different in the normal path and the exception path. The frontend does not handle infinite number of in-flight exceptions; it terminates at some point.

So making cleanup code shared between the two paths is not very feasible in the first place. Of course code generation will be a nightmare. If we really should do that, we can't even use clang's EH CodeGen. (For some details, wasm is currently using a modified variant of LLVM's Windows exception handling IR, which is different from Itanium ABI, but the all EH generation schemes separately generate cleanup code for the normal and the exception path.) And I don't see what we achieve from deviating from the normal EH codegen in such a significant way.

  1. Releasing of resources

Now for the most important consideration. Unfortunately catch-all bundles exception-handling code with resource-freeing code, making it only work for exceptions (and likely more slowly because the stack-unwinder has to keep putting that (updated?) exnref on the stack even though it will be rethrown many times but handled only once). But exceptions aren't the only situation where resource-freeing code needs to be run—they're just the most familiar situation.

Since Andreas just spoke about it, a less familiar situation is continuations. The basic idea of continuations is to make stacks be just another construct on the heap. In Andreas's lightweight-threads example, he had a stack/continuation for each thread. Now suppose a thread gets discarded because the lightweight system discovered that its work had already been completed. That discarded (i.e. to-be-garbage-collected) thread might have acquired resources that need to be released, but the code for doing so is buried in the stack. Before garbage-collecting that stack, we need to free those resources. In other words, stacks-as-heap-objects, i.e. continuations, have finalizers. The finally clause nicely separates the finalizing code from the exception-handling code, so we can still free all those resources even though no exception was thrown to be handled. (You could try to emulate this by throwing a "stack finalizer" exception to trigger all that resource-releasing code, but what should happen if the exception is caught and not rethrown by a catch-all?)

Clang compiles away finally into normal landing pads in languages that have finally, such as obj-c, so I don't think we need to worry about other languages' uses of finally. If the coroutine needs any cleanup actions, that can be handled in a similar way. If finally capability ever becomes important with coroutines or resumable exceptions later, maybe it can be added in that proposal. I still think code generation in Clang, which basically has to reconstruct low-level things into finally, will still be a nightmare though.

@RossTate
Copy link
Contributor

Okay, so I had a chance to talk with someone who is both a security expert and an exceptions expert about this topic, relaying this useful conversation to catch them up on the specific issues we're considering.

They were not particularly concerned about catch-all from a security perspective, which is a relief. They also remembered that the JVM bytecode had been extended with specialized features to reduce code duplication due to finally clauses. We dug around and found that this paper showed that the code-size savings was negligible, at least for Java where finally clauses are rare (destructors are much more common in C++, so it's possible there would be more significant savings there). Due to this finding, the specialized features were removed from the bytecode, and instead finally is supported by the bytecode generator through code duplication via this process, which seems to be what the current design encourages.

So the only remaining concerns that came up in this discussion are

  1. it seems heavy handed to introduce memory management just for exceptions;
  2. we might need to add yet another duplication of destructors for something like dynamic-wind to support continuations;
  3. y'all want Interface Types to somehow to take care of exceptions.

(1) now seems pretty orthogonal to this discussion, though this was very useful in understanding it better. The study above seems to suggest that (2) shouldn't be a big cause of code bloat. And (3) seems out of scope, though I do get the impression y'all are expecting magic to happen there :) Regardless, all of these seem to be separate from this issue.

@RossTate
Copy link
Contributor

Oh, I forgot, they did raise a new concern. For languages that want to have fast exceptions, you don’t want to collect the stack trace by default so that a single-level catch/throw can be just a few times slower than a return.

@lukewagner
Copy link
Member

Just to give my 2c here:

I also don't see the security use case of trying to unwind through untrusted code while still calling into that untrusted code. It seems like, if you reach a point where you want to kill the guest, you don't want to go and then run a bit more guest code. Rather, you should just trap. Now you may say "but that will kill the host", but since the guest can also trap, you already have to worry about the host being taken down by a guest trap. If anything, this is a use case for some separate feature providing a unit of isolation and trap recovery (maybe not in core wasm, but perhaps WASI or Interface Types...).

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

No branches or pull requests

6 participants