Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/wasmparser/src/binary_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,10 @@ impl<'a> BinaryReader<'a> {
index: self.read_var_u32()?,
table_index: self.read_var_u32()?,
},
0x18 => Operator::Delegate {
relative_depth: self.read_var_u32()?,
},
0x19 => Operator::CatchAll,
0x1a => Operator::Drop,
0x1b => Operator::Select,
0x1c => {
Expand Down
66 changes: 45 additions & 21 deletions crates/wasmparser/src/operators_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,12 @@ impl OperatorValidator {
// Read the expected type and expected height of the operand stack the
// end of the frame.
let frame = self.control.last().unwrap();
let ty = frame.block_type;
// The end of an `unwind` should check against the empty block type.
let ty = if frame.kind == FrameKind::Unwind {
TypeOrFuncType::Type(Type::EmptyBlockType)
} else {
frame.block_type
};
let height = frame.height;

// Pop all the result types, in reverse order, from the operand stack.
Expand Down Expand Up @@ -578,24 +583,10 @@ impl OperatorValidator {
}
Operator::Else => {
let frame = self.pop_ctrl(resources)?;
// The `catch_all` instruction shares an opcode with `else`,
// so we check the frame to see how it's interpreted.
match frame.kind {
FrameKind::If => {
self.push_ctrl(FrameKind::Else, frame.block_type, resources)?
}
FrameKind::Try | FrameKind::Catch => {
// We assume `self.features.exceptions` is true when
// these frame kinds are present.
self.control.push(Frame {
kind: FrameKind::CatchAll,
block_type: frame.block_type,
height: self.operands.len(),
unreachable: false,
});
}
_ => bail_op_err!("else found outside of an `if` block"),
if frame.kind != FrameKind::If {
bail_op_err!("else found outside of an `if` block");
}
self.push_ctrl(FrameKind::Else, frame.block_type, resources)?
}
Operator::Try { ty } => {
self.check_exceptions_enabled()?;
Expand Down Expand Up @@ -648,15 +639,48 @@ impl OperatorValidator {
}
Operator::Unwind => {
self.check_exceptions_enabled()?;
// Switch from `try` to an `unwind` frame, so we can check that
// the result type is empty.
// Switch from `try` to an `unwind` frame.
let frame = self.pop_ctrl(resources)?;
if frame.kind != FrameKind::Try {
bail_op_err!("unwind found outside of an `try` block");
}
self.control.push(Frame {
kind: FrameKind::Unwind,
block_type: TypeOrFuncType::Type(Type::EmptyBlockType),
block_type: frame.block_type,
height: self.operands.len(),
unreachable: false,
});
}
Operator::Delegate { relative_depth } => {
self.check_exceptions_enabled()?;
let frame = self.pop_ctrl(resources)?;
if frame.kind != FrameKind::Try {
bail_op_err!("delegate found outside of an `try` block");
}
// This operation is not a jump, but we need to check the
// depth for validity and that it targets a `try`.
let (_, kind) = self.jump(relative_depth)?;
if kind != FrameKind::Try
&& (kind != FrameKind::Block
|| self.control.len() != relative_depth as usize + 1)
{
bail_op_err!("must delegate to a try block or caller");
}
for ty in results(frame.block_type, resources)? {
self.push_operand(ty)?;
}
}
Operator::CatchAll => {
self.check_exceptions_enabled()?;
let frame = self.pop_ctrl(resources)?;
if frame.kind == FrameKind::CatchAll {
bail_op_err!("only one catch_all allowed per `try` block");
} else if frame.kind != FrameKind::Try && frame.kind != FrameKind::Catch {
bail_op_err!("catch_all found outside of a `try` block");
}
self.control.push(Frame {
kind: FrameKind::CatchAll,
block_type: frame.block_type,
height: self.operands.len(),
unreachable: false,
});
Expand Down
4 changes: 4 additions & 0 deletions crates/wasmparser/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,10 @@ pub enum Operator<'a> {
index: u32,
table_index: u32,
},
Delegate {
relative_depth: u32,
},
CatchAll,
Drop,
Select,
TypedSelect {
Expand Down
14 changes: 11 additions & 3 deletions crates/wasmprinter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,15 +677,18 @@ impl Printer {
// `else`/`catch` are special in that it's printed at
// the previous indentation, but it doesn't actually change
// our nesting level.
Operator::Else | Operator::Catch { .. } | Operator::Unwind => {
Operator::Else
| Operator::Catch { .. }
| Operator::CatchAll
| Operator::Unwind => {
self.nesting -= 1;
self.newline();
self.nesting += 1;
}

// Exiting a block prints `end` at the previous indentation
// level.
Operator::End if self.nesting > nesting_start => {
// level. `delegate` also ends a block like `end` for `try`.
Operator::End | Operator::Delegate { .. } if self.nesting > nesting_start => {
self.nesting -= 1;
self.newline();
}
Expand Down Expand Up @@ -805,6 +808,11 @@ impl Printer {
write!(self.result, " (type {})", index)?;
}

Delegate { relative_depth } => {
write!(self.result, "delegate {}", relative_depth)?;
}
CatchAll => self.result.push_str("catch_all"),

Drop => self.result.push_str("drop"),
Select => self.result.push_str("select"),
TypedSelect { ty } => {
Expand Down
30 changes: 22 additions & 8 deletions crates/wast/src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,12 @@ enum If<'a> {
enum Try<'a> {
/// Next thing to parse is the `do` block.
Do(Instruction<'a>),
/// Next thing to parse is `catch`/`catch_all`, or `unwind`.
CatchOrUnwind,
/// Next thing to parse is `catch`/`catch_all`, `unwind`, or `delegate`.
CatchUnwindOrDelegate,
/// Next thing to parse is a `catch` block or `catch_all`.
Catch,
/// Finished parsing like the `End` case, but does not push `end` opcode.
Delegate,
/// This `try` statement has finished parsing and if anything remains it's a
/// syntax error.
End,
Expand Down Expand Up @@ -193,15 +195,16 @@ impl<'a> ExpressionParser<'a> {

// Both `do` and `catch` are required in a `try` statement, so
// we will signal those errors here. Otherwise, terminate with
// an `end` instruction.
// an `end` or `delegate` instruction.
Level::Try(Try::Do(_)) => {
return Err(parser.error("previous `try` had no `do`"));
}
Level::Try(Try::CatchOrUnwind) => {
Level::Try(Try::CatchUnwindOrDelegate) => {
return Err(
parser.error("previous `try` had no `catch`, `catch_all`, or `unwind`")
parser.error("previous `try` had no `catch`, `catch_all`, `unwind`, or `delegate`")
);
}
Level::Try(Try::Delegate) => {}
Level::Try(_) => {
self.instrs.push(Instruction::End(None));
}
Expand Down Expand Up @@ -332,7 +335,7 @@ impl<'a> ExpressionParser<'a> {
if parser.parse::<Option<kw::r#do>>()?.is_some() {
// The state is advanced here only if the parse succeeds in
// order to strictly require the keyword.
*i = Try::CatchOrUnwind;
*i = Try::CatchUnwindOrDelegate;
self.stack.push(Level::TryArm);
return Ok(true);
}
Expand All @@ -343,7 +346,7 @@ impl<'a> ExpressionParser<'a> {
}

// After a try's `do`, there are several possible kinds of handlers.
if let Try::CatchOrUnwind = i {
if let Try::CatchUnwindOrDelegate = i {
// `catch` may be followed by more `catch`s or `catch_all`.
if parser.parse::<Option<kw::catch>>()?.is_some() {
let evt = parser.parse::<ast::Index<'a>>()?;
Expand All @@ -366,6 +369,16 @@ impl<'a> ExpressionParser<'a> {
self.stack.push(Level::TryArm);
return Ok(true);
}
// `delegate` has an index, and also ends the block like `end`.
if parser.parse::<Option<kw::delegate>>()?.is_some() {
let depth = parser.parse::<ast::Index<'a>>()?;
self.instrs.push(Instruction::Delegate(depth));
*i = Try::Delegate;
match self.paren(parser)? {
Paren::Left | Paren::None => { return Ok(false) }
Paren::Right => { return Ok(true) }
}
}
return Ok(false);
}

Expand Down Expand Up @@ -1100,12 +1113,13 @@ instructions! {
V128Load64Zero(MemArg<8>) : [0xfd, 0xfd] : "v128.load64_zero",

// Exception handling proposal
CatchAll : [0x05] : "catch_all", // Reuses the else opcode.
Try(BlockType<'a>) : [0x06] : "try",
Catch(ast::Index<'a>) : [0x07] : "catch",
Throw(ast::Index<'a>) : [0x08] : "throw",
Rethrow(ast::Index<'a>) : [0x09] : "rethrow",
Unwind : [0x0a] : "unwind",
Delegate(ast::Index<'a>) : [0x18] : "delegate",
CatchAll : [0x19] : "catch_all",
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/wast/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ pub mod kw {
custom_keyword!(data);
custom_keyword!(dataref);
custom_keyword!(declare);
custom_keyword!(delegate);
custom_keyword!(r#do = "do");
custom_keyword!(elem);
custom_keyword!(end);
Expand Down
3 changes: 1 addition & 2 deletions tests/local/exception-handling.wast
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@
(assert_invalid
(module
(func try catch_all catch_all end))
;; we can't distinguish between `catch_all` and `else` in error cases
"else found outside of an `if` block")
"only one catch_all allowed per `try` block")

(assert_invalid
(module
Expand Down
6 changes: 6 additions & 0 deletions tests/local/try.wast
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@
"(func (try (do) (unwind) (drop)))"
)
"too many payloads inside of `(try)`")

(assert_malformed
(module quote
"(func (try (do) (delegate 0) (drop)))"
)
"too many payloads inside of `(try)`")
5 changes: 3 additions & 2 deletions tests/local/try.wat
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
(module $m
(type (func))
(event $exn (type 0))
(func (try (do) (catch $exn drop)))
(func (try (do) (catch $exn)))
(func (try (do) (catch $exn rethrow 0)))
(func (try (do) (catch_all rethrow 0)))
(func (try (do) (catch $exn) (catch_all rethrow 0)))
(func (try (do) (unwind nop)))
(func (try (do (try (do) (delegate 0))) (catch $exn)))
(func (result i32)
(try (result i32)
(do (i32.const 42))
(catch $exn drop (i32.const 42)))))
(catch $exn (i32.const 42)))))