stackframe.go (2891B)
1 package errors 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "os" 8 "runtime" 9 "strings" 10 ) 11 12 // A StackFrame contains all necessary information about to generate a line 13 // in a callstack. 14 type StackFrame struct { 15 // The path to the file containing this ProgramCounter 16 File string 17 // The LineNumber in that file 18 LineNumber int 19 // The Name of the function that contains this ProgramCounter 20 Name string 21 // The Package that contains this function 22 Package string 23 // The underlying ProgramCounter 24 ProgramCounter uintptr 25 } 26 27 // NewStackFrame popoulates a stack frame object from the program counter. 28 func NewStackFrame(pc uintptr) (frame StackFrame) { 29 30 frame = StackFrame{ProgramCounter: pc} 31 if frame.Func() == nil { 32 return 33 } 34 frame.Package, frame.Name = packageAndName(frame.Func()) 35 36 // pc -1 because the program counters we use are usually return addresses, 37 // and we want to show the line that corresponds to the function call 38 frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1) 39 return 40 41 } 42 43 // Func returns the function that contained this frame. 44 func (frame *StackFrame) Func() *runtime.Func { 45 if frame.ProgramCounter == 0 { 46 return nil 47 } 48 return runtime.FuncForPC(frame.ProgramCounter) 49 } 50 51 // String returns the stackframe formatted in the same way as go does 52 // in runtime/debug.Stack() 53 func (frame *StackFrame) String() string { 54 str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter) 55 56 source, err := frame.SourceLine() 57 if err != nil { 58 return str 59 } 60 61 return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source) 62 } 63 64 // SourceLine gets the line of code (from File and Line) of the original source if possible. 65 func (frame *StackFrame) SourceLine() (string, error) { 66 if frame.LineNumber <= 0 { 67 return "???", nil 68 } 69 70 file, err := os.Open(frame.File) 71 if err != nil { 72 return "", New(err) 73 } 74 defer file.Close() 75 76 scanner := bufio.NewScanner(file) 77 currentLine := 1 78 for scanner.Scan() { 79 if currentLine == frame.LineNumber { 80 return string(bytes.Trim(scanner.Bytes(), " \t")), nil 81 } 82 currentLine++ 83 } 84 if err := scanner.Err(); err != nil { 85 return "", New(err) 86 } 87 88 return "???", nil 89 } 90 91 func packageAndName(fn *runtime.Func) (string, string) { 92 name := fn.Name() 93 pkg := "" 94 95 // The name includes the path name to the package, which is unnecessary 96 // since the file name is already included. Plus, it has center dots. 97 // That is, we see 98 // runtime/debug.*T·ptrmethod 99 // and want 100 // *T.ptrmethod 101 // Since the package path might contains dots (e.g. code.google.com/...), 102 // we first remove the path prefix if there is one. 103 if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 { 104 pkg += name[:lastslash] + "/" 105 name = name[lastslash+1:] 106 } 107 if period := strings.Index(name, "."); period >= 0 { 108 pkg += name[:period] 109 name = name[period+1:] 110 } 111 112 name = strings.Replace(name, "·", ".", -1) 113 return pkg, name 114 }