-
Notifications
You must be signed in to change notification settings - Fork 4
/
panicwrap.go
101 lines (89 loc) · 2.7 KB
/
panicwrap.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
package throw
import (
"errors"
"math"
)
const NoStackTrace int = math.MaxInt32
// PanicHolder is a marker interface for a recovered panic
type PanicHolder interface {
StackTraceHolder
Recovered() interface{}
}
const recoverSkipFrames = 2 // defer() + runtime.panic()
// WrapPanic returns an error for the given recovered panic with a stack trace.
// The error is also marked as recovered panic. Will return nil for a nil value
// NB! Should be used inside a defer.
func WrapPanic(recovered interface{}) error {
return WrapPanicExt(recovered, recoverSkipFrames+1) // WrapPanic() + defer
}
// WrapPanicNoStack returns an error for the given recovered panic without a stack trace.
// The error is also marked as recovered panic. Will return nil for a nil value
// NB! Should be used inside a defer.
func WrapPanicNoStack(recovered interface{}) error {
return WrapPanicExt(recovered, NoStackTrace)
}
// WrapPanicNoStack returns an error for the given recovered panic with or without a stack trace.
// When (skipFrames) = NoStackTrace then no stack trace is included.
// The error is also marked as recovered panic. Will return nil for a nil value
func WrapPanicExt(recovered interface{}, skipFrames int) error {
if recovered == nil {
return nil
}
var st StackTrace
if skipFrames < NoStackTrace {
st = CaptureStack(skipFrames + 1)
}
var stDeepest StackTrace
var stDeepMod DeepestStackMode
switch vv := recovered.(type) {
case panicWrap:
if st == nil {
return vv
}
switch CompareStackTrace(vv.st, st) {
case EqualStack, SupersetStack:
return vv
}
case stackWrap:
stDeepest, stDeepMod = reuseSupersetTrace(st, vv.stDeepest)
case fmtWrap:
return panicWrap{st: st, stDeepest: stDeepest, fmtWrap: vv}
case error:
sth := OutermostStack(vv)
if sth != nil {
stDeep, _ := sth.DeepestStackTrace()
stDeepest, stDeepMod = reuseSupersetTrace(st, stDeep)
}
}
return panicWrap{st, recovered, stDeepest, wrapInternal(recovered), stDeepMod}
}
// InnermostPanicWithStack returns the most distant panic holder from the chain.
// The value can be either error or PanicHolder. Otherwise will return nil.
func InnermostPanicWithStack(errorOrHolder interface{}) PanicHolder {
switch vv := errorOrHolder.(type) {
case PanicHolder:
st := vv.ShallowStackTrace()
if st != nil {
return vv
}
return innermostWithStack(vv.Cause())
case error:
return innermostWithStack(vv)
default:
return nil
}
}
func innermostWithStack(errChain error) PanicHolder {
for errChain != nil {
if sw, ok := errChain.(panicWrap); ok {
nextErr := sw.Unwrap()
if sw.ShallowStackTrace() != nil && nextErr != nil {
return sw
}
errChain = nextErr
continue
}
errChain = errors.Unwrap(errChain)
}
return nil
}