Switch to runtime.CallersFrames (#183)

Fixes #160
Fixes #107

Signed-off-by: Dave Cheney <dave@cheney.net>
diff --git a/stack.go b/stack.go
index 2874a04..cb8024b 100644
--- a/stack.go
+++ b/stack.go
@@ -9,32 +9,26 @@
 )
 
 // Frame represents a program counter inside a stack frame.
-type Frame uintptr
+type Frame runtime.Frame
 
 // 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 }
+func (f Frame) pc() uintptr { return runtime.Frame(f).PC }
 
 // 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 {
+	file := runtime.Frame(f).File
+	if file == "" {
 		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
+	return runtime.Frame(f).Line
 }
 
 // Format formats the frame according to the fmt.Formatter interface.
@@ -54,12 +48,11 @@
 	case 's':
 		switch {
 		case s.Flag('+'):
-			pc := f.pc()
-			fn := runtime.FuncForPC(pc)
+			fn := runtime.Frame(f).Func
 			if fn == nil {
 				io.WriteString(s, "unknown")
 			} else {
-				file, _ := fn.FileLine(pc)
+				file := runtime.Frame(f).File
 				fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
 			}
 		default:
@@ -114,20 +107,29 @@
 	case 'v':
 		switch {
 		case st.Flag('+'):
-			for _, pc := range *s {
-				f := Frame(pc)
-				fmt.Fprintf(st, "\n%+v", f)
+			frames := runtime.CallersFrames(*s)
+			for {
+				frame, more := frames.Next()
+				fmt.Fprintf(st, "\n%+v", Frame(frame))
+				if !more {
+					break
+				}
 			}
 		}
 	}
 }
 
 func (s *stack) StackTrace() StackTrace {
-	f := make([]Frame, len(*s))
-	for i := 0; i < len(f); i++ {
-		f[i] = Frame((*s)[i])
+	var st []Frame
+	frames := runtime.CallersFrames(*s)
+	for {
+		frame, more := frames.Next()
+		st = append(st, Frame(frame))
+		if !more {
+			break
+		}
 	}
-	return f
+	return st
 }
 
 func callers() *stack {
diff --git a/stack_test.go b/stack_test.go
index 85fc419..c444be0 100644
--- a/stack_test.go
+++ b/stack_test.go
@@ -6,51 +6,18 @@
 	"testing"
 )
 
-var initpc, _, _, _ = runtime.Caller(0)
-
-func TestFrameLine(t *testing.T) {
-	var tests = []struct {
-		Frame
-		want int
-	}{{
-		Frame(initpc),
-		9,
-	}, {
-		func() Frame {
-			var pc, _, _, _ = runtime.Caller(0)
-			return Frame(pc)
-		}(),
-		20,
-	}, {
-		func() Frame {
-			var pc, _, _, _ = runtime.Caller(1)
-			return Frame(pc)
-		}(),
-		28,
-	}, {
-		Frame(0), // invalid PC
-		0,
-	}}
-
-	for _, tt := range tests {
-		got := tt.Frame.line()
-		want := tt.want
-		if want != got {
-			t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got)
-		}
-	}
-}
+var initpc = caller()
 
 type X struct{}
 
+//go:noinline
 func (x X) val() Frame {
-	var pc, _, _, _ = runtime.Caller(0)
-	return Frame(pc)
+	return caller()
 }
 
+//go:noinline
 func (x *X) ptr() Frame {
-	var pc, _, _, _ = runtime.Caller(0)
-	return Frame(pc)
+	return caller()
 }
 
 func TestFrameFormat(t *testing.T) {
@@ -59,32 +26,32 @@
 		format string
 		want   string
 	}{{
-		Frame(initpc),
+		initpc,
 		"%s",
 		"stack_test.go",
 	}, {
-		Frame(initpc),
+		initpc,
 		"%+s",
 		"github.com/pkg/errors.init\n" +
 			"\t.+/github.com/pkg/errors/stack_test.go",
 	}, {
-		Frame(0),
+		Frame{},
 		"%s",
 		"unknown",
 	}, {
-		Frame(0),
+		Frame{},
 		"%+s",
 		"unknown",
 	}, {
-		Frame(initpc),
+		initpc,
 		"%d",
 		"9",
 	}, {
-		Frame(0),
+		Frame{},
 		"%d",
 		"0",
 	}, {
-		Frame(initpc),
+		initpc,
 		"%n",
 		"init",
 	}, {
@@ -102,20 +69,20 @@
 		"%n",
 		"X.val",
 	}, {
-		Frame(0),
+		Frame{},
 		"%n",
 		"",
 	}, {
-		Frame(initpc),
+		initpc,
 		"%v",
 		"stack_test.go:9",
 	}, {
-		Frame(initpc),
+		initpc,
 		"%+v",
 		"github.com/pkg/errors.init\n" +
 			"\t.+/github.com/pkg/errors/stack_test.go:9",
 	}, {
-		Frame(0),
+		Frame{},
 		"%v",
 		"unknown:0",
 	}}
@@ -153,24 +120,24 @@
 	}{{
 		New("ooh"), []string{
 			"github.com/pkg/errors.TestStackTrace\n" +
-				"\t.+/github.com/pkg/errors/stack_test.go:154",
+				"\t.+/github.com/pkg/errors/stack_test.go:121",
 		},
 	}, {
 		Wrap(New("ooh"), "ahh"), []string{
 			"github.com/pkg/errors.TestStackTrace\n" +
-				"\t.+/github.com/pkg/errors/stack_test.go:159", // this is the stack of Wrap, not New
+				"\t.+/github.com/pkg/errors/stack_test.go:126", // this is the stack of Wrap, not New
 		},
 	}, {
 		Cause(Wrap(New("ooh"), "ahh")), []string{
 			"github.com/pkg/errors.TestStackTrace\n" +
-				"\t.+/github.com/pkg/errors/stack_test.go:164", // this is the stack of New
+				"\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New
 		},
 	}, {
-		func() error { return New("ooh") }(), []string{
+		func() error { noinline(); return New("ooh") }(), []string{
 			`github.com/pkg/errors.(func·009|TestStackTrace.func1)` +
-				"\n\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New
+				"\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New
 			"github.com/pkg/errors.TestStackTrace\n" +
-				"\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New's caller
+				"\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New's caller
 		},
 	}, {
 		Cause(func() error {
@@ -179,11 +146,11 @@
 			}()
 		}()), []string{
 			`github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` +
-				"\n\t.+/github.com/pkg/errors/stack_test.go:178", // this is the stack of Errorf
+				"\n\t.+/github.com/pkg/errors/stack_test.go:145", // this is the stack of Errorf
 			`github.com/pkg/errors.(func·011|TestStackTrace.func2)` +
-				"\n\t.+/github.com/pkg/errors/stack_test.go:179", // this is the stack of Errorf's caller
+				"\n\t.+/github.com/pkg/errors/stack_test.go:146", // this is the stack of Errorf's caller
 			"github.com/pkg/errors.TestStackTrace\n" +
-				"\t.+/github.com/pkg/errors/stack_test.go:180", // this is the stack of Errorf's caller's caller
+				"\t.+/github.com/pkg/errors/stack_test.go:147", // this is the stack of Errorf's caller's caller
 		},
 	}}
 	for i, tt := range tests {
@@ -253,22 +220,35 @@
 	}, {
 		stackTrace()[:2],
 		"%v",
-		`\[stack_test.go:207 stack_test.go:254\]`,
+		`\[stack_test.go:174 stack_test.go:221\]`,
 	}, {
 		stackTrace()[:2],
 		"%+v",
 		"\n" +
 			"github.com/pkg/errors.stackTrace\n" +
-			"\t.+/github.com/pkg/errors/stack_test.go:207\n" +
+			"\t.+/github.com/pkg/errors/stack_test.go:174\n" +
 			"github.com/pkg/errors.TestStackTraceFormat\n" +
-			"\t.+/github.com/pkg/errors/stack_test.go:258",
+			"\t.+/github.com/pkg/errors/stack_test.go:225",
 	}, {
 		stackTrace()[:2],
 		"%#v",
-		`\[\]errors.Frame{stack_test.go:207, stack_test.go:266}`,
+		`\[\]errors.Frame{stack_test.go:174, stack_test.go:233}`,
 	}}
 
 	for i, tt := range tests {
 		testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
 	}
 }
+
+// a version of runtime.Caller that returns a Frame, not a uintptr.
+func caller() Frame {
+	var pcs [3]uintptr
+	n := runtime.Callers(2, pcs[:])
+	frames := runtime.CallersFrames(pcs[:n])
+	frame, _ := frames.Next()
+	return Frame(frame)
+}
+
+//go:noinline
+// noinline prevents the caller being inlined
+func noinline() {}