]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: skip logical frames in runtime.Caller
authorDavid Lazar <lazard@golang.org>
Mon, 10 Apr 2017 18:33:07 +0000 (14:33 -0400)
committerDavid Lazar <lazard@golang.org>
Tue, 18 Apr 2017 19:56:30 +0000 (19:56 +0000)
This rewrites runtime.Caller in terms of stackExpander, which already
handles inlined frames and partially skipped frames. This also has the
effect of making runtime.Caller understand cgo frames if there is a cgo
symbolizer.

Updates #19348.

Change-Id: Icdf4df921aab5aa394d4d92e3becc4dd169c9a6e
Reviewed-on: https://go-review.googlesource.com/40270
Run-TryBot: David Lazar <lazard@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
src/runtime/extern.go
test/inline_caller.go [new file with mode: 0644]

index 319d6495bdd4fcc9bcc956a2f1f93568bfe75ddf..2e67d4c3a91531dd40d9cddbffe797b906fe8a8d 100644 (file)
@@ -166,33 +166,26 @@ import "runtime/internal/sys"
 // program counter, file name, and line number within the file of the corresponding
 // call. The boolean ok is false if it was not possible to recover the information.
 func Caller(skip int) (pc uintptr, file string, line int, ok bool) {
-       // Ask for two PCs: the one we were asked for
-       // and what it called, so that we can see if it
-       // "called" sigpanic.
-       var rpc [2]uintptr
+       // Make room for three PCs: the one we were asked for,
+       // what it called, so that CallersFrames can see if it "called"
+       // sigpanic, and possibly a PC for skipPleaseUseCallersFrames.
+       var rpc [3]uintptr
        if callers(1+skip-1, rpc[:]) < 2 {
                return
        }
-       f := findfunc(rpc[1])
-       if !f.valid() {
-               // TODO(rsc): Probably a bug?
-               // The C version said "have retpc at least"
-               // but actually returned pc=0.
-               ok = true
+       var stackExpander stackExpander
+       callers := stackExpander.init(rpc[:])
+       // We asked for one extra, so skip that one. If this is sigpanic,
+       // stepping over this frame will set up state in Frames so the
+       // next frame is correct.
+       callers, _, ok = stackExpander.next(callers)
+       if !ok {
                return
        }
-       pc = rpc[1]
-       xpc := pc
-       g := findfunc(rpc[0])
-       // All architectures turn faults into apparent calls to sigpanic.
-       // If we see a call to sigpanic, we do not back up the PC to find
-       // the line number of the call instruction, because there is no call.
-       if xpc > f.entry && (!g.valid() || g.entry != funcPC(sigpanic)) {
-               xpc--
-       }
-       file, line32 := funcline(f, xpc)
-       line = int(line32)
-       ok = true
+       _, frame, _ := stackExpander.next(callers)
+       pc = frame.PC
+       file = frame.File
+       line = frame.Line
        return
 }
 
diff --git a/test/inline_caller.go b/test/inline_caller.go
new file mode 100644 (file)
index 0000000..79039a6
--- /dev/null
@@ -0,0 +1,77 @@
+// run -gcflags -l=4
+
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+       "fmt"
+       "runtime"
+)
+
+type frame struct {
+       pc   uintptr
+       file string
+       line int
+       ok   bool
+}
+
+var (
+       skip        int
+       globalFrame frame
+)
+
+func f() {
+       g() // line 27
+}
+
+func g() {
+       h() // line 31
+}
+
+func h() {
+       x := &globalFrame
+       x.pc, x.file, x.line, x.ok = runtime.Caller(skip) // line 36
+}
+
+//go:noinline
+func testCaller(skp int) frame {
+       skip = skp
+       f() // line 42
+       frame := globalFrame
+       if !frame.ok {
+               panic(fmt.Sprintf("skip=%d runtime.Caller failed", skp))
+       }
+       return frame
+}
+
+type wantFrame struct {
+       funcName string
+       line     int
+}
+
+// -1 means don't care
+var expected = []wantFrame{
+       0: {"main.testCaller", 36},
+       1: {"main.testCaller", 31},
+       2: {"main.testCaller", 27},
+       3: {"main.testCaller", 42},
+       4: {"main.main", 68},
+       5: {"runtime.main", -1},
+       6: {"runtime.goexit", -1},
+}
+
+func main() {
+       for i := 0; i <= 6; i++ {
+               frame := testCaller(i) // line 68
+               fn := runtime.FuncForPC(frame.pc)
+               if expected[i].line >= 0 && frame.line != expected[i].line {
+                       panic(fmt.Sprintf("skip=%d expected line %d, got line %d", i, expected[i].line, frame.line))
+               }
+               if fn.Name() != expected[i].funcName {
+                       panic(fmt.Sprintf("skip=%d expected function %s, got %s", i, expected[i].funcName, fn.Name()))
+               }
+       }
+}