...
1 package errors
2
3 import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "os"
8 "runtime"
9 "strings"
10 )
11
12
13
14 type StackFrame struct {
15
16 File string
17
18 LineNumber int
19
20 Name string
21
22 Package string
23
24 ProgramCounter uintptr
25 }
26
27
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
37
38 frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
39 return
40
41 }
42
43
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
52
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
65 func (frame *StackFrame) SourceLine() (string, error) {
66 source, err := frame.sourceLine()
67 if err != nil {
68 return source, New(err)
69 }
70 return source, err
71 }
72
73 func (frame *StackFrame) sourceLine() (string, error) {
74 if frame.LineNumber <= 0 {
75 return "???", nil
76 }
77
78 file, err := os.Open(frame.File)
79 if err != nil {
80 return "", err
81 }
82 defer file.Close()
83
84 scanner := bufio.NewScanner(file)
85 currentLine := 1
86 for scanner.Scan() {
87 if currentLine == frame.LineNumber {
88 return string(bytes.Trim(scanner.Bytes(), " \t")), nil
89 }
90 currentLine++
91 }
92 if err := scanner.Err(); err != nil {
93 return "", err
94 }
95
96 return "???", nil
97 }
98
99 func packageAndName(fn *runtime.Func) (string, string) {
100 name := fn.Name()
101 pkg := ""
102
103
104
105
106
107
108
109
110
111 if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
112 pkg += name[:lastslash] + "/"
113 name = name[lastslash+1:]
114 }
115 if period := strings.Index(name, "."); period >= 0 {
116 pkg += name[:period]
117 name = name[period+1:]
118 }
119
120 name = strings.Replace(name, "·", ".", -1)
121 return pkg, name
122 }
123
View as plain text