blob: 6b1f2891a5ac09218a5b7de82f3e479a16f44298 [file] [log] [blame]
David K. Bainbridge215e0242017-09-05 23:18:24 -07001package errors
2
3import (
4 "fmt"
5 "io"
6 "path"
7 "runtime"
8 "strings"
9)
10
11// Frame represents a program counter inside a stack frame.
12type Frame uintptr
13
14// pc returns the program counter for this frame;
15// multiple frames may have the same PC value.
16func (f Frame) pc() uintptr { return uintptr(f) - 1 }
17
18// file returns the full path to the file that contains the
19// function for this Frame's pc.
20func (f Frame) file() string {
21 fn := runtime.FuncForPC(f.pc())
22 if fn == nil {
23 return "unknown"
24 }
25 file, _ := fn.FileLine(f.pc())
26 return file
27}
28
29// line returns the line number of source code of the
30// function for this Frame's pc.
31func (f Frame) line() int {
32 fn := runtime.FuncForPC(f.pc())
33 if fn == nil {
34 return 0
35 }
36 _, line := fn.FileLine(f.pc())
37 return line
38}
39
40// Format formats the frame according to the fmt.Formatter interface.
41//
42// %s source file
43// %d source line
44// %n function name
45// %v equivalent to %s:%d
46//
47// Format accepts flags that alter the printing of some verbs, as follows:
48//
49// %+s path of source file relative to the compile time GOPATH
50// %+v equivalent to %+s:%d
51func (f Frame) Format(s fmt.State, verb rune) {
52 switch verb {
53 case 's':
54 switch {
55 case s.Flag('+'):
56 pc := f.pc()
57 fn := runtime.FuncForPC(pc)
58 if fn == nil {
59 io.WriteString(s, "unknown")
60 } else {
61 file, _ := fn.FileLine(pc)
62 fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
63 }
64 default:
65 io.WriteString(s, path.Base(f.file()))
66 }
67 case 'd':
68 fmt.Fprintf(s, "%d", f.line())
69 case 'n':
70 name := runtime.FuncForPC(f.pc()).Name()
71 io.WriteString(s, funcname(name))
72 case 'v':
73 f.Format(s, 's')
74 io.WriteString(s, ":")
75 f.Format(s, 'd')
76 }
77}
78
79// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
80type StackTrace []Frame
81
82func (st StackTrace) Format(s fmt.State, verb rune) {
83 switch verb {
84 case 'v':
85 switch {
86 case s.Flag('+'):
87 for _, f := range st {
88 fmt.Fprintf(s, "\n%+v", f)
89 }
90 case s.Flag('#'):
91 fmt.Fprintf(s, "%#v", []Frame(st))
92 default:
93 fmt.Fprintf(s, "%v", []Frame(st))
94 }
95 case 's':
96 fmt.Fprintf(s, "%s", []Frame(st))
97 }
98}
99
100// stack represents a stack of program counters.
101type stack []uintptr
102
103func (s *stack) Format(st fmt.State, verb rune) {
104 switch verb {
105 case 'v':
106 switch {
107 case st.Flag('+'):
108 for _, pc := range *s {
109 f := Frame(pc)
110 fmt.Fprintf(st, "\n%+v", f)
111 }
112 }
113 }
114}
115
116func (s *stack) StackTrace() StackTrace {
117 f := make([]Frame, len(*s))
118 for i := 0; i < len(f); i++ {
119 f[i] = Frame((*s)[i])
120 }
121 return f
122}
123
124func callers() *stack {
125 const depth = 32
126 var pcs [depth]uintptr
127 n := runtime.Callers(3, pcs[:])
128 var st stack = pcs[0:n]
129 return &st
130}
131
132// funcname removes the path prefix component of a function's name reported by func.Name().
133func funcname(name string) string {
134 i := strings.LastIndex(name, "/")
135 name = name[i+1:]
136 i = strings.Index(name, ".")
137 return name[i+1:]
138}
139
140func trimGOPATH(name, file string) string {
141 // Here we want to get the source file path relative to the compile time
142 // GOPATH. As of Go 1.6.x there is no direct way to know the compiled
143 // GOPATH at runtime, but we can infer the number of path segments in the
144 // GOPATH. We note that fn.Name() returns the function name qualified by
145 // the import path, which does not include the GOPATH. Thus we can trim
146 // segments from the beginning of the file path until the number of path
147 // separators remaining is one more than the number of path separators in
148 // the function name. For example, given:
149 //
150 // GOPATH /home/user
151 // file /home/user/src/pkg/sub/file.go
152 // fn.Name() pkg/sub.Type.Method
153 //
154 // We want to produce:
155 //
156 // pkg/sub/file.go
157 //
158 // From this we can easily see that fn.Name() has one less path separator
159 // than our desired output. We count separators from the end of the file
160 // path until it finds two more than in the function name and then move
161 // one character forward to preserve the initial path segment without a
162 // leading separator.
163 const sep = "/"
164 goal := strings.Count(name, sep) + 2
165 i := len(file)
166 for n := 0; n < goal; n++ {
167 i = strings.LastIndex(file[:i], sep)
168 if i == -1 {
169 // not enough separators found, set i so that the slice expression
170 // below leaves file unmodified
171 i = -len(sep)
172 break
173 }
174 }
175 // get back to 0 or trim the leading separator
176 file = file[i+len(sep):]
177 return file
178}