1
+ // +build go1.7
2
+
1
3
// Package stack implements utilities to capture, manipulate, and format call
2
4
// stacks. It provides a simpler API than package runtime.
3
5
//
@@ -21,29 +23,31 @@ import (
21
23
22
24
// Call records a single function invocation from a goroutine stack.
23
25
type Call struct {
24
- fn * runtime.Func
25
- pc uintptr
26
+ frame runtime.Frame
26
27
}
27
28
28
29
// Caller returns a Call from the stack of the current goroutine. The argument
29
30
// skip is the number of stack frames to ascend, with 0 identifying the
30
31
// calling function.
31
32
func Caller (skip int ) Call {
32
- var pcs [2 ]uintptr
33
+ // As of Go 1.9 we need room for up to three PC entries.
34
+ //
35
+ // 0. An entry for the stack frame prior to the target to check for
36
+ // special handling needed if that prior entry is runtime.sigpanic.
37
+ // 1. A possible second entry to hold metadata about skipped inlined
38
+ // functions. If inline functions were not skipped the target frame
39
+ // PC will be here.
40
+ // 2. A third entry for the target frame PC when the second entry
41
+ // is used for skipped inline functions.
42
+ var pcs [3 ]uintptr
33
43
n := runtime .Callers (skip + 1 , pcs [:])
44
+ frames := runtime .CallersFrames (pcs [:n ])
45
+ frame , _ := frames .Next ()
46
+ frame , _ = frames .Next ()
34
47
35
- var c Call
36
-
37
- if n < 2 {
38
- return c
48
+ return Call {
49
+ frame : frame ,
39
50
}
40
-
41
- c .pc = pcs [1 ]
42
- if runtime .FuncForPC (pcs [0 ]).Name () != "runtime.sigpanic" {
43
- c .pc --
44
- }
45
- c .fn = runtime .FuncForPC (c .pc )
46
- return c
47
51
}
48
52
49
53
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
@@ -54,9 +58,10 @@ func (c Call) String() string {
54
58
// MarshalText implements encoding.TextMarshaler. It formats the Call the same
55
59
// as fmt.Sprintf("%v", c).
56
60
func (c Call ) MarshalText () ([]byte , error ) {
57
- if c .fn == nil {
61
+ if c .frame == (runtime. Frame {}) {
58
62
return nil , ErrNoFunc
59
63
}
64
+
60
65
buf := bytes.Buffer {}
61
66
fmt .Fprint (& buf , c )
62
67
return buf .Bytes (), nil
@@ -83,19 +88,19 @@ var ErrNoFunc = errors.New("no call stack information")
83
88
// %+v equivalent to %+s:%d
84
89
// %#v equivalent to %#s:%d
85
90
func (c Call ) Format (s fmt.State , verb rune ) {
86
- if c .fn == nil {
91
+ if c .frame == (runtime. Frame {}) {
87
92
fmt .Fprintf (s , "%%!%c(NOFUNC)" , verb )
88
93
return
89
94
}
90
95
91
96
switch verb {
92
97
case 's' , 'v' :
93
- file , line := c .fn . FileLine ( c . pc )
98
+ file := c .frame . File
94
99
switch {
95
100
case s .Flag ('#' ):
96
101
// done
97
102
case s .Flag ('+' ):
98
- file = file [pkgIndex (file , c .fn . Name () ):]
103
+ file = file [pkgIndex (file , c .frame . Function ):]
99
104
default :
100
105
const sep = "/"
101
106
if i := strings .LastIndex (file , sep ); i != - 1 {
@@ -105,16 +110,15 @@ func (c Call) Format(s fmt.State, verb rune) {
105
110
io .WriteString (s , file )
106
111
if verb == 'v' {
107
112
buf := [7 ]byte {':' }
108
- s .Write (strconv .AppendInt (buf [:1 ], int64 (line ), 10 ))
113
+ s .Write (strconv .AppendInt (buf [:1 ], int64 (c . frame . Line ), 10 ))
109
114
}
110
115
111
116
case 'd' :
112
- _ , line := c .fn .FileLine (c .pc )
113
117
buf := [6 ]byte {}
114
- s .Write (strconv .AppendInt (buf [:0 ], int64 (line ), 10 ))
118
+ s .Write (strconv .AppendInt (buf [:0 ], int64 (c . frame . Line ), 10 ))
115
119
116
120
case 'k' :
117
- name := c .fn . Name ()
121
+ name := c .frame . Function
118
122
const pathSep = "/"
119
123
start , end := 0 , len (name )
120
124
if i := strings .LastIndex (name , pathSep ); i != - 1 {
@@ -130,7 +134,7 @@ func (c Call) Format(s fmt.State, verb rune) {
130
134
io .WriteString (s , name [start :end ])
131
135
132
136
case 'n' :
133
- name := c .fn . Name ()
137
+ name := c .frame . Function
134
138
if ! s .Flag ('+' ) {
135
139
const pathSep = "/"
136
140
if i := strings .LastIndex (name , pathSep ); i != - 1 {
@@ -145,35 +149,17 @@ func (c Call) Format(s fmt.State, verb rune) {
145
149
}
146
150
}
147
151
152
+ // Frame returns the call frame infomation for the Call.
153
+ func (c Call ) Frame () runtime.Frame {
154
+ return c .frame
155
+ }
156
+
148
157
// PC returns the program counter for this call frame; multiple frames may
149
158
// have the same PC value.
159
+ //
160
+ // Deprecated: Use Call.Frame instead.
150
161
func (c Call ) PC () uintptr {
151
- return c .pc
152
- }
153
-
154
- // name returns the import path qualified name of the function containing the
155
- // call.
156
- func (c Call ) name () string {
157
- if c .fn == nil {
158
- return "???"
159
- }
160
- return c .fn .Name ()
161
- }
162
-
163
- func (c Call ) file () string {
164
- if c .fn == nil {
165
- return "???"
166
- }
167
- file , _ := c .fn .FileLine (c .pc )
168
- return file
169
- }
170
-
171
- func (c Call ) line () int {
172
- if c .fn == nil {
173
- return 0
174
- }
175
- _ , line := c .fn .FileLine (c .pc )
176
- return line
162
+ return c .frame .PC
177
163
}
178
164
179
165
// CallStack records a sequence of function invocations from a goroutine
@@ -197,9 +183,6 @@ func (cs CallStack) MarshalText() ([]byte, error) {
197
183
buf := bytes.Buffer {}
198
184
buf .Write (openBracketBytes )
199
185
for i , pc := range cs {
200
- if pc .fn == nil {
201
- return nil , ErrNoFunc
202
- }
203
186
if i > 0 {
204
187
buf .Write (spaceBytes )
205
188
}
@@ -227,18 +210,18 @@ func (cs CallStack) Format(s fmt.State, verb rune) {
227
210
// identifying the calling function.
228
211
func Trace () CallStack {
229
212
var pcs [512 ]uintptr
230
- n := runtime .Callers (2 , pcs [:])
231
- cs := make ([]Call , n )
213
+ n := runtime .Callers (1 , pcs [:])
232
214
233
- for i , pc := range pcs [:n ] {
234
- pcFix := pc
235
- if i > 0 && cs [i - 1 ].fn .Name () != "runtime.sigpanic" {
236
- pcFix --
237
- }
238
- cs [i ] = Call {
239
- fn : runtime .FuncForPC (pcFix ),
240
- pc : pcFix ,
241
- }
215
+ frames := runtime .CallersFrames (pcs [:n ])
216
+ cs := make (CallStack , 0 , n )
217
+
218
+ // Skip extra frame retrieved just to make sure the runtime.sigpanic
219
+ // special case is handled.
220
+ frame , more := frames .Next ()
221
+
222
+ for more {
223
+ frame , more = frames .Next ()
224
+ cs = append (cs , Call {frame : frame })
242
225
}
243
226
244
227
return cs
@@ -247,7 +230,7 @@ func Trace() CallStack {
247
230
// TrimBelow returns a slice of the CallStack with all entries below c
248
231
// removed.
249
232
func (cs CallStack ) TrimBelow (c Call ) CallStack {
250
- for len (cs ) > 0 && cs [0 ]. pc != c . pc {
233
+ for len (cs ) > 0 && cs [0 ] != c {
251
234
cs = cs [1 :]
252
235
}
253
236
return cs
@@ -256,7 +239,7 @@ func (cs CallStack) TrimBelow(c Call) CallStack {
256
239
// TrimAbove returns a slice of the CallStack with all entries above c
257
240
// removed.
258
241
func (cs CallStack ) TrimAbove (c Call ) CallStack {
259
- for len (cs ) > 0 && cs [len (cs )- 1 ]. pc != c . pc {
242
+ for len (cs ) > 0 && cs [len (cs )- 1 ] != c {
260
243
cs = cs [:len (cs )- 1 ]
261
244
}
262
245
return cs
@@ -305,12 +288,13 @@ func pkgIndex(file, funcName string) int {
305
288
var runtimePath string
306
289
307
290
func init () {
308
- var pcs [1 ]uintptr
291
+ var pcs [3 ]uintptr
309
292
runtime .Callers (0 , pcs [:])
310
- fn := runtime .FuncForPC (pcs [0 ])
311
- file , _ := fn .FileLine (pcs [0 ])
293
+ frames := runtime .CallersFrames (pcs [:])
294
+ frame , _ := frames .Next ()
295
+ file := frame .File
312
296
313
- idx := pkgIndex (file , fn . Name () )
297
+ idx := pkgIndex (frame . File , frame . Function )
314
298
315
299
runtimePath = file [:idx ]
316
300
if runtime .GOOS == "windows" {
@@ -319,7 +303,7 @@ func init() {
319
303
}
320
304
321
305
func inGoroot (c Call ) bool {
322
- file := c .file ()
306
+ file := c .frame . File
323
307
if len (file ) == 0 || file [0 ] == '?' {
324
308
return true
325
309
}
0 commit comments