From e1ac100e466767d12265e46f25690de9bcd29e3e Mon Sep 17 00:00:00 2001 From: Chris Stockton Date: Sat, 17 Feb 2018 09:38:59 -0700 Subject: [PATCH 01/34] reduce allocations when printing stack traces (#149) --- bench_test.go | 49 ++++++++++++++++++++++++++++++++++++- stack.go | 68 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 102 insertions(+), 15 deletions(-) diff --git a/bench_test.go b/bench_test.go index 903b5f2d..c906870e 100644 --- a/bench_test.go +++ b/bench_test.go @@ -25,7 +25,7 @@ func yesErrors(at, depth int) error { // GlobalE is an exported global to store the result of benchmark results, // preventing the compiler from optimising the benchmark functions away. -var GlobalE error +var GlobalE interface{} func BenchmarkErrors(b *testing.B) { type run struct { @@ -61,3 +61,50 @@ func BenchmarkErrors(b *testing.B) { }) } } + +func BenchmarkStackFormatting(b *testing.B) { + type run struct { + stack int + format string + } + runs := []run{ + {10, "%s"}, + {10, "%v"}, + {10, "%+v"}, + {30, "%s"}, + {30, "%v"}, + {30, "%+v"}, + {60, "%s"}, + {60, "%v"}, + {60, "%+v"}, + } + + var stackStr string + for _, r := range runs { + name := fmt.Sprintf("%s-stack-%d", r.format, r.stack) + b.Run(name, func(b *testing.B) { + err := yesErrors(0, r.stack) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + stackStr = fmt.Sprintf(r.format, err) + } + b.StopTimer() + }) + } + + for _, r := range runs { + name := fmt.Sprintf("%s-stacktrace-%d", r.format, r.stack) + b.Run(name, func(b *testing.B) { + err := yesErrors(0, r.stack) + st := err.(*fundamental).stack.StackTrace() + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + stackStr = fmt.Sprintf(r.format, st) + } + b.StopTimer() + }) + } + GlobalE = stackStr +} diff --git a/stack.go b/stack.go index 2874a048..c9def772 100644 --- a/stack.go +++ b/stack.go @@ -1,10 +1,12 @@ package errors import ( + "bytes" "fmt" "io" "path" "runtime" + "strconv" "strings" ) @@ -50,6 +52,11 @@ func (f Frame) line() int { // GOPATH separated by \n\t (\n\t) // %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { + f.format(s, s, verb) +} + +// format allows stack trace printing calls to be made with a bytes.Buffer. +func (f Frame) format(w io.Writer, s fmt.State, verb rune) { switch verb { case 's': switch { @@ -57,23 +64,25 @@ func (f Frame) Format(s fmt.State, verb rune) { pc := f.pc() fn := runtime.FuncForPC(pc) if fn == nil { - io.WriteString(s, "unknown") + io.WriteString(w, "unknown") } else { file, _ := fn.FileLine(pc) - fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + io.WriteString(w, fn.Name()) + io.WriteString(w, "\n\t") + io.WriteString(w, file) } default: - io.WriteString(s, path.Base(f.file())) + io.WriteString(w, path.Base(f.file())) } case 'd': - fmt.Fprintf(s, "%d", f.line()) + io.WriteString(w, strconv.Itoa(f.line())) case 'n': name := runtime.FuncForPC(f.pc()).Name() - io.WriteString(s, funcname(name)) + io.WriteString(w, funcname(name)) case 'v': - f.Format(s, 's') - io.WriteString(s, ":") - f.Format(s, 'd') + f.format(w, s, 's') + io.WriteString(w, ":") + f.format(w, s, 'd') } } @@ -89,23 +98,50 @@ type StackTrace []Frame // // %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { + var b bytes.Buffer switch verb { case 'v': switch { case s.Flag('+'): - for _, f := range st { - fmt.Fprintf(s, "\n%+v", f) + b.Grow(len(st) * stackMinLen) + for _, fr := range st { + b.WriteByte('\n') + fr.format(&b, s, verb) } case s.Flag('#'): - fmt.Fprintf(s, "%#v", []Frame(st)) + fmt.Fprintf(&b, "%#v", []Frame(st)) default: - fmt.Fprintf(s, "%v", []Frame(st)) + st.formatSlice(&b, s, verb) } case 's': - fmt.Fprintf(s, "%s", []Frame(st)) + st.formatSlice(&b, s, verb) + } + io.Copy(s, &b) +} + +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) { + b.WriteByte('[') + if len(st) == 0 { + b.WriteByte(']') + return + } + + b.Grow(len(st) * (stackMinLen / 4)) + st[0].format(b, s, verb) + for _, fr := range st[1:] { + b.WriteByte(' ') + fr.format(b, s, verb) } + b.WriteByte(']') } +// stackMinLen is a best-guess at the minimum length of a stack trace. It +// doesn't need to be exact, just give a good enough head start for the buffer +// to avoid the expensive early growth. +const stackMinLen = 96 + // stack represents a stack of program counters. type stack []uintptr @@ -114,10 +150,14 @@ func (s *stack) Format(st fmt.State, verb rune) { case 'v': switch { case st.Flag('+'): + var b bytes.Buffer + b.Grow(len(*s) * stackMinLen) for _, pc := range *s { f := Frame(pc) - fmt.Fprintf(st, "\n%+v", f) + b.WriteByte('\n') + f.format(&b, st, 'v') } + io.Copy(st, &b) } } } From c059e472caf75dbe73903f6521a20abac245b17f Mon Sep 17 00:00:00 2001 From: Eldar Rakhimberdin Date: Tue, 11 Sep 2018 09:21:13 +0300 Subject: [PATCH 02/34] fixed spelling (#156) --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 842ee804..dae1a0cb 100644 --- a/errors.go +++ b/errors.go @@ -192,7 +192,7 @@ func Wrap(err error, message string) error { } // Wrapf returns an error annotating err with a stack trace -// at the point Wrapf is call, and the format specifier. +// at the point Wrapf is called, and the format specifier. // If err is nil, Wrapf returns nil. func Wrapf(err error, format string, args ...interface{}) error { if err == nil { From e981d1a2c5fa0227e1f3ee3f56b73ca5498460ed Mon Sep 17 00:00:00 2001 From: Dariusz Jedrzejczyk Date: Mon, 8 Oct 2018 06:50:16 +0200 Subject: [PATCH 03/34] Add WithMessagef function (#118) WithMessagef utility function to accompany WithMessage, similar to exiting Wrapf function accompanying Wrap. --- errors.go | 12 ++++++++++++ errors_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/errors.go b/errors.go index dae1a0cb..6b01b7e6 100644 --- a/errors.go +++ b/errors.go @@ -220,6 +220,18 @@ func WithMessage(err error, message string) error { } } +// WithMessagef annotates err with the format specifier. +// If err is nil, WithMessagef returns nil. +func WithMessagef(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } +} + type withMessage struct { cause error msg string diff --git a/errors_test.go b/errors_test.go index c4e6eef6..2089b2f7 100644 --- a/errors_test.go +++ b/errors_test.go @@ -198,6 +198,32 @@ func TestWithMessage(t *testing.T) { } } +func TestWithMessagefNil(t *testing.T) { + got := WithMessagef(nil, "no error") + if got != nil { + t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWithMessagef(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {WithMessagef(io.EOF, "read error without format specifier"), "client error", "client error: read error without format specifier: EOF"}, + {WithMessagef(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, + } + + for _, tt := range tests { + got := WithMessagef(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want) + } + } +} + // errors.New, etc values are not expected to be compared by value // but the change in errors#27 made them incomparable. Assert that // various kinds of errors have a functional equality operator, even From 2233dee583dcf88f3c8b22cb7a33f05a499800d8 Mon Sep 17 00:00:00 2001 From: "Steven E. Harris" Date: Mon, 8 Oct 2018 00:53:15 -0400 Subject: [PATCH 04/34] Copyedit the package documentation (#135) Remove spurious words, add missing words, and smooth out a few sentences. --- errors.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/errors.go b/errors.go index 6b01b7e6..1963d86b 100644 --- a/errors.go +++ b/errors.go @@ -6,7 +6,7 @@ // return err // } // -// which applied recursively up the call stack results in error reports +// which when applied recursively up the call stack results in error reports // without context or debugging information. The errors package allows // programmers to add context to the failure path in their code in a way // that does not destroy the original value of the error. @@ -15,16 +15,17 @@ // // The errors.Wrap function returns a new error that adds context to the // original error by recording a stack trace at the point Wrap is called, -// and the supplied message. For example +// together with the supplied message. For example // // _, err := ioutil.ReadAll(r) // if err != nil { // return errors.Wrap(err, "read failed") // } // -// If additional control is required the errors.WithStack and errors.WithMessage -// functions destructure errors.Wrap into its component operations of annotating -// an error with a stack trace and an a message, respectively. +// If additional control is required, the errors.WithStack and +// errors.WithMessage functions destructure errors.Wrap into its component +// operations: annotating an error with a stack trace and with a message, +// respectively. // // Retrieving the cause of an error // @@ -38,7 +39,7 @@ // } // // can be inspected by errors.Cause. errors.Cause will recursively retrieve -// the topmost error which does not implement causer, which is assumed to be +// the topmost error that does not implement causer, which is assumed to be // the original cause. For example: // // switch err := errors.Cause(err).(type) { @@ -48,16 +49,16 @@ // // unknown error // } // -// causer interface is not exported by this package, but is considered a part -// of stable public API. +// Although the causer interface is not exported by this package, it is +// considered a part of its stable public interface. // // Formatted printing of errors // // All error values returned from this package implement fmt.Formatter and can -// be formatted by the fmt package. The following verbs are supported +// be formatted by the fmt package. The following verbs are supported: // // %s print the error. If the error has a Cause it will be -// printed recursively +// printed recursively. // %v see %s // %+v extended format. Each Frame of the error's StackTrace will // be printed in detail. @@ -65,13 +66,13 @@ // Retrieving the stack trace of an error or wrapper // // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are -// invoked. This information can be retrieved with the following interface. +// invoked. This information can be retrieved with the following interface: // // type stackTracer interface { // StackTrace() errors.StackTrace // } // -// Where errors.StackTrace is defined as +// The returned errors.StackTrace type is defined as // // type StackTrace []Frame // @@ -85,8 +86,8 @@ // } // } // -// stackTracer interface is not exported by this package, but is considered a part -// of stable public API. +// Although the stackTracer interface is not exported by this package, it is +// considered a part of its stable public interface. // // See the documentation for Frame.Format for more details. package errors From 6ed0a2e59ebeb03114ec0c38fa6de63106cbf457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sun, 14 Oct 2018 16:58:47 +0200 Subject: [PATCH 05/34] Fix StackTrace print example --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 1963d86b..f67346fe 100644 --- a/errors.go +++ b/errors.go @@ -82,7 +82,7 @@ // // if err, ok := err.(stackTracer); ok { // for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d", f) +// fmt.Printf("%+s:%d\n", f, f) // } // } // From d58f94251046e7f70ac45aceea6cf6f61415ccca Mon Sep 17 00:00:00 2001 From: Harald Nordgren Date: Sun, 21 Oct 2018 00:29:33 +0200 Subject: [PATCH 06/34] Bump Travis versions (#172) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 15e5a192..d4b92663 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ go: - 1.8.x - 1.9.x - 1.10.x + - 1.11.x - tip script: From 059132a15dd08d6704c67711dae0cf35ab991756 Mon Sep 17 00:00:00 2001 From: Komu Wairagu Date: Wed, 24 Oct 2018 02:59:46 +0300 Subject: [PATCH 07/34] Update .travis.yml (#168) From ba968bfe8b2f7e042a574c888954fccecfa385b4 Mon Sep 17 00:00:00 2001 From: Tariq Ibrahim Date: Wed, 2 Jan 2019 22:52:24 -0800 Subject: [PATCH 08/34] gofmt -w errors.go (#179) --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 1963d86b..7421f326 100644 --- a/errors.go +++ b/errors.go @@ -229,7 +229,7 @@ func WithMessagef(err error, format string, args ...interface{}) error { } return &withMessage{ cause: err, - msg: fmt.Sprintf(format, args...), + msg: fmt.Sprintf(format, args...), } } From 5ac96aea2923776ad605502bfb75d1d787f7be64 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 5 Jan 2019 13:32:11 +1100 Subject: [PATCH 09/34] Update README.md --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6483ba2a..cf771e7d 100644 --- a/README.md +++ b/README.md @@ -41,11 +41,18 @@ default: [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). +## Roadmap + +With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: + +- 0.9. Remove pre Go 1.9 support, address outstanding pull requests (if possible) +- 1.0. Final release. + ## Contributing -We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. +Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. -Before proposing a change, please discuss your change by raising an issue. +Before sending a PR, please discuss your change by raising an issue. ## License From 31aac83bad79a69b36527d4e6812885c67d46163 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 5 Jan 2019 14:54:17 +1100 Subject: [PATCH 10/34] travis: use Makefile (#181) Add a bunch of useful makefile targets Signed-off-by: Dave Cheney --- .travis.yml | 2 +- Makefile | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 Makefile diff --git a/.travis.yml b/.travis.yml index d4b92663..3569e24d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,4 @@ go: - tip script: - - go test -v ./... + - make check diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..46ecb350 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +PKGS := github.com/pkg/errors +SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) +GO := go + +check: test vet gofmt unused misspell unconvert gosimple ineffassign + +test: + $(GO) test $(PKGS) + +vet: | test + $(GO) vet $(PKGS) + +staticcheck: + $(GO) get honnef.co/go/tools/cmd/staticcheck + staticcheck $(PKGS) + +unused: + $(GO) get honnef.co/go/tools/cmd/unused + unused -exported $(PKGS) + +misspell: + $(GO) get github.com/client9/misspell/cmd/misspell + misspell \ + -locale GB \ + -error \ + *.md *.go + +unconvert: + $(GO) get github.com/mdempsky/unconvert + unconvert -v $(PKGS) + +gosimple: + $(GO) get honnef.co/go/tools/cmd/gosimple + gosimple $(PKGS) + +ineffassign: + $(GO) get github.com/gordonklaus/ineffassign + find $(SRCDIRS) -name '*.go' | xargs ineffassign + +pedantic: check unparam errcheck staticcheck + +unparam: + $(GO) get mvdan.cc/unparam + unparam ./... + +errcheck: + $(GO) get github.com/kisielk/errcheck + errcheck $(PKGS) + +gofmt: + @echo Checking code is gofmted + @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" From 537896ad6e7adba6ce0edf33642da47ab86cd436 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 5 Jan 2019 15:00:06 +1100 Subject: [PATCH 11/34] travis: remove Go 1.8 and earlier (#182) Remove support for Go 1.8 and earlier as they are a. no longer supported upstream b. lack support for runtime.CallerFrames Signed-off-by: Dave Cheney --- .travis.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3569e24d..0ac2b7ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,6 @@ language: go go_import_path: github.com/pkg/errors go: - - 1.4.x - - 1.5.x - - 1.6.x - - 1.7.x - - 1.8.x - 1.9.x - 1.10.x - 1.11.x From 4f47277723cbe176eaef3bccb66a69de7a531157 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 5 Jan 2019 19:23:03 +1100 Subject: [PATCH 12/34] Switch to runtime.CallersFrames (#183) Fixes #160 Fixes #107 Signed-off-by: Dave Cheney --- stack.go | 44 +++++++++++---------- stack_test.go | 104 ++++++++++++++++++++------------------------------ 2 files changed, 65 insertions(+), 83 deletions(-) diff --git a/stack.go b/stack.go index 2874a048..cb8024b6 100644 --- a/stack.go +++ b/stack.go @@ -9,32 +9,26 @@ import ( ) // Frame represents a program counter inside a stack frame. -type Frame uintptr +type Frame runtime.Frame // 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 } +func (f Frame) pc() uintptr { return runtime.Frame(f).PC } // 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 { + file := runtime.Frame(f).File + if file == "" { 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 + return runtime.Frame(f).Line } // Format formats the frame according to the fmt.Formatter interface. @@ -54,12 +48,11 @@ func (f Frame) Format(s fmt.State, verb rune) { case 's': switch { case s.Flag('+'): - pc := f.pc() - fn := runtime.FuncForPC(pc) + fn := runtime.Frame(f).Func if fn == nil { io.WriteString(s, "unknown") } else { - file, _ := fn.FileLine(pc) + file := runtime.Frame(f).File fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) } default: @@ -114,20 +107,29 @@ func (s *stack) Format(st fmt.State, verb rune) { case 'v': switch { case st.Flag('+'): - for _, pc := range *s { - f := Frame(pc) - fmt.Fprintf(st, "\n%+v", f) + frames := runtime.CallersFrames(*s) + for { + frame, more := frames.Next() + fmt.Fprintf(st, "\n%+v", Frame(frame)) + if !more { + break + } } } } } func (s *stack) StackTrace() StackTrace { - f := make([]Frame, len(*s)) - for i := 0; i < len(f); i++ { - f[i] = Frame((*s)[i]) + var st []Frame + frames := runtime.CallersFrames(*s) + for { + frame, more := frames.Next() + st = append(st, Frame(frame)) + if !more { + break + } } - return f + return st } func callers() *stack { diff --git a/stack_test.go b/stack_test.go index 85fc4195..c444be06 100644 --- a/stack_test.go +++ b/stack_test.go @@ -6,51 +6,18 @@ import ( "testing" ) -var initpc, _, _, _ = runtime.Caller(0) - -func TestFrameLine(t *testing.T) { - var tests = []struct { - Frame - want int - }{{ - Frame(initpc), - 9, - }, { - func() Frame { - var pc, _, _, _ = runtime.Caller(0) - return Frame(pc) - }(), - 20, - }, { - func() Frame { - var pc, _, _, _ = runtime.Caller(1) - return Frame(pc) - }(), - 28, - }, { - Frame(0), // invalid PC - 0, - }} - - for _, tt := range tests { - got := tt.Frame.line() - want := tt.want - if want != got { - t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got) - } - } -} +var initpc = caller() type X struct{} +//go:noinline func (x X) val() Frame { - var pc, _, _, _ = runtime.Caller(0) - return Frame(pc) + return caller() } +//go:noinline func (x *X) ptr() Frame { - var pc, _, _, _ = runtime.Caller(0) - return Frame(pc) + return caller() } func TestFrameFormat(t *testing.T) { @@ -59,32 +26,32 @@ func TestFrameFormat(t *testing.T) { format string want string }{{ - Frame(initpc), + initpc, "%s", "stack_test.go", }, { - Frame(initpc), + initpc, "%+s", "github.com/pkg/errors.init\n" + "\t.+/github.com/pkg/errors/stack_test.go", }, { - Frame(0), + Frame{}, "%s", "unknown", }, { - Frame(0), + Frame{}, "%+s", "unknown", }, { - Frame(initpc), + initpc, "%d", "9", }, { - Frame(0), + Frame{}, "%d", "0", }, { - Frame(initpc), + initpc, "%n", "init", }, { @@ -102,20 +69,20 @@ func TestFrameFormat(t *testing.T) { "%n", "X.val", }, { - Frame(0), + Frame{}, "%n", "", }, { - Frame(initpc), + initpc, "%v", "stack_test.go:9", }, { - Frame(initpc), + initpc, "%+v", "github.com/pkg/errors.init\n" + "\t.+/github.com/pkg/errors/stack_test.go:9", }, { - Frame(0), + Frame{}, "%v", "unknown:0", }} @@ -153,24 +120,24 @@ func TestStackTrace(t *testing.T) { }{{ New("ooh"), []string{ "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:154", + "\t.+/github.com/pkg/errors/stack_test.go:121", }, }, { Wrap(New("ooh"), "ahh"), []string{ "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:159", // this is the stack of Wrap, not New + "\t.+/github.com/pkg/errors/stack_test.go:126", // this is the stack of Wrap, not New }, }, { Cause(Wrap(New("ooh"), "ahh")), []string{ "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:164", // this is the stack of New + "\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New }, }, { - func() error { return New("ooh") }(), []string{ + func() error { noinline(); return New("ooh") }(), []string{ `github.com/pkg/errors.(func·009|TestStackTrace.func1)` + - "\n\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New + "\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:169", // this is the stack of New's caller + "\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New's caller }, }, { Cause(func() error { @@ -179,11 +146,11 @@ func TestStackTrace(t *testing.T) { }() }()), []string{ `github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` + - "\n\t.+/github.com/pkg/errors/stack_test.go:178", // this is the stack of Errorf + "\n\t.+/github.com/pkg/errors/stack_test.go:145", // this is the stack of Errorf `github.com/pkg/errors.(func·011|TestStackTrace.func2)` + - "\n\t.+/github.com/pkg/errors/stack_test.go:179", // this is the stack of Errorf's caller + "\n\t.+/github.com/pkg/errors/stack_test.go:146", // this is the stack of Errorf's caller "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:180", // this is the stack of Errorf's caller's caller + "\t.+/github.com/pkg/errors/stack_test.go:147", // this is the stack of Errorf's caller's caller }, }} for i, tt := range tests { @@ -253,22 +220,35 @@ func TestStackTraceFormat(t *testing.T) { }, { stackTrace()[:2], "%v", - `\[stack_test.go:207 stack_test.go:254\]`, + `\[stack_test.go:174 stack_test.go:221\]`, }, { stackTrace()[:2], "%+v", "\n" + "github.com/pkg/errors.stackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:207\n" + + "\t.+/github.com/pkg/errors/stack_test.go:174\n" + "github.com/pkg/errors.TestStackTraceFormat\n" + - "\t.+/github.com/pkg/errors/stack_test.go:258", + "\t.+/github.com/pkg/errors/stack_test.go:225", }, { stackTrace()[:2], "%#v", - `\[\]errors.Frame{stack_test.go:207, stack_test.go:266}`, + `\[\]errors.Frame{stack_test.go:174, stack_test.go:233}`, }} for i, tt := range tests { testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) } } + +// a version of runtime.Caller that returns a Frame, not a uintptr. +func caller() Frame { + var pcs [3]uintptr + n := runtime.Callers(2, pcs[:]) + frames := runtime.CallersFrames(pcs[:n]) + frame, _ := frames.Next() + return Frame(frame) +} + +//go:noinline +// noinline prevents the caller being inlined +func noinline() {} From e19cb699adc254d953725092e02b3612565bafc4 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 5 Jan 2019 19:41:10 +1100 Subject: [PATCH 13/34] Remove last reference to runtime.FuncForPC (#184) Signed-off-by: Dave Cheney --- stack.go | 6 +----- stack_test.go | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/stack.go b/stack.go index cb8024b6..23321e9b 100644 --- a/stack.go +++ b/stack.go @@ -11,10 +11,6 @@ import ( // Frame represents a program counter inside a stack frame. type Frame runtime.Frame -// pc returns the program counter for this frame; -// multiple frames may have the same PC value. -func (f Frame) pc() uintptr { return runtime.Frame(f).PC } - // file returns the full path to the file that contains the // function for this Frame's pc. func (f Frame) file() string { @@ -61,7 +57,7 @@ func (f Frame) Format(s fmt.State, verb rune) { case 'd': fmt.Fprintf(s, "%d", f.line()) case 'n': - name := runtime.FuncForPC(f.pc()).Name() + name := runtime.Frame(f).Function io.WriteString(s, funcname(name)) case 'v': f.Format(s, 's') diff --git a/stack_test.go b/stack_test.go index c444be06..975cb1aa 100644 --- a/stack_test.go +++ b/stack_test.go @@ -10,12 +10,12 @@ var initpc = caller() type X struct{} -//go:noinline +// val returns a Frame pointing to itself. func (x X) val() Frame { return caller() } -//go:noinline +// ptr returns a Frame pointing to itself. func (x *X) ptr() Frame { return caller() } From 937e8c55285fe9d0c5c2cf16e40e71c65e4b4c6c Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 5 Jan 2019 19:54:25 +1100 Subject: [PATCH 14/34] gofmt -w Signed-off-by: Dave Cheney --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index dde0b69e..8617beef 100644 --- a/errors.go +++ b/errors.go @@ -82,7 +82,7 @@ // // if err, ok := err.(stackTracer); ok { // for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d\n", f, f) +// fmt.Printf("%+s:%d\n", f, f) // } // } // From 42ce1b6a1217850a839e7004415e8bc64865e2ae Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 5 Jan 2019 20:05:53 +1100 Subject: [PATCH 15/34] Remove Frame methods (#185) errors.Frame is convertable from/to a runtime.Frame and know how to print itself. Signed-off-by: Dave Cheney --- stack.go | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/stack.go b/stack.go index 23321e9b..582b3e97 100644 --- a/stack.go +++ b/stack.go @@ -11,22 +11,6 @@ import ( // Frame represents a program counter inside a stack frame. type Frame runtime.Frame -// file returns the full path to the file that contains the -// function for this Frame's pc. -func (f Frame) file() string { - file := runtime.Frame(f).File - if file == "" { - return "unknown" - } - return file -} - -// line returns the line number of source code of the -// function for this Frame's pc. -func (f Frame) line() int { - return runtime.Frame(f).Line -} - // Format formats the frame according to the fmt.Formatter interface. // // %s source file @@ -52,10 +36,14 @@ func (f Frame) Format(s fmt.State, verb rune) { fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) } default: - io.WriteString(s, path.Base(f.file())) + file := runtime.Frame(f).File + if file == "" { + file = "unknown" + } + io.WriteString(s, path.Base(file)) } case 'd': - fmt.Fprintf(s, "%d", f.line()) + fmt.Fprintf(s, "%d", runtime.Frame(f).Line) case 'n': name := runtime.Frame(f).Function io.WriteString(s, funcname(name)) From 584cbace2812d28e0c74c770b2773ab542cc5757 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 5 Jan 2019 21:07:35 +1100 Subject: [PATCH 16/34] Remove checks for old style anon funcs (#186) Signed-off-by: Dave Cheney --- stack_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stack_test.go b/stack_test.go index 975cb1aa..7f766943 100644 --- a/stack_test.go +++ b/stack_test.go @@ -134,7 +134,7 @@ func TestStackTrace(t *testing.T) { }, }, { func() error { noinline(); return New("ooh") }(), []string{ - `github.com/pkg/errors.(func·009|TestStackTrace.func1)` + + `github.com/pkg/errors.TestStackTrace.func1` + "\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New "github.com/pkg/errors.TestStackTrace\n" + "\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New's caller @@ -145,9 +145,9 @@ func TestStackTrace(t *testing.T) { return Errorf("hello %s", fmt.Sprintf("world")) }() }()), []string{ - `github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` + + `github.com/pkg/errors.TestStackTrace.func2.1` + "\n\t.+/github.com/pkg/errors/stack_test.go:145", // this is the stack of Errorf - `github.com/pkg/errors.(func·011|TestStackTrace.func2)` + + `github.com/pkg/errors.TestStackTrace.func2` + "\n\t.+/github.com/pkg/errors/stack_test.go:146", // this is the stack of Errorf's caller "github.com/pkg/errors.TestStackTrace\n" + "\t.+/github.com/pkg/errors/stack_test.go:147", // this is the stack of Errorf's caller's caller From ee4766c291f3183c14554893f17265d80de9a42a Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 5 Jan 2019 21:50:40 +1100 Subject: [PATCH 17/34] Fix error during merge Signed-off-by: Dave Cheney --- stack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stack.go b/stack.go index 47938074..e5d6847e 100644 --- a/stack.go +++ b/stack.go @@ -49,7 +49,7 @@ func (f Frame) format(w io.Writer, s fmt.State, verb rune) { if file == "" { file = "unknown" } - io.WriteString(s, path.Base(file)) + io.WriteString(w, path.Base(file)) } case 'd': io.WriteString(w, strconv.Itoa(runtime.Frame(f).Line)) From c9e70be2405e428f24bbc455d40d6b34543d5771 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sat, 5 Jan 2019 22:04:53 +1100 Subject: [PATCH 18/34] Makefile: switch to staticcheck (#187) Remove deprecated linters that have been rolled into staticcheck. Signed-off-by: Dave Cheney --- Makefile | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 46ecb350..ce9d7cde 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PKGS := github.com/pkg/errors SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) GO := go -check: test vet gofmt unused misspell unconvert gosimple ineffassign +check: test vet gofmt misspell unconvert staticcheck ineffassign unparam test: $(GO) test $(PKGS) @@ -12,11 +12,7 @@ vet: | test staticcheck: $(GO) get honnef.co/go/tools/cmd/staticcheck - staticcheck $(PKGS) - -unused: - $(GO) get honnef.co/go/tools/cmd/unused - unused -exported $(PKGS) + staticcheck -checks all $(PKGS) misspell: $(GO) get github.com/client9/misspell/cmd/misspell @@ -29,15 +25,11 @@ unconvert: $(GO) get github.com/mdempsky/unconvert unconvert -v $(PKGS) -gosimple: - $(GO) get honnef.co/go/tools/cmd/gosimple - gosimple $(PKGS) - ineffassign: $(GO) get github.com/gordonklaus/ineffassign find $(SRCDIRS) -name '*.go' | xargs ineffassign -pedantic: check unparam errcheck staticcheck +pedantic: check errcheck unparam: $(GO) get mvdan.cc/unparam From c38ea53d8c3f78ac85b9b0f1247abeb6d38f6988 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Sun, 6 Jan 2019 12:53:32 +1100 Subject: [PATCH 19/34] Remove errors.Frame to runtime.Frame conversions (#189) Avoid the unnecessary conversions from errors.Frame to runtime.Frame to access the latter's fields as by definition the former also has the same fields. Signed-off-by: Dave Cheney --- stack.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/stack.go b/stack.go index e5d6847e..3b582c25 100644 --- a/stack.go +++ b/stack.go @@ -35,26 +35,25 @@ func (f Frame) format(w io.Writer, s fmt.State, verb rune) { case 's': switch { case s.Flag('+'): - fn := runtime.Frame(f).Func + fn := f.Func if fn == nil { io.WriteString(w, "unknown") } else { - file := runtime.Frame(f).File io.WriteString(w, fn.Name()) io.WriteString(w, "\n\t") - io.WriteString(w, file) + io.WriteString(w, f.File) } default: - file := runtime.Frame(f).File + file := f.File if file == "" { file = "unknown" } io.WriteString(w, path.Base(file)) } case 'd': - io.WriteString(w, strconv.Itoa(runtime.Frame(f).Line)) + io.WriteString(w, strconv.Itoa(f.Line)) case 'n': - name := runtime.Frame(f).Function + name := f.Function io.WriteString(s, funcname(name)) case 'v': f.format(w, s, 's') From 72fa05efae23f148d216faa1a168ab60f9056779 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 8 Jan 2019 16:58:33 -0800 Subject: [PATCH 20/34] errors: detect unknown frames correctly (#192) With Go 1.12, we will now be doing mid-stack inlining. This exposes inlined frames to runtime.Callers and friends. The spec says that a runtime.Frame may have a nil Func field for inlined functions. Instead, use the Function field to detect whether we really have an empty Frame. --- format_test.go | 24 ++++++++++++++++++++++++ stack.go | 5 ++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/format_test.go b/format_test.go index c2eef5f0..fd35ca37 100644 --- a/format_test.go +++ b/format_test.go @@ -360,6 +360,30 @@ func TestFormatGeneric(t *testing.T) { } } +func wrappedNew(message string) error { // This function will be mid-stack inlined in go 1.12+ + return New(message) +} + +func TestFormatWrappedNew(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + wrappedNew("error"), + "%+v", + "error\n" + + "github.com/pkg/errors.wrappedNew\n" + + "\t.+/github.com/pkg/errors/format_test.go:364\n" + + "github.com/pkg/errors.TestFormatWrappedNew\n" + + "\t.+/github.com/pkg/errors/format_test.go:373", + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { got := fmt.Sprintf(format, arg) gotLines := strings.SplitN(got, "\n", -1) diff --git a/stack.go b/stack.go index 3b582c25..5be46277 100644 --- a/stack.go +++ b/stack.go @@ -35,11 +35,10 @@ func (f Frame) format(w io.Writer, s fmt.State, verb rune) { case 's': switch { case s.Flag('+'): - fn := f.Func - if fn == nil { + if f.Function == "" { io.WriteString(w, "unknown") } else { - io.WriteString(w, fn.Name()) + io.WriteString(w, f.Function) io.WriteString(w, "\n\t") io.WriteString(w, f.File) } From ee1923e96d846c3dff95f34b333f58cbc290d448 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Wed, 9 Jan 2019 15:30:26 +1100 Subject: [PATCH 21/34] Return errors.Frame to a uintptr Updates aws/aws-xray-sdk-go#77 Updates evalphobia/logrus_sentry#74 Go 1.12 has updated the behaviour of runtime.FuncForPC so that it behaves as it did in Go 1.11 and earlier. This allows errors.Frame to return to a uintptr representing the PC +1 of the caller. This will fix the build breakages of projects that were tracking HEAD of this package. Signed-off-by: Dave Cheney --- format_test.go | 1 + stack.go | 119 ++++++++++++++++++++++--------------------------- stack_test.go | 12 ++--- 3 files changed, 61 insertions(+), 71 deletions(-) diff --git a/format_test.go b/format_test.go index fd35ca37..cb1df821 100644 --- a/format_test.go +++ b/format_test.go @@ -385,6 +385,7 @@ func TestFormatWrappedNew(t *testing.T) { } func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { + t.Helper() got := fmt.Sprintf(format, arg) gotLines := strings.SplitN(got, "\n", -1) wantLines := strings.SplitN(want, "\n", -1) diff --git a/stack.go b/stack.go index 5be46277..c370d875 100644 --- a/stack.go +++ b/stack.go @@ -1,7 +1,6 @@ package errors import ( - "bytes" "fmt" "io" "path" @@ -11,7 +10,42 @@ import ( ) // Frame represents a program counter inside a stack frame. -type Frame runtime.Frame +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 +} + +// name returns the name of this function, if known. +func (f Frame) name() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + return fn.Name() +} // Format formats the frame according to the fmt.Formatter interface. // @@ -35,25 +69,16 @@ func (f Frame) format(w io.Writer, s fmt.State, verb rune) { case 's': switch { case s.Flag('+'): - if f.Function == "" { - io.WriteString(w, "unknown") - } else { - io.WriteString(w, f.Function) - io.WriteString(w, "\n\t") - io.WriteString(w, f.File) - } + io.WriteString(w, f.name()) + io.WriteString(w, "\n\t") + io.WriteString(w, f.file()) default: - file := f.File - if file == "" { - file = "unknown" - } - io.WriteString(w, path.Base(file)) + io.WriteString(w, path.Base(f.file())) } case 'd': - io.WriteString(w, strconv.Itoa(f.Line)) + io.WriteString(w, strconv.Itoa(f.line())) case 'n': - name := f.Function - io.WriteString(s, funcname(name)) + io.WriteString(w, funcname(f.name())) case 'v': f.format(w, s, 's') io.WriteString(w, ":") @@ -73,50 +98,23 @@ type StackTrace []Frame // // %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { - var b bytes.Buffer switch verb { case 'v': switch { case s.Flag('+'): - b.Grow(len(st) * stackMinLen) - for _, fr := range st { - b.WriteByte('\n') - fr.format(&b, s, verb) + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) } case s.Flag('#'): - fmt.Fprintf(&b, "%#v", []Frame(st)) + fmt.Fprintf(s, "%#v", []Frame(st)) default: - st.formatSlice(&b, s, verb) + fmt.Fprintf(s, "%v", []Frame(st)) } case 's': - st.formatSlice(&b, s, verb) + fmt.Fprintf(s, "%s", []Frame(st)) } - io.Copy(s, &b) } -// formatSlice will format this StackTrace into the given buffer as a slice of -// Frame, only valid when called with '%s' or '%v'. -func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) { - b.WriteByte('[') - if len(st) == 0 { - b.WriteByte(']') - return - } - - b.Grow(len(st) * (stackMinLen / 4)) - st[0].format(b, s, verb) - for _, fr := range st[1:] { - b.WriteByte(' ') - fr.format(b, s, verb) - } - b.WriteByte(']') -} - -// stackMinLen is a best-guess at the minimum length of a stack trace. It -// doesn't need to be exact, just give a good enough head start for the buffer -// to avoid the expensive early growth. -const stackMinLen = 96 - // stack represents a stack of program counters. type stack []uintptr @@ -125,29 +123,20 @@ func (s *stack) Format(st fmt.State, verb rune) { case 'v': switch { case st.Flag('+'): - frames := runtime.CallersFrames(*s) - for { - frame, more := frames.Next() - fmt.Fprintf(st, "\n%+v", Frame(frame)) - if !more { - break - } + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) } } } } func (s *stack) StackTrace() StackTrace { - var st []Frame - frames := runtime.CallersFrames(*s) - for { - frame, more := frames.Next() - st = append(st, Frame(frame)) - if !more { - break - } + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) } - return st + return f } func callers() *stack { diff --git a/stack_test.go b/stack_test.go index 7f766943..172a57d5 100644 --- a/stack_test.go +++ b/stack_test.go @@ -35,11 +35,11 @@ func TestFrameFormat(t *testing.T) { "github.com/pkg/errors.init\n" + "\t.+/github.com/pkg/errors/stack_test.go", }, { - Frame{}, + 0, "%s", "unknown", }, { - Frame{}, + 0, "%+s", "unknown", }, { @@ -47,7 +47,7 @@ func TestFrameFormat(t *testing.T) { "%d", "9", }, { - Frame{}, + 0, "%d", "0", }, { @@ -69,7 +69,7 @@ func TestFrameFormat(t *testing.T) { "%n", "X.val", }, { - Frame{}, + 0, "%n", "", }, { @@ -82,7 +82,7 @@ func TestFrameFormat(t *testing.T) { "github.com/pkg/errors.init\n" + "\t.+/github.com/pkg/errors/stack_test.go:9", }, { - Frame{}, + 0, "%v", "unknown:0", }} @@ -246,7 +246,7 @@ func caller() Frame { n := runtime.Callers(2, pcs[:]) frames := runtime.CallersFrames(pcs[:n]) frame, _ := frames.Next() - return Frame(frame) + return Frame(frame.PC) } //go:noinline From e9933c1c09fbbc45a9af4788f95d672c4e90054d Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Wed, 9 Jan 2019 15:37:53 +1100 Subject: [PATCH 22/34] Restore performance improvements from #150 --- stack.go | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/stack.go b/stack.go index c370d875..ddc8c300 100644 --- a/stack.go +++ b/stack.go @@ -1,6 +1,7 @@ package errors import ( + "bytes" "fmt" "io" "path" @@ -98,23 +99,50 @@ type StackTrace []Frame // // %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { + var b bytes.Buffer switch verb { case 'v': switch { case s.Flag('+'): + b.Grow(len(st) * stackMinLen) for _, f := range st { - fmt.Fprintf(s, "\n%+v", f) + b.WriteByte('\n') + f.format(&b, s, verb) } case s.Flag('#'): - fmt.Fprintf(s, "%#v", []Frame(st)) + fmt.Fprintf(&b, "%#v", []Frame(st)) default: - fmt.Fprintf(s, "%v", []Frame(st)) + st.formatSlice(&b, s, verb) } case 's': - fmt.Fprintf(s, "%s", []Frame(st)) + st.formatSlice(&b, s, verb) } + io.Copy(s, &b) } +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) { + b.WriteByte('[') + if len(st) == 0 { + b.WriteByte(']') + return + } + + b.Grow(len(st) * (stackMinLen / 4)) + st[0].format(b, s, verb) + for _, fr := range st[1:] { + b.WriteByte(' ') + fr.format(b, s, verb) + } + b.WriteByte(']') +} + +// stackMinLen is a best-guess at the minimum length of a stack trace. It +// doesn't need to be exact, just give a good enough head start for the buffer +// to avoid the expensive early growth. +const stackMinLen = 96 + // stack represents a stack of program counters. type stack []uintptr From ffb6e22f01932bf7ac35e0bad9be11f01d1c8685 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Wed, 9 Jan 2019 17:16:28 +1100 Subject: [PATCH 23/34] Reduce allocations in StackTrace.Format (#194) Updates #150 Signed-off-by: Dave Cheney --- stack.go | 65 +++++++++++++++++++-------------------------------- stack_test.go | 6 +---- 2 files changed, 25 insertions(+), 46 deletions(-) diff --git a/stack.go b/stack.go index ddc8c300..81f88564 100644 --- a/stack.go +++ b/stack.go @@ -1,7 +1,6 @@ package errors import ( - "bytes" "fmt" "io" "path" @@ -11,6 +10,8 @@ import ( ) // Frame represents a program counter inside a stack frame. +// For historical reasons if Frame is interpreted as a uintptr +// its value represents the program counter + 1. type Frame uintptr // pc returns the program counter for this frame; @@ -61,29 +62,24 @@ func (f Frame) name() string { // GOPATH separated by \n\t (\n\t) // %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { - f.format(s, s, verb) -} - -// format allows stack trace printing calls to be made with a bytes.Buffer. -func (f Frame) format(w io.Writer, s fmt.State, verb rune) { switch verb { case 's': switch { case s.Flag('+'): - io.WriteString(w, f.name()) - io.WriteString(w, "\n\t") - io.WriteString(w, f.file()) + io.WriteString(s, f.name()) + io.WriteString(s, "\n\t") + io.WriteString(s, f.file()) default: - io.WriteString(w, path.Base(f.file())) + io.WriteString(s, path.Base(f.file())) } case 'd': - io.WriteString(w, strconv.Itoa(f.line())) + io.WriteString(s, strconv.Itoa(f.line())) case 'n': - io.WriteString(w, funcname(f.name())) + io.WriteString(s, funcname(f.name())) case 'v': - f.format(w, s, 's') - io.WriteString(w, ":") - f.format(w, s, 'd') + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') } } @@ -99,50 +95,37 @@ type StackTrace []Frame // // %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { - var b bytes.Buffer switch verb { case 'v': switch { case s.Flag('+'): - b.Grow(len(st) * stackMinLen) for _, f := range st { - b.WriteByte('\n') - f.format(&b, s, verb) + io.WriteString(s, "\n") + f.Format(s, verb) } case s.Flag('#'): - fmt.Fprintf(&b, "%#v", []Frame(st)) + fmt.Fprintf(s, "%#v", []Frame(st)) default: - st.formatSlice(&b, s, verb) + st.formatSlice(s, verb) } case 's': - st.formatSlice(&b, s, verb) + st.formatSlice(s, verb) } - io.Copy(s, &b) } // formatSlice will format this StackTrace into the given buffer as a slice of // Frame, only valid when called with '%s' or '%v'. -func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) { - b.WriteByte('[') - if len(st) == 0 { - b.WriteByte(']') - return - } - - b.Grow(len(st) * (stackMinLen / 4)) - st[0].format(b, s, verb) - for _, fr := range st[1:] { - b.WriteByte(' ') - fr.format(b, s, verb) +func (st StackTrace) formatSlice(s fmt.State, verb rune) { + io.WriteString(s, "[") + for i, f := range st { + if i > 0 { + io.WriteString(s, " ") + } + f.Format(s, verb) } - b.WriteByte(']') + io.WriteString(s, "]") } -// stackMinLen is a best-guess at the minimum length of a stack trace. It -// doesn't need to be exact, just give a good enough head start for the buffer -// to avoid the expensive early growth. -const stackMinLen = 96 - // stack represents a stack of program counters. type stack []uintptr diff --git a/stack_test.go b/stack_test.go index 172a57d5..1acd719b 100644 --- a/stack_test.go +++ b/stack_test.go @@ -133,7 +133,7 @@ func TestStackTrace(t *testing.T) { "\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New }, }, { - func() error { noinline(); return New("ooh") }(), []string{ + func() error { return New("ooh") }(), []string{ `github.com/pkg/errors.TestStackTrace.func1` + "\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New "github.com/pkg/errors.TestStackTrace\n" + @@ -248,7 +248,3 @@ func caller() Frame { frame, _ := frames.Next() return Frame(frame.PC) } - -//go:noinline -// noinline prevents the caller being inlined -func noinline() {} From 856c240a51a2bf8fb8269ea7f3f9b046aadde36e Mon Sep 17 00:00:00 2001 From: Jonathan Hall Date: Sun, 17 Feb 2019 23:52:12 +0100 Subject: [PATCH 24/34] Add json.Marshaler support to the Frame type. (#197) * Add json.Marshaler support to the Frame type. * Update regex for Go tip * Escape periods in regular expression tests * Implement encoding.TextMarshaler instead of json.Marshaler --- json_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ stack.go | 10 ++++++++++ 2 files changed, 61 insertions(+) create mode 100644 json_test.go diff --git a/json_test.go b/json_test.go new file mode 100644 index 00000000..ad1adec9 --- /dev/null +++ b/json_test.go @@ -0,0 +1,51 @@ +package errors + +import ( + "encoding/json" + "regexp" + "testing" +) + +func TestFrameMarshalText(t *testing.T) { + var tests = []struct { + Frame + want string + }{{ + initpc, + `^github.com/pkg/errors\.init(\.ializers)? .+/github\.com/pkg/errors/stack_test.go:\d+$`, + }, { + 0, + `^unknown$`, + }} + for i, tt := range tests { + got, err := tt.Frame.MarshalText() + if err != nil { + t.Fatal(err) + } + if !regexp.MustCompile(tt.want).Match(got) { + t.Errorf("test %d: MarshalJSON:\n got %q\n want %q", i+1, string(got), tt.want) + } + } +} + +func TestFrameMarshalJSON(t *testing.T) { + var tests = []struct { + Frame + want string + }{{ + initpc, + `^"github\.com/pkg/errors\.init(\.ializers)? .+/github\.com/pkg/errors/stack_test.go:\d+"$`, + }, { + 0, + `^"unknown"$`, + }} + for i, tt := range tests { + got, err := json.Marshal(tt.Frame) + if err != nil { + t.Fatal(err) + } + if !regexp.MustCompile(tt.want).Match(got) { + t.Errorf("test %d: MarshalJSON:\n got %q\n want %q", i+1, string(got), tt.want) + } + } +} diff --git a/stack.go b/stack.go index 81f88564..779a8348 100644 --- a/stack.go +++ b/stack.go @@ -83,6 +83,16 @@ func (f Frame) Format(s fmt.State, verb rune) { } } +// MarshalText formats a stacktrace Frame as a text string. The output is the +// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs. +func (f Frame) MarshalText() ([]byte, error) { + name := f.name() + if name == "unknown" { + return []byte(name), nil + } + return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil +} + // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). type StackTrace []Frame From 27936f6d90f9c8e1145f11ed52ffffbfdb9e0af7 Mon Sep 17 00:00:00 2001 From: Dave Cheney Date: Wed, 27 Feb 2019 11:00:51 +1100 Subject: [PATCH 25/34] travis.yml: add Go 1.12 (#200) Also deprecate Go 1.9 Signed-off-by: Dave Cheney --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0ac2b7ad..d2dfad40 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: go go_import_path: github.com/pkg/errors go: - - 1.9.x - 1.10.x - 1.11.x + - 1.12.x - tip script: From ca0248e19befb8f46baae51a5a9951dd47740db5 Mon Sep 17 00:00:00 2001 From: aperezg Date: Fri, 1 Nov 2019 17:49:06 +0100 Subject: [PATCH 26/34] fix travis, 1.10 doesnt support by unconvert anymore --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d2dfad40..eb23af5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,13 @@ language: go go_import_path: github.com/pkg/errors go: - - 1.10.x - 1.11.x - 1.12.x + - 1.13.x - tip +env: + - GO111MODULE=off + script: - make check From 91f169312d9bf0c7709dc198651b9505ba85a239 Mon Sep 17 00:00:00 2001 From: aperezg Date: Sat, 9 Nov 2019 09:18:04 +0100 Subject: [PATCH 27/34] travis.yml: add Go 1.13 --- .travis.yml | 3 --- README.md | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index eb23af5a..9159de03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,5 @@ go: - 1.13.x - tip -env: - - GO111MODULE=off - script: - make check diff --git a/README.md b/README.md index cf771e7d..54dfdcb1 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ default: With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: -- 0.9. Remove pre Go 1.9 support, address outstanding pull requests (if possible) +- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible) - 1.0. Final release. ## Contributing From 7f95ac13edff643b8ce5398b6ccab125f8a20c1a Mon Sep 17 00:00:00 2001 From: Jay Petacat Date: Sat, 9 Nov 2019 05:23:16 -0500 Subject: [PATCH 28/34] Add support for Go 1.13 error chains (#206) * Add support for Go 1.13 error chains Go 1.13 adds support for error chains to the standard libary's errors package. The new standard library functions require an Unwrap method to be provided by an error type. This change adds a new Unwrap method (identical to the existing Cause method) to the unexported error types. --- errors.go | 6 ++++++ go113_test.go | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 go113_test.go diff --git a/errors.go b/errors.go index 8617beef..161aea25 100644 --- a/errors.go +++ b/errors.go @@ -159,6 +159,9 @@ type withStack struct { func (w *withStack) Cause() error { return w.error } +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withStack) Unwrap() error { return w.error } + func (w *withStack) Format(s fmt.State, verb rune) { switch verb { case 'v': @@ -241,6 +244,9 @@ type withMessage struct { func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } func (w *withMessage) Cause() error { return w.cause } +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withMessage) Unwrap() error { return w.cause } + func (w *withMessage) Format(s fmt.State, verb rune) { switch verb { case 'v': diff --git a/go113_test.go b/go113_test.go new file mode 100644 index 00000000..39263b03 --- /dev/null +++ b/go113_test.go @@ -0,0 +1,16 @@ +// +build go1.13 + +package errors + +import ( + stdlib_errors "errors" + "testing" +) + +func TestErrorChainCompat(t *testing.T) { + err := stdlib_errors.New("error that gets wrapped") + wrapped := Wrap(err, "wrapped up") + if !stdlib_errors.Is(wrapped, err) { + t.Errorf("Wrap does not support Go 1.13 error chains") + } +} From 6d954f502eb89cd315e4baae5b0e0db516d6f787 Mon Sep 17 00:00:00 2001 From: Sherlock Holo Date: Fri, 3 Jan 2020 19:25:31 +0800 Subject: [PATCH 29/34] feat: support std errors functions (#213) * feat: support std errors functions add function `Is`, `As` and `Unwrap`, like std errors, so that we can continue to use pkg/errors with go1.13 compatibility Signed-off-by: Sherlock Holo * style: delete useless comments Signed-off-by: Sherlock Holo * build: update makefile update makefile to download dependencies before test anything Signed-off-by: Sherlock Holo * build: fix makefile Signed-off-by: Sherlock Holo * chore: delete useless comments Signed-off-by: Sherlock Holo * Restore Makefile * revert: revert some change some change are doing by PR #206 and #212 , so I don't need to do it Signed-off-by: Sherlock Holo * test: add more check for As unit test Signed-off-by: Sherlock Holo * revert: only support Is As Unwrap for >=go1.13 Signed-off-by: Sherlock Holo * feat(Unwrap): allow * test: add go1.13 errors compatibility check Signed-off-by: Sherlock Holo * refactor(Unwrap): don't allow --- go113.go | 38 ++++++++++++ go113_test.go | 168 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 go113.go diff --git a/go113.go b/go113.go new file mode 100644 index 00000000..be0d10d0 --- /dev/null +++ b/go113.go @@ -0,0 +1,38 @@ +// +build go1.13 + +package errors + +import ( + stderrors "errors" +) + +// Is reports whether any error in err's chain matches target. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +func Is(err, target error) bool { return stderrors.Is(err, target) } + +// As finds the first error in err's chain that matches target, and if so, sets +// target to that error value and returns true. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error matches target if the error's concrete value is assignable to the value +// pointed to by target, or if the error has a method As(interface{}) bool such that +// As(target) returns true. In the latter case, the As method is responsible for +// setting target. +// +// As will panic if target is not a non-nil pointer to either a type that implements +// error, or to any interface type. As returns false if err is nil. +func As(err error, target interface{}) bool { return stderrors.As(err, target) } + +// 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. +func Unwrap(err error) error { + return stderrors.Unwrap(err) +} diff --git a/go113_test.go b/go113_test.go index 39263b03..4ea37e61 100644 --- a/go113_test.go +++ b/go113_test.go @@ -3,14 +3,176 @@ package errors import ( - stdlib_errors "errors" + stderrors "errors" + "fmt" + "reflect" "testing" ) func TestErrorChainCompat(t *testing.T) { - err := stdlib_errors.New("error that gets wrapped") + err := stderrors.New("error that gets wrapped") wrapped := Wrap(err, "wrapped up") - if !stdlib_errors.Is(wrapped, err) { + if !stderrors.Is(wrapped, err) { t.Errorf("Wrap does not support Go 1.13 error chains") } } + +func TestIs(t *testing.T) { + err := New("test") + + type args struct { + err error + target error + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "with stack", + args: args{ + err: WithStack(err), + target: err, + }, + want: true, + }, + { + name: "with message", + args: args{ + err: WithMessage(err, "test"), + target: err, + }, + want: true, + }, + { + name: "with message format", + args: args{ + err: WithMessagef(err, "%s", "test"), + target: err, + }, + want: true, + }, + { + name: "std errors compatibility", + args: args{ + err: fmt.Errorf("wrap it: %w", err), + target: err, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Is(tt.args.err, tt.args.target); got != tt.want { + t.Errorf("Is() = %v, want %v", got, tt.want) + } + }) + } +} + +type customErr struct { + msg string +} + +func (c customErr) Error() string { return c.msg } + +func TestAs(t *testing.T) { + var err = customErr{msg: "test message"} + + type args struct { + err error + target interface{} + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "with stack", + args: args{ + err: WithStack(err), + target: new(customErr), + }, + want: true, + }, + { + name: "with message", + args: args{ + err: WithMessage(err, "test"), + target: new(customErr), + }, + want: true, + }, + { + name: "with message format", + args: args{ + err: WithMessagef(err, "%s", "test"), + target: new(customErr), + }, + want: true, + }, + { + name: "std errors compatibility", + args: args{ + err: fmt.Errorf("wrap it: %w", err), + target: new(customErr), + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := As(tt.args.err, tt.args.target); got != tt.want { + t.Errorf("As() = %v, want %v", got, tt.want) + } + + ce := tt.args.target.(*customErr) + if !reflect.DeepEqual(err, *ce) { + t.Errorf("set target error failed, target error is %v", *ce) + } + }) + } +} + +func TestUnwrap(t *testing.T) { + err := New("test") + + type args struct { + err error + } + tests := []struct { + name string + args args + want error + }{ + { + name: "with stack", + args: args{err: WithStack(err)}, + want: err, + }, + { + name: "with message", + args: args{err: WithMessage(err, "test")}, + want: err, + }, + { + name: "with message format", + args: args{err: WithMessagef(err, "%s", "test")}, + want: err, + }, + { + name: "std errors compatibility", + args: args{err: fmt.Errorf("wrap: %w", err)}, + want: err, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Unwrap(tt.args.err); !reflect.DeepEqual(err, tt.want) { + t.Errorf("Unwrap() error = %v, want %v", err, tt.want) + } + }) + } +} From 004deef56200d8bd57ebfd6f8734c08fbd003f6d Mon Sep 17 00:00:00 2001 From: Adrian Perez Date: Fri, 3 Jan 2020 13:36:54 +0100 Subject: [PATCH 30/34] remove unnecessary use of fmt.Sprintf (#217) * remove unnecessary use of fmt.Sprintf --- example_test.go | 2 +- stack_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example_test.go b/example_test.go index c1fc13e3..7d0e286f 100644 --- a/example_test.go +++ b/example_test.go @@ -195,7 +195,7 @@ func Example_stackTrace() { func ExampleCause_printf() { err := errors.Wrap(func() error { return func() error { - return errors.Errorf("hello %s", fmt.Sprintf("world")) + return errors.New("hello world") }() }(), "failed") diff --git a/stack_test.go b/stack_test.go index 1acd719b..aa10a72e 100644 --- a/stack_test.go +++ b/stack_test.go @@ -142,7 +142,7 @@ func TestStackTrace(t *testing.T) { }, { Cause(func() error { return func() error { - return Errorf("hello %s", fmt.Sprintf("world")) + return Errorf("hello %s", fmt.Sprintf("world: %s", "ooh")) }() }()), []string{ `github.com/pkg/errors.TestStackTrace.func2.1` + From 49f8f617296114c890ae0b7ac18c5953d2b1ca0f Mon Sep 17 00:00:00 2001 From: Jay Petacat Date: Tue, 7 Jan 2020 16:33:24 -0500 Subject: [PATCH 31/34] Support Go 1.13 error chains in `Cause` (#215) --- cause.go | 29 +++++++++++++++++++++++++++++ errors.go | 26 -------------------------- go113.go | 33 +++++++++++++++++++++++++++++++++ go113_test.go | 24 +++++++++++++++++++++++- 4 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 cause.go diff --git a/cause.go b/cause.go new file mode 100644 index 00000000..566f88bb --- /dev/null +++ b/cause.go @@ -0,0 +1,29 @@ +// +build !go1.13 + +package errors + +// Cause recursively unwraps an error chain and returns the underlying cause of +// the error, if possible. An error value has a cause if it implements the +// following interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/errors.go b/errors.go index 161aea25..a9840ece 100644 --- a/errors.go +++ b/errors.go @@ -260,29 +260,3 @@ func (w *withMessage) Format(s fmt.State, verb rune) { io.WriteString(s, w.Error()) } } - -// Cause returns the underlying cause of the error, if possible. -// An error value has a cause if it implements the following -// interface: -// -// type causer interface { -// Cause() error -// } -// -// If the error does not implement Cause, the original error will -// be returned. If the error is nil, nil will be returned without further -// investigation. -func Cause(err error) error { - type causer interface { - Cause() error - } - - for err != nil { - cause, ok := err.(causer) - if !ok { - break - } - err = cause.Cause() - } - return err -} diff --git a/go113.go b/go113.go index be0d10d0..ed0dc7a6 100644 --- a/go113.go +++ b/go113.go @@ -36,3 +36,36 @@ func As(err error, target interface{}) bool { return stderrors.As(err, target) } func Unwrap(err error) error { return stderrors.Unwrap(err) } + +// Cause recursively unwraps an error chain and returns the underlying cause of +// the error, if possible. There are two ways that an error value may provide a +// cause. First, the error may implement the following interface: +// +// type causer interface { +// Cause() error +// } +// +// Second, the error may return a non-nil value when passed as an argument to +// the Unwrap function. This makes Cause forwards-compatible with Go 1.13 error +// chains. +// +// If an error value satisfies both methods of unwrapping, Cause will use the +// causer interface. +// +// If the error is nil, nil will be returned without further investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + if cause, ok := err.(causer); ok { + err = cause.Cause() + } else if unwrapped := Unwrap(err); unwrapped != nil { + err = unwrapped + } else { + break + } + } + return err +} diff --git a/go113_test.go b/go113_test.go index 4ea37e61..7da37883 100644 --- a/go113_test.go +++ b/go113_test.go @@ -9,7 +9,29 @@ import ( "testing" ) -func TestErrorChainCompat(t *testing.T) { +func TestCauseErrorChainCompat(t *testing.T) { + err := stderrors.New("the cause!") + + // Wrap error using the standard library + wrapped := fmt.Errorf("wrapped with stdlib: %w", err) + if Cause(wrapped) != err { + t.Errorf("Cause does not support Go 1.13 error chains") + } + + // Wrap in another layer using pkg/errors + wrapped = WithMessage(wrapped, "wrapped with pkg/errors") + if Cause(wrapped) != err { + t.Errorf("Cause does not support Go 1.13 error chains") + } + + // Wrap in another layer using the standard library + wrapped = fmt.Errorf("wrapped with stdlib: %w", wrapped) + if Cause(wrapped) != err { + t.Errorf("Cause does not support Go 1.13 error chains") + } +} + +func TestWrapErrorChainCompat(t *testing.T) { err := stderrors.New("error that gets wrapped") wrapped := Wrap(err, "wrapped up") if !stderrors.Is(wrapped, err) { From 614d223910a179a466c1767a985424175c39b465 Mon Sep 17 00:00:00 2001 From: Adrian Perez Date: Tue, 14 Jan 2020 20:47:44 +0100 Subject: [PATCH 32/34] Revert "Support Go 1.13 error chains in `Cause` (#215)" (#220) This reverts commit 49f8f617296114c890ae0b7ac18c5953d2b1ca0f. --- cause.go | 29 ----------------------------- errors.go | 26 ++++++++++++++++++++++++++ go113.go | 33 --------------------------------- go113_test.go | 24 +----------------------- 4 files changed, 27 insertions(+), 85 deletions(-) delete mode 100644 cause.go diff --git a/cause.go b/cause.go deleted file mode 100644 index 566f88bb..00000000 --- a/cause.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build !go1.13 - -package errors - -// Cause recursively unwraps an error chain and returns the underlying cause of -// the error, if possible. An error value has a cause if it implements the -// following interface: -// -// type causer interface { -// Cause() error -// } -// -// If the error does not implement Cause, the original error will -// be returned. If the error is nil, nil will be returned without further -// investigation. -func Cause(err error) error { - type causer interface { - Cause() error - } - - for err != nil { - cause, ok := err.(causer) - if !ok { - break - } - err = cause.Cause() - } - return err -} diff --git a/errors.go b/errors.go index a9840ece..161aea25 100644 --- a/errors.go +++ b/errors.go @@ -260,3 +260,29 @@ func (w *withMessage) Format(s fmt.State, verb rune) { io.WriteString(s, w.Error()) } } + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/go113.go b/go113.go index ed0dc7a6..be0d10d0 100644 --- a/go113.go +++ b/go113.go @@ -36,36 +36,3 @@ func As(err error, target interface{}) bool { return stderrors.As(err, target) } func Unwrap(err error) error { return stderrors.Unwrap(err) } - -// Cause recursively unwraps an error chain and returns the underlying cause of -// the error, if possible. There are two ways that an error value may provide a -// cause. First, the error may implement the following interface: -// -// type causer interface { -// Cause() error -// } -// -// Second, the error may return a non-nil value when passed as an argument to -// the Unwrap function. This makes Cause forwards-compatible with Go 1.13 error -// chains. -// -// If an error value satisfies both methods of unwrapping, Cause will use the -// causer interface. -// -// If the error is nil, nil will be returned without further investigation. -func Cause(err error) error { - type causer interface { - Cause() error - } - - for err != nil { - if cause, ok := err.(causer); ok { - err = cause.Cause() - } else if unwrapped := Unwrap(err); unwrapped != nil { - err = unwrapped - } else { - break - } - } - return err -} diff --git a/go113_test.go b/go113_test.go index 7da37883..4ea37e61 100644 --- a/go113_test.go +++ b/go113_test.go @@ -9,29 +9,7 @@ import ( "testing" ) -func TestCauseErrorChainCompat(t *testing.T) { - err := stderrors.New("the cause!") - - // Wrap error using the standard library - wrapped := fmt.Errorf("wrapped with stdlib: %w", err) - if Cause(wrapped) != err { - t.Errorf("Cause does not support Go 1.13 error chains") - } - - // Wrap in another layer using pkg/errors - wrapped = WithMessage(wrapped, "wrapped with pkg/errors") - if Cause(wrapped) != err { - t.Errorf("Cause does not support Go 1.13 error chains") - } - - // Wrap in another layer using the standard library - wrapped = fmt.Errorf("wrapped with stdlib: %w", wrapped) - if Cause(wrapped) != err { - t.Errorf("Cause does not support Go 1.13 error chains") - } -} - -func TestWrapErrorChainCompat(t *testing.T) { +func TestErrorChainCompat(t *testing.T) { err := stderrors.New("error that gets wrapped") wrapped := Wrap(err, "wrapped up") if !stderrors.Is(wrapped, err) { From 5dd12d0cfe7f152f80558d591504ce685299311e Mon Sep 17 00:00:00 2001 From: santosh653 <70637961+santosh653@users.noreply.github.com> Date: Mon, 14 Dec 2020 01:45:52 -0500 Subject: [PATCH 33/34] AddingPowerSupport_CI/Testing (#234) * Update .travis.yml Adding Power & Updating the go versions to 1.13/1.14/1.15 as lower versions are not supported. * Update .travis.yml Removing go 1.13 as desired in the comment section, * Update .travis.yml As desired taking out the power(ppc64le) related support., --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9159de03..81e650b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ +arch: + - amd64 + language: go go_import_path: github.com/pkg/errors go: - - 1.11.x - - 1.12.x - - 1.13.x + - 1.14 + - 1.15 - tip script: From 87f8819acf6dc28bf5d3c14b334268236d686f48 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Fri, 27 Mar 2026 08:10:00 -0700 Subject: [PATCH 34/34] Dummy workflow to enable GitHub Actions Signed-off-by: Tonis Tiigi --- .github/workflows/ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..f6fc4468 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,14 @@ +name: ci + +on: + workflow_dispatch: + push: + pull_request: + +jobs: + dummy: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 \ No newline at end of file