-
Notifications
You must be signed in to change notification settings - Fork 715
Introduce Stacktrace and Frame #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| package errors | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "io" | ||
| "path" | ||
| "runtime" | ||
| "strings" | ||
| ) | ||
|
|
||
| // Frame repesents an activation record. | ||
| type Frame uintptr | ||
|
|
||
| // pc returns the program counter for this frame; | ||
| // multiple frames may have the same PC value. | ||
| func (f Frame) pc() uintptr { return uintptr(f) - 1 } | ||
|
|
||
| // file returns the full path to the file that contains the | ||
| // function for this Frame's pc. | ||
| func (f Frame) file() string { | ||
| fn := runtime.FuncForPC(f.pc()) | ||
| if fn == nil { | ||
| return "unknown" | ||
| } | ||
| file, _ := fn.FileLine(f.pc()) | ||
| return file | ||
| } | ||
|
|
||
| // line returns the line number of source code of the | ||
| // function for this Frame's pc. | ||
| func (f Frame) line() int { | ||
| fn := runtime.FuncForPC(f.pc()) | ||
| if fn == nil { | ||
| return 0 | ||
| } | ||
| _, line := fn.FileLine(f.pc()) | ||
| return line | ||
| } | ||
|
|
||
| // Format formats the frame according to the fmt.Formatter interface. | ||
| // | ||
| // %s source file | ||
| // %d source line | ||
| // %n function name | ||
| // %v equivalent to %s:%d | ||
| // | ||
| // Format accepts flags that alter the printing of some verbs, as follows: | ||
| // | ||
| // %+s path of source file relative to the compile time GOPATH | ||
| // %+v equivalent to %+s:%d | ||
| func (f Frame) Format(s fmt.State, verb rune) { | ||
| switch verb { | ||
| case 's': | ||
| switch { | ||
| case s.Flag('+'): | ||
| pc := f.pc() | ||
| fn := runtime.FuncForPC(pc) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, that is true, I'm leaning on the Can you think of a way we could write a test case for this ? We might have to extract the body of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add this test to }, {
Frame(0),
"%+s",
"unknown",
}, {It will fail. Fix it for TDD points.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| if fn == nil { | ||
| io.WriteString(s, "unknown") | ||
| } else { | ||
| file, _ := fn.FileLine(pc) | ||
| io.WriteString(s, trimGOPATH(fn.Name(), file)) | ||
| } | ||
| default: | ||
| io.WriteString(s, path.Base(f.file())) | ||
| } | ||
| case 'd': | ||
| fmt.Fprintf(s, "%d", f.line()) | ||
| case 'n': | ||
| name := runtime.FuncForPC(f.pc()).Name() | ||
| i := strings.LastIndex(name, "/") | ||
| name = name[i+1:] | ||
| i = strings.Index(name, ".") | ||
| io.WriteString(s, name[i+1:]) | ||
| case 'v': | ||
| f.Format(s, 's') | ||
| io.WriteString(s, ":") | ||
| f.Format(s, 'd') | ||
| } | ||
| } | ||
|
|
||
| // stack represents a stack of program counters. | ||
| type stack []uintptr | ||
|
|
||
| // Deprecated: use Stacktrace() | ||
| func (s *stack) Stack() []uintptr { return *s } | ||
|
|
||
| // Deprecated: use Stacktrace()[0] | ||
| func (s *stack) Location() (string, int) { | ||
| frame := s.Stacktrace()[0] | ||
| return fmt.Sprintf("%+s", frame), frame.line() | ||
| } | ||
|
|
||
| func (s *stack) Stacktrace() []Frame { | ||
| f := make([]Frame, len(*s)) | ||
| for i := 0; i < len(f); i++ { | ||
| f[i] = Frame((*s)[i]) | ||
| } | ||
| return f | ||
| } | ||
|
|
||
| func callers() *stack { | ||
| const depth = 32 | ||
| var pcs [depth]uintptr | ||
| n := runtime.Callers(3, pcs[:]) | ||
| var st stack = pcs[0:n] | ||
| return &st | ||
| } | ||
|
|
||
| func trimGOPATH(name, file string) string { | ||
| // Here we want to get the source file path relative to the compile time | ||
| // GOPATH. As of Go 1.6.x there is no direct way to know the compiled | ||
| // GOPATH at runtime, but we can infer the number of path segments in the | ||
| // GOPATH. We note that fn.Name() returns the function name qualified by | ||
| // the import path, which does not include the GOPATH. Thus we can trim | ||
| // segments from the beginning of the file path until the number of path | ||
| // separators remaining is one more than the number of path separators in | ||
| // the function name. For example, given: | ||
| // | ||
| // GOPATH /home/user | ||
| // file /home/user/src/pkg/sub/file.go | ||
| // fn.Name() pkg/sub.Type.Method | ||
| // | ||
| // We want to produce: | ||
| // | ||
| // pkg/sub/file.go | ||
| // | ||
| // From this we can easily see that fn.Name() has one less path separator | ||
| // than our desired output. We count separators from the end of the file | ||
| // path until it finds two more than in the function name and then move | ||
| // one character forward to preserve the initial path segment without a | ||
| // leading separator. | ||
| const sep = "/" | ||
| goal := strings.Count(name, sep) + 2 | ||
| i := len(file) | ||
| for n := 0; n < goal; n++ { | ||
| i = strings.LastIndex(file[:i], sep) | ||
| if i == -1 { | ||
| // not enough separators found, set i so that the slice expression | ||
| // below leaves file unmodified | ||
| i = -len(sep) | ||
| break | ||
| } | ||
| } | ||
| // get back to 0 or trim the leading separator | ||
| file = file[i+len(sep):] | ||
| return file | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
%+v is also supported because
case 'v'recursively callsf.Format(s, 's').There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't want to add it til I had written a test case for it (I try to TDD where I can). As you say, it'll probably work because of the way I wrote
%vThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added test and docs.