Skip to content

Commit d644040

Browse files
committed
Abandon lazily calling runtime.CallersFrames.
Although the idea is attractive, Go 1.9 deprecated the ability to reliably use slices of the []uintptr returned from runtime.Callers. It is difficult to implement the CallStack Trim methods without being able to subslice or reverse iterate the []uintptr.
1 parent 498933c commit d644040

File tree

4 files changed

+177
-138
lines changed

4 files changed

+177
-138
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.go

Lines changed: 55 additions & 97 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,23 +23,31 @@ import (
2123

2224
// Call records a single function invocation from a goroutine stack.
2325
type Call struct {
24-
pcs [2]uintptr
26+
frame runtime.Frame
2527
}
2628

2729
// Caller returns a Call from the stack of the current goroutine. The argument
2830
// skip is the number of stack frames to ascend, with 0 identifying the
2931
// calling function.
3032
func Caller(skip int) Call {
31-
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
3243
n := runtime.Callers(skip+1, pcs[:])
44+
frames := runtime.CallersFrames(pcs[:n])
45+
frame, _ := frames.Next()
46+
frame, _ = frames.Next()
3347

34-
var c Call
35-
if n < 2 {
36-
return c
48+
return Call{
49+
frame: frame,
3750
}
38-
39-
c.pcs = pcs
40-
return c
4151
}
4252

4353
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
@@ -48,7 +58,7 @@ func (c Call) String() string {
4858
// MarshalText implements encoding.TextMarshaler. It formats the Call the same
4959
// as fmt.Sprintf("%v", c).
5060
func (c Call) MarshalText() ([]byte, error) {
51-
if c.pcs[0] == 0 {
61+
if c.frame == (runtime.Frame{}) {
5262
return nil, ErrNoFunc
5363
}
5464

@@ -78,31 +88,19 @@ var ErrNoFunc = errors.New("no call stack information")
7888
// %+v equivalent to %+s:%d
7989
// %#v equivalent to %#s:%d
8090
func (c Call) Format(s fmt.State, verb rune) {
81-
frames := runtime.CallersFrames(c.pcs[:])
82-
head, _ := frames.Next()
83-
frame, _ := frames.Next()
84-
85-
if head.Function == "runtime.signpanic" {
86-
frame, _ = frames.Next()
87-
}
88-
89-
format(&frame, s, verb)
90-
}
91-
92-
func format(frame *runtime.Frame, s fmt.State, verb rune) {
93-
if frame.Func == nil {
91+
if c.frame == (runtime.Frame{}) {
9492
fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
9593
return
9694
}
9795

9896
switch verb {
9997
case 's', 'v':
100-
file, line := frame.File, frame.Line
98+
file := c.frame.File
10199
switch {
102100
case s.Flag('#'):
103101
// done
104102
case s.Flag('+'):
105-
file = file[pkgIndex(file, frame.Function):]
103+
file = file[pkgIndex(file, c.frame.Function):]
106104
default:
107105
const sep = "/"
108106
if i := strings.LastIndex(file, sep); i != -1 {
@@ -112,16 +110,15 @@ func format(frame *runtime.Frame, s fmt.State, verb rune) {
112110
io.WriteString(s, file)
113111
if verb == 'v' {
114112
buf := [7]byte{':'}
115-
s.Write(strconv.AppendInt(buf[:1], int64(line), 10))
113+
s.Write(strconv.AppendInt(buf[:1], int64(c.frame.Line), 10))
116114
}
117115

118116
case 'd':
119-
line := frame.Line
120117
buf := [6]byte{}
121-
s.Write(strconv.AppendInt(buf[:0], int64(line), 10))
118+
s.Write(strconv.AppendInt(buf[:0], int64(c.frame.Line), 10))
122119

123120
case 'k':
124-
name := frame.Function
121+
name := c.frame.Function
125122
const pathSep = "/"
126123
start, end := 0, len(name)
127124
if i := strings.LastIndex(name, pathSep); i != -1 {
@@ -137,7 +134,7 @@ func format(frame *runtime.Frame, s fmt.State, verb rune) {
137134
io.WriteString(s, name[start:end])
138135

139136
case 'n':
140-
name := frame.Function
137+
name := c.frame.Function
141138
if !s.Flag('+') {
142139
const pathSep = "/"
143140
if i := strings.LastIndex(name, pathSep); i != -1 {
@@ -152,43 +149,17 @@ func format(frame *runtime.Frame, s fmt.State, verb rune) {
152149
}
153150
}
154151

152+
// Frame returns the call frame infomation for the Call.
153+
func (c Call) Frame() runtime.Frame {
154+
return c.frame
155+
}
156+
155157
// PC returns the program counter for this call frame; multiple frames may
156158
// have the same PC value.
159+
//
160+
// Deprecated: Use Call.Frame instead.
157161
func (c Call) PC() uintptr {
158-
return c.pcs[1]
159-
}
160-
161-
func (c Call) frame() *runtime.Frame {
162-
frames := runtime.CallersFrames(c.pcs[:])
163-
frame, _ := frames.Next()
164-
frame, _ = frames.Next()
165-
return &frame
166-
}
167-
168-
// name returns the import path qualified name of the function containing the
169-
// call.
170-
func (c Call) name() string {
171-
frame := c.frame()
172-
if frame.Func == nil {
173-
return "???"
174-
}
175-
return frame.Function
176-
}
177-
178-
func (c Call) file() string {
179-
frame := c.frame()
180-
if frame.Func == nil {
181-
return "???"
182-
}
183-
return frame.File
184-
}
185-
186-
func (c Call) line() int {
187-
frame := c.frame()
188-
if frame.Func == nil {
189-
return 0
190-
}
191-
return frame.Line
162+
return c.frame.PC
192163
}
193164

194165
// CallStack records a sequence of function invocations from a goroutine
@@ -226,31 +197,12 @@ func (cs CallStack) MarshalText() ([]byte, error) {
226197
// supplied verb and options.
227198
func (cs CallStack) Format(s fmt.State, verb rune) {
228199
s.Write(openBracketBytes)
229-
230-
var pcs []uintptr
231-
for _, c := range cs {
232-
pcs = append(pcs, c.pcs[:]...)
233-
}
234-
235-
frames := runtime.CallersFrames(pcs[:])
236-
237-
initial := true
238-
for frame, more := frames.Next(); more; frame, more = frames.Next() {
239-
if !initial {
200+
for i, pc := range cs {
201+
if i > 0 {
240202
s.Write(spaceBytes)
241203
}
242-
243-
next, _ := frames.Next()
244-
if frame.Function == "runtime.sigpanic" {
245-
frame, _ = runtime.CallersFrames([]uintptr{next.PC - 1}).Next()
246-
} else {
247-
frame = next
248-
}
249-
250-
format(&frame, s, verb)
251-
initial = false
204+
pc.Format(s, verb)
252205
}
253-
254206
s.Write(closeBracketBytes)
255207
}
256208

@@ -259,12 +211,17 @@ func (cs CallStack) Format(s fmt.State, verb rune) {
259211
func Trace() CallStack {
260212
var pcs [512]uintptr
261213
n := runtime.Callers(1, pcs[:])
262-
cs := make([]Call, n)
263214

264-
for i := range pcs[:n] {
265-
cs[i] = Call{
266-
pcs: [2]uintptr{pcs[i], pcs[i+1]},
267-
}
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})
268225
}
269226

270227
return cs
@@ -273,7 +230,7 @@ func Trace() CallStack {
273230
// TrimBelow returns a slice of the CallStack with all entries below c
274231
// removed.
275232
func (cs CallStack) TrimBelow(c Call) CallStack {
276-
for len(cs) > 0 && cs[0].pcs[1] != c.pcs[1] {
233+
for len(cs) > 0 && cs[0] != c {
277234
cs = cs[1:]
278235
}
279236
return cs
@@ -282,7 +239,7 @@ func (cs CallStack) TrimBelow(c Call) CallStack {
282239
// TrimAbove returns a slice of the CallStack with all entries above c
283240
// removed.
284241
func (cs CallStack) TrimAbove(c Call) CallStack {
285-
for len(cs) > 0 && cs[len(cs)-1].pcs[1] != c.pcs[1] {
242+
for len(cs) > 0 && cs[len(cs)-1] != c {
286243
cs = cs[:len(cs)-1]
287244
}
288245
return cs
@@ -331,12 +288,13 @@ func pkgIndex(file, funcName string) int {
331288
var runtimePath string
332289

333290
func init() {
334-
var pcs [1]uintptr
291+
var pcs [3]uintptr
335292
runtime.Callers(0, pcs[:])
336-
fn := runtime.FuncForPC(pcs[0])
337-
file, _ := fn.FileLine(pcs[0])
293+
frames := runtime.CallersFrames(pcs[:])
294+
frame, _ := frames.Next()
295+
file := frame.File
338296

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

341299
runtimePath = file[:idx]
342300
if runtime.GOOS == "windows" {
@@ -345,7 +303,7 @@ func init() {
345303
}
346304

347305
func inGoroot(c Call) bool {
348-
file := c.file()
306+
file := c.frame.File
349307
if len(file) == 0 || file[0] == '?' {
350308
return true
351309
}

stack_test.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ const importPath = "github.com/go-stack/stack"
1717

1818
type testType struct{}
1919

20-
func (tt testType) testMethod() (c stack.Call, pc uintptr, file string, line int, ok bool) {
20+
func (tt testType) testMethod() (c stack.Call, file string, line int, ok bool) {
2121
c = stack.Caller(0)
22-
pc, file, line, ok = runtime.Caller(0)
22+
_, file, line, ok = runtime.Caller(0)
2323
line--
2424
return
2525
}
@@ -28,14 +28,14 @@ func TestCallFormat(t *testing.T) {
2828
t.Parallel()
2929

3030
c := stack.Caller(0)
31-
pc, file, line, ok := runtime.Caller(0)
31+
_, file, line, ok := runtime.Caller(0)
3232
line--
3333
if !ok {
3434
t.Fatal("runtime.Caller(0) failed")
3535
}
3636
relFile := path.Join(importPath, filepath.Base(file))
3737

38-
c2, pc2, file2, line2, ok2 := testType{}.testMethod()
38+
c2, file2, line2, ok2 := testType{}.testMethod()
3939
if !ok2 {
4040
t.Fatal("runtime.Caller(0) failed")
4141
}
@@ -54,7 +54,7 @@ func TestCallFormat(t *testing.T) {
5454
{c, "func", "%#s", file},
5555
{c, "func", "%d", fmt.Sprint(line)},
5656
{c, "func", "%n", "TestCallFormat"},
57-
{c, "func", "%+n", runtime.FuncForPC(pc - 1).Name()},
57+
{c, "func", "%+n", "github.com/go-stack/stack_test.TestCallFormat"},
5858
{c, "func", "%k", "stack_test"},
5959
{c, "func", "%+k", "github.com/go-stack/stack_test"},
6060
{c, "func", "%v", fmt.Sprint(path.Base(file), ":", line)},
@@ -66,7 +66,7 @@ func TestCallFormat(t *testing.T) {
6666
{c2, "meth", "%#s", file2},
6767
{c2, "meth", "%d", fmt.Sprint(line2)},
6868
{c2, "meth", "%n", "testType.testMethod"},
69-
{c2, "meth", "%+n", runtime.FuncForPC(pc2).Name()},
69+
{c2, "meth", "%+n", "github.com/go-stack/stack_test.testType.testMethod"},
7070
{c2, "meth", "%k", "stack_test"},
7171
{c2, "meth", "%+k", "github.com/go-stack/stack_test"},
7272
{c2, "meth", "%v", fmt.Sprint(path.Base(file2), ":", line2)},
@@ -92,7 +92,7 @@ func TestCallString(t *testing.T) {
9292
t.Fatal("runtime.Caller(0) failed")
9393
}
9494

95-
c2, _, file2, line2, ok2 := testType{}.testMethod()
95+
c2, file2, line2, ok2 := testType{}.testMethod()
9696
if !ok2 {
9797
t.Fatal("runtime.Caller(0) failed")
9898
}
@@ -125,7 +125,7 @@ func TestCallMarshalText(t *testing.T) {
125125
t.Fatal("runtime.Caller(0) failed")
126126
}
127127

128-
c2, _, file2, line2, ok2 := testType{}.testMethod()
128+
c2, file2, line2, ok2 := testType{}.testMethod()
129129
if !ok2 {
130130
t.Fatal("runtime.Caller(0) failed")
131131
}
@@ -191,7 +191,7 @@ func getTrace(t *testing.T) (stack.CallStack, int) {
191191
func TestTrimAbove(t *testing.T) {
192192
trace := trimAbove()
193193
if got, want := len(trace), 2; got != want {
194-
t.Errorf("got len(trace) == %v, want %v, trace: %n", got, want, trace)
194+
t.Fatalf("got len(trace) == %v, want %v, trace: %n", got, want, trace)
195195
}
196196
if got, want := fmt.Sprintf("%n", trace[1]), "TestTrimAbove"; got != want {
197197
t.Errorf("got %q, want %q", got, want)
@@ -224,8 +224,6 @@ func TestTrimRuntime(t *testing.T) {
224224
}
225225
}
226226

227-
228-
229227
func BenchmarkCallVFmt(b *testing.B) {
230228
c := stack.Caller(0)
231229
b.ResetTimer()

0 commit comments

Comments
 (0)