pax_global_header00006660000000000000000000000064145011125160014506gustar00rootroot0000000000000052 comment=1b7bbfb2f065f79536b843f8e2047e36aadd7846 errors-1.5.1/000077500000000000000000000000001450111251600130265ustar00rootroot00000000000000errors-1.5.1/.travis.yml000066400000000000000000000001051450111251600151330ustar00rootroot00000000000000language: go go: - "1.8.x" - "1.11.x" - "1.16.x" - "1.21.x" errors-1.5.1/LICENSE.MIT000066400000000000000000000020651450111251600144660ustar00rootroot00000000000000Copyright (c) 2015 Conrad Irwin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. errors-1.5.1/README.md000066400000000000000000000046271450111251600143160ustar00rootroot00000000000000go-errors/errors ================ [![Build Status](https://travis-ci.org/go-errors/errors.svg?branch=master)](https://travis-ci.org/go-errors/errors) Package errors adds stacktrace support to errors in go. This is particularly useful when you want to understand the state of execution when an error was returned unexpectedly. It provides the type \*Error which implements the standard golang error interface, so you can use this library interchangeably with code that is expecting a normal error return. Usage ----- Full documentation is available on [godoc](https://godoc.org/github.com/go-errors/errors), but here's a simple example: ```go package crashy import "github.com/go-errors/errors" var Crashed = errors.Errorf("oh dear") func Crash() error { return errors.New(Crashed) } ``` This can be called as follows: ```go package main import ( "crashy" "fmt" "github.com/go-errors/errors" ) func main() { err := crashy.Crash() if err != nil { if errors.Is(err, crashy.Crashed) { fmt.Println(err.(*errors.Error).ErrorStack()) } else { panic(err) } } } ``` Meta-fu ------- This package was original written to allow reporting to [Bugsnag](https://bugsnag.com/) from [bugsnag-go](https://github.com/bugsnag/bugsnag-go), but after I found similar packages by Facebook and Dropbox, it was moved to one canonical location so everyone can benefit. This package is licensed under the MIT license, see LICENSE.MIT for details. ## Changelog * v1.1.0 updated to use go1.13's standard-library errors.Is method instead of == in errors.Is * v1.2.0 added `errors.As` from the standard library. * v1.3.0 *BREAKING* updated error methods to return `error` instead of `*Error`. > Code that needs access to the underlying `*Error` can use the new errors.AsError(e) > ``` > // before > errors.New(err).ErrorStack() > // after >. errors.AsError(errors.Wrap(err)).ErrorStack() > ``` * v1.4.0 *BREAKING* v1.4.0 reverted all changes from v1.3.0 and is identical to v1.2.0 * v1.4.1 no code change, but now without an unnecessary cover.out file. * v1.4.2 performance improvement to ErrorStack() to avoid unnecessary work https://github.com/go-errors/errors/pull/40 * v1.5.0 add errors.Join() and errors.Unwrap() copying the stdlib https://github.com/go-errors/errors/pull/40 * v1.5.1 fix build on go1.13..go1.19 (broken by adding Join and Unwrap with wrong build constraints) errors-1.5.1/error.go000066400000000000000000000117371450111251600145170ustar00rootroot00000000000000// Package errors provides errors that have stack-traces. // // This is particularly useful when you want to understand the // state of execution when an error was returned unexpectedly. // // It provides the type *Error which implements the standard // golang error interface, so you can use this library interchangably // with code that is expecting a normal error return. // // For example: // // package crashy // // import "github.com/go-errors/errors" // // var Crashed = errors.Errorf("oh dear") // // func Crash() error { // return errors.New(Crashed) // } // // This can be called as follows: // // package main // // import ( // "crashy" // "fmt" // "github.com/go-errors/errors" // ) // // func main() { // err := crashy.Crash() // if err != nil { // if errors.Is(err, crashy.Crashed) { // fmt.Println(err.(*errors.Error).ErrorStack()) // } else { // panic(err) // } // } // } // // This package was original written to allow reporting to Bugsnag, // but after I found similar packages by Facebook and Dropbox, it // was moved to one canonical location so everyone can benefit. package errors import ( "bytes" "fmt" "reflect" "runtime" ) // The maximum number of stackframes on any error. var MaxStackDepth = 50 // Error is an error with an attached stacktrace. It can be used // wherever the builtin error interface is expected. type Error struct { Err error stack []uintptr frames []StackFrame prefix string } // New makes an Error from the given value. If that value is already an // error then it will be used directly, if not, it will be passed to // fmt.Errorf("%v"). The stacktrace will point to the line of code that // called New. func New(e interface{}) *Error { var err error switch e := e.(type) { case error: err = e default: err = fmt.Errorf("%v", e) } stack := make([]uintptr, MaxStackDepth) length := runtime.Callers(2, stack[:]) return &Error{ Err: err, stack: stack[:length], } } // Wrap makes an Error from the given value. If that value is already an // error then it will be used directly, if not, it will be passed to // fmt.Errorf("%v"). The skip parameter indicates how far up the stack // to start the stacktrace. 0 is from the current call, 1 from its caller, etc. func Wrap(e interface{}, skip int) *Error { if e == nil { return nil } var err error switch e := e.(type) { case *Error: return e case error: err = e default: err = fmt.Errorf("%v", e) } stack := make([]uintptr, MaxStackDepth) length := runtime.Callers(2+skip, stack[:]) return &Error{ Err: err, stack: stack[:length], } } // WrapPrefix makes an Error from the given value. If that value is already an // error then it will be used directly, if not, it will be passed to // fmt.Errorf("%v"). The prefix parameter is used to add a prefix to the // error message when calling Error(). The skip parameter indicates how far // up the stack to start the stacktrace. 0 is from the current call, // 1 from its caller, etc. func WrapPrefix(e interface{}, prefix string, skip int) *Error { if e == nil { return nil } err := Wrap(e, 1+skip) if err.prefix != "" { prefix = fmt.Sprintf("%s: %s", prefix, err.prefix) } return &Error{ Err: err.Err, stack: err.stack, prefix: prefix, } } // Errorf creates a new error with the given message. You can use it // as a drop-in replacement for fmt.Errorf() to provide descriptive // errors in return values. func Errorf(format string, a ...interface{}) *Error { return Wrap(fmt.Errorf(format, a...), 1) } // Error returns the underlying error's message. func (err *Error) Error() string { msg := err.Err.Error() if err.prefix != "" { msg = fmt.Sprintf("%s: %s", err.prefix, msg) } return msg } // Stack returns the callstack formatted the same way that go does // in runtime/debug.Stack() func (err *Error) Stack() []byte { buf := bytes.Buffer{} for _, frame := range err.StackFrames() { buf.WriteString(frame.String()) } return buf.Bytes() } // Callers satisfies the bugsnag ErrorWithCallerS() interface // so that the stack can be read out. func (err *Error) Callers() []uintptr { return err.stack } // ErrorStack returns a string that contains both the // error message and the callstack. func (err *Error) ErrorStack() string { return err.TypeName() + " " + err.Error() + "\n" + string(err.Stack()) } // StackFrames returns an array of frames containing information about the // stack. func (err *Error) StackFrames() []StackFrame { if err.frames == nil { err.frames = make([]StackFrame, len(err.stack)) for i, pc := range err.stack { err.frames[i] = NewStackFrame(pc) } } return err.frames } // TypeName returns the type this error. e.g. *errors.stringError. func (err *Error) TypeName() string { if _, ok := err.Err.(uncaughtPanic); ok { return "panic" } return reflect.TypeOf(err.Err).String() } // Return the wrapped error (implements api for As function). func (err *Error) Unwrap() error { return err.Err } errors-1.5.1/error_1_13.go000066400000000000000000000015021450111251600152270ustar00rootroot00000000000000//go:build go1.13 // +build go1.13 package errors import ( baseErrors "errors" ) // As finds the first error in err's tree that matches target, and if one is found, sets // target to that error value and returns true. Otherwise, it returns false. // // For more information see stdlib errors.As. func As(err error, target interface{}) bool { return baseErrors.As(err, target) } // Is detects whether the error is equal to a given error. Errors // are considered equal by this function if they are matched by errors.Is // or if their contained errors are matched through errors.Is. func Is(e error, original error) bool { if baseErrors.Is(e, original) { return true } if e, ok := e.(*Error); ok { return Is(e.Err, original) } if original, ok := original.(*Error); ok { return Is(e, original.Err) } return false } errors-1.5.1/error_1_13_test.go000066400000000000000000000025671450111251600163020ustar00rootroot00000000000000// +build go1.13 package errors import ( "io" "testing" ) // This test should work only for go 1.13 and latter func TestIs113(t *testing.T) { custErr := errorWithCustomIs{ Key: "TestForFun", Err: io.EOF, } shouldMatch := errorWithCustomIs{ Key: "TestForFun", } shouldNotMatch := errorWithCustomIs{Key: "notOk"} if !Is(custErr, shouldMatch) { t.Errorf("custErr is not a TestForFun customError") } if Is(custErr, shouldNotMatch) { t.Errorf("custErr is a notOk customError") } if !Is(custErr, New(shouldMatch)) { t.Errorf("custErr is not a New(TestForFun customError)") } if Is(custErr, New(shouldNotMatch)) { t.Errorf("custErr is a New(notOk customError)") } if !Is(New(custErr), shouldMatch) { t.Errorf("New(custErr) is not a TestForFun customError") } if Is(New(custErr), shouldNotMatch) { t.Errorf("New(custErr) is a notOk customError") } if !Is(New(custErr), New(shouldMatch)) { t.Errorf("New(custErr) is not a New(TestForFun customError)") } if Is(New(custErr), New(shouldNotMatch)) { t.Errorf("New(custErr) is a New(notOk customError)") } } type errorWithCustomIs struct { Key string Err error } func (ewci errorWithCustomIs) Error() string { return "[" + ewci.Key + "]: " + ewci.Err.Error() } func (ewci errorWithCustomIs) Is(target error) bool { matched, ok := target.(errorWithCustomIs) return ok && matched.Key == ewci.Key } errors-1.5.1/error_backward.go000066400000000000000000000047721450111251600163560ustar00rootroot00000000000000//go:build !go1.13 // +build !go1.13 package errors import ( "reflect" ) type unwrapper interface { Unwrap() error } // As assigns error or any wrapped error to the value target points // to. If there is no value of the target type of target As returns // false. func As(err error, target interface{}) bool { targetType := reflect.TypeOf(target) for { errType := reflect.TypeOf(err) if errType == nil { return false } if reflect.PtrTo(errType) == targetType { reflect.ValueOf(target).Elem().Set(reflect.ValueOf(err)) return true } wrapped, ok := err.(unwrapper) if ok { err = wrapped.Unwrap() } else { return false } } } // Is detects whether the error is equal to a given error. Errors // are considered equal by this function if they are the same object, // or if they both contain the same error inside an errors.Error. func Is(e error, original error) bool { if e == original { return true } if e, ok := e.(*Error); ok { return Is(e.Err, original) } if original, ok := original.(*Error); ok { return Is(e, original.Err) } return false } // Disclaimer: functions Join and Unwrap are copied from the stdlib errors // package v1.21.0. // Join returns an error that wraps the given errors. // Any nil error values are discarded. // Join returns nil if every value in errs is nil. // The error formats as the concatenation of the strings obtained // by calling the Error method of each element of errs, with a newline // between each string. // // A non-nil error returned by Join implements the Unwrap() []error method. func Join(errs ...error) error { n := 0 for _, err := range errs { if err != nil { n++ } } if n == 0 { return nil } e := &joinError{ errs: make([]error, 0, n), } for _, err := range errs { if err != nil { e.errs = append(e.errs, err) } } return e } type joinError struct { errs []error } func (e *joinError) Error() string { var b []byte for i, err := range e.errs { if i > 0 { b = append(b, '\n') } b = append(b, err.Error()...) } return string(b) } func (e *joinError) Unwrap() []error { return e.errs } // Unwrap returns the result of calling the Unwrap method on err, if err's // type contains an Unwrap method returning error. // Otherwise, Unwrap returns nil. // // Unwrap only calls a method of the form "Unwrap() error". // In particular Unwrap does not unwrap errors returned by [Join]. func Unwrap(err error) error { u, ok := err.(interface { Unwrap() error }) if !ok { return nil } return u.Unwrap() } errors-1.5.1/error_test.go000066400000000000000000000163171450111251600155550ustar00rootroot00000000000000package errors import ( "bytes" "fmt" "io" "reflect" "runtime" "strings" "testing" ) func BenchmarkStackFormat(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { func() { defer func() { err := recover() if err != 'a' { b.Fatal(err) } e := Errorf("hi") _ = string(e.Stack()) }() a() }() } } func TestAs(t *testing.T) { var errStrIn errorString = "TestForFun" var errStrOut errorString if As(errStrIn, &errStrOut) { if errStrOut != "TestForFun" { t.Errorf("direct errStr value is not returned") } } else { t.Errorf("direct errStr is not returned") } errStrOut = "" err := Wrap(errStrIn, 0) if As(err, &errStrOut) { if errStrOut != "TestForFun" { t.Errorf("wrapped errStr value is not returned") } } else { t.Errorf("wrapped errStr is not returned") } } func TestStackFormat(t *testing.T) { defer func() { err := recover() if err != 'a' { t.Fatal(err) } e, expected := Errorf("hi"), callers() bs := [][]uintptr{e.stack, expected} if err := compareStacks(bs[0], bs[1]); err != nil { t.Errorf("Stack didn't match") t.Errorf(err.Error()) } stack := string(e.Stack()) if !strings.Contains(stack, "a: b(5)") { t.Errorf("Stack trace does not contain source line: 'a: b(5)'") t.Errorf(stack) } if !strings.Contains(stack, "error_test.go:") { t.Errorf("Stack trace does not contain file name: 'error_test.go:'") t.Errorf(stack) } }() a() } func TestSkipWorks(t *testing.T) { defer func() { err := recover() if err != 'a' { t.Fatal(err) } bs := [][]uintptr{Wrap("hi", 2).stack, callersSkip(2)} if err := compareStacks(bs[0], bs[1]); err != nil { t.Errorf("Stack didn't match") t.Errorf(err.Error()) } }() a() } func TestNew(t *testing.T) { err := New("foo") if err.Error() != "foo" { t.Errorf("Wrong message") } err = New(fmt.Errorf("foo")) if err.Error() != "foo" { t.Errorf("Wrong message") } bs := [][]uintptr{New("foo").stack, callers()} if err := compareStacks(bs[0], bs[1]); err != nil { t.Errorf("Stack didn't match") t.Errorf(err.Error()) } if err.ErrorStack() != err.TypeName()+" "+err.Error()+"\n"+string(err.Stack()) { t.Errorf("ErrorStack is in the wrong format") } } // This test should work for any go version func TestIs(t *testing.T) { if Is(nil, io.EOF) { t.Errorf("nil is an error") } if !Is(io.EOF, io.EOF) { t.Errorf("io.EOF is not io.EOF") } if !Is(io.EOF, New(io.EOF)) { t.Errorf("io.EOF is not New(io.EOF)") } if !Is(New(io.EOF), New(io.EOF)) { t.Errorf("New(io.EOF) is not New(io.EOF)") } if Is(io.EOF, fmt.Errorf("io.EOF")) { t.Errorf("io.EOF is fmt.Errorf") } } func TestWrapError(t *testing.T) { e := func() error { return Wrap("hi", 1) }() if e.Error() != "hi" { t.Errorf("Constructor with a string failed") } if Wrap(fmt.Errorf("yo"), 0).Error() != "yo" { t.Errorf("Constructor with an error failed") } if Wrap(e, 0) != e { t.Errorf("Constructor with an Error failed") } if Wrap(nil, 0) != nil { t.Errorf("Constructor with nil failed") } } func TestWrapPrefixError(t *testing.T) { e := func() error { return WrapPrefix("hi", "prefix", 1) }() if e.Error() != "prefix: hi" { t.Errorf("Constructor with a string failed") } if WrapPrefix(fmt.Errorf("yo"), "prefix", 0).Error() != "prefix: yo" { t.Errorf("Constructor with an error failed") } prefixed := WrapPrefix(e, "prefix", 0) original := e.(*Error) if prefixed.Err != original.Err || !reflect.DeepEqual(prefixed.stack, original.stack) || !reflect.DeepEqual(prefixed.frames, original.frames) || prefixed.Error() != "prefix: prefix: hi" { t.Errorf("Constructor with an Error failed") } if original.Error() == prefixed.Error() { t.Errorf("WrapPrefix changed the original error") } if WrapPrefix(nil, "prefix", 0) != nil { t.Errorf("Constructor with nil failed") } if !strings.HasSuffix(original.StackFrames()[0].File, "error_test.go") || strings.HasSuffix(original.StackFrames()[1].File, "error_test.go") { t.Errorf("Skip failed") } } func ExampleErrorf(x int) (int, error) { if x%2 == 1 { return 0, Errorf("can only halve even numbers, got %d", x) } return x / 2, nil } func ExampleWrapError() (error, error) { // Wrap io.EOF with the current stack-trace and return it return nil, Wrap(io.EOF, 0) } func ExampleWrapError_skip() { defer func() { if err := recover(); err != nil { // skip 1 frame (the deferred function) and then return the wrapped err err = Wrap(err, 1) } }() } func ExampleIs(reader io.Reader, buff []byte) { _, err := reader.Read(buff) if Is(err, io.EOF) { return } } func ExampleNew(UnexpectedEOF error) error { // calling New attaches the current stacktrace to the existing UnexpectedEOF error return New(UnexpectedEOF) } func ExampleWrap() error { if err := recover(); err != nil { return Wrap(err, 1) } return a() } func ExampleError_Error(err error) { fmt.Println(err.Error()) } func ExampleError_ErrorStack(err error) { fmt.Println(err.(*Error).ErrorStack()) } func ExampleError_Stack(err *Error) { fmt.Println(err.Stack()) } func ExampleError_TypeName(err *Error) { fmt.Println(err.TypeName(), err.Error()) } func ExampleError_StackFrames(err *Error) { for _, frame := range err.StackFrames() { fmt.Println(frame.File, frame.LineNumber, frame.Package, frame.Name) } } func a() error { b(5) return nil } func b(i int) { c() } func c() { panic('a') } // compareStacks will compare a stack created using the errors package (actual) // with a reference stack created with the callers function (expected). The // first entry is not compared since the actual and expected stacks cannot // be created at the exact same program counter position so the first entry // will always differ somewhat. Returns nil if the stacks are equal enough and // an error containing a detailed error message otherwise. func compareStacks(actual, expected []uintptr) error { if len(actual) != len(expected) { return stackCompareError("Stacks does not have equal length", actual, expected) } for i, pc := range actual { if i != 0 && pc != expected[i] { return stackCompareError(fmt.Sprintf("Stacks does not match entry %d (and maybe others)", i), actual, expected) } } return nil } func stackCompareError(msg string, actual, expected []uintptr) error { return fmt.Errorf("%s\nActual stack trace:\n%s\nExpected stack trace:\n%s", msg, readableStackTrace(actual), readableStackTrace(expected)) } func callers() []uintptr { return callersSkip(1) } func callersSkip(skip int) []uintptr { callers := make([]uintptr, MaxStackDepth) length := runtime.Callers(skip+2, callers[:]) return callers[:length] } func readableStackTrace(callers []uintptr) string { var result bytes.Buffer frames := callersToFrames(callers) for _, frame := range frames { result.WriteString(fmt.Sprintf("%s:%d (%#x)\n\t%s\n", frame.File, frame.Line, frame.PC, frame.Function)) } return result.String() } func callersToFrames(callers []uintptr) []runtime.Frame { frames := make([]runtime.Frame, 0, len(callers)) framesPtr := runtime.CallersFrames(callers) for { frame, more := framesPtr.Next() frames = append(frames, frame) if !more { return frames } } } type errorString string func (e errorString) Error() string { return string(e) } errors-1.5.1/go.mod000066400000000000000000000001661450111251600141370ustar00rootroot00000000000000module github.com/go-errors/errors go 1.14 // Was not API-compatible with earlier or later releases. retract v1.3.0 errors-1.5.1/join_unwrap_1_20.go000066400000000000000000000017371450111251600164410ustar00rootroot00000000000000//go:build go1.20 // +build go1.20 package errors import baseErrors "errors" // Join returns an error that wraps the given errors. // Any nil error values are discarded. // Join returns nil if every value in errs is nil. // The error formats as the concatenation of the strings obtained // by calling the Error method of each element of errs, with a newline // between each string. // // A non-nil error returned by Join implements the Unwrap() []error method. // // For more information see stdlib errors.Join. func Join(errs ...error) error { return baseErrors.Join(errs...) } // Unwrap returns the result of calling the Unwrap method on err, if err's // type contains an Unwrap method returning error. // Otherwise, Unwrap returns nil. // // Unwrap only calls a method of the form "Unwrap() error". // In particular Unwrap does not unwrap errors returned by [Join]. // // For more information see stdlib errors.Unwrap. func Unwrap(err error) error { return baseErrors.Unwrap(err) } errors-1.5.1/join_unwrap_backward.go000066400000000000000000000027371450111251600175570ustar00rootroot00000000000000//go:build !go1.20 // +build !go1.20 package errors // Disclaimer: functions Join and Unwrap are copied from the stdlib errors // package v1.21.0. // Join returns an error that wraps the given errors. // Any nil error values are discarded. // Join returns nil if every value in errs is nil. // The error formats as the concatenation of the strings obtained // by calling the Error method of each element of errs, with a newline // between each string. // // A non-nil error returned by Join implements the Unwrap() []error method. func Join(errs ...error) error { n := 0 for _, err := range errs { if err != nil { n++ } } if n == 0 { return nil } e := &joinError{ errs: make([]error, 0, n), } for _, err := range errs { if err != nil { e.errs = append(e.errs, err) } } return e } type joinError struct { errs []error } func (e *joinError) Error() string { var b []byte for i, err := range e.errs { if i > 0 { b = append(b, '\n') } b = append(b, err.Error()...) } return string(b) } func (e *joinError) Unwrap() []error { return e.errs } // Unwrap returns the result of calling the Unwrap method on err, if err's // type contains an Unwrap method returning error. // Otherwise, Unwrap returns nil. // // Unwrap only calls a method of the form "Unwrap() error". // In particular Unwrap does not unwrap errors returned by [Join]. func Unwrap(err error) error { u, ok := err.(interface { Unwrap() error }) if !ok { return nil } return u.Unwrap() } errors-1.5.1/parse_panic.go000066400000000000000000000057011450111251600156440ustar00rootroot00000000000000package errors import ( "strconv" "strings" ) type uncaughtPanic struct{ message string } func (p uncaughtPanic) Error() string { return p.message } // ParsePanic allows you to get an error object from the output of a go program // that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap. func ParsePanic(text string) (*Error, error) { lines := strings.Split(text, "\n") state := "start" var message string var stack []StackFrame for i := 0; i < len(lines); i++ { line := lines[i] if state == "start" { if strings.HasPrefix(line, "panic: ") { message = strings.TrimPrefix(line, "panic: ") state = "seek" } else { return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line) } } else if state == "seek" { if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") { state = "parsing" } } else if state == "parsing" { if line == "" { state = "done" break } createdBy := false if strings.HasPrefix(line, "created by ") { line = strings.TrimPrefix(line, "created by ") createdBy = true } i++ if i >= len(lines) { return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line) } frame, err := parsePanicFrame(line, lines[i], createdBy) if err != nil { return nil, err } stack = append(stack, *frame) if createdBy { state = "done" break } } } if state == "done" || state == "parsing" { return &Error{Err: uncaughtPanic{message}, frames: stack}, nil } return nil, Errorf("could not parse panic: %v", text) } // The lines we're passing look like this: // // main.(*foo).destruct(0xc208067e98) // /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151 func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) { idx := strings.LastIndex(name, "(") if idx == -1 && !createdBy { return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name) } if idx != -1 { name = name[:idx] } pkg := "" 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) if !strings.HasPrefix(line, "\t") { return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line) } idx = strings.LastIndex(line, ":") if idx == -1 { return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line) } file := line[1:idx] number := line[idx+1:] if idx = strings.Index(number, " +"); idx > -1 { number = number[:idx] } lno, err := strconv.ParseInt(number, 10, 32) if err != nil { return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line) } return &StackFrame{ File: file, LineNumber: int(lno), Package: pkg, Name: name, }, nil } errors-1.5.1/parse_panic_test.go000066400000000000000000000125551450111251600167100ustar00rootroot00000000000000package errors import ( "reflect" "testing" ) var createdBy = `panic: hello! goroutine 54 [running]: runtime.panic(0x35ce40, 0xc208039db0) /0/c/go/src/pkg/runtime/panic.c:279 +0xf5 github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001() /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74 net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0) /0/c/go/src/pkg/net/http/server.go:1698 +0x91 created by github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.App.Index /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:14 +0x3e goroutine 16 [IO wait]: net.runtime_pollWait(0x911c30, 0x72, 0x0) /0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66 net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0) /0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46 net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0) /0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42 net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23) /0/c/go/src/pkg/net/fd_unix.go:409 +0x343 net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0) /0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0) /0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b github.com/revel/revel.Run(0xe6d9) /0/go/src/github.com/revel/revel/server.go:113 +0x926 main.main() /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a ` var normalSplit = `panic: hello! goroutine 54 [running]: runtime.panic(0x35ce40, 0xc208039db0) /0/c/go/src/pkg/runtime/panic.c:279 +0xf5 github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001() /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74 net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0) /0/c/go/src/pkg/net/http/server.go:1698 +0x91 goroutine 16 [IO wait]: net.runtime_pollWait(0x911c30, 0x72, 0x0) /0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66 net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0) /0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46 net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0) /0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42 net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23) /0/c/go/src/pkg/net/fd_unix.go:409 +0x343 net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0) /0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0) /0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b github.com/revel/revel.Run(0xe6d9) /0/go/src/github.com/revel/revel/server.go:113 +0x926 main.main() /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a ` var lastGoroutine = `panic: hello! goroutine 16 [IO wait]: net.runtime_pollWait(0x911c30, 0x72, 0x0) /0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66 net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0) /0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46 net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0) /0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42 net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23) /0/c/go/src/pkg/net/fd_unix.go:409 +0x343 net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0) /0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0) /0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b github.com/revel/revel.Run(0xe6d9) /0/go/src/github.com/revel/revel/server.go:113 +0x926 main.main() /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a goroutine 54 [running]: runtime.panic(0x35ce40, 0xc208039db0) /0/c/go/src/pkg/runtime/panic.c:279 +0xf5 github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001() /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74 net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0) /0/c/go/src/pkg/net/http/server.go:1698 +0x91 ` var result = []StackFrame{ StackFrame{File: "/0/c/go/src/pkg/runtime/panic.c", LineNumber: 279, Name: "panic", Package: "runtime"}, StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 13, Name: "func.001", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers"}, StackFrame{File: "/0/c/go/src/pkg/net/http/server.go", LineNumber: 1698, Name: "(*Server).Serve", Package: "net/http"}, } var resultCreatedBy = append(result, StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 14, Name: "App.Index", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers", ProgramCounter: 0x0}) func TestParsePanic(t *testing.T) { todo := map[string]string{ "createdBy": createdBy, "normalSplit": normalSplit, "lastGoroutine": lastGoroutine, } for key, val := range todo { Err, err := ParsePanic(val) if err != nil { t.Fatal(err) } if Err.TypeName() != "panic" { t.Errorf("Wrong type: %s", Err.TypeName()) } if Err.Error() != "hello!" { t.Errorf("Wrong message: %s", Err.TypeName()) } if Err.StackFrames()[0].Func() != nil { t.Errorf("Somehow managed to find a func...") } result := result if key == "createdBy" { result = resultCreatedBy } if !reflect.DeepEqual(Err.StackFrames(), result) { t.Errorf("Wrong stack for %s: %#v", key, Err.StackFrames()) } } } errors-1.5.1/stackframe.go000066400000000000000000000057411450111251600155040ustar00rootroot00000000000000package 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) { source, err := frame.sourceLine() if err != nil { return source, New(err) } return source, err } func (frame *StackFrame) sourceLine() (string, error) { if frame.LineNumber <= 0 { return "???", nil } file, err := os.Open(frame.File) if err != nil { return "", 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 "", 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 }