From f55fdad268aeaa476ebc95ba170d9c7e267b1bdb Mon Sep 17 00:00:00 2001 From: Martin Kobetic Date: Tue, 2 Apr 2019 09:52:40 -0400 Subject: [PATCH] Use CallersFames to build StackFrames (required for go 1.12) --- errors/error.go | 12 ++++++--- errors/stackframe.go | 21 +++++++-------- errors/stackframe_test.go | 55 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 errors/stackframe_test.go diff --git a/errors/error.go b/errors/error.go index be7b7c89..82a36080 100644 --- a/errors/error.go +++ b/errors/error.go @@ -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 + } } } diff --git a/errors/stackframe.go b/errors/stackframe.go index 4edadbc5..1221e26e 100644 --- a/errors/stackframe.go +++ b/errors/stackframe.go @@ -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 diff --git a/errors/stackframe_test.go b/errors/stackframe_test.go new file mode 100644 index 00000000..893fbdf3 --- /dev/null +++ b/errors/stackframe_test.go @@ -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 +}