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 }