Skip to content
This repository has been archived by the owner on May 11, 2020. It is now read-only.

Commit

Permalink
Improve validation for ELSE blocks, fixes #173
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom committed Oct 15, 2019
1 parent 01d3d33 commit d9f6e2b
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 75 deletions.
8 changes: 8 additions & 0 deletions validate/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ func (e InvalidLabelError) Error() string {
return fmt.Sprintf("invalid nesting depth %d", uint32(e))
}

// UnmatchedIfValueErr is returned if an if block returns a value, but
// no else block is present.
type UnmatchedIfValueErr wasm.ValueType

func (e UnmatchedIfValueErr) Error() string {
return fmt.Sprintf("if block returns value of type %v but no else present", wasm.ValueType(e))
}

// InvalidTableIndexError is returned if a table is referenced with an
// out-of-bounds index.
type InvalidTableIndexError struct {
Expand Down
7 changes: 6 additions & 1 deletion validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,13 @@ func verifyBody(fn *wasm.FunctionSig, body *wasm.FunctionBody, module *wasm.Modu
if err != nil {
return vm, err
}
if frame == nil || frame.op == operators.Call {
switch {
// END should match with a IF/BLOCK/LOOP frame.
case frame == nil || frame.op == operators.Call:
return vm, UnmatchedOpError(op)
// IF block with no else cannot have a result.
case (frame.op == operators.If) && len(frame.endTypes) > 0:
return vm, UnmatchedIfValueErr(frame.endTypes[0])
}
for _, t := range frame.endTypes {
vm.pushOperand(t)
Expand Down
253 changes: 179 additions & 74 deletions validate/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,25 +583,6 @@ func TestValidateBlockTypecheck(t *testing.T) {
},
err: UnmatchedOpError(operators.Loop),
},
{
name: "dangling if",
// (i32.const 0) (if) (block (nop))
code: []byte{
operators.I32Const, 0,
operators.If, byte(wasm.BlockTypeEmpty),
operators.Block, byte(wasm.BlockTypeEmpty), operators.Nop, operators.End,
},
err: UnmatchedOpError(operators.If),
},
{
name: "dangling else",
// (block (nop)) (else (nop))
code: []byte{
operators.Block, byte(wasm.BlockTypeEmpty), operators.Nop, operators.End,
operators.Else, operators.Nop, operators.End,
},
err: UnmatchedOpError(operators.Else),
},
{
name: "dangling end",
// (block (nop)) (nop) (end)
Expand Down Expand Up @@ -879,31 +860,156 @@ func TestValidateBlockTypecheck(t *testing.T) {
err: nil,
},
{
name: "if i32-i32",
// (i32.const 0) (if (result i32) (i32.const 0)) (drop)
name: "brtable void-void",
// (block (block (i32.const 0) (brtable 0 0 1)))
code: []byte{
operators.Block, byte(wasm.BlockTypeEmpty),
operators.Block, byte(wasm.BlockTypeEmpty),
operators.I32Const, 0,
operators.If, byte(wasm.ValueTypeI32),
operators.BrTable,
/* target count */ 2,
0, 0, 1,
operators.End,
operators.End,
},
err: nil,
},
{
name: "brtable i64-i32",
// (block (return i64) (i64.const 0) (block (return i64) (i32.const 0) (brtable 0 0 1)))
code: []byte{
operators.Block, byte(wasm.ValueTypeI64),
operators.I64Const, 0,
operators.Block, byte(wasm.ValueTypeI32),
operators.I32Const, 0,
operators.BrTable,
/* target count */ 2,
0, 0, 1,
operators.End,
operators.I32Const, 0,
operators.End,
},
err: InvalidTypeError{wasm.ValueTypeI64, wasm.ValueTypeI32},
},
{
name: "brtable invalid default branch",
// (i32.const 0) (br_table 1)
code: []byte{
operators.I32Const, 0,
operators.BrTable,
/* target count */ 0,
1,
},
err: InvalidLabelError(1),
},
{
name: "brtable invalid entry branch",
// (i32.const 0) (br_table 3 0)
code: []byte{
operators.I32Const, 0,
operators.BrTable,
/* target count */ 1,
3, 0,
},
err: InvalidLabelError(3),
},
{
name: "brtable default i32-i32",
// (block (return i32) (i32.const 0) (i32.const 0) (br_table 0 0))
code: []byte{
operators.Block, byte(wasm.ValueTypeI32),
operators.I32Const, 0,
operators.I32Const, 0,
operators.BrTable,
/* target count */ 0,
0,
operators.End,
operators.Drop,
},
err: nil,
},
}

for i := range tcs {
tc := tcs[i]
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

mod := wasm.Module{}
sig := wasm.FunctionSig{Form: 0x60 /* Must always be 0x60 */}
fn := wasm.FunctionBody{Module: &mod, Code: tc.code}

_, err := verifyBody(&sig, &fn, &mod)
if err != tc.err {
t.Fatalf("verify returned '%v', want '%v'", err, tc.err)
}
})
}
}

func TestValidateIfBlock(t *testing.T) {
tcs := []struct {
name string
code []byte
err error
}{
{
name: "if else i32-i32-i32",
// (i32.const 0) (if (result i32) (i32.const 0) (else (i32.const 1))) (drop)
name: "if nominal",
// (i32.const 0) (if (nop)
code: []byte{
operators.I32Const, 0,
operators.If, byte(wasm.ValueTypeI32),
operators.If, byte(wasm.BlockTypeEmpty),
operators.Nop,
operators.End,
},
err: nil,
},
{
name: "if else nominal",
// (i32.const 0) (if (nop) (else (nop))
code: []byte{
operators.I32Const, 0,
operators.If, byte(wasm.BlockTypeEmpty),
operators.Nop,
operators.Else,
operators.I32Const, 1,
operators.Nop,
operators.End,
},
err: nil,
},
{
name: "if else with value nominal",
// (i32.const 0) (if (result i64) (i64.const 1) (else (i64.const 2)) (drop)
code: []byte{
operators.I32Const, 0,
operators.If, byte(wasm.ValueTypeI64),
operators.I64Const, 1,
operators.Else,
operators.I64Const, 2,
operators.End,
operators.Drop,
},
err: nil,
},
{
name: "dangling if",
// (i32.const 0) (if) (block (nop))
code: []byte{
operators.I32Const, 0,
operators.If, byte(wasm.BlockTypeEmpty),
operators.Block, byte(wasm.BlockTypeEmpty), operators.Nop, operators.End,
},
err: UnmatchedOpError(operators.If),
},
{
name: "dangling else",
// (block (nop)) (else (nop))
code: []byte{
operators.Block, byte(wasm.BlockTypeEmpty), operators.Nop, operators.End,
operators.Else, operators.Nop, operators.End,
},
err: UnmatchedOpError(operators.Else),
},
{
name: "if else i32-i32-i64",
// (i32.const 0) (if (result i32) (i32.const 0) (else (i64.const 1))) (drop)
Expand All @@ -919,96 +1025,95 @@ func TestValidateBlockTypecheck(t *testing.T) {
err: InvalidTypeError{wasm.ValueTypeI32, wasm.ValueTypeI64},
},
{
name: "if void-i32",
// (i32.const 0) (if (result i32) (i32.const 0)) (drop)
name: "if else i64-i32-i64",
// (i32.const 0) (if (result i64) (i32.const 0) (else (i64.const 1))) (drop)
code: []byte{
operators.I32Const, 0,
operators.If, byte(wasm.BlockTypeEmpty),
operators.If, byte(wasm.ValueTypeI64),
operators.I32Const, 0,
operators.Else,
operators.I64Const, 1,
operators.End,
operators.Drop,
},
err: UnbalancedStackErr(wasm.ValueTypeI32),
err: InvalidTypeError{wasm.ValueTypeI64, wasm.ValueTypeI32},
},
{
name: "if i32-void",
// (i32.const 0) (if (nop))
name: "if else i64-i32-i32",
// (i32.const 0) (if (result i64) (i32.const 0) (else (i64.const 1))) (drop)
code: []byte{
operators.I32Const, 0,
operators.If, byte(wasm.ValueTypeI32),
operators.Nop,
operators.If, byte(wasm.ValueTypeI64),
operators.I32Const, 0,
operators.Else,
operators.I32Const, 1,
operators.End,
operators.Drop,
},
err: ErrStackUnderflow,
err: InvalidTypeError{wasm.ValueTypeI64, wasm.ValueTypeI32},
},
{
name: "brtable void-void",
// (block (block (i32.const 0) (brtable 0 0 1)))
name: "if void-i32",
// (i32.const 0) (if (result i32) (i32.const 0)) (drop)
code: []byte{
operators.Block, byte(wasm.BlockTypeEmpty),
operators.Block, byte(wasm.BlockTypeEmpty),
operators.I32Const, 0,
operators.BrTable,
/* target count */ 2,
0, 0, 1,
operators.End,
operators.If, byte(wasm.BlockTypeEmpty),
operators.I32Const, 0,
operators.End,
operators.Drop,
},
err: nil,
err: UnbalancedStackErr(wasm.ValueTypeI32),
},
{
name: "brtable i64-i32",
// (block (return i64) (i64.const 0) (block (return i64) (i32.const 0) (brtable 0 0 1)))
name: "if i32-void",
// (i32.const 0) (if (result i32) (nop))
code: []byte{
operators.Block, byte(wasm.ValueTypeI64),
operators.I64Const, 0,
operators.Block, byte(wasm.ValueTypeI32),
operators.I32Const, 0,
operators.BrTable,
/* target count */ 2,
0, 0, 1,
operators.End,
operators.I32Const, 0,
operators.If, byte(wasm.ValueTypeI32),
operators.Nop,
operators.End,
},
err: InvalidTypeError{wasm.ValueTypeI64, wasm.ValueTypeI32},
err: ErrStackUnderflow,
},
{
name: "brtable invalid default branch",
// (i32.const 0) (br_table 1)
name: "if i32 missing else",
// (i32.const 0) (if (nop))
code: []byte{
operators.I32Const, 0,
operators.BrTable,
/* target count */ 0,
1,
operators.If, byte(wasm.ValueTypeI32),
operators.I32Const, 0,
operators.End,
operators.Drop,
},
err: InvalidLabelError(1),
err: UnmatchedIfValueErr(wasm.ValueTypeI32),
},
{
name: "brtable invalid entry branch",
// (i32.const 0) (br_table 3 0)
name: "if with else missing main block result",
// (i32.const 0) (if (result i32) (nop) else (i32.const 0))
code: []byte{
operators.I32Const, 0,
operators.BrTable,
/* target count */ 1,
3, 0,
operators.If, byte(wasm.ValueTypeI32),
operators.Nop,
operators.Else,
operators.I32Const, 0,
operators.End,
operators.Drop,
},
err: InvalidLabelError(3),
err: ErrStackUnderflow,
},
{
name: "brtable default i32-i32",
// (block (return i32) (i32.const 0) (i32.const 0) (br_table 0 0))
name: "if with else missing else block result",
// (i32.const 0) (if (result i32) (i32.const 0) else (nop))
code: []byte{
operators.Block, byte(wasm.ValueTypeI32),
operators.I32Const, 0,
operators.If, byte(wasm.ValueTypeI32),
operators.I32Const, 0,
operators.BrTable,
/* target count */ 0,
0,
operators.Else,
operators.Nop,
operators.End,
operators.Drop,
},
err: nil,
err: ErrStackUnderflow,
},
}

Expand Down

0 comments on commit d9f6e2b

Please sign in to comment.