Skip to content

Commit 64ead41

Browse files
authored
Merge pull request #19 from go-stack/feature/callers-frames
Use runtime.CallersFrames
2 parents 44dafbc + 93d005d commit 64ead41

File tree

5 files changed

+313
-142
lines changed

5 files changed

+313
-142
lines changed

.travis.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
language: go
22
sudo: false
33
go:
4-
- 1.2
5-
- 1.3
6-
- 1.4
7-
- 1.5
8-
- 1.6
4+
- 1.7
5+
- 1.8
6+
- 1.9
97
- tip
108

119
before_install:
1210
- go get github.com/mattn/goveralls
13-
- go get golang.org/x/tools/cmd/cover
1411

1512
script:
1613
- goveralls -service=travis-ci

stack-go19_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// +build go1.9
2+
3+
package stack_test
4+
5+
import (
6+
"runtime"
7+
"testing"
8+
9+
"github.com/go-stack/stack"
10+
)
11+
12+
func TestCallerInlinedPanic(t *testing.T) {
13+
t.Parallel()
14+
15+
var line int
16+
17+
defer func() {
18+
if recover() != nil {
19+
var pcs [32]uintptr
20+
n := runtime.Callers(1, pcs[:])
21+
frames := runtime.CallersFrames(pcs[:n])
22+
// count frames to runtime.sigpanic
23+
panicIdx := 0
24+
for {
25+
f, more := frames.Next()
26+
if f.Function == "runtime.sigpanic" {
27+
break
28+
}
29+
panicIdx++
30+
if !more {
31+
t.Fatal("no runtime.sigpanic entry on the stack")
32+
}
33+
}
34+
35+
c := stack.Caller(panicIdx)
36+
if got, want := c.Frame().Function, "runtime.sigpanic"; got != want {
37+
t.Errorf("sigpanic frame: got name == %v, want name == %v", got, want)
38+
}
39+
40+
c1 := stack.Caller(panicIdx + 1)
41+
if got, want := c1.Frame().Function, "github.com/go-stack/stack_test.inlinablePanic"; got != want {
42+
t.Errorf("TestCallerInlinedPanic frame: got name == %v, want name == %v", got, want)
43+
}
44+
if got, want := c1.Frame().Line, line; got != want {
45+
t.Errorf("TestCallerInlinedPanic frame: got line == %v, want line == %v", got, want)
46+
}
47+
}
48+
}()
49+
50+
doPanic(t, &line)
51+
t.Fatal("failed to panic")
52+
}
53+
54+
func doPanic(t *testing.T, panicLine *int) {
55+
_, _, line, ok := runtime.Caller(0)
56+
*panicLine = line + 11 // adjust to match line of panic below
57+
if !ok {
58+
t.Fatal("runtime.Caller(0) failed")
59+
}
60+
inlinablePanic()
61+
}
62+
63+
func inlinablePanic() {
64+
// Initiate a sigpanic.
65+
var x *uintptr
66+
_ = *x
67+
}

stack.go

Lines changed: 54 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build go1.7
2+
13
// Package stack implements utilities to capture, manipulate, and format call
24
// stacks. It provides a simpler API than package runtime.
35
//
@@ -21,29 +23,31 @@ import (
2123

2224
// Call records a single function invocation from a goroutine stack.
2325
type Call struct {
24-
fn *runtime.Func
25-
pc uintptr
26+
frame runtime.Frame
2627
}
2728

2829
// Caller returns a Call from the stack of the current goroutine. The argument
2930
// skip is the number of stack frames to ascend, with 0 identifying the
3031
// calling function.
3132
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
3343
n := runtime.Callers(skip+1, pcs[:])
44+
frames := runtime.CallersFrames(pcs[:n])
45+
frame, _ := frames.Next()
46+
frame, _ = frames.Next()
3447

35-
var c Call
36-
37-
if n < 2 {
38-
return c
48+
return Call{
49+
frame: frame,
3950
}
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
4751
}
4852

4953
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
@@ -54,9 +58,10 @@ func (c Call) String() string {
5458
// MarshalText implements encoding.TextMarshaler. It formats the Call the same
5559
// as fmt.Sprintf("%v", c).
5660
func (c Call) MarshalText() ([]byte, error) {
57-
if c.fn == nil {
61+
if c.frame == (runtime.Frame{}) {
5862
return nil, ErrNoFunc
5963
}
64+
6065
buf := bytes.Buffer{}
6166
fmt.Fprint(&buf, c)
6267
return buf.Bytes(), nil
@@ -83,19 +88,19 @@ var ErrNoFunc = errors.New("no call stack information")
8388
// %+v equivalent to %+s:%d
8489
// %#v equivalent to %#s:%d
8590
func (c Call) Format(s fmt.State, verb rune) {
86-
if c.fn == nil {
91+
if c.frame == (runtime.Frame{}) {
8792
fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
8893
return
8994
}
9095

9196
switch verb {
9297
case 's', 'v':
93-
file, line := c.fn.FileLine(c.pc)
98+
file := c.frame.File
9499
switch {
95100
case s.Flag('#'):
96101
// done
97102
case s.Flag('+'):
98-
file = file[pkgIndex(file, c.fn.Name()):]
103+
file = file[pkgIndex(file, c.frame.Function):]
99104
default:
100105
const sep = "/"
101106
if i := strings.LastIndex(file, sep); i != -1 {
@@ -105,16 +110,15 @@ func (c Call) Format(s fmt.State, verb rune) {
105110
io.WriteString(s, file)
106111
if verb == 'v' {
107112
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))
109114
}
110115

111116
case 'd':
112-
_, line := c.fn.FileLine(c.pc)
113117
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))
115119

116120
case 'k':
117-
name := c.fn.Name()
121+
name := c.frame.Function
118122
const pathSep = "/"
119123
start, end := 0, len(name)
120124
if i := strings.LastIndex(name, pathSep); i != -1 {
@@ -130,7 +134,7 @@ func (c Call) Format(s fmt.State, verb rune) {
130134
io.WriteString(s, name[start:end])
131135

132136
case 'n':
133-
name := c.fn.Name()
137+
name := c.frame.Function
134138
if !s.Flag('+') {
135139
const pathSep = "/"
136140
if i := strings.LastIndex(name, pathSep); i != -1 {
@@ -145,35 +149,17 @@ func (c Call) Format(s fmt.State, verb rune) {
145149
}
146150
}
147151

152+
// Frame returns the call frame infomation for the Call.
153+
func (c Call) Frame() runtime.Frame {
154+
return c.frame
155+
}
156+
148157
// PC returns the program counter for this call frame; multiple frames may
149158
// have the same PC value.
159+
//
160+
// Deprecated: Use Call.Frame instead.
150161
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
177163
}
178164

179165
// CallStack records a sequence of function invocations from a goroutine
@@ -197,9 +183,6 @@ func (cs CallStack) MarshalText() ([]byte, error) {
197183
buf := bytes.Buffer{}
198184
buf.Write(openBracketBytes)
199185
for i, pc := range cs {
200-
if pc.fn == nil {
201-
return nil, ErrNoFunc
202-
}
203186
if i > 0 {
204187
buf.Write(spaceBytes)
205188
}
@@ -227,18 +210,18 @@ func (cs CallStack) Format(s fmt.State, verb rune) {
227210
// identifying the calling function.
228211
func Trace() CallStack {
229212
var pcs [512]uintptr
230-
n := runtime.Callers(2, pcs[:])
231-
cs := make([]Call, n)
213+
n := runtime.Callers(1, pcs[:])
232214

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})
242225
}
243226

244227
return cs
@@ -247,7 +230,7 @@ func Trace() CallStack {
247230
// TrimBelow returns a slice of the CallStack with all entries below c
248231
// removed.
249232
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 {
251234
cs = cs[1:]
252235
}
253236
return cs
@@ -256,7 +239,7 @@ func (cs CallStack) TrimBelow(c Call) CallStack {
256239
// TrimAbove returns a slice of the CallStack with all entries above c
257240
// removed.
258241
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 {
260243
cs = cs[:len(cs)-1]
261244
}
262245
return cs
@@ -305,12 +288,13 @@ func pkgIndex(file, funcName string) int {
305288
var runtimePath string
306289

307290
func init() {
308-
var pcs [1]uintptr
291+
var pcs [3]uintptr
309292
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
312296

313-
idx := pkgIndex(file, fn.Name())
297+
idx := pkgIndex(frame.File, frame.Function)
314298

315299
runtimePath = file[:idx]
316300
if runtime.GOOS == "windows" {
@@ -319,7 +303,7 @@ func init() {
319303
}
320304

321305
func inGoroot(c Call) bool {
322-
file := c.file()
306+
file := c.frame.File
323307
if len(file) == 0 || file[0] == '?' {
324308
return true
325309
}

0 commit comments

Comments
 (0)