-
Notifications
You must be signed in to change notification settings - Fork 0
/
stack.go
137 lines (114 loc) · 2.68 KB
/
stack.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
package xerrors
import (
"errors"
"fmt"
"runtime"
"strings"
)
// Frames is a slice of uintptrs representing stack frames.
type Frames []uintptr
// Frames returns the list of Frame objects associated with the Frames.
func (s Frames) Frames() []Frame {
r := make([]Frame, len(s))
f := runtime.CallersFrames(s)
n := 0
for more := true; more; {
var frame runtime.Frame
frame, more = f.Next()
r[n] = Frame{
File: frame.File,
Line: frame.Line,
Function: frame.Function,
}
n++
}
return r
}
// Frame represents a single stack frame with file, line, and function information.
type Frame struct {
File string
Line int
Function string
}
// String returns a string representation of the Frame.
func (s Frame) String() string {
builder := bufferPool.Get().(*strings.Builder)
builder.Reset()
defer bufferPool.Put(builder)
_, _ = fmt.Fprintf(builder, "%s %s:%d", s.Function, s.File, s.Line)
return builder.String()
}
type stack struct {
err error
callers Frames
}
func (err *stack) Error() string {
return stringify(err)
}
func (err *stack) message(verbose bool) string {
if !verbose {
return ""
}
builder := bufferPool.Get().(*strings.Builder)
builder.Reset()
defer bufferPool.Put(builder)
builder.WriteString("stack\n")
framesString := StackFrames(err)
for _, fr := range framesString.Frames() {
builder.WriteString("\t" + fr.String() + "\n")
}
return "\n" + builder.String()
}
// Is implements the anonymous interface Is
// it provides an alternative to As that allocates less memory if you are not interested in the actual stacktrace.
func (err *stack) Is(target error) bool {
_, ok := target.(*stack)
return ok
}
func (err *stack) Unwrap() error {
return err.err
}
func (err *stack) StackFrames() Frames {
return err.callers
}
// Format implements the [fmt.Formatter] interface
// it is our main point of entry to format the error using the [fmt] package.
func (err *stack) Format(s fmt.State, verb rune) {
format(err, s, verb)
}
// StackFrames returns the list of *Frames associated to an error.
func StackFrames(err error) *Frames {
var fss *Frames
for ; err != nil; err = errors.Unwrap(err) {
var errS *stack
ok := errors.As(err, &errS)
if ok {
fs := errS.StackFrames()
return &fs
}
}
return fss
}
func callers(skip int) Frames {
pc := make([]uintptr, 32)
n := runtime.Callers(skip+1, pc)
return pc[:n]
}
func ensureStack(err error, skip int) error {
if !hasStack(err) {
err = withStack(err, skip+1)
}
return err
}
func hasStack(err error) bool {
return Is(err, &stack{})
}
func withStack(err error, skip int) error {
if err == nil {
return nil
}
return &stack{
err: err,
callers: callers(skip + 1),
}
}