Bläddra i källkod

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 <dave@cheney.net>
Dave Cheney 7 år sedan
förälder
incheckning
ee1923e96d
3 ändrade filer med 61 tillägg och 71 borttagningar
  1. 1 0
      format_test.go
  2. 54 65
      stack.go
  3. 6 6
      stack_test.go

+ 1 - 0
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)

+ 54 - 65
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 {

+ 6 - 6
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