Skip to content
This repository was archived by the owner on Dec 1, 2021. It is now read-only.

Commit 4f47277

Browse files
authored
Switch to runtime.CallersFrames (#183)
Fixes #160 Fixes #107 Signed-off-by: Dave Cheney <[email protected]>
1 parent 537896a commit 4f47277

File tree

2 files changed

+65
-83
lines changed

2 files changed

+65
-83
lines changed

stack.go

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,26 @@ import (
99
)
1010

1111
// Frame represents a program counter inside a stack frame.
12-
type Frame uintptr
12+
type Frame runtime.Frame
1313

1414
// pc returns the program counter for this frame;
1515
// multiple frames may have the same PC value.
16-
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
16+
func (f Frame) pc() uintptr { return runtime.Frame(f).PC }
1717

1818
// file returns the full path to the file that contains the
1919
// function for this Frame's pc.
2020
func (f Frame) file() string {
21-
fn := runtime.FuncForPC(f.pc())
22-
if fn == nil {
21+
file := runtime.Frame(f).File
22+
if file == "" {
2323
return "unknown"
2424
}
25-
file, _ := fn.FileLine(f.pc())
2625
return file
2726
}
2827

2928
// line returns the line number of source code of the
3029
// function for this Frame's pc.
3130
func (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
31+
return runtime.Frame(f).Line
3832
}
3933

4034
// Format formats the frame according to the fmt.Formatter interface.
@@ -54,12 +48,11 @@ func (f Frame) Format(s fmt.State, verb rune) {
5448
case 's':
5549
switch {
5650
case s.Flag('+'):
57-
pc := f.pc()
58-
fn := runtime.FuncForPC(pc)
51+
fn := runtime.Frame(f).Func
5952
if fn == nil {
6053
io.WriteString(s, "unknown")
6154
} else {
62-
file, _ := fn.FileLine(pc)
55+
file := runtime.Frame(f).File
6356
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
6457
}
6558
default:
@@ -114,20 +107,29 @@ func (s *stack) Format(st fmt.State, verb rune) {
114107
case 'v':
115108
switch {
116109
case st.Flag('+'):
117-
for _, pc := range *s {
118-
f := Frame(pc)
119-
fmt.Fprintf(st, "\n%+v", f)
110+
frames := runtime.CallersFrames(*s)
111+
for {
112+
frame, more := frames.Next()
113+
fmt.Fprintf(st, "\n%+v", Frame(frame))
114+
if !more {
115+
break
116+
}
120117
}
121118
}
122119
}
123120
}
124121

125122
func (s *stack) StackTrace() StackTrace {
126-
f := make([]Frame, len(*s))
127-
for i := 0; i < len(f); i++ {
128-
f[i] = Frame((*s)[i])
123+
var st []Frame
124+
frames := runtime.CallersFrames(*s)
125+
for {
126+
frame, more := frames.Next()
127+
st = append(st, Frame(frame))
128+
if !more {
129+
break
130+
}
129131
}
130-
return f
132+
return st
131133
}
132134

133135
func callers() *stack {

stack_test.go

Lines changed: 42 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,51 +6,18 @@ import (
66
"testing"
77
)
88

9-
var initpc, _, _, _ = runtime.Caller(0)
10-
11-
func TestFrameLine(t *testing.T) {
12-
var tests = []struct {
13-
Frame
14-
want int
15-
}{{
16-
Frame(initpc),
17-
9,
18-
}, {
19-
func() Frame {
20-
var pc, _, _, _ = runtime.Caller(0)
21-
return Frame(pc)
22-
}(),
23-
20,
24-
}, {
25-
func() Frame {
26-
var pc, _, _, _ = runtime.Caller(1)
27-
return Frame(pc)
28-
}(),
29-
28,
30-
}, {
31-
Frame(0), // invalid PC
32-
0,
33-
}}
34-
35-
for _, tt := range tests {
36-
got := tt.Frame.line()
37-
want := tt.want
38-
if want != got {
39-
t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got)
40-
}
41-
}
42-
}
9+
var initpc = caller()
4310

4411
type X struct{}
4512

13+
//go:noinline
4614
func (x X) val() Frame {
47-
var pc, _, _, _ = runtime.Caller(0)
48-
return Frame(pc)
15+
return caller()
4916
}
5017

18+
//go:noinline
5119
func (x *X) ptr() Frame {
52-
var pc, _, _, _ = runtime.Caller(0)
53-
return Frame(pc)
20+
return caller()
5421
}
5522

5623
func TestFrameFormat(t *testing.T) {
@@ -59,32 +26,32 @@ func TestFrameFormat(t *testing.T) {
5926
format string
6027
want string
6128
}{{
62-
Frame(initpc),
29+
initpc,
6330
"%s",
6431
"stack_test.go",
6532
}, {
66-
Frame(initpc),
33+
initpc,
6734
"%+s",
6835
"github.com/pkg/errors.init\n" +
6936
"\t.+/github.com/pkg/errors/stack_test.go",
7037
}, {
71-
Frame(0),
38+
Frame{},
7239
"%s",
7340
"unknown",
7441
}, {
75-
Frame(0),
42+
Frame{},
7643
"%+s",
7744
"unknown",
7845
}, {
79-
Frame(initpc),
46+
initpc,
8047
"%d",
8148
"9",
8249
}, {
83-
Frame(0),
50+
Frame{},
8451
"%d",
8552
"0",
8653
}, {
87-
Frame(initpc),
54+
initpc,
8855
"%n",
8956
"init",
9057
}, {
@@ -102,20 +69,20 @@ func TestFrameFormat(t *testing.T) {
10269
"%n",
10370
"X.val",
10471
}, {
105-
Frame(0),
72+
Frame{},
10673
"%n",
10774
"",
10875
}, {
109-
Frame(initpc),
76+
initpc,
11077
"%v",
11178
"stack_test.go:9",
11279
}, {
113-
Frame(initpc),
80+
initpc,
11481
"%+v",
11582
"github.com/pkg/errors.init\n" +
11683
"\t.+/github.com/pkg/errors/stack_test.go:9",
11784
}, {
118-
Frame(0),
85+
Frame{},
11986
"%v",
12087
"unknown:0",
12188
}}
@@ -153,24 +120,24 @@ func TestStackTrace(t *testing.T) {
153120
}{{
154121
New("ooh"), []string{
155122
"github.com/pkg/errors.TestStackTrace\n" +
156-
"\t.+/github.com/pkg/errors/stack_test.go:154",
123+
"\t.+/github.com/pkg/errors/stack_test.go:121",
157124
},
158125
}, {
159126
Wrap(New("ooh"), "ahh"), []string{
160127
"github.com/pkg/errors.TestStackTrace\n" +
161-
"\t.+/github.com/pkg/errors/stack_test.go:159", // this is the stack of Wrap, not New
128+
"\t.+/github.com/pkg/errors/stack_test.go:126", // this is the stack of Wrap, not New
162129
},
163130
}, {
164131
Cause(Wrap(New("ooh"), "ahh")), []string{
165132
"github.com/pkg/errors.TestStackTrace\n" +
166-
"\t.+/github.com/pkg/errors/stack_test.go:164", // this is the stack of New
133+
"\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New
167134
},
168135
}, {
169-
func() error { return New("ooh") }(), []string{
136+
func() error { noinline(); return New("ooh") }(), []string{
170137
`github.com/pkg/errors.(func·009|TestStackTrace.func1)` +
171-
"\n\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New
138+
"\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New
172139
"github.com/pkg/errors.TestStackTrace\n" +
173-
"\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New's caller
140+
"\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New's caller
174141
},
175142
}, {
176143
Cause(func() error {
@@ -179,11 +146,11 @@ func TestStackTrace(t *testing.T) {
179146
}()
180147
}()), []string{
181148
`github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` +
182-
"\n\t.+/github.com/pkg/errors/stack_test.go:178", // this is the stack of Errorf
149+
"\n\t.+/github.com/pkg/errors/stack_test.go:145", // this is the stack of Errorf
183150
`github.com/pkg/errors.(func·011|TestStackTrace.func2)` +
184-
"\n\t.+/github.com/pkg/errors/stack_test.go:179", // this is the stack of Errorf's caller
151+
"\n\t.+/github.com/pkg/errors/stack_test.go:146", // this is the stack of Errorf's caller
185152
"github.com/pkg/errors.TestStackTrace\n" +
186-
"\t.+/github.com/pkg/errors/stack_test.go:180", // this is the stack of Errorf's caller's caller
153+
"\t.+/github.com/pkg/errors/stack_test.go:147", // this is the stack of Errorf's caller's caller
187154
},
188155
}}
189156
for i, tt := range tests {
@@ -253,22 +220,35 @@ func TestStackTraceFormat(t *testing.T) {
253220
}, {
254221
stackTrace()[:2],
255222
"%v",
256-
`\[stack_test.go:207 stack_test.go:254\]`,
223+
`\[stack_test.go:174 stack_test.go:221\]`,
257224
}, {
258225
stackTrace()[:2],
259226
"%+v",
260227
"\n" +
261228
"github.com/pkg/errors.stackTrace\n" +
262-
"\t.+/github.com/pkg/errors/stack_test.go:207\n" +
229+
"\t.+/github.com/pkg/errors/stack_test.go:174\n" +
263230
"github.com/pkg/errors.TestStackTraceFormat\n" +
264-
"\t.+/github.com/pkg/errors/stack_test.go:258",
231+
"\t.+/github.com/pkg/errors/stack_test.go:225",
265232
}, {
266233
stackTrace()[:2],
267234
"%#v",
268-
`\[\]errors.Frame{stack_test.go:207, stack_test.go:266}`,
235+
`\[\]errors.Frame{stack_test.go:174, stack_test.go:233}`,
269236
}}
270237

271238
for i, tt := range tests {
272239
testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
273240
}
274241
}
242+
243+
// a version of runtime.Caller that returns a Frame, not a uintptr.
244+
func caller() Frame {
245+
var pcs [3]uintptr
246+
n := runtime.Callers(2, pcs[:])
247+
frames := runtime.CallersFrames(pcs[:n])
248+
frame, _ := frames.Next()
249+
return Frame(frame)
250+
}
251+
252+
//go:noinline
253+
// noinline prevents the caller being inlined
254+
func noinline() {}

0 commit comments

Comments
 (0)