You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
2.8 KiB
114 lines
2.8 KiB
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 |
|
}
|
|
|