diff --git a/Makefile b/Makefile index 9017050180..eb87648e75 100644 --- a/Makefile +++ b/Makefile @@ -185,6 +185,7 @@ build.spectest.threads: test: @go test $(go_test_options) $$(go list ./... | grep -vE '$(spectest_v1_dir)|$(spectest_v2_dir)') @cd internal/version/testdata && go test $(go_test_options) ./... + @cd internal/integration_test/fuzz/wazerolib && CGO_ENABLED=0 WASM_BINARY_PATH=testdata/test.wasm go test ./... .PHONY: coverage # replace spaces with commas diff --git a/internal/integration_test/fuzz/predicate.sh b/internal/integration_test/fuzz/predicate.sh index de376eed6e..9a1bae4fe9 100755 --- a/internal/integration_test/fuzz/predicate.sh +++ b/internal/integration_test/fuzz/predicate.sh @@ -8,5 +8,5 @@ echo "Testing $WASM" export WASM_BINARY_PATH=$WASM # Run the test and reverse the exit code so that a non-zero exit code indicates interesting case. -./nodiff.test +./nodiff.test -test.run=TestReRunFailedRequireNoDiffCase exit $((! $?)) diff --git a/internal/integration_test/fuzz/wazerolib/extern.go b/internal/integration_test/fuzz/wazerolib/extern.go new file mode 100644 index 0000000000..9f5d9de636 --- /dev/null +++ b/internal/integration_test/fuzz/wazerolib/extern.go @@ -0,0 +1,140 @@ +package main + +import "C" +import ( + "context" + "math" + "reflect" + "strings" + "unsafe" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/experimental/opt" + "github.com/tetratelabs/wazero/internal/leb128" + "github.com/tetratelabs/wazero/internal/testing/binaryencoding" + "github.com/tetratelabs/wazero/internal/wasm" +) + +func main() {} + +// require_no_diff ensures that the behavior is the same between the compiler and the interpreter for any given binary. +// And if there's diff, this also saves the problematic binary and wat into testdata directory. +// +//export require_no_diff +func require_no_diff(binaryPtr uintptr, binarySize int, checkMemory bool) { + // TODO: use unsafe.Slice after flooring Go 1.20. + var wasmBin []byte + wasmHdr := (*reflect.SliceHeader)(unsafe.Pointer(&wasmBin)) + wasmHdr.Data = binaryPtr + wasmHdr.Len = binarySize + wasmHdr.Cap = binarySize + + failed := true + defer func() { + if failed { + // If the test fails, we save the binary and wat into testdata directory. + saveFailedBinary(wasmBin, "TestReRunFailedRequireNoDiffCase") + } + }() + + requireNoDiff(wasmBin, checkMemory, func(err error) { + if err != nil { + panic(err) + } + }) + + failed = false +} + +// validate accepts maybe-invalid Wasm module bytes and ensures that our validation phase works correctly +// as well as the compiler doesn't panic during compilation! +// +//export validate +func validate(binaryPtr uintptr, binarySize int) { + // TODO: use unsafe.Slice after flooring Go 1.20. + var wasmBin []byte + wasmHdr := (*reflect.SliceHeader)(unsafe.Pointer(&wasmBin)) + wasmHdr.Data = binaryPtr + wasmHdr.Len = binarySize + wasmHdr.Cap = binarySize + + failed := true + defer func() { + if failed { + // If the test fails, we save the binary and wat into testdata directory. + saveFailedBinary(wasmBin, "TestReRunFailedValidateCase") + } + }() + + tryCompile(wasmBin) + failed = false +} + +//export test_signal_stack +func test_signal_stack() { + // (module + // (func (export "long_loop") + // (loop + // global.get 0 + // i32.eqz + // if ;; label = @1 + // unreachable + // end + // global.get 0 + // i32.const 1 + // i32.sub + // global.set 0 + // br 0 + // ) + // ) + // (global (;0;) (mut i32) i32.const 2147483648) + // ) + bin := binaryencoding.EncodeModule(&wasm.Module{ + TypeSection: []wasm.FunctionType{{}}, + FunctionSection: []wasm.Index{0}, + GlobalSection: []wasm.Global{{ + Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}, + Init: wasm.ConstantExpression{ + Opcode: wasm.OpcodeI32Const, + Data: leb128.EncodeInt32(math.MaxInt32), + }, + }}, + ExportSection: []wasm.Export{{Type: wasm.ExternTypeFunc, Name: "long_loop", Index: 0}}, + CodeSection: []wasm.Code{ + { + Body: []byte{ + wasm.OpcodeLoop, 0, + wasm.OpcodeGlobalGet, 0, + wasm.OpcodeI32Eqz, + wasm.OpcodeIf, 0, + wasm.OpcodeUnreachable, + wasm.OpcodeEnd, + wasm.OpcodeGlobalGet, 0, + wasm.OpcodeI32Const, 1, + wasm.OpcodeI32Sub, + wasm.OpcodeGlobalSet, 0, + wasm.OpcodeBr, 0, + wasm.OpcodeEnd, + wasm.OpcodeEnd, + }, + }, + }, + }) + ctx := context.Background() + config := opt.NewRuntimeConfigOptimizingCompiler() + r := wazero.NewRuntimeWithConfig(ctx, config) + module, err := r.Instantiate(ctx, bin) + if err != nil { + panic(err) + } + defer func() { + if err = module.Close(ctx); err != nil { + panic(err) + } + }() + + _, err = module.ExportedFunction("long_loop").Call(ctx) + if !strings.Contains(err.Error(), "unreachable") { + panic("long_loop should be unreachable") + } +} diff --git a/internal/integration_test/fuzz/wazerolib/lib.go b/internal/integration_test/fuzz/wazerolib/lib.go index f6a2bc7a7c..54bd13823a 100644 --- a/internal/integration_test/fuzz/wazerolib/lib.go +++ b/internal/integration_test/fuzz/wazerolib/lib.go @@ -1,26 +1,17 @@ package main -import "C" import ( - "context" "crypto/sha256" _ "embed" "encoding/hex" "fmt" - "math" "os" "path" - "strings" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/experimental/opt" - "github.com/tetratelabs/wazero/internal/leb128" - "github.com/tetratelabs/wazero/internal/testing/binaryencoding" - "github.com/tetratelabs/wazero/internal/wasm" ) -func main() {} - const failedCasesDir = "wazerolib/testdata" // saveFailedBinary writes binary and wat into failedCasesDir so that it is easy to reproduce the error. @@ -62,72 +53,3 @@ func newCompilerConfig() wazero.RuntimeConfig { } return c } - -//export test_signal_stack -func test_signal_stack() { - // (module - // (func (export "long_loop") - // (loop - // global.get 0 - // i32.eqz - // if ;; label = @1 - // unreachable - // end - // global.get 0 - // i32.const 1 - // i32.sub - // global.set 0 - // br 0 - // ) - // ) - // (global (;0;) (mut i32) i32.const 2147483648) - // ) - bin := binaryencoding.EncodeModule(&wasm.Module{ - TypeSection: []wasm.FunctionType{{}}, - FunctionSection: []wasm.Index{0}, - GlobalSection: []wasm.Global{{ - Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}, - Init: wasm.ConstantExpression{ - Opcode: wasm.OpcodeI32Const, - Data: leb128.EncodeInt32(math.MaxInt32), - }, - }}, - ExportSection: []wasm.Export{{Type: wasm.ExternTypeFunc, Name: "long_loop", Index: 0}}, - CodeSection: []wasm.Code{ - { - Body: []byte{ - wasm.OpcodeLoop, 0, - wasm.OpcodeGlobalGet, 0, - wasm.OpcodeI32Eqz, - wasm.OpcodeIf, 0, - wasm.OpcodeUnreachable, - wasm.OpcodeEnd, - wasm.OpcodeGlobalGet, 0, - wasm.OpcodeI32Const, 1, - wasm.OpcodeI32Sub, - wasm.OpcodeGlobalSet, 0, - wasm.OpcodeBr, 0, - wasm.OpcodeEnd, - wasm.OpcodeEnd, - }, - }, - }, - }) - ctx := context.Background() - config := opt.NewRuntimeConfigOptimizingCompiler() - r := wazero.NewRuntimeWithConfig(ctx, config) - module, err := r.Instantiate(ctx, bin) - if err != nil { - panic(err) - } - defer func() { - if err = module.Close(ctx); err != nil { - panic(err) - } - }() - - _, err = module.ExportedFunction("long_loop").Call(ctx) - if !strings.Contains(err.Error(), "unreachable") { - panic("long_loop should be unreachable") - } -} diff --git a/internal/integration_test/fuzz/wazerolib/nodiff.go b/internal/integration_test/fuzz/wazerolib/nodiff.go index 460f7206d2..037488bc89 100644 --- a/internal/integration_test/fuzz/wazerolib/nodiff.go +++ b/internal/integration_test/fuzz/wazerolib/nodiff.go @@ -1,12 +1,10 @@ package main -import "C" import ( "bytes" "context" "errors" "fmt" - "reflect" "sort" "strings" "unsafe" @@ -18,35 +16,6 @@ import ( "github.com/tetratelabs/wazero/internal/wasm" ) -// require_no_diff ensures that the behavior is the same between the compiler and the interpreter for any given binary. -// And if there's diff, this also saves the problematic binary and wat into testdata directory. -// -//export require_no_diff -func require_no_diff(binaryPtr uintptr, binarySize int, checkMemory bool) { - // TODO: use unsafe.Slice after flooring Go 1.20. - var wasmBin []byte - wasmHdr := (*reflect.SliceHeader)(unsafe.Pointer(&wasmBin)) - wasmHdr.Data = binaryPtr - wasmHdr.Len = binarySize - wasmHdr.Cap = binarySize - - failed := true - defer func() { - if failed { - // If the test fails, we save the binary and wat into testdata directory. - saveFailedBinary(wasmBin, "TestReRunFailedRequireNoDiffCase") - } - }() - - requireNoDiff(wasmBin, checkMemory, func(err error) { - if err != nil { - panic(err) - } - }) - - failed = false -} - // We haven't had public APIs for referencing all the imported entries from wazero.CompiledModule, // so we use the unsafe.Pointer and the internal memory layout to get the internal *wasm.Module // from wazero.CompiledFunction. This must be synced with the struct definition of wazero.compiledModule (internal one). @@ -154,10 +123,12 @@ func ensureMutableGlobalsMatch(compilerMod, interpreterMod api.Module, requireNo } if !ok { - if ig.Type.ValType == wasm.ValueTypeV128 { - es = append(es, fmt.Sprintf("mutable global[%d] value mismatch: (%v,%v) != (%v,%v)", i, cVal, cValHi, iVal, iValHi)) + if typ := ig.Type.ValType; typ == wasm.ValueTypeV128 { + es = append(es, fmt.Sprintf("\t[%d] %s: (%v,%v) != (%v,%v)", + i, wasm.ValueTypeName(wasm.ValueTypeV128), cVal, cValHi, iVal, iValHi)) } else { - es = append(es, fmt.Sprintf("mutable global[%d] value mismatch: %v != %v", i, cVal, iVal)) + es = append(es, fmt.Sprintf("\t[%d] %s: %v != %v", + i, wasm.ValueTypeName(typ), cVal, iVal)) } } } diff --git a/internal/integration_test/fuzz/wazerolib/nodiff_test.go b/internal/integration_test/fuzz/wazerolib/nodiff_test.go index 9807f7e748..7b51f46dde 100644 --- a/internal/integration_test/fuzz/wazerolib/nodiff_test.go +++ b/internal/integration_test/fuzz/wazerolib/nodiff_test.go @@ -60,7 +60,8 @@ func Test_ensureMutableGlobalsMatch(t *testing.T) { {Val: 11, Type: wasm.GlobalType{Mutable: true, ValType: wasm.ValueTypeI32}}, }, }, - expErr: "mutable global[1] value mismatch: 10 != 11", + expErr: `mutable globals mismatch: + [1] i32: 10 != 11`, }, { name: "i64 match", @@ -93,7 +94,8 @@ func Test_ensureMutableGlobalsMatch(t *testing.T) { {Val: 1 << 63, Type: wasm.GlobalType{Mutable: true, ValType: wasm.ValueTypeI64}}, }, }, - expErr: "mutable global[2] value mismatch: 4611686018427387904 != 9223372036854775808", + expErr: `mutable globals mismatch: + [2] i64: 4611686018427387904 != 9223372036854775808`, }, { name: "f32 match", @@ -124,7 +126,8 @@ func Test_ensureMutableGlobalsMatch(t *testing.T) { {Val: 11, Type: wasm.GlobalType{Mutable: true, ValType: wasm.ValueTypeF32}}, }, }, - expErr: "mutable global[1] value mismatch: 10 != 11", + expErr: `mutable globals mismatch: + [1] f32: 10 != 11`, }, { name: "f64 match", @@ -157,7 +160,8 @@ func Test_ensureMutableGlobalsMatch(t *testing.T) { {Val: 1 << 63, Type: wasm.GlobalType{Mutable: true, ValType: wasm.ValueTypeF64}}, }, }, - expErr: "mutable global[2] value mismatch: 4611686018427387904 != 9223372036854775808", + expErr: `mutable globals mismatch: + [2] f64: 4611686018427387904 != 9223372036854775808`, }, { @@ -191,7 +195,8 @@ func Test_ensureMutableGlobalsMatch(t *testing.T) { {Val: 1 << 62, ValHi: 1234, Type: wasm.GlobalType{Mutable: true, ValType: wasm.ValueTypeV128}}, }, }, - expErr: "mutable global[2] value mismatch: (4611686018427387904,0) != (4611686018427387904,1234)", + expErr: `mutable globals mismatch: + [2] v128: (4611686018427387904,0) != (4611686018427387904,1234)`, }, } { t.Run(tc.name, func(t *testing.T) { diff --git a/internal/integration_test/fuzz/wazerolib/testdata/.keep b/internal/integration_test/fuzz/wazerolib/testdata/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/internal/integration_test/fuzz/wazerolib/testdata/test.wasm b/internal/integration_test/fuzz/wazerolib/testdata/test.wasm new file mode 100644 index 0000000000..7ba12f9429 Binary files /dev/null and b/internal/integration_test/fuzz/wazerolib/testdata/test.wasm differ diff --git a/internal/integration_test/fuzz/wazerolib/validate.go b/internal/integration_test/fuzz/wazerolib/validate.go index 4f39e5eec6..b7ca229553 100644 --- a/internal/integration_test/fuzz/wazerolib/validate.go +++ b/internal/integration_test/fuzz/wazerolib/validate.go @@ -1,38 +1,11 @@ package main -import "C" import ( "context" - "reflect" - "unsafe" "github.com/tetratelabs/wazero" ) -// validate accepts maybe-invalid Wasm module bytes and ensures that our validation phase works correctly -// as well as the compiler doesn't panic during compilation! -// -//export validate -func validate(binaryPtr uintptr, binarySize int) { - // TODO: use unsafe.Slice after flooring Go 1.20. - var wasmBin []byte - wasmHdr := (*reflect.SliceHeader)(unsafe.Pointer(&wasmBin)) - wasmHdr.Data = binaryPtr - wasmHdr.Len = binarySize - wasmHdr.Cap = binarySize - - failed := true - defer func() { - if failed { - // If the test fails, we save the binary and wat into testdata directory. - saveFailedBinary(wasmBin, "TestReRunFailedValidateCase") - } - }() - - tryCompile(wasmBin) - failed = false -} - // Ensure that validation and compilation do not panic! func tryCompile(wasmBin []byte) { ctx := context.Background()