Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use CallersFrames to build StackFrames (required for go 1.12) #114

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,14 @@ func (err *Error) Stack() []byte {
// stack.
func (err *Error) StackFrames() []StackFrame {
if err.frames == nil {
err.frames = make([]StackFrame, len(err.stack))

for i, pc := range err.stack {
err.frames[i] = NewStackFrame(pc)
err.frames = make([]StackFrame, 0, len(err.stack))
callers := runtime.CallersFrames(err.stack)
for {
frame, more := callers.Next()
err.frames = append(err.frames, NewStackFrame(frame))
if !more {
break
}
}
}

Expand Down
21 changes: 10 additions & 11 deletions errors/stackframe.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,29 @@ type StackFrame struct {
Name string
Package string
ProgramCounter uintptr
rFunc *runtime.Func
}

// NewStackFrame popoulates a stack frame object from the program counter.
func NewStackFrame(pc uintptr) (frame StackFrame) {
// NewStackFrame popoulates a stack frame object from runtime frame.
func NewStackFrame(f runtime.Frame) (frame StackFrame) {

frame = StackFrame{ProgramCounter: pc}
frame = StackFrame{
ProgramCounter: f.PC,
rFunc: f.Func,
File: f.File,
LineNumber: f.Line,
}
if frame.Func() == nil {
return
}
frame.Package, frame.Name = packageAndName(frame.Func())

// pc -1 because the program counters we use are usually return addresses,
// and we want to show the line that corresponds to the function call
frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
return

}

// Func returns the function that this stackframe corresponds to
func (frame *StackFrame) Func() *runtime.Func {
if frame.ProgramCounter == 0 {
return nil
}
return runtime.FuncForPC(frame.ProgramCounter)
return frame.rFunc
}

// String returns the stackframe formatted in the same way as go does
Expand Down
55 changes: 55 additions & 0 deletions errors/stackframe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package errors

import (
"runtime"
"testing"

"github.com/pkg/errors"
)

func Test_StackFrames(t *testing.T) {
err := boom(5)
stack := getStack(err)
frames := getFrames(stack)
// for _, frame := range frames {
// fmt.Printf("%s:%d\n\t%s\n", frame.File, frame.Line, frame.Function)
// }

err2 := &Error{Err: err, stack: stack}
// fmt.Println(string(err2.Stack()))
frames2 := err2.StackFrames()
if name, name2 := frames[0].Function, frames2[0].Func().Name(); name != name2 {
t.Errorf("top frames don't match\n%s\n%s", name, name2)
}
}

func boom(depth int) error {
if depth > 0 {
return boom(depth - 1)
}
return errors.New("boom")
}

func getStack(err error) []uintptr {
type withStackTrace interface {
StackTrace() errors.StackTrace
}
frames := err.(withStackTrace).StackTrace()
stack := make([]uintptr, len(frames))
for i, f := range frames {
stack[i] = uintptr(f)
}
return stack
}

func getFrames(stack []uintptr) (frames []runtime.Frame) {
callers := runtime.CallersFrames(stack)
for {
frame, more := callers.Next()
frames = append(frames, frame)
if !more {
break
}
}
return frames
}