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

Commit

Permalink
Switch to runtime.CallersFrames (#183)
Browse files Browse the repository at this point in the history
Fixes #160
Fixes #107

Signed-off-by: Dave Cheney <dave@cheney.net>
  • Loading branch information
davecheney committed Jan 5, 2019
1 parent 537896a commit 4f47277
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 83 deletions.
44 changes: 23 additions & 21 deletions stack.go
Expand Up @@ -9,32 +9,26 @@ import (
)

// Frame represents a program counter inside a stack frame.
type Frame uintptr
type Frame runtime.Frame

// pc returns the program counter for this frame;
// 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
// function for this Frame's pc.
func (f Frame) file() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
file := runtime.Frame(f).File
if file == "" {
return "unknown"
}
file, _ := fn.FileLine(f.pc())
return file
}

// line returns the line number of source code of the
// function for this Frame's pc.
func (f Frame) line() int {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return 0
}
_, line := fn.FileLine(f.pc())
return line
return runtime.Frame(f).Line
}

// Format formats the frame according to the fmt.Formatter interface.
Expand All @@ -54,12 +48,11 @@ func (f Frame) Format(s fmt.State, verb rune) {
case 's':
switch {
case s.Flag('+'):
pc := f.pc()
fn := runtime.FuncForPC(pc)
fn := runtime.Frame(f).Func
if fn == nil {
io.WriteString(s, "unknown")
} else {
file, _ := fn.FileLine(pc)
file := runtime.Frame(f).File
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
}
default:
Expand Down Expand Up @@ -114,20 +107,29 @@ func (s *stack) Format(st fmt.State, verb rune) {
case 'v':
switch {
case st.Flag('+'):
for _, pc := range *s {
f := Frame(pc)
fmt.Fprintf(st, "\n%+v", f)
frames := runtime.CallersFrames(*s)
for {
frame, more := frames.Next()
fmt.Fprintf(st, "\n%+v", Frame(frame))
if !more {
break
}
}
}
}
}

func (s *stack) StackTrace() StackTrace {
f := make([]Frame, len(*s))
for i := 0; i < len(f); i++ {
f[i] = Frame((*s)[i])
var st []Frame
frames := runtime.CallersFrames(*s)
for {
frame, more := frames.Next()
st = append(st, Frame(frame))
if !more {
break
}
}
return f
return st
}

func callers() *stack {
Expand Down
104 changes: 42 additions & 62 deletions stack_test.go
Expand Up @@ -6,51 +6,18 @@ import (
"testing"
)

var initpc, _, _, _ = runtime.Caller(0)

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)
}
}
}
var initpc = caller()

type X struct{}

//go:noinline
func (x X) val() Frame {
var pc, _, _, _ = runtime.Caller(0)
return Frame(pc)
return caller()
}

//go:noinline
func (x *X) ptr() Frame {
var pc, _, _, _ = runtime.Caller(0)
return Frame(pc)
return caller()
}

func TestFrameFormat(t *testing.T) {
Expand All @@ -59,32 +26,32 @@ func TestFrameFormat(t *testing.T) {
format string
want string
}{{
Frame(initpc),
initpc,
"%s",
"stack_test.go",
}, {
Frame(initpc),
initpc,
"%+s",
"github.com/pkg/errors.init\n" +
"\t.+/github.com/pkg/errors/stack_test.go",
}, {
Frame(0),
Frame{},
"%s",
"unknown",
}, {
Frame(0),
Frame{},
"%+s",
"unknown",
}, {
Frame(initpc),
initpc,
"%d",
"9",
}, {
Frame(0),
Frame{},
"%d",
"0",
}, {
Frame(initpc),
initpc,
"%n",
"init",
}, {
Expand All @@ -102,20 +69,20 @@ func TestFrameFormat(t *testing.T) {
"%n",
"X.val",
}, {
Frame(0),
Frame{},
"%n",
"",
}, {
Frame(initpc),
initpc,
"%v",
"stack_test.go:9",
}, {
Frame(initpc),
initpc,
"%+v",
"github.com/pkg/errors.init\n" +
"\t.+/github.com/pkg/errors/stack_test.go:9",
}, {
Frame(0),
Frame{},
"%v",
"unknown:0",
}}
Expand Down Expand Up @@ -153,24 +120,24 @@ func TestStackTrace(t *testing.T) {
}{{
New("ooh"), []string{
"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{
"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{
"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)` +
"\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" +
"\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 {
Expand All @@ -179,11 +146,11 @@ func TestStackTrace(t *testing.T) {
}()
}()), []string{
`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)` +
"\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" +
"\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 {
Expand Down Expand Up @@ -253,22 +220,35 @@ func TestStackTraceFormat(t *testing.T) {
}, {
stackTrace()[:2],
"%v",
`\[stack_test.go:207 stack_test.go:254\]`,
`\[stack_test.go:174 stack_test.go:221\]`,
}, {
stackTrace()[:2],
"%+v",
"\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" +
"\t.+/github.com/pkg/errors/stack_test.go:258",
"\t.+/github.com/pkg/errors/stack_test.go:225",
}, {
stackTrace()[:2],
"%#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 {
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() {}

0 comments on commit 4f47277

Please sign in to comment.