| package errors |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io/ioutil" |
| "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) { |
| data, err := ioutil.ReadFile(frame.File) |
| |
| if err != nil { |
| return "", New(err) |
| } |
| |
| lines := bytes.Split(data, []byte{'\n'}) |
| if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) { |
| return "???", nil |
| } |
| // -1 because line-numbers are 1 based, but our array is 0 based |
| return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), 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 |
| } |