From ee1923e96d846c3dff95f34b333f58cbc290d448 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Wed, 9 Jan 2019 15:30:26 +1100 Subject: [PATCH] Return errors.Frame to a uintptr Updates aws/aws-xray-sdk-go#77 Updates evalphobia/logrus_sentry#74 Go 1.12 has updated the behaviour of runtime.FuncForPC so that it behaves as it did in Go 1.11 and earlier. This allows errors.Frame to return to a uintptr representing the PC +1 of the caller. This will fix the build breakages of projects that were tracking HEAD of this package. Signed-off-by: Dave Cheney --- format_test.go | 1 + stack.go | 119 ++++++++++++++++++++++--------------------------- stack_test.go | 12 ++--- 3 files changed, 61 insertions(+), 71 deletions(-) diff --git a/format_test.go b/format_test.go index fd35ca3..cb1df82 100644 --- a/format_test.go +++ b/format_test.go @@ -385,6 +385,7 @@ func TestFormatWrappedNew(t *testing.T) { } func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { + t.Helper() got := fmt.Sprintf(format, arg) gotLines := strings.SplitN(got, "\n", -1) wantLines := strings.SplitN(want, "\n", -1) diff --git a/stack.go b/stack.go index 5be4627..c370d87 100644 --- a/stack.go +++ b/stack.go @@ -1,7 +1,6 @@ package errors import ( - "bytes" "fmt" "io" "path" @@ -11,7 +10,42 @@ import ( ) // Frame represents a program counter inside a stack frame. -type Frame runtime.Frame +type Frame uintptr + +// 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 } + +// 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 { + 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 +} + +// name returns the name of this function, if known. +func (f Frame) name() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + return fn.Name() +} // Format formats the frame according to the fmt.Formatter interface. // @@ -35,25 +69,16 @@ func (f Frame) format(w io.Writer, s fmt.State, verb rune) { case 's': switch { case s.Flag('+'): - if f.Function == "" { - io.WriteString(w, "unknown") - } else { - io.WriteString(w, f.Function) - io.WriteString(w, "\n\t") - io.WriteString(w, f.File) - } + io.WriteString(w, f.name()) + io.WriteString(w, "\n\t") + io.WriteString(w, f.file()) default: - file := f.File - if file == "" { - file = "unknown" - } - io.WriteString(w, path.Base(file)) + io.WriteString(w, path.Base(f.file())) } case 'd': - io.WriteString(w, strconv.Itoa(f.Line)) + io.WriteString(w, strconv.Itoa(f.line())) case 'n': - name := f.Function - io.WriteString(s, funcname(name)) + io.WriteString(w, funcname(f.name())) case 'v': f.format(w, s, 's') io.WriteString(w, ":") @@ -73,50 +98,23 @@ type StackTrace []Frame // // %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { - var b bytes.Buffer switch verb { case 'v': switch { case s.Flag('+'): - b.Grow(len(st) * stackMinLen) - for _, fr := range st { - b.WriteByte('\n') - fr.format(&b, s, verb) + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) } case s.Flag('#'): - fmt.Fprintf(&b, "%#v", []Frame(st)) + fmt.Fprintf(s, "%#v", []Frame(st)) default: - st.formatSlice(&b, s, verb) + fmt.Fprintf(s, "%v", []Frame(st)) } case 's': - st.formatSlice(&b, s, verb) + fmt.Fprintf(s, "%s", []Frame(st)) } - io.Copy(s, &b) } -// formatSlice will format this StackTrace into the given buffer as a slice of -// Frame, only valid when called with '%s' or '%v'. -func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) { - b.WriteByte('[') - if len(st) == 0 { - b.WriteByte(']') - return - } - - b.Grow(len(st) * (stackMinLen / 4)) - st[0].format(b, s, verb) - for _, fr := range st[1:] { - b.WriteByte(' ') - fr.format(b, s, verb) - } - b.WriteByte(']') -} - -// stackMinLen is a best-guess at the minimum length of a stack trace. It -// doesn't need to be exact, just give a good enough head start for the buffer -// to avoid the expensive early growth. -const stackMinLen = 96 - // stack represents a stack of program counters. type stack []uintptr @@ -125,29 +123,20 @@ func (s *stack) Format(st fmt.State, verb rune) { case 'v': switch { case st.Flag('+'): - frames := runtime.CallersFrames(*s) - for { - frame, more := frames.Next() - fmt.Fprintf(st, "\n%+v", Frame(frame)) - if !more { - break - } + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) } } } } func (s *stack) StackTrace() StackTrace { - var st []Frame - frames := runtime.CallersFrames(*s) - for { - frame, more := frames.Next() - st = append(st, Frame(frame)) - if !more { - break - } + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) } - return st + return f } func callers() *stack { diff --git a/stack_test.go b/stack_test.go index 7f76694..172a57d 100644 --- a/stack_test.go +++ b/stack_test.go @@ -35,11 +35,11 @@ func TestFrameFormat(t *testing.T) { "github.com/pkg/errors.init\n" + "\t.+/github.com/pkg/errors/stack_test.go", }, { - Frame{}, + 0, "%s", "unknown", }, { - Frame{}, + 0, "%+s", "unknown", }, { @@ -47,7 +47,7 @@ func TestFrameFormat(t *testing.T) { "%d", "9", }, { - Frame{}, + 0, "%d", "0", }, { @@ -69,7 +69,7 @@ func TestFrameFormat(t *testing.T) { "%n", "X.val", }, { - Frame{}, + 0, "%n", "", }, { @@ -82,7 +82,7 @@ func TestFrameFormat(t *testing.T) { "github.com/pkg/errors.init\n" + "\t.+/github.com/pkg/errors/stack_test.go:9", }, { - Frame{}, + 0, "%v", "unknown:0", }} @@ -246,7 +246,7 @@ func caller() Frame { n := runtime.Callers(2, pcs[:]) frames := runtime.CallersFrames(pcs[:n]) frame, _ := frames.Next() - return Frame(frame) + return Frame(frame.PC) } //go:noinline