Switch to runtime.CallersFrames (#183)

Fixes #160
Fixes #107

Signed-off-by: Dave Cheney <dave@cheney.net>
This commit is contained in:
Dave Cheney 2019-01-05 19:23:03 +11:00 committed by GitHub
parent 537896ad6e
commit 4f47277723
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 83 deletions

View File

@ -9,32 +9,26 @@ import (
) )
// Frame represents a program counter inside a stack frame. // Frame represents a program counter inside a stack frame.
type Frame uintptr type Frame runtime.Frame
// pc returns the program counter for this frame; // pc returns the program counter for this frame;
// multiple frames may have the same PC value. // multiple frames may have the same PC value.
func (f Frame) pc() uintptr { return uintptr(f) - 1 } func (f Frame) pc() uintptr { return runtime.Frame(f).PC }
// file returns the full path to the file that contains the // file returns the full path to the file that contains the
// function for this Frame's pc. // function for this Frame's pc.
func (f Frame) file() string { func (f Frame) file() string {
fn := runtime.FuncForPC(f.pc()) file := runtime.Frame(f).File
if fn == nil { if file == "" {
return "unknown" return "unknown"
} }
file, _ := fn.FileLine(f.pc())
return file return file
} }
// line returns the line number of source code of the // line returns the line number of source code of the
// function for this Frame's pc. // function for this Frame's pc.
func (f Frame) line() int { func (f Frame) line() int {
fn := runtime.FuncForPC(f.pc()) return runtime.Frame(f).Line
if fn == nil {
return 0
}
_, line := fn.FileLine(f.pc())
return line
} }
// Format formats the frame according to the fmt.Formatter interface. // Format formats the frame according to the fmt.Formatter interface.
@ -54,12 +48,11 @@ func (f Frame) Format(s fmt.State, verb rune) {
case 's': case 's':
switch { switch {
case s.Flag('+'): case s.Flag('+'):
pc := f.pc() fn := runtime.Frame(f).Func
fn := runtime.FuncForPC(pc)
if fn == nil { if fn == nil {
io.WriteString(s, "unknown") io.WriteString(s, "unknown")
} else { } else {
file, _ := fn.FileLine(pc) file := runtime.Frame(f).File
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
} }
default: default:
@ -114,20 +107,29 @@ func (s *stack) Format(st fmt.State, verb rune) {
case 'v': case 'v':
switch { switch {
case st.Flag('+'): case st.Flag('+'):
for _, pc := range *s { frames := runtime.CallersFrames(*s)
f := Frame(pc) for {
fmt.Fprintf(st, "\n%+v", f) frame, more := frames.Next()
fmt.Fprintf(st, "\n%+v", Frame(frame))
if !more {
break
}
} }
} }
} }
} }
func (s *stack) StackTrace() StackTrace { func (s *stack) StackTrace() StackTrace {
f := make([]Frame, len(*s)) var st []Frame
for i := 0; i < len(f); i++ { frames := runtime.CallersFrames(*s)
f[i] = Frame((*s)[i]) for {
frame, more := frames.Next()
st = append(st, Frame(frame))
if !more {
break
} }
return f }
return st
} }
func callers() *stack { func callers() *stack {

View File

@ -6,51 +6,18 @@ import (
"testing" "testing"
) )
var initpc, _, _, _ = runtime.Caller(0) var initpc = caller()
func TestFrameLine(t *testing.T) {
var tests = []struct {
Frame
want int
}{{
Frame(initpc),
9,
}, {
func() Frame {
var pc, _, _, _ = runtime.Caller(0)
return Frame(pc)
}(),
20,
}, {
func() Frame {
var pc, _, _, _ = runtime.Caller(1)
return Frame(pc)
}(),
28,
}, {
Frame(0), // invalid PC
0,
}}
for _, tt := range tests {
got := tt.Frame.line()
want := tt.want
if want != got {
t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got)
}
}
}
type X struct{} type X struct{}
//go:noinline
func (x X) val() Frame { func (x X) val() Frame {
var pc, _, _, _ = runtime.Caller(0) return caller()
return Frame(pc)
} }
//go:noinline
func (x *X) ptr() Frame { func (x *X) ptr() Frame {
var pc, _, _, _ = runtime.Caller(0) return caller()
return Frame(pc)
} }
func TestFrameFormat(t *testing.T) { func TestFrameFormat(t *testing.T) {
@ -59,32 +26,32 @@ func TestFrameFormat(t *testing.T) {
format string format string
want string want string
}{{ }{{
Frame(initpc), initpc,
"%s", "%s",
"stack_test.go", "stack_test.go",
}, { }, {
Frame(initpc), initpc,
"%+s", "%+s",
"github.com/pkg/errors.init\n" + "github.com/pkg/errors.init\n" +
"\t.+/github.com/pkg/errors/stack_test.go", "\t.+/github.com/pkg/errors/stack_test.go",
}, { }, {
Frame(0), Frame{},
"%s", "%s",
"unknown", "unknown",
}, { }, {
Frame(0), Frame{},
"%+s", "%+s",
"unknown", "unknown",
}, { }, {
Frame(initpc), initpc,
"%d", "%d",
"9", "9",
}, { }, {
Frame(0), Frame{},
"%d", "%d",
"0", "0",
}, { }, {
Frame(initpc), initpc,
"%n", "%n",
"init", "init",
}, { }, {
@ -102,20 +69,20 @@ func TestFrameFormat(t *testing.T) {
"%n", "%n",
"X.val", "X.val",
}, { }, {
Frame(0), Frame{},
"%n", "%n",
"", "",
}, { }, {
Frame(initpc), initpc,
"%v", "%v",
"stack_test.go:9", "stack_test.go:9",
}, { }, {
Frame(initpc), initpc,
"%+v", "%+v",
"github.com/pkg/errors.init\n" + "github.com/pkg/errors.init\n" +
"\t.+/github.com/pkg/errors/stack_test.go:9", "\t.+/github.com/pkg/errors/stack_test.go:9",
}, { }, {
Frame(0), Frame{},
"%v", "%v",
"unknown:0", "unknown:0",
}} }}
@ -153,24 +120,24 @@ func TestStackTrace(t *testing.T) {
}{{ }{{
New("ooh"), []string{ New("ooh"), []string{
"github.com/pkg/errors.TestStackTrace\n" + "github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:154", "\t.+/github.com/pkg/errors/stack_test.go:121",
}, },
}, { }, {
Wrap(New("ooh"), "ahh"), []string{ Wrap(New("ooh"), "ahh"), []string{
"github.com/pkg/errors.TestStackTrace\n" + "github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:159", // this is the stack of Wrap, not New "\t.+/github.com/pkg/errors/stack_test.go:126", // this is the stack of Wrap, not New
}, },
}, { }, {
Cause(Wrap(New("ooh"), "ahh")), []string{ Cause(Wrap(New("ooh"), "ahh")), []string{
"github.com/pkg/errors.TestStackTrace\n" + "github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:164", // this is the stack of New "\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New
}, },
}, { }, {
func() error { return New("ooh") }(), []string{ func() error { noinline(); return New("ooh") }(), []string{
`github.com/pkg/errors.(func·009|TestStackTrace.func1)` + `github.com/pkg/errors.(func·009|TestStackTrace.func1)` +
"\n\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New "\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New
"github.com/pkg/errors.TestStackTrace\n" + "github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New's caller "\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New's caller
}, },
}, { }, {
Cause(func() error { Cause(func() error {
@ -179,11 +146,11 @@ func TestStackTrace(t *testing.T) {
}() }()
}()), []string{ }()), []string{
`github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` + `github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` +
"\n\t.+/github.com/pkg/errors/stack_test.go:178", // this is the stack of Errorf "\n\t.+/github.com/pkg/errors/stack_test.go:145", // this is the stack of Errorf
`github.com/pkg/errors.(func·011|TestStackTrace.func2)` + `github.com/pkg/errors.(func·011|TestStackTrace.func2)` +
"\n\t.+/github.com/pkg/errors/stack_test.go:179", // this is the stack of Errorf's caller "\n\t.+/github.com/pkg/errors/stack_test.go:146", // this is the stack of Errorf's caller
"github.com/pkg/errors.TestStackTrace\n" + "github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:180", // this is the stack of Errorf's caller's caller "\t.+/github.com/pkg/errors/stack_test.go:147", // this is the stack of Errorf's caller's caller
}, },
}} }}
for i, tt := range tests { for i, tt := range tests {
@ -253,22 +220,35 @@ func TestStackTraceFormat(t *testing.T) {
}, { }, {
stackTrace()[:2], stackTrace()[:2],
"%v", "%v",
`\[stack_test.go:207 stack_test.go:254\]`, `\[stack_test.go:174 stack_test.go:221\]`,
}, { }, {
stackTrace()[:2], stackTrace()[:2],
"%+v", "%+v",
"\n" + "\n" +
"github.com/pkg/errors.stackTrace\n" + "github.com/pkg/errors.stackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:207\n" + "\t.+/github.com/pkg/errors/stack_test.go:174\n" +
"github.com/pkg/errors.TestStackTraceFormat\n" + "github.com/pkg/errors.TestStackTraceFormat\n" +
"\t.+/github.com/pkg/errors/stack_test.go:258", "\t.+/github.com/pkg/errors/stack_test.go:225",
}, { }, {
stackTrace()[:2], stackTrace()[:2],
"%#v", "%#v",
`\[\]errors.Frame{stack_test.go:207, stack_test.go:266}`, `\[\]errors.Frame{stack_test.go:174, stack_test.go:233}`,
}} }}
for i, tt := range tests { for i, tt := range tests {
testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
} }
} }
// a version of runtime.Caller that returns a Frame, not a uintptr.
func caller() Frame {
var pcs [3]uintptr
n := runtime.Callers(2, pcs[:])
frames := runtime.CallersFrames(pcs[:n])
frame, _ := frames.Next()
return Frame(frame)
}
//go:noinline
// noinline prevents the caller being inlined
func noinline() {}