blob: 47d8ae55450aba4eee224d655502a525bef3769c [file] [log] [blame] [view] [edit]
# Exception handling
This explainer reflects the up-to-date version of the exception handling
proposal agreed on [Oct 2023 CG
meeting](https://github.com/WebAssembly/meetings/blob/main/main/2023/CG-10.md#exception-handling-vote-on-proposal-to-re-introduce-exnref).
See the [legacy
proposal](https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/legacy/Exceptions.md)
for the previous version of the proposal that is still supported in several
browsers.
---
## Overview
Exception handling allows code to break control flow when an exception is
thrown. The exception can be any exception known by the WebAssembly module, or
it may an unknown exception that was thrown by a called imported function.
One of the problems with exception handling is that both WebAssembly and an
embedder have different notions of what exceptions are, but both must be aware
of the other.
It is difficult to define exceptions in WebAssembly because (in general) it
doesn't have knowledge of any embedder. Further, adding such knowledge to
WebAssembly would limit the ability for other embedders to support WebAssembly
exceptions.
One issue is that both sides need to know if an exception was thrown by the
other, because cleanup may need to be performed.
Another problem is that WebAssembly doesn't have direct access to an embedder's
memory. As a result, WebAssembly defers the handling of exceptions to the host
VM.
To access exceptions, WebAssembly provides instructions to check if the
exception is one that WebAssembly understands. If so, the data of the
WebAssembly exception is extracted and copied onto the stack, allowing
succeeding instructions to process the data.
A WebAssembly exception is created when you throw it with the `throw`
instruction. Thrown exceptions are handled as follows:
1. They can be caught by one of the *catch clauses* in an enclosing try block of
a function body.
1. Throws not caught within a function body continue up the call stack, popping
call frames, until an enclosing try block is found.
1. If the call stack is exhausted without any enclosing try blocks, the embedder
defines how to handle the uncaught exception.
### Exception handling
This proposal adds exception handling to WebAssembly. Part of this proposal is
to define a new section to declare exceptions. However, rather than limiting
this new section to just defining exceptions, it defines a more general format
`tag` that allows the declaration of other forms of typed tags in future.
WebAssembly tags are defined in a new `tag` section of a WebAssembly module. The
tag section is a list of declared tags that are created fresh each time the
module is instantiated.
Each tag has an `attribute` and a `type`. Currently, the attribute can only
specify that the tag is for an exception. In the future, additional attribute
values may be added when other kinds of tags are added to WebAssembly.
To allow for such a future extension possibility, we reserve a byte in the
binary format of an exception definition, set to 0 to denote an exception
attribute.
### Exceptions
An `exception tag` is a value to distinguish different exceptions, while an
`exception tag index` is a numeric name to refer to an (imported or defined)
exception tag within a module (see [tag index space](#tag-index-space) for
details). Exception tags are declared in the tag and import sections of a
module.
An `exception` is an internal construct in WebAssembly that represents a runtime
object that can be thrown. A WebAssembly exception consists of an exception tag
and its runtime arguments.
The type of an exception tag is denoted by an index to a function signature
defined in the `type` section. The parameters of the function signature define
the list of argument values associated with the tag. The result type must be
empty.
Exception tag indices are used by:
1. The `throw` instruction which creates a WebAssembly exception with the
corresponding exception tag, and then throws it.
2. Catch clauses use a tag to identify the thrown exception it can catch. If it
matches, it pushes the corresponding argument values of the exception onto
the stack.
### Exception references
When caught, an exception is reified into an _exception reference_, a value of
the new type `exnref`. Exception references can be used to rethrow the caught
exception.
### Try blocks
A _try block_ defines a list of instructions that may need to process exceptions
and/or clean up state when an exception is thrown. Like other higher-level
constructs, a try block begins with a `try_table` instruction, and ends with an
`end` instruction. That is, a try block is sequence of instructions having the
following form:
```
try_table blocktype catch*
instruction*
end
```
A try block contains zero or more _catch clauses_. If there are no catch
clauses, then the try block does not catch any exceptions.
The _body_ of the try block is the list of instructions after the last catch
clause, if any.
Each `catch` clause can be in one of 4 forms:
```
catch tag label
catch_ref tag label
catch_all label
catch_all_ref label
```
All forms have a label which is branched to when an exception is caught (see
below). The former two forms have an exception tag associated with it that
identifies what exceptions it will catch. The latter two forms catch any
exception, so that they can be used to define a _default_ handler.
Try blocks, like control-flow blocks, have a _block type_. The block type of a
try block defines the values yielded by evaluating the try block when either no
exception is thrown, or the exception is successfully caught by the catch
clause. Because `try_table` defines a control-flow block, it can be targets for
branches (`br` and `br_if`) as well.
### Throwing an exception
The `throw` instruction takes an exception tag index as an immediate argument.
That index is used to identify the exception tag to use to create and throw the
corresponding exception.
The values on top of the stack must correspond to the type associated with the
exception tag. These values are popped off the stack and are used (along with
the corresponding exception tag) to create the corresponding exception. That
exception is then thrown.
When an exception is thrown, the embedder searches for the nearest enclosing try
block body that execution is in. That try block is called the _catching_ try
block.
If the throw appears within the body of a try block, it is the catching try
block.
If a throw occurs within a function body, and it doesn't appear inside the body
of a try block, the throw continues up the call stack until it is in the body of
an an enclosing try block, or the call stack is flushed. If the call stack is
flushed, the embedder defines how to handle uncaught exceptions. Otherwise, the
found enclosing try block is the catching try block.
Once a catching try block is found for the thrown exception, the operand stack
is popped back to the size the operand stack had when the try block was entered
after possible block parameters were popped.
Then catch clauses are tried in the order they appear in the catching try block,
until one matches. If a matching catch clause is found, control is transferred
to the label of that catch clause. In case of `catch` or `catch_ref`, the
arguments of the exception are pushed back onto the stack. For `catch_ref` and
`catch_all_ref`, an exception reference is then pushed to the stack, which
represents the caught exception.
If no catch clauses were matched, the exception is implicitly rethrown.
Note that a caught exception can be rethrown explicitly using the `exnref` and
the `throw_ref` instruction.
### Rethrowing an exception
The `throw_ref` takes an operand of type `exnref` and re-throws the
corresponding caught exception. If the operand is null, a trap occurs.
### JS API
#### Traps
Catch clauses handle exceptions generated by the `throw` instruction, but do not
catch traps. The rationale for this is that in general traps are not locally
recoverable and are not needed to be handled in local scopes like try blocks.
The `try_table` instruction catches foreign exceptions generated from calls to
function imports as well, including JavaScript exceptions, with a few
exceptions:
1. In order to be consistent before and after a trap reaches a JavaScript frame,
the `try_table` instruction does not catch exceptions generated from traps.
1. The `try_table` instruction does not catch JavaScript exceptions generated
from stack overflow and out of memory.
Filtering these exceptions should be based on a predicate that is not observable
by JavaScript. Traps currently generate instances of
[`WebAssembly.RuntimeError`](https://webassembly.github.io/reference-types/js-api/#exceptiondef-runtimeerror),
but this detail is not used to decide type. Implementations are supposed to
specially mark non-catchable exceptions.
([`instanceof`](https://tc39.es/ecma262/#sec-instanceofoperator) predicate can
be intercepted in JS, and types of exceptions generated from stack overflow and
out of memory are implementation-defined.)
#### API additions
The following additional classes are added to the JS API in order to allow
JavaScript to interact with WebAssembly exceptions:
* `WebAssembly.Tag`
* `WebAssembly.Exception`
The `WebAssembly.Tag` class represents a typed tag defined in the tag section
and exported from a WebAssembly module. It allows querying the type of a tag
following the [JS type reflection
proposal](https://github.com/WebAssembly/js-types/blob/master/proposals/js-types/Overview.md).
Constructing an instance of `Tag` creates a fresh tag, and the new tag can be
passed to a WebAssembly module as a tag import.
In the future, `WebAssembly.Tag` may be used for other proposals that require a
typed tag and its constructor may be extended to accept other types and/or a tag
attribute to differentiate them from tags used for exceptions.
The `WebAssembly.Exception` class represents an exception thrown from
WebAssembly, or an exception that is constructed in JavaScript and is to be
thrown to a WebAssembly exception handler. The `Exception` constructor accepts a
`Tag` argument and a sequence of arguments for the exception's data fields. The
`Tag` argument determines the exception tag to use. The data field arguments
must match the types specified by the `Tag`'s type. The `is` method can be used
to query if the `Exception` matches a given tag. The `getArg` method allows
access to the data fields of a `Exception` if a matching tag is given. This last
check ensures that without access to a WebAssembly module's exported exception
tag, the associated data fields cannot be read.
The `Exception` constructor can take an optional `ExceptionOptions` argument,
which can optionally contain `traceStack` entry. When `traceStack` is `true`,
JavaScript VMs are permitted to attach a stack trace string to `Exception.stack`
field, as in JavaScript's `Error` class. `traceStack` serves as a request to the
WebAssembly engine to attach a stack trace; it is not necessary to honour if
`true`, but `trace` may not be populated if `traceStack` is `false`. While
`Exception` is not a subclass of JavaScript's `Error` and it can be used to
represent normal control flow constructs, `traceStack` field can be set when we
use it to represent errors. The format of stack trace strings conform to the
[WebAssembly stack trace
conventions](https://webassembly.github.io/spec/web-api/index.html#conventions).
When `ExceptionOption` is not provided or it does not contain `traceStack`
entry, `traceStack` is considered `false` by default.
To preserve stack trace info when crossing the JS to Wasm boundary, exceptions
can internally contain a stack trace, which is propagated when caught by a
`catch[_all]_ref` clause and rethrown by `throw_ref`.
More formally, the added interfaces look like the following:
```WebIDL
dictionary TagType {
required sequence<ValueType> parameters;
};
[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
interface Tag {
constructor(TagType type);
TagType type();
};
dictionary ExceptionOptions {
boolean traceStack = false;
};
[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
interface Exception {
constructor(Tag tag, sequence<any> payload, optional ExceptionOptions options);
any getArg(Tag tag, unsigned long index);
boolean is(Tag tag);
readonly attribute (DOMString or undefined) stack;
};
```
`TagType` corresponds to a `FunctionType` in [the type reflection
proposal](https://github.com/WebAssembly/js-types/blob/main/proposals/js-types/Overview.md),
without a `results` property). `TagType` could be extended in the future for
other proposals that require a richer type specification.
## Changes to the text format
This section describes change in the [instruction syntax
document](https://github.com/WebAssembly/spec/blob/master/document/core/text/instructions.rst).
### New instructions
The following rules are added to *instructions*:
```
try_table blocktype catch* instruction* end |
throw tag_index |
throw_ref |
```
Like the `block`, `loop`, and `if` instructions, the `try_table` instruction is
*structured* control flow instruction, and can be labeled. This allows branch
instructions to exit try blocks.
The `tag_index` of the `throw` and `catch[_ref]` clauses denotes the exception
tag to use when creating/extract from an exception. See [tag index
space](#tag-index-space) for further clarification of exception tags.
## Changes to Modules document
This section describes change in the [Modules
document](https://github.com/WebAssembly/design/blob/master/Modules.md).
### Tag index space
The `tag index space` indexes all imported and internally-defined exception
tags, assigning monotonically-increasing indices based on the order defined in
the import and tag sections. Thus, the index space starts at zero with imported
tags, followed by internally-defined tags in the [tag section](#tag-section).
For tag indices that are not imported/exported, the corresponding exception tag
is guaranteed to be unique over all loaded modules. Exceptions that are imported
or exported alias the respective exceptions defined elsewhere, and use the same
tag.
## Changes to the binary model
This section describes changes in the [binary encoding design
document](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md).
#### Other Types
##### exnref
The type `exnref` is represented by the type opcode `-0x17`.
When combined with the [GC
proposal](https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP.md),
there also is a value type `nullexnref` with opcode `-0x0c`. Furthermore, these
opcodes also function as heap type, i.e., `exn` is a new heap type with opcode
`-0x17`, and `noexn` is a new heap type with opcode `-0x0c`; `exnref` and
`nullexnref` are shorthands for `(ref null exn)` and `(ref null noexn)`,
respectively.
The heap type `noexn` is a subtype of `exn`. They are not in a subtype relation
with any other type (except bottom), such that they form a new disjoint
hierarchy of heap types.
##### tag_type
We reserve a bit to denote the exception attribute:
| Name | Value |
|-----------|-------|
| Exception | 0 |
Each tag type has the fields:
| Field | Type | Description |
|-------|------|-------------|
| `attribute` | `uint8` | The attribute of a tag. |
| `type` | `varuint32` | The type index for its corresponding type signature |
##### external_kind
A single-byte unsigned integer indicating the kind of definition being imported
or defined:
* `4` indicating a `Tag`
[import](https://github.com/WebAssembly/design/blob/main/BinaryEncoding.md#import-section) or
[definition](#tag-section)
### Module structure
#### High-level structure
A new `tag` section is introduced.
##### Tag section
The `tag` section comes after the [memory
section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#memory-section)
and before the [global
section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#global-section).
So the list of all sections will be:
| Section Name | Code | Description |
| ------------ | ---- | ----------- |
| Type | `1` | Function signature declarations |
| Import | `2` | Import declarations |
| Function | `3` | Function declarations |
| Table | `4` | Indirect function table and other tables |
| Memory | `5` | Memory attributes |
| Tag | `13` | Tag declarations |
| Global | `6` | Global declarations |
| Export | `7` | Exports |
| Start | `8` | Start function declaration |
| Element | `9` | Elements section |
| Data count | `12` | Data count section |
| Code | `10` | Function bodies (code) |
| Data | `11` | Data segments |
The tag section declares a list of tag types as follows:
| Field | Type | Description |
|-------|------|-------------|
| count | `varuint32` | count of the number of tags to follow |
| type | `tag_type*` | The definitions of the tag types |
##### Import section
The import section is extended to include tag definitions by extending an
`import_entry` as follows:
If the `kind` is `Tag`:
| Field | Type | Description |
|-------|------|-------------|
| `type` | `tag_type` | the tag being imported |
##### Export section
The export section is extended to reference tag types by extending an
`export_entry` as follows:
If the `kind` is `Tag`:
| Field | Type | Description |
|-------|------|-------------|
| `index` | `varuint32` | the index into the corresponding tag index space |
##### Name section
The set of known values for `name_type` of a name section is extended as
follows:
| Name Type | Code | Description |
| --------- | ---- | ----------- |
| [Function](#function-names) | `1` | Assigns names to functions |
| [Local](#local-names) | `2` | Assigns names to locals in functions |
| [Tag](#tag-names) | `11` | Assigns names to tags |
###### Tag names
The tag names subsection is a `name_map` which assigns names to a subset of the
tag indices (Used for both imports and module-defined).
### Control flow instructions
The control flow instructions are extended to define try blocks and throws as
follows:
| Name | Opcode | Immediates | Description |
| ---- | ---- | ---- | ---- |
| `try_table` | `0x1f` | sig : `blocktype`, n : `varuint32`, catch : `catch^n` | begins a block which can handle thrown exceptions |
| `throw` | `0x08` | index : `varuint32` | Creates an exception defined by the tag and then throws it |
| `throw_ref` | `0x0a` | | Pops an `exnref` from the stack and throws it |
The *sig* fields of `block`, `if`, and `try_table` instructions are block types
which describe their use of the operand stack.
A `catch` handler is a pair of tag and label index:
| Name | Opcode | Immediates |
| ------- | ------ | ----------- |
| `catch` | `0x00` | tag : `varuint32`, label : `varuint32` |
| `catch_ref` | `0x01` | tag : `varuint32`, label : `varuint32` |
| `catch_all` | `0x02` | label : `varuint32` |
| `catch_all_ref` | `0x03` | label : `varuint32` |