-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy patherrors.go
More file actions
387 lines (344 loc) · 10.5 KB
/
errors.go
File metadata and controls
387 lines (344 loc) · 10.5 KB
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
package errors
// Attribution: portions of the below code and documentation are modeled
// directly on the https://pkg.go.dev/golang.org/x/xerrors library, used
// with the permission available under the software license
// (BSD 3-Clause):
// https://cs.opensource.google/go/x/xerrors/+/master:LICENSE
//
// Attribution: portions of the below code and documentation are modeled
// directly on the https://github.com/pkg/errors library, used
// with the permission available under the software license
// (BSD 2-Clause):
// https://github.com/pkg/errors/blob/master/LICENSE
import (
"bytes"
"fmt"
"io"
)
// Stack trace error wrapper.
// withStackTrace implements an error type annotated with a list of
// frames as a full stack trace.
type withStackTrace struct {
error error
frames frames
}
var _ interface { // Assert interface implementation.
error
stackTracer
framer
Unwrap() error
fmt.Formatter
} = (*withStackTrace)(nil)
// NewWithStackTrace returns a new error annotated with a stack trace.
func NewWithStackTrace(msg string) error {
return &withStackTrace{
error: New(msg),
frames: getStack(3),
}
}
// WithStackTrace adds a stack trace to the error by wrapping it.
func WithStackTrace(err error) error {
if err == nil {
return nil
}
return &withStackTrace{
error: err,
frames: getStack(3),
}
}
func (w *withStackTrace) Error() string { return w.error.Error() }
func (w *withStackTrace) Unwrap() error { return w.error }
// StackTrace returns the call stack frames associated with this error
// in the form of program counters; for examples of this see
// https://pkg.go.dev/runtime or
// https://pkg.go.dev/github.com/pkg/errors#Frame, both of which use the
// uintptr type to represent program counters
//
// This method is only available on when the error was generated using
// WithStackTrace or NewWithStackTrace and only returns the frames
// associated with the stack trace on *this specific error* in the error
// chain. This interface is provided to ease interoperability with error
// packages or APIs that expect stack traces to be represented with
// uintptrs: pPrefer the Frames method for general interoperability
// across this package.
func (w *withStackTrace) StackTrace() []uintptr {
return w.frames.StackTrace()
}
// Frames returns the call stack frames associated with this error.
//
// This method only returns the frames associated with the stack trace
// on *this specific error* in the error chain. Use FramesFrom to get
// all the Frames associated with an error chain.
func (w *withStackTrace) Frames() Frames {
return w.frames.Frames()
}
func (w *withStackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
// NOTE: removes '+' from wrapped error formatters, to stop recursive
// calls to FramesFrom. May have unintended consequences for errors from
// outside libraries. Don't mix and match.
fmt.Fprintf(s, "%v", w.error)
FramesFrom(w).Format(s, verb)
return
}
if s.Flag('#') {
fmt.Fprintf(s, "&errors.withStackTrace{%q}", w.error)
return
}
fallthrough
case 's':
io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
default:
// empty
}
}
// Caller frame error wrapper.
// withFrames implements an error type annotated with list of Frames.
type withFrames struct {
error error
frames frames
}
var _ interface { // Assert interface implementation.
error
framer
Unwrap() error
fmt.Formatter
} = (*withFrames)(nil)
// NewWithFrame returns a new error annotated with a call stack frame.
func NewWithFrame(msg string) error {
return NewWithFrameAt(msg, 1)
}
// WithFrame adds a call stack frame to the error by wrapping it.
func WithFrame(err error) error {
return WithFrameAt(err, 1)
}
// NewWithFrameAt returns a new error annotated with a call stack frame.
// The second param allows you to tune how many callers to skip (in case
// this is called in a helper you want to ignore, for example).
func NewWithFrameAt(msg string, skipCallers int) error {
return &withFrames{
error: New(msg),
frames: frames{getFrame(3 + skipCallers)},
}
}
// WithFrameAt adds a call stack frame to the error by wrapping it. The
// second param allows you to tune how many callers to skip (in case
// this is called in a helper you want to ignore, for example).
func WithFrameAt(err error, skipCallers int) error {
if err == nil {
return nil
}
return &withFrames{
error: err,
frames: frames{getFrame(3 + skipCallers)},
}
}
// NewWithFrames returns a new error annotated with a list of frames.
func NewWithFrames(msg string, ff Frames) error {
return WithFrames(New(msg), ff)
}
// WithFrames adds a list of frames to the error by wrapping it.
func WithFrames(err error, ff Frames) error {
if err == nil {
return nil
}
fframes := make([]*frame, len(ff))
for i, fr := range ff {
pc := PCFromFrame(fr)
if pc != 0 {
fframes[i] = frameFromPC(pc)
continue
}
fframes[i] = newFrameFrom(fr)
}
return &withFrames{
error: err,
frames: fframes,
}
}
func (w *withFrames) Error() string { return w.error.Error() }
func (w *withFrames) Unwrap() error { return w.error }
// Frames returns the call stack frame associated with this error.
//
// This method only returns the frame on *this specific error* in the
// error chain (the result will have a length of 0 or 1). Use FramesFrom
// to get all the Frames associated with an error chain.
func (w *withFrames) Frames() Frames {
return w.frames.Frames()
}
func (w *withFrames) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
// NOTE: removes '+' from wrapped error formatters, to stop recursive
// calls to FramesFrom. May have unintended consequences for errors from
// outside libraries. Don't mix and match.
fmt.Fprintf(s, "%v", w.error)
FramesFrom(w).Format(s, verb)
return
}
if s.Flag('#') {
fmt.Fprintf(s, "&errors.withFrames{%q}", w.error)
return
}
fallthrough
case 's':
io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
default:
// empty
}
}
// Helpers to extract data from the error interface.
// FramesFrom extracts all the Frames annotated across an error chain in
// order (if any). To do this it traverses the chain while aggregating
// frames.
//
// If this method finds any frames on an error that were added as a
// stack trace (ie, the error was wrapped by WithStackTrace) then the
// stack trace deepest in the chain is returned alone, ignoring all
// other stack traces and frames. This lets us we retain the most
// information possible without returning a confusing frame set.
// Therefore, try not to mix the WithFrame and WithStackTrace patterns
// in a single error chain.
//
// FramesFrom will not traverse a multierror, since there is no sensible
// way to structure the returned frames.
func FramesFrom(err error) (ff Frames) {
var traceFound bool
for err != nil {
var errHasTrace bool
traceErr, ok := err.(stackTracer)
if ok {
traceFound = true
errHasTrace = true
}
if framesErr, ok := err.(framer); ok {
if traceFound && !errHasTrace { // Ignore frames after trace.
} else if errHasTrace {
ff = framesFromPCs(traceErr.StackTrace()) // Set, not append, traces.
} else {
ff = prependFrame(ff, framesErr.Frames()) // Prepend frames.
}
} else if errHasTrace { // Set, not append, traces.
ff = framesFromPCs(traceErr.StackTrace())
}
err = Unwrap(err)
}
return
}
func prependFrame(slice Frames, frames Frames) Frames {
slice = append(slice, frames...)
copy(slice[len(frames):], slice)
copy(slice, frames)
return slice
}
// Helpers to remove context from the error interface.
// Message error wrapper.
// withMessage implements an error type annotated with a message that
// overwrites the wrapped message context.
type withMessage struct {
error error
message string
}
var _ interface { // Assert interface implementation.
error
Unwrap() error
fmt.Formatter
} = (*withMessage)(nil)
// WithMessage overwrites the message for the error by wrapping it. The
// error chain is maintained so that As, Is, and FramesFrom all continue
// to work.
func WithMessage(err error, msg string) error {
if err == nil {
return nil
}
return &withMessage{
error: err,
message: msg,
}
}
func (w *withMessage) Error() string { return w.message }
func (w *withMessage) Unwrap() error { return w.error }
func (w *withMessage) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('#') {
fmt.Fprintf(s, "&errors.withMessage{%q}", w.Error())
return
}
fallthrough
case 's':
io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
default:
// empty
}
}
// Error masking.
// Mask returns an error with the same message context as err, but that
// does not match err and can't be unwrapped. As and Is will return
// false for all meaningful values.
func Mask(err error) error {
if err == nil {
return nil
}
return New(err.Error())
}
// Opaque returns an error with the same message context as err, but
// that does not match err. As and Is will return false for all
// meaningful values.
//
// If err is a chain with Frames, then those are retained as wrappers
// around the opaque error, so that the error does not lose any
// information. Otherwise, err cannot be unwrapped.
//
// You can think of Opaque as squashing the history of an error.
func Opaque(err error) error {
if err == nil {
return nil
}
newErr := Mask(err)
if fframes := FramesFrom(err); len(fframes) > 0 {
newErr = WithFrames(newErr, fframes)
}
return newErr
}
// Error deserialization.
// ErrorFromBytes parses a stack trace or stack dump provided as bytes
// into an error. The format of the text is expected to match the output
// of printing with a formatter using the `%+v` verb. When an error is
// successfully parsed the second result is true; otherwise it is false.
// If you receive an error and the second result is false, well congrats
// you got an error.
//
// Currently, this only supports single errors with or without a stack
// trace or appended frames.
//
// TODO(PH): ensure ErrorFromBytes works with: multierror.
func ErrorFromBytes(byt []byte) (err error, ok bool) {
trimbyt := bytes.TrimRight(byt, "\n")
if len(trimbyt) == 0 || bytes.Equal(trimbyt, []byte("nil")) || bytes.Equal(trimbyt, []byte("<nil>")) {
return nil, false
}
ok = true
n := bytes.IndexByte(byt, '\n')
if n == -1 {
return New(string(byt)), true
}
err = New(string(byt[:n]))
stack, actualErr := FramesFromBytes(byt[n+1:])
if actualErr != nil {
return actualErr, false
}
if len(stack) > 0 {
err = WithFrames(err, stack)
}
return
}