Extracts stack trace formatting logic and adds more context (#434)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-04-04 19:47:51 +08:00
committed by GitHub
parent abb3559310
commit 3a6cabfb8a
10 changed files with 367 additions and 99 deletions

View File

@@ -27,6 +27,7 @@ import (
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wasmdebug"
)
type EngineTester interface {
@@ -310,70 +311,70 @@ func RunTestModuleEngine_Call_Errors(t *testing.T, et EngineTester) {
input: []uint64{0},
module: imported.Ctx,
fn: imported.Exports[wasmFnName].Function,
expectedErr: `wasm runtime error: integer divide by zero
wasm backtrace:
0: wasm_div_by`,
expectedErr: `wasm error: integer divide by zero
wasm stack trace:
imported.wasm_div_by(i32) i32`,
},
{
name: "host function that panics",
input: []uint64{math.MaxUint32},
module: imported.Ctx,
fn: imported.Exports[hostFnName].Function,
expectedErr: `wasm runtime error: host-function panic
wasm backtrace:
0: host_div_by`,
expectedErr: `host-function panic (recovered by wazero)
wasm stack trace:
imported.host_div_by(i32) i32`,
},
{
name: "host function panics with runtime.Error",
input: []uint64{0},
module: imported.Ctx,
fn: imported.Exports[hostFnName].Function, // TODO: This should be a normal runtime error
expectedErr: `wasm runtime error: runtime error: integer divide by zero
wasm backtrace:
0: host_div_by`,
fn: imported.Exports[hostFnName].Function,
expectedErr: `runtime error: integer divide by zero (recovered by wazero)
wasm stack trace:
imported.host_div_by(i32) i32`,
},
{
name: "wasm calls host function that panics",
input: []uint64{math.MaxUint32},
module: imported.Ctx,
fn: imported.Exports[callHostFnName].Function,
expectedErr: `wasm runtime error: host-function panic
wasm backtrace:
0: host_div_by
1: call->host_div_by`,
expectedErr: `host-function panic (recovered by wazero)
wasm stack trace:
imported.host_div_by(i32) i32
imported.call->host_div_by(i32) i32`,
},
{
name: "wasm calls imported wasm that calls host function panics with runtime.Error",
input: []uint64{0},
module: importing.Ctx,
fn: importing.Exports[callImportCallHostFnName].Function,
expectedErr: `wasm runtime error: runtime error: integer divide by zero
wasm backtrace:
0: host_div_by
1: call->host_div_by
2: call_import->call->host_div_by`,
expectedErr: `runtime error: integer divide by zero (recovered by wazero)
wasm stack trace:
imported.host_div_by(i32) i32
imported.call->host_div_by(i32) i32
importing.call_import->call->host_div_by(i32) i32`,
},
{
name: "wasm calls imported wasm that calls host function that panics",
input: []uint64{math.MaxUint32},
module: importing.Ctx,
fn: importing.Exports[callImportCallHostFnName].Function,
expectedErr: `wasm runtime error: host-function panic
wasm backtrace:
0: host_div_by
1: call->host_div_by
2: call_import->call->host_div_by`,
expectedErr: `host-function panic (recovered by wazero)
wasm stack trace:
imported.host_div_by(i32) i32
imported.call->host_div_by(i32) i32
importing.call_import->call->host_div_by(i32) i32`,
},
{
name: "wasm calls imported wasm calls host function panics with runtime.Error",
input: []uint64{0},
module: importing.Ctx,
fn: importing.Exports[callImportCallHostFnName].Function,
expectedErr: `wasm runtime error: runtime error: integer divide by zero
wasm backtrace:
0: host_div_by
1: call->host_div_by
2: call_import->call->host_div_by`,
expectedErr: `runtime error: integer divide by zero (recovered by wazero)
wasm stack trace:
imported.host_div_by(i32) i32
imported.call->host_div_by(i32) i32
importing.call_import->call->host_div_by(i32) i32`,
},
}
for _, tt := range tests {
@@ -433,7 +434,7 @@ func setupCallTests(t *testing.T, e wasm.Engine) (*wasm.ModuleInstance, wasm.Mod
}
// To use the function, we first need to add it to a module.
imported := &wasm.ModuleInstance{Name: t.Name()}
imported := &wasm.ModuleInstance{Name: "imported"}
addFunction(imported, wasmFnName, wasmFn)
addFunction(imported, hostFnName, hostFn)
addFunction(imported, callHostFnName, callHostFn)
@@ -444,7 +445,7 @@ func setupCallTests(t *testing.T, e wasm.Engine) (*wasm.ModuleInstance, wasm.Mod
linkModuleToEngine(imported, importedMe)
// To test stack traces, call the same function from another module
importing := &wasm.ModuleInstance{Name: t.Name() + "-importing"}
importing := &wasm.ModuleInstance{Name: "importing"}
// Don't add imported functions yet as NewModuleEngine requires them split.
importedFunctions := []*wasm.FunctionInstance{callHostFn}
@@ -484,7 +485,7 @@ func linkModuleToEngine(module *wasm.ModuleInstance, me wasm.ModuleEngine) {
// addFunction assigns and adds a function to the module.
func addFunction(module *wasm.ModuleInstance, funcName string, fn *wasm.FunctionInstance) {
fn.DebugName = funcName
fn.DebugName = wasmdebug.FuncName(module.Name, funcName, fn.Index)
module.Functions = append(module.Functions, fn)
if module.Exports == nil {
module.Exports = map[string]*wasm.ExportInstance{}

View File

@@ -4,6 +4,8 @@ import (
"fmt"
"reflect"
"sort"
"github.com/tetratelabs/wazero/internal/wasmdebug"
)
// NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small.
@@ -88,17 +90,19 @@ func (m *Module) validateHostFunctions() error {
return nil
}
func (m *Module) buildHostFunctionInstances() (functions []*FunctionInstance) {
func (m *Module) buildHostFunctions(moduleName string) (functions []*FunctionInstance) {
// ModuleBuilder has no imports, which means the FunctionSection index is the same as the position in the function
// index namespace. Also, it ensures every function has a name. That's why there is less error checking here.
var functionNames = m.NameSection.FunctionNames
for idx, typeIndex := range m.FunctionSection {
fn := m.HostFunctionSection[idx]
f := &FunctionInstance{
DebugName: functionNames[idx].Name,
Kind: kind(fn.Type()),
Type: m.TypeSection[typeIndex],
GoFunc: fn,
Index: Index(idx),
Kind: kind(fn.Type()),
Type: m.TypeSection[typeIndex],
GoFunc: fn,
Index: Index(idx),
}
f.DebugName = wasmdebug.FuncName(moduleName, functionNames[f.Index].Name, f.Index)
functions = append(functions, f)
}
return

View File

@@ -6,13 +6,13 @@ import (
"math"
"math/bits"
"reflect"
"runtime/debug"
"strings"
"sync"
"github.com/tetratelabs/wazero/internal/buildoptions"
"github.com/tetratelabs/wazero/internal/moremath"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wasmdebug"
"github.com/tetratelabs/wazero/internal/wasmruntime"
"github.com/tetratelabs/wazero/internal/wazeroir"
)
@@ -516,28 +516,14 @@ func (me *moduleEngine) Call(m *wasm.ModuleContext, f *wasm.FunctionInstance, pa
// TODO: ^^ Will not fail if the function was imported from a closed module.
if v := recover(); v != nil {
if buildoptions.IsDebugMode {
debug.PrintStack()
}
traces := make([]string, len(ce.frames))
for i := 0; i < len(traces); i++ {
builder := wasmdebug.NewErrorBuilder()
frameCount := len(ce.frames)
for i := 0; i < frameCount; i++ {
frame := ce.popFrame()
name := frame.f.source.DebugName
// TODO: include DWARF symbols. See #58
traces[i] = fmt.Sprintf("\t%d: %s", i, name)
}
runtimeErr, ok := v.(error)
if ok {
err = fmt.Errorf("wasm runtime error: %w", runtimeErr)
} else {
err = fmt.Errorf("wasm runtime error: %v", v)
}
if len(traces) > 0 {
err = fmt.Errorf("%w\nwasm backtrace:\n%s", err, strings.Join(traces, "\n"))
fn := frame.f.source
builder.AddFrame(fn.DebugName, fn.ParamTypes(), fn.ResultTypes())
}
err = builder.FromRecovered(v)
}
}()

View File

@@ -5,13 +5,12 @@ import (
"math"
"reflect"
"runtime"
"runtime/debug"
"strings"
"sync"
"unsafe"
"github.com/tetratelabs/wazero/internal/buildoptions"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wasmdebug"
"github.com/tetratelabs/wazero/internal/wasmruntime"
"github.com/tetratelabs/wazero/internal/wazeroir"
)
@@ -481,32 +480,17 @@ func (me *moduleEngine) Call(m *wasm.ModuleContext, f *wasm.FunctionInstance, pa
// TODO: ^^ Will not fail if the function was imported from a closed module.
if v := recover(); v != nil {
if buildoptions.IsDebugMode {
debug.PrintStack()
}
var frames []string
builder := wasmdebug.NewErrorBuilder()
// Handle edge-case where the host function is called directly by Go.
if ce.globalContext.callFrameStackPointer == 0 {
fn := compiled.source
frames = append(frames, fmt.Sprintf("\t%d: %s", 0, fn.DebugName))
builder.AddFrame(fn.DebugName, fn.ParamTypes(), fn.ResultTypes())
}
for i := uint64(0); i < ce.globalContext.callFrameStackPointer; i++ {
f := ce.callFrameStack[ce.globalContext.callFrameStackPointer-1-i].compiledFunction
frames = append(frames, fmt.Sprintf("\t%d: %s", i, f.source.DebugName))
// TODO: include DWARF symbols. See #58
}
runtimeErr, ok := v.(error)
if ok {
err = fmt.Errorf("wasm runtime error: %w", runtimeErr)
} else {
err = fmt.Errorf("wasm runtime error: %v", v)
}
if len(frames) > 0 {
err = fmt.Errorf("%w\nwasm backtrace:\n%s", err, strings.Join(frames, "\n"))
fn := ce.callFrameStack[ce.globalContext.callFrameStackPointer-1-i].compiledFunction.source
builder.AddFrame(fn.DebugName, fn.ParamTypes(), fn.ResultTypes())
}
err = builder.FromRecovered(v)
}
}()

View File

@@ -11,6 +11,7 @@ import (
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/ieee754"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasmdebug"
)
// DecodeModule parses the configured source into a Module. This function returns when the source is exhausted or
@@ -456,7 +457,7 @@ func (m *Module) buildGlobals(importedGlobals []*GlobalInstance) (globals []*Glo
return
}
func (m *Module) buildFunctions() (functions []*FunctionInstance) {
func (m *Module) buildFunctions(moduleName string) (functions []*FunctionInstance) {
var functionNames NameMap
if m.NameSection != nil {
functionNames = m.NameSection.FunctionNames
@@ -465,27 +466,28 @@ func (m *Module) buildFunctions() (functions []*FunctionInstance) {
importCount := m.ImportFuncCount()
n, nLen := 0, len(functionNames)
for codeIndex, typeIndex := range m.FunctionSection {
// The function name section begins with imports, but can be sparse. This keeps track of how far in the name
// section we've already searched.
funcIdx := importCount + uint32(codeIndex)
// Seek to see if there's a better name than "unknown"
name := "unknown"
var funcName string
for ; n < nLen; n++ {
next := functionNames[n]
if next.Index > funcIdx {
break // we have function names, but starting at a later index
} else if next.Index == funcIdx {
name = next.Name
funcName = next.Name
break
}
}
f := &FunctionInstance{
DebugName: name,
Kind: FunctionKindWasm,
Type: m.TypeSection[typeIndex],
Body: m.CodeSection[codeIndex].Body,
LocalTypes: m.CodeSection[codeIndex].LocalTypes,
Index: importCount + uint32(codeIndex),
Index: funcIdx,
}
f.DebugName = wasmdebug.FuncName(moduleName, funcName, funcIdx)
functions = append(functions, f)
}
return

View File

@@ -701,11 +701,13 @@ func TestModule_buildFunctionInstances(t *testing.T) {
{Index: Index(5), Name: "five"},
},
},
CodeSection: []*Code{nopCode, nopCode, nopCode, nopCode, nopCode},
FunctionSection: []Index{0, 0, 0, 0, 0},
CodeSection: []*Code{nopCode, nopCode, nopCode, nopCode, nopCode},
}
actual := m.buildFunctions()
expectedNames := []string{"unknown", "two", "unknown", "four", "five"}
// Note: This only returns module-defined functions, not imported ones. That's why the index starts with 1, not 0.
actual := m.buildFunctions("counter")
expectedNames := []string{"counter.[1]", "counter.two", "counter.[3]", "counter.four", "counter.five"}
for i, f := range actual {
require.Equal(t, expectedNames[i], f.DebugName)
require.Equal(t, nopCode.Body, f.Body)

View File

@@ -87,7 +87,7 @@ type (
// FunctionInstance represents a function instance in a Store.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-instances%E2%91%A0
FunctionInstance struct {
// DebugName is for debugging purpose, and is used to augment the stack traces.
// DebugName is for debugging purpose, and is used to augment stack traces.
DebugName string
// Kind describes how this function should be called.
@@ -275,10 +275,10 @@ func (s *Store) Instantiate(ctx context.Context, module *Module, name string, sy
var funcSection SectionID
if module.HostFunctionSection == nil {
funcSection = SectionIDFunction
functions = module.buildFunctions()
functions = module.buildFunctions(name)
} else {
funcSection = SectionIDHostFunction
functions = module.buildHostFunctionInstances()
functions = module.buildHostFunctions(name)
}
// Now we have all instances from imports and local ones, so ready to create a new ModuleInstance.

144
internal/wasmdebug/debug.go Normal file
View File

@@ -0,0 +1,144 @@
// Package wasmdebug contains utilities used to give consistent search keys between stack traces and error messages.
// Note: This is named wasmdebug to avoid conflicts with the normal go module.
// Note: This only imports "api" as importing "wasm" would create a cyclic dependency.
package wasmdebug
import (
"fmt"
"runtime"
"runtime/debug"
"strconv"
"strings"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/buildoptions"
"github.com/tetratelabs/wazero/internal/wasmruntime"
)
// FuncName returns the naming convention of "moduleName.funcName".
//
// * moduleName is the possibly empty name the module was instantiated with.
// * funcName is the name in the Custom Name section, an export name, or what the host defines.
// * funcIdx is the position in the function index namespace, prefixed with imported functions.
//
// Note: "moduleName.[funcIdx]" is used when the funcName is empty, as commonly the case in TinyGo.
func FuncName(moduleName, funcName string, funcIdx uint32) string {
var ret strings.Builder
// Start module.function
ret.WriteString(moduleName)
ret.WriteByte('.')
if funcName == "" {
ret.WriteByte('[')
ret.WriteString(strconv.Itoa(int(funcIdx)))
ret.WriteByte(']')
} else {
ret.WriteString(funcName)
}
return ret.String()
}
// signature returns a formatted signature similar to how it is defined in Go.
//
// * paramTypes should be from wasm.FunctionType
// * resultTypes should be from wasm.FunctionType
func addSignature(funcName string, paramTypes []api.ValueType, resultTypes []api.ValueType) string {
var ret strings.Builder
ret.WriteString(funcName)
// Start params
ret.WriteByte('(')
paramCount := len(paramTypes)
switch paramCount {
case 0:
case 1:
ret.WriteString(api.ValueTypeName(paramTypes[0]))
default:
ret.WriteString(api.ValueTypeName(paramTypes[0]))
for _, vt := range paramTypes[1:] {
ret.WriteByte(',')
ret.WriteString(api.ValueTypeName(vt))
}
}
ret.WriteByte(')')
// Start results
resultCount := len(resultTypes)
switch resultCount {
case 0:
case 1:
ret.WriteByte(' ')
ret.WriteString(api.ValueTypeName(resultTypes[0]))
default: // As this is used for errors, don't panic if there are multiple returns, even if that's invalid!
ret.WriteByte(' ')
ret.WriteByte('(')
ret.WriteString(api.ValueTypeName(resultTypes[0]))
for _, vt := range resultTypes[1:] {
ret.WriteByte(',')
ret.WriteString(api.ValueTypeName(vt))
}
ret.WriteByte(')')
}
return ret.String()
}
// ErrorBuilder helps build consistent errors, particularly adding a WASM stack trace.
//
// AddFrame should be called beginning at the frame that panicked until no more frames exist. Once done, call Format.
type ErrorBuilder interface {
// AddFrame adds the next frame.
//
// * funcName should be from FuncName
// * paramTypes should be from wasm.FunctionType
// * resultTypes should be from wasm.FunctionType
//
// Note: paramTypes and resultTypes are present because signature misunderstanding, mismatch or overflow are common.
AddFrame(funcName string, paramTypes, resultTypes []api.ValueType)
// FromRecovered returns an error with the wasm stack trace appended to it.
FromRecovered(recovered interface{}) error
}
func NewErrorBuilder() ErrorBuilder {
return &stackTrace{}
}
type stackTrace struct {
frames []string
}
func (s *stackTrace) FromRecovered(recovered interface{}) error {
if buildoptions.IsDebugMode {
debug.PrintStack()
}
stack := strings.Join(s.frames, "\n\t")
// If the error was internal, don't mention it was recovered.
if wasmErr, ok := recovered.(*wasmruntime.Error); ok {
return fmt.Errorf("wasm error: %w\nwasm stack trace:\n\t%s", wasmErr, stack)
}
// If we have a runtime.Error, something severe happened which should include the stack trace. This could be
// a nil pointer from wazero or a user-defined function from ModuleBuilder.
if runtimeErr, ok := recovered.(runtime.Error); ok {
// TODO: consider adding debug.Stack(), but last time we attempted, some tests became unstable.
return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s", runtimeErr, stack)
}
// At this point we expect the error was from a function defined by ModuleBuilder that intentionally called panic.
if runtimeErr, ok := recovered.(error); ok { // Ex. panic(errors.New("whoops"))
return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s", runtimeErr, stack)
} else { // Ex. panic("whoops")
return fmt.Errorf("%v (recovered by wazero)\nwasm stack trace:\n\t%s", recovered, stack)
}
}
// AddFrame implements ErrorBuilder.Format
func (s *stackTrace) AddFrame(funcName string, paramTypes, resultTypes []api.ValueType) {
// Format as best as we can, considering we don't yet have source and line numbers,
// TODO: include DWARF symbols. See #58
s.frames = append(s.frames, addSignature(funcName, paramTypes, resultTypes))
}

View File

@@ -0,0 +1,145 @@
package wasmdebug
import (
"errors"
"runtime"
"testing"
"github.com/stretchr/testify/require"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasmruntime"
)
func TestFuncName(t *testing.T) {
for _, tc := range []struct {
name, moduleName, funcName string
funcIdx uint32
expected string
}{ // Only tests a few edge cases to show what it might end up as.
{name: "empty", expected: ".[0]"},
{name: "empty module", funcName: "y", expected: ".y"},
{name: "empty function", moduleName: "x", funcIdx: 255, expected: "x.[255]"},
{name: "looks like index in function", moduleName: "x", funcName: "[255]", expected: "x.[255]"},
{name: "no special characters", moduleName: "x", funcName: "y", expected: "x.y"},
{name: "dots in module", moduleName: "w.x", funcName: "y", expected: "w.x.y"},
{name: "dots in function", moduleName: "x", funcName: "y.z", expected: "x.y.z"},
{name: "spaces in module", moduleName: "w x", funcName: "y", expected: "w x.y"},
{name: "spaces in function", moduleName: "x", funcName: "y z", expected: "x.y z"},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
funcName := FuncName(tc.moduleName, tc.funcName, tc.funcIdx)
require.Equal(t, tc.expected, funcName)
})
}
}
func TestAddSignature(t *testing.T) {
i32, i64, f32, f64 := api.ValueTypeI32, api.ValueTypeI64, api.ValueTypeF32, api.ValueTypeF64
for _, tc := range []struct {
name string
paramTypes, resultTypes []api.ValueType
expected string
}{
{name: "v_v", expected: "x.y()"},
{name: "i32_v", paramTypes: []api.ValueType{i32}, expected: "x.y(i32)"},
{name: "i32f64_v", paramTypes: []api.ValueType{i32, f64}, expected: "x.y(i32,f64)"},
{name: "f32i32f64_v", paramTypes: []api.ValueType{f32, i32, f64}, expected: "x.y(f32,i32,f64)"},
{name: "v_i64", resultTypes: []api.ValueType{i64}, expected: "x.y() i64"},
{name: "v_i64f32", resultTypes: []api.ValueType{i64, f32}, expected: "x.y() (i64,f32)"},
{name: "v_f32i32f64", resultTypes: []api.ValueType{f32, i32, f64}, expected: "x.y() (f32,i32,f64)"},
{name: "i32_i64", paramTypes: []api.ValueType{i32}, resultTypes: []api.ValueType{i64}, expected: "x.y(i32) i64"},
{name: "i64f32_i64f32", paramTypes: []api.ValueType{i64, f32}, resultTypes: []api.ValueType{i64, f32}, expected: "x.y(i64,f32) (i64,f32)"},
{name: "i64f32f64_f32i32f64", paramTypes: []api.ValueType{i64, f32, f64}, resultTypes: []api.ValueType{f32, i32, f64}, expected: "x.y(i64,f32,f64) (f32,i32,f64)"},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
withSignature := addSignature("x.y", tc.paramTypes, tc.resultTypes)
require.Equal(t, tc.expected, withSignature)
})
}
}
func TestErrorBuilder(t *testing.T) {
argErr := errors.New("invalid argument")
rteErr := testRuntimeErr("index out of bounds")
i32 := api.ValueTypeI32
i32i32i32i32 := []api.ValueType{i32, i32, i32, i32}
for _, tc := range []struct {
name string
build func(ErrorBuilder) error
expectedErr string
expectUnwrap error
}{
{
name: "one",
build: func(builder ErrorBuilder) error {
builder.AddFrame("x.y", nil, nil)
return builder.FromRecovered(argErr)
},
expectedErr: `invalid argument (recovered by wazero)
wasm stack trace:
x.y()`,
expectUnwrap: argErr,
},
{
name: "two",
build: func(builder ErrorBuilder) error {
builder.AddFrame("wasi_snapshot_preview1.fd_write", i32i32i32i32, []api.ValueType{i32})
builder.AddFrame("x.y", nil, nil)
return builder.FromRecovered(argErr)
},
expectedErr: `invalid argument (recovered by wazero)
wasm stack trace:
wasi_snapshot_preview1.fd_write(i32,i32,i32,i32) i32
x.y()`,
expectUnwrap: argErr,
},
{
name: "runtime.Error",
build: func(builder ErrorBuilder) error {
builder.AddFrame("wasi_snapshot_preview1.fd_write", i32i32i32i32, []api.ValueType{i32})
builder.AddFrame("x.y", nil, nil)
return builder.FromRecovered(rteErr)
},
expectedErr: `index out of bounds (recovered by wazero)
wasm stack trace:
wasi_snapshot_preview1.fd_write(i32,i32,i32,i32) i32
x.y()`,
expectUnwrap: rteErr,
},
{
name: "wasmruntime.Error",
build: func(builder ErrorBuilder) error {
builder.AddFrame("wasi_snapshot_preview1.fd_write", i32i32i32i32, []api.ValueType{i32})
builder.AddFrame("x.y", nil, nil)
return builder.FromRecovered(wasmruntime.ErrRuntimeCallStackOverflow)
},
expectedErr: `wasm error: callstack overflow
wasm stack trace:
wasi_snapshot_preview1.fd_write(i32,i32,i32,i32) i32
x.y()`,
expectUnwrap: wasmruntime.ErrRuntimeCallStackOverflow,
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
withStackTrace := tc.build(NewErrorBuilder())
require.Equal(t, tc.expectUnwrap, errors.Unwrap(withStackTrace))
require.EqualError(t, withStackTrace, tc.expectedErr)
})
}
}
// compile-time check to ensure testRuntimeErr implements runtime.Error.
var _ runtime.Error = testRuntimeErr("")
type testRuntimeErr string
func (e testRuntimeErr) RuntimeError() {}
func (e testRuntimeErr) Error() string {
return string(e)
}

View File

@@ -85,12 +85,12 @@ func testUnreachable(t *testing.T, r wazero.Runtime) {
defer module.Close()
_, err = module.ExportedFunction("main").Call(nil)
exp := `wasm runtime error: panic in host function
wasm backtrace:
0: cause_unreachable
1: two
2: one
3: main`
exp := `panic in host function (recovered by wazero)
wasm stack trace:
host.cause_unreachable()
.two()
.one()
.main()`
require.Equal(t, exp, err.Error())
}