|
package errors |
|
|
|
import ( |
|
"fmt" |
|
"reflect" |
|
"runtime" |
|
"unsafe" |
|
|
|
"github.com/pkg/errors" |
|
) |
|
|
|
// Export a number of functions or variables from pkg/errors. We want people to be able to |
|
// use them, if only via the entrypoints we've vetted in this file. |
|
var ( |
|
As = errors.As |
|
Is = errors.Is |
|
Cause = errors.Cause |
|
Unwrap = errors.Unwrap |
|
) |
|
|
|
// StackTrace should be aliases rather than newtype'd, so it can work with any of the |
|
// functions we export from pkg/errors. |
|
type StackTrace = errors.StackTrace |
|
|
|
type StackTracer interface { |
|
StackTrace() errors.StackTrace |
|
} |
|
|
|
// Sentinel is used to create compile-time errors that are intended to be value only, with |
|
// no associated stack trace. |
|
func Sentinel(msg string, args ...interface{}) error { |
|
return fmt.Errorf(msg, args...) |
|
} |
|
|
|
// New acts as pkg/errors.New does, producing a stack traced error, but supports |
|
// interpolating of message parameters. Use this when you want the stack trace to start at |
|
// the place you create the error. |
|
func New(msg string, args ...interface{}) error { |
|
return PopStack(errors.New(fmt.Sprintf(msg, args...))) |
|
} |
|
|
|
// Wrap creates a new error from a cause, decorating the original error message with a |
|
// prefix. |
|
// |
|
// It differs from the pkg/errors Wrap/Wrapf by idempotently creating a stack trace, |
|
// meaning we won't create another stack trace when there is already a stack trace present |
|
// that matches our current program position. |
|
func Wrap(cause error, msg string, args ...interface{}) error { |
|
causeStackTracer := new(StackTracer) |
|
if errors.As(cause, causeStackTracer) { |
|
// If our cause has set a stack trace, and that trace is a child of our own function |
|
// as inferred by prefix matching our current program counter stack, then we only want |
|
// to decorate the error message rather than add a redundant stack trace. |
|
if ancestorOfCause(callers(1), (*causeStackTracer).StackTrace()) { |
|
return errors.WithMessagef(cause, msg, args...) // no stack added, no pop required |
|
} |
|
} |
|
|
|
// Otherwise we can't see a stack trace that represents ourselves, so let's add one. |
|
return PopStack(errors.Wrapf(cause, msg, args...)) |
|
} |
|
|
|
// ancestorOfCause returns true if the caller looks to be an ancestor of the given stack |
|
// trace. We check this by seeing whether our stack prefix-matches the cause stack, which |
|
// should imply the error was generated directly from our goroutine. |
|
func ancestorOfCause(ourStack []uintptr, causeStack errors.StackTrace) bool { |
|
// Stack traces are ordered such that the deepest frame is first. We'll want to check |
|
// for prefix matching in reverse. |
|
// |
|
// As an example, imagine we have a prefix-matching stack for ourselves: |
|
// [ |
|
// "github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync", |
|
// "github.com/incident-io/core/server/pkg/errors_test.TestSuite", |
|
// "testing.tRunner", |
|
// "runtime.goexit" |
|
// ] |
|
// |
|
// We'll want to compare this against an error cause that will have happened further |
|
// down the stack. An example stack trace from such an error might be: |
|
// [ |
|
// "github.com/incident-io/core/server/pkg/errors.New", |
|
// "github.com/incident-io/core/server/pkg/errors_test.glob..func1.2.2.2.1",, |
|
// "github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync", |
|
// "github.com/incident-io/core/server/pkg/errors_test.TestSuite", |
|
// "testing.tRunner", |
|
// "runtime.goexit" |
|
// ] |
|
// |
|
// They prefix match, but we'll have to handle the match carefully as we need to match |
|
// from back to forward. |
|
|
|
// We can't possibly prefix match if our stack is larger than the cause stack. |
|
if len(ourStack) > len(causeStack) { |
|
return false |
|
} |
|
|
|
// We know the sizes are compatible, so compare program counters from back to front. |
|
for idx := 0; idx < len(ourStack); idx++ { |
|
if ourStack[len(ourStack)-1] != (uintptr)(causeStack[len(causeStack)-1]) { |
|
return false |
|
} |
|
} |
|
|
|
// All comparisons checked out, these stacks match |
|
return true |
|
} |
|
|
|
func callers(skip int) []uintptr { |
|
pc := make([]uintptr, 32) // assume we'll have at most 32 frames |
|
n := runtime.Callers(skip+3, pc) // capture those frames, skipping runtime.Callers, ourself and the calling function |
|
|
|
return pc[:n] // return everything that we captured |
|
} |
|
|
|
// RecoverPanic turns a panic into an error, adjusting the stacktrace so it originates at |
|
// the line that caused it. |
|
// |
|
// Example: |
|
// |
|
// func Do() (err error) { |
|
// defer func() { |
|
// errors.RecoverPanic(recover(), &err) |
|
// }() |
|
// } |
|
func RecoverPanic(r interface{}, errPtr *error) { |
|
var err error |
|
if r != nil { |
|
if panicErr, ok := r.(error); ok { |
|
err = errors.Wrap(panicErr, "caught panic") |
|
} else { |
|
err = errors.New(fmt.Sprintf("caught panic: %v", r)) |
|
} |
|
} |
|
|
|
if err != nil { |
|
// Pop twice: once for the errors package, then again for the defer function we must |
|
// run this under. We want the stacktrace to originate at the source of the panic, not |
|
// in the infrastructure that catches it. |
|
err = PopStack(err) // errors.go |
|
err = PopStack(err) // defer |
|
|
|
*errPtr = err |
|
} |
|
} |
|
|
|
// PopStack removes the top of the stack from an errors stack trace. |
|
func PopStack(err error) error { |
|
if err == nil { |
|
return err |
|
} |
|
|
|
// We want to remove us, the internal/errors.New function, from the error stack we just |
|
// produced. There's no official way of reaching into the error and adjusting this, as |
|
// the stack is stored as a private field on an unexported struct. |
|
// |
|
// This does some unsafe badness to adjust that field, which should not be repeated |
|
// anywhere else. |
|
stackField := reflect.ValueOf(err).Elem().FieldByName("stack") |
|
if stackField.IsZero() { |
|
return err |
|
} |
|
stackFieldPtr := (**[]uintptr)(unsafe.Pointer(stackField.UnsafeAddr())) |
|
|
|
// Remove the first of the frames, dropping 'us' from the error stack trace. |
|
frames := (**stackFieldPtr)[1:] |
|
|
|
// Assign to the internal stack field |
|
*stackFieldPtr = &frames |
|
|
|
return err |
|
} |