diff --git a/src/runtime/extern.go b/src/runtime/extern.go index 319d6495bdd..2e67d4c3a91 100644 --- a/src/runtime/extern.go +++ b/src/runtime/extern.go @@ -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 index 00000000000..79039a6bb53 --- /dev/null +++ b/test/inline_caller.go @@ -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())) + } + } +}