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

Commit

Permalink
Return errors.Frame to a uintptr
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
davecheney committed Jan 9, 2019
1 parent 72fa05e commit ee1923e
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 71 deletions.
1 change: 1 addition & 0 deletions format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
119 changes: 54 additions & 65 deletions stack.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package errors

import (
"bytes"
"fmt"
"io"
"path"
Expand All @@ -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.
//
Expand All @@ -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, ":")
Expand All @@ -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

Expand All @@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,19 @@ 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",
}, {
initpc,
"%d",
"9",
}, {
Frame{},
0,
"%d",
"0",
}, {
Expand All @@ -69,7 +69,7 @@ func TestFrameFormat(t *testing.T) {
"%n",
"X.val",
}, {
Frame{},
0,
"%n",
"",
}, {
Expand All @@ -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",
}}
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit ee1923e

Please sign in to comment.