-
Notifications
You must be signed in to change notification settings - Fork 238
/
debug.go
170 lines (148 loc) · 5.51 KB
/
debug.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// 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/wasmruntime"
"github.com/tetratelabs/wazero/sys"
)
// 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.
// - funcIdx is the position in the function index, 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)))
} 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
// TODO: add paramNames
func signature(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
// * sources is the source code information for this frame and can be empty.
//
// Note: paramTypes and resultTypes are present because signature misunderstanding, mismatch or overflow are common.
AddFrame(funcName string, paramTypes, resultTypes []api.ValueType, sources []string)
// FromRecovered returns an error with the wasm stack trace appended to it.
FromRecovered(recovered interface{}) error
}
func NewErrorBuilder() ErrorBuilder {
return &stackTrace{}
}
type stackTrace struct {
// frameCount is the number of stack frame currently pushed into lines.
frameCount int
// lines contains the stack trace and possibly the inlined source code information.
lines []string
}
// GoRuntimeErrorTracePrefix is the prefix coming before the Go runtime stack trace included in the face of runtime.Error.
// This is exported for testing purpose.
const GoRuntimeErrorTracePrefix = "Go runtime stack trace:"
func (s *stackTrace) FromRecovered(recovered interface{}) error {
if false {
debug.PrintStack()
}
if exitErr, ok := recovered.(*sys.ExitError); ok { // Don't wrap an exit error!
return exitErr
}
stack := strings.Join(s.lines, "\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 HostModuleBuilder.
if runtimeErr, ok := recovered.(runtime.Error); ok {
return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s\n\n%s\n%s",
runtimeErr, stack, GoRuntimeErrorTracePrefix, debug.Stack())
}
// At this point we expect the error was from a function defined by HostModuleBuilder that intentionally called panic.
if runtimeErr, ok := recovered.(error); ok { // e.g. panic(errors.New("whoops"))
return fmt.Errorf("%w (recovered by wazero)\nwasm stack trace:\n\t%s", runtimeErr, stack)
} else { // e.g. panic("whoops")
return fmt.Errorf("%v (recovered by wazero)\nwasm stack trace:\n\t%s", recovered, stack)
}
}
// MaxFrames is the maximum number of frames to include in the stack trace.
const MaxFrames = 30
// AddFrame implements ErrorBuilder.AddFrame
func (s *stackTrace) AddFrame(funcName string, paramTypes, resultTypes []api.ValueType, sources []string) {
if s.frameCount == MaxFrames {
return
}
s.frameCount++
sig := signature(funcName, paramTypes, resultTypes)
s.lines = append(s.lines, sig)
for _, source := range sources {
s.lines = append(s.lines, "\t"+source)
}
if s.frameCount == MaxFrames {
s.lines = append(s.lines, "... maybe followed by omitted frames")
}
}