115 lines
2.8 KiB
Go
115 lines
2.8 KiB
Go
|
package errors
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// A StackFrame contains all necessary information about to generate a line
|
||
|
// in a callstack.
|
||
|
type StackFrame struct {
|
||
|
// The path to the file containing this ProgramCounter
|
||
|
File string
|
||
|
// The LineNumber in that file
|
||
|
LineNumber int
|
||
|
// The Name of the function that contains this ProgramCounter
|
||
|
Name string
|
||
|
// The Package that contains this function
|
||
|
Package string
|
||
|
// The underlying ProgramCounter
|
||
|
ProgramCounter uintptr
|
||
|
}
|
||
|
|
||
|
// NewStackFrame popoulates a stack frame object from the program counter.
|
||
|
func NewStackFrame(pc uintptr) (frame StackFrame) {
|
||
|
|
||
|
frame = StackFrame{ProgramCounter: pc}
|
||
|
if frame.Func() == nil {
|
||
|
return
|
||
|
}
|
||
|
frame.Package, frame.Name = packageAndName(frame.Func())
|
||
|
|
||
|
// pc -1 because the program counters we use are usually return addresses,
|
||
|
// and we want to show the line that corresponds to the function call
|
||
|
frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
|
||
|
return
|
||
|
|
||
|
}
|
||
|
|
||
|
// Func returns the function that contained this frame.
|
||
|
func (frame *StackFrame) Func() *runtime.Func {
|
||
|
if frame.ProgramCounter == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
return runtime.FuncForPC(frame.ProgramCounter)
|
||
|
}
|
||
|
|
||
|
// String returns the stackframe formatted in the same way as go does
|
||
|
// in runtime/debug.Stack()
|
||
|
func (frame *StackFrame) String() string {
|
||
|
str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
|
||
|
|
||
|
source, err := frame.SourceLine()
|
||
|
if err != nil {
|
||
|
return str
|
||
|
}
|
||
|
|
||
|
return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
|
||
|
}
|
||
|
|
||
|
// SourceLine gets the line of code (from File and Line) of the original source if possible.
|
||
|
func (frame *StackFrame) SourceLine() (string, error) {
|
||
|
if frame.LineNumber <= 0 {
|
||
|
return "???", nil
|
||
|
}
|
||
|
|
||
|
file, err := os.Open(frame.File)
|
||
|
if err != nil {
|
||
|
return "", New(err)
|
||
|
}
|
||
|
defer file.Close()
|
||
|
|
||
|
scanner := bufio.NewScanner(file)
|
||
|
currentLine := 1
|
||
|
for scanner.Scan() {
|
||
|
if currentLine == frame.LineNumber {
|
||
|
return string(bytes.Trim(scanner.Bytes(), " \t")), nil
|
||
|
}
|
||
|
currentLine++
|
||
|
}
|
||
|
if err := scanner.Err(); err != nil {
|
||
|
return "", New(err)
|
||
|
}
|
||
|
|
||
|
return "???", nil
|
||
|
}
|
||
|
|
||
|
func packageAndName(fn *runtime.Func) (string, string) {
|
||
|
name := fn.Name()
|
||
|
pkg := ""
|
||
|
|
||
|
// The name includes the path name to the package, which is unnecessary
|
||
|
// since the file name is already included. Plus, it has center dots.
|
||
|
// That is, we see
|
||
|
// runtime/debug.*T·ptrmethod
|
||
|
// and want
|
||
|
// *T.ptrmethod
|
||
|
// Since the package path might contains dots (e.g. code.google.com/...),
|
||
|
// we first remove the path prefix if there is one.
|
||
|
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
|
||
|
pkg += name[:lastslash] + "/"
|
||
|
name = name[lastslash+1:]
|
||
|
}
|
||
|
if period := strings.Index(name, "."); period >= 0 {
|
||
|
pkg += name[:period]
|
||
|
name = name[period+1:]
|
||
|
}
|
||
|
|
||
|
name = strings.Replace(name, "·", ".", -1)
|
||
|
return pkg, name
|
||
|
}
|