-
Notifications
You must be signed in to change notification settings - Fork 69
/
Copy patherror.go
195 lines (174 loc) · 4.49 KB
/
error.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Package errors provides errors that have stack-traces.
package errors
import (
"bytes"
"fmt"
"github.com/pkg/errors"
"reflect"
"runtime"
)
// The maximum number of stackframes on any error.
var MaxStackDepth = 50
// Error is an error with an attached stacktrace. It can be used
// wherever the builtin error interface is expected.
type Error struct {
Err error
Cause *Error
stack []uintptr
frames []StackFrame
}
// ErrorWithCallers allows passing in error objects that
// also have caller information attached.
type ErrorWithCallers interface {
Error() string
Callers() []uintptr
}
// ErrorWithStackFrames allows the stack to be rebuilt from the stack frames, thus
// allowing to use the Error type when the program counter is not available.
type ErrorWithStackFrames interface {
Error() string
StackFrames() []StackFrame
}
type errorWithStack interface {
StackTrace() errors.StackTrace
Error() string
}
type errorWithCause interface {
Unwrap() error
}
// New makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
// to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
func New(e interface{}, skip int) *Error {
var err error
switch e := e.(type) {
case *Error:
return e
case ErrorWithCallers:
return &Error{
Err: e,
stack: e.Callers(),
Cause: unwrapCause(e),
}
case errorWithStack:
trace := e.StackTrace()
stack := make([]uintptr, len(trace))
for i, ptr := range trace {
stack[i] = uintptr(ptr) - 1
}
return &Error{
Err: e,
Cause: unwrapCause(e),
stack: stack,
}
case ErrorWithStackFrames:
stack := make([]uintptr, len(e.StackFrames()))
for i, frame := range e.StackFrames() {
stack[i] = frame.ProgramCounter
}
return &Error{
Err: e,
Cause: unwrapCause(e),
stack: stack,
frames: e.StackFrames(),
}
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
stack := make([]uintptr, MaxStackDepth)
length := runtime.Callers(2+skip, stack[:])
return &Error{
Err: err,
Cause: unwrapCause(err),
stack: stack[:length],
}
}
// Errorf creates a new error with the given message. You can use it
// as a drop-in replacement for fmt.Errorf() to provide descriptive
// errors in return values.
func Errorf(format string, a ...interface{}) *Error {
return New(fmt.Errorf(format, a...), 1)
}
// Error returns the underlying error's message.
func (err *Error) Error() string {
return err.Err.Error()
}
// Callers returns the raw stack frames as returned by runtime.Callers()
func (err *Error) Callers() []uintptr {
return err.stack[:]
}
// Stack returns the callstack formatted the same way that go does
// in runtime/debug.Stack()
func (err *Error) Stack() []byte {
buf := bytes.Buffer{}
for _, frame := range err.StackFrames() {
buf.WriteString(frame.String())
}
return buf.Bytes()
}
// StackFrames returns an array of frames containing information about the
// stack.
func (err *Error) StackFrames() []StackFrame {
if err.frames == nil {
callers := runtime.CallersFrames(err.stack)
err.frames = make([]StackFrame, 0, len(err.stack))
for frame, more := callers.Next(); more; frame, more = callers.Next() {
if frame.Func == nil {
// Ignore fully inlined functions
continue
}
pkg, name := packageAndName(frame.Func)
err.frames = append(err.frames, StackFrame{
function: frame.Func,
File: frame.File,
LineNumber: frame.Line,
Name: name,
Package: pkg,
ProgramCounter: frame.PC,
})
}
}
return err.frames
}
// TypeName returns the type this error. e.g. *errors.stringError.
func (err *Error) TypeName() string {
if p, ok := err.Err.(uncaughtPanic); ok {
return p.typeName
}
if name := reflect.TypeOf(err.Err).String(); len(name) > 0 {
return name
}
return "error"
}
func unwrapCause(err interface{}) *Error {
if causer, ok := err.(errorWithCause); ok {
cause := causer.Unwrap()
if cause == nil {
return nil
} else if hasStack(cause) { // avoid generating a (duplicate) stack from the current frame
return New(cause, 0)
} else {
return &Error{
Err: cause,
Cause: unwrapCause(cause),
stack: []uintptr{},
}
}
}
return nil
}
func hasStack(err error) bool {
if _, ok := err.(errorWithStack); ok {
return true
}
if _, ok := err.(ErrorWithStackFrames); ok {
return true
}
if _, ok := err.(ErrorWithCallers); ok {
return true
}
return false
}