diff options
author | qrort <qrort@yandex-team.com> | 2022-11-30 23:47:12 +0300 |
---|---|---|
committer | qrort <qrort@yandex-team.com> | 2022-11-30 23:47:12 +0300 |
commit | 22f8ae0e3f5d68b92aecccdf96c1d841a0334311 (patch) | |
tree | bffa27765faf54126ad44bcafa89fadecb7a73d7 /library/go/core/xerrors | |
parent | 332b99e2173f0425444abb759eebcb2fafaa9209 (diff) | |
download | ydb-22f8ae0e3f5d68b92aecccdf96c1d841a0334311.tar.gz |
validate canons without yatest_common
Diffstat (limited to 'library/go/core/xerrors')
-rw-r--r-- | library/go/core/xerrors/doc.go | 2 | ||||
-rw-r--r-- | library/go/core/xerrors/errorf.go | 88 | ||||
-rw-r--r-- | library/go/core/xerrors/forward.go | 56 | ||||
-rw-r--r-- | library/go/core/xerrors/internal/modes/stack_frames_count.go | 22 | ||||
-rw-r--r-- | library/go/core/xerrors/internal/modes/stack_trace_mode.go | 48 | ||||
-rw-r--r-- | library/go/core/xerrors/mode.go | 93 | ||||
-rw-r--r-- | library/go/core/xerrors/new.go | 48 | ||||
-rw-r--r-- | library/go/core/xerrors/sentinel.go | 150 | ||||
-rw-r--r-- | library/go/core/xerrors/stacktrace.go | 80 |
9 files changed, 587 insertions, 0 deletions
diff --git a/library/go/core/xerrors/doc.go b/library/go/core/xerrors/doc.go new file mode 100644 index 0000000000..de06dd15d2 --- /dev/null +++ b/library/go/core/xerrors/doc.go @@ -0,0 +1,2 @@ +// package xerrors is a drop in replacement for errors and golang.org/x/xerrors packages and functionally for github.com/pkg/errors. +package xerrors diff --git a/library/go/core/xerrors/errorf.go b/library/go/core/xerrors/errorf.go new file mode 100644 index 0000000000..de9e5248bb --- /dev/null +++ b/library/go/core/xerrors/errorf.go @@ -0,0 +1,88 @@ +package xerrors + +import ( + "fmt" + "io" + "strings" + + "a.yandex-team.ru/library/go/x/xruntime" +) + +type wrappedErrorf struct { + err error + stacktrace *xruntime.StackTrace +} + +var _ ErrorStackTrace = &wrappedErrorf{} + +func Errorf(format string, a ...interface{}) error { + err := fmt.Errorf(format, a...) + return &wrappedErrorf{ + err: err, + stacktrace: newStackTrace(1, err), + } +} + +func SkipErrorf(skip int, format string, a ...interface{}) error { + err := fmt.Errorf(format, a...) + return &wrappedErrorf{ + err: err, + stacktrace: newStackTrace(skip+1, err), + } +} + +func (e *wrappedErrorf) Format(s fmt.State, v rune) { + switch v { + case 'v': + if s.Flag('+') { + msg := e.err.Error() + inner := Unwrap(e.err) + // If Errorf wrapped another error then it will be our message' suffix. If so, cut it since otherwise we will + // print it again as part of formatting that error. + if inner != nil { + if strings.HasSuffix(msg, inner.Error()) { + msg = msg[:len(msg)-len(inner.Error())] + // Cut last space if needed but only if there is stacktrace present (very likely) + if e.stacktrace != nil && strings.HasSuffix(msg, ": ") { + msg = msg[:len(msg)-1] + } + } + } + + _, _ = io.WriteString(s, msg) + if e.stacktrace != nil { + // New line is useful only when printing frames, otherwise it is better to print next error in the chain + // right after we print this one + _, _ = io.WriteString(s, "\n") + writeStackTrace(s, e.stacktrace) + } + + // Print next error down the chain if there is one + if inner != nil { + _, _ = fmt.Fprintf(s, "%+v", inner) + } + + return + } + fallthrough + case 's': + _, _ = io.WriteString(s, e.err.Error()) + case 'q': + _, _ = fmt.Fprintf(s, "%q", e.err.Error()) + } +} + +func (e *wrappedErrorf) Error() string { + // Wrapped error has correct formatting + return e.err.Error() +} + +func (e *wrappedErrorf) Unwrap() error { + // Skip wrapped error and return whatever it is wrapping + // TODO: test for correct unwrap + return Unwrap(e.err) +} + +func (e *wrappedErrorf) StackTrace() *xruntime.StackTrace { + return e.stacktrace +} diff --git a/library/go/core/xerrors/forward.go b/library/go/core/xerrors/forward.go new file mode 100644 index 0000000000..aaa900133c --- /dev/null +++ b/library/go/core/xerrors/forward.go @@ -0,0 +1,56 @@ +package xerrors + +import "errors" + +// 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 errors.Unwrap(err) +} + +// 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. +// +// An error type might provide an Is method so it can be treated as equivalent +// to an existing error. For example, if MyError defines +// +// func (m MyError) Is(target error) bool { return target == os.ErrExist } +// +// then Is(MyError{}, os.ErrExist) returns true. See syscall.Errno.Is for +// an example in the standard library. +func Is(err, target error) bool { + return errors.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. Otherwise, it returns false. +// +// 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. +// +// An error type might provide an As method so it can be treated as if it were a +// different error type. +// +// As panics if target is not a non-nil pointer to either a type that implements +// error, or to any interface type. +func As(err error, target interface{}) bool { + return errors.As(err, target) +} + +// Wrapper provides context around another error. +type Wrapper interface { + // Unwrap returns the next error in the error chain. + // If there is no next error, Unwrap returns nil. + Unwrap() error +} diff --git a/library/go/core/xerrors/internal/modes/stack_frames_count.go b/library/go/core/xerrors/internal/modes/stack_frames_count.go new file mode 100644 index 0000000000..c117becf6a --- /dev/null +++ b/library/go/core/xerrors/internal/modes/stack_frames_count.go @@ -0,0 +1,22 @@ +package modes + +import "sync/atomic" + +type StackFramesCount = int32 + +const ( + StackFramesCount16 StackFramesCount = 16 + StackFramesCount32 StackFramesCount = 32 + StackFramesCount64 StackFramesCount = 64 + StackFramesCount128 StackFramesCount = 128 +) + +var StackFramesCountMax = StackFramesCount32 + +func SetStackFramesCountMax(count StackFramesCount) { + atomic.StoreInt32(&StackFramesCountMax, count) +} + +func GetStackFramesCountMax() StackFramesCount { + return atomic.LoadInt32(&StackFramesCountMax) +} diff --git a/library/go/core/xerrors/internal/modes/stack_trace_mode.go b/library/go/core/xerrors/internal/modes/stack_trace_mode.go new file mode 100644 index 0000000000..04f78ffd3d --- /dev/null +++ b/library/go/core/xerrors/internal/modes/stack_trace_mode.go @@ -0,0 +1,48 @@ +package modes + +import "sync/atomic" + +type StackTraceMode int32 + +const ( + StackTraceModeFrames StackTraceMode = iota + StackTraceModeStacks + StackTraceModeStackThenFrames + StackTraceModeStackThenNothing + StackTraceModeNothing +) + +func (m StackTraceMode) String() string { + return []string{"Frames", "Stacks", "StackThenFrames", "StackThenNothing", "Nothing"}[m] +} + +const defaultStackTraceMode = StackTraceModeFrames + +var ( + // Default mode + stackTraceMode = defaultStackTraceMode + // Known modes (used in tests) + knownStackTraceModes = []StackTraceMode{ + StackTraceModeFrames, + StackTraceModeStacks, + StackTraceModeStackThenFrames, + StackTraceModeStackThenNothing, + StackTraceModeNothing, + } +) + +func SetStackTraceMode(v StackTraceMode) { + atomic.StoreInt32((*int32)(&stackTraceMode), int32(v)) +} + +func GetStackTraceMode() StackTraceMode { + return StackTraceMode(atomic.LoadInt32((*int32)(&stackTraceMode))) +} + +func DefaultStackTraceMode() { + SetStackTraceMode(defaultStackTraceMode) +} + +func KnownStackTraceModes() []StackTraceMode { + return knownStackTraceModes +} diff --git a/library/go/core/xerrors/mode.go b/library/go/core/xerrors/mode.go new file mode 100644 index 0000000000..e6051625b2 --- /dev/null +++ b/library/go/core/xerrors/mode.go @@ -0,0 +1,93 @@ +package xerrors + +import ( + "fmt" + + "a.yandex-team.ru/library/go/core/xerrors/internal/modes" + "a.yandex-team.ru/library/go/x/xruntime" +) + +func DefaultStackTraceMode() { + modes.DefaultStackTraceMode() +} + +func EnableFrames() { + modes.SetStackTraceMode(modes.StackTraceModeFrames) +} + +func EnableStacks() { + modes.SetStackTraceMode(modes.StackTraceModeStacks) +} + +func EnableStackThenFrames() { + modes.SetStackTraceMode(modes.StackTraceModeStackThenFrames) +} + +func EnableStackThenNothing() { + modes.SetStackTraceMode(modes.StackTraceModeStackThenNothing) +} + +func DisableStackTraces() { + modes.SetStackTraceMode(modes.StackTraceModeNothing) +} + +// newStackTrace returns stacktrace based on current mode and frames count +func newStackTrace(skip int, err error) *xruntime.StackTrace { + skip++ + m := modes.GetStackTraceMode() + switch m { + case modes.StackTraceModeFrames: + return xruntime.NewFrame(skip) + case modes.StackTraceModeStackThenFrames: + if err != nil && StackTraceOfEffect(err) != nil { + return xruntime.NewFrame(skip) + } + + return _newStackTrace(skip) + case modes.StackTraceModeStackThenNothing: + if err != nil && StackTraceOfEffect(err) != nil { + return nil + } + + return _newStackTrace(skip) + case modes.StackTraceModeStacks: + return _newStackTrace(skip) + case modes.StackTraceModeNothing: + return nil + } + + panic(fmt.Sprintf("unknown stack trace mode %d", m)) +} + +func MaxStackFrames16() { + modes.SetStackFramesCountMax(modes.StackFramesCount16) +} + +func MaxStackFrames32() { + modes.SetStackFramesCountMax(modes.StackFramesCount32) +} + +func MaxStackFrames64() { + modes.SetStackFramesCountMax(modes.StackFramesCount64) +} + +func MaxStackFrames128() { + modes.SetStackFramesCountMax(modes.StackFramesCount128) +} + +func _newStackTrace(skip int) *xruntime.StackTrace { + skip++ + count := modes.GetStackFramesCountMax() + switch count { + case 16: + return xruntime.NewStackTrace16(skip) + case 32: + return xruntime.NewStackTrace32(skip) + case 64: + return xruntime.NewStackTrace64(skip) + case 128: + return xruntime.NewStackTrace128(skip) + } + + panic(fmt.Sprintf("unknown stack frames count %d", count)) +} diff --git a/library/go/core/xerrors/new.go b/library/go/core/xerrors/new.go new file mode 100644 index 0000000000..e4ad213410 --- /dev/null +++ b/library/go/core/xerrors/new.go @@ -0,0 +1,48 @@ +package xerrors + +import ( + "fmt" + "io" + + "a.yandex-team.ru/library/go/x/xruntime" +) + +type newError struct { + msg string + stacktrace *xruntime.StackTrace +} + +var _ ErrorStackTrace = &newError{} + +func New(text string) error { + return &newError{ + msg: text, + stacktrace: newStackTrace(1, nil), + } +} + +func (e *newError) Error() string { + return e.msg +} + +func (e *newError) Format(s fmt.State, v rune) { + switch v { + case 'v': + if s.Flag('+') && e.stacktrace != nil { + _, _ = io.WriteString(s, e.msg) + _, _ = io.WriteString(s, "\n") + writeStackTrace(s, e.stacktrace) + return + } + + fallthrough + case 's': + _, _ = io.WriteString(s, e.msg) + case 'q': + _, _ = fmt.Fprintf(s, "%q", e.msg) + } +} + +func (e *newError) StackTrace() *xruntime.StackTrace { + return e.stacktrace +} diff --git a/library/go/core/xerrors/sentinel.go b/library/go/core/xerrors/sentinel.go new file mode 100644 index 0000000000..9727b84824 --- /dev/null +++ b/library/go/core/xerrors/sentinel.go @@ -0,0 +1,150 @@ +package xerrors + +import ( + "errors" + "fmt" + "io" + "strings" + + "a.yandex-team.ru/library/go/x/xreflect" + "a.yandex-team.ru/library/go/x/xruntime" +) + +// NewSentinel acts as New but does not add stack frame +func NewSentinel(text string) *Sentinel { + return &Sentinel{error: errors.New(text)} +} + +// Sentinel error +type Sentinel struct { + error +} + +// WithFrame adds stack frame to sentinel error (DEPRECATED) +func (s *Sentinel) WithFrame() error { + return &sentinelWithStackTrace{ + err: s, + stacktrace: newStackTrace(1, nil), + } +} + +func (s *Sentinel) WithStackTrace() error { + return &sentinelWithStackTrace{ + err: s, + stacktrace: newStackTrace(1, nil), + } +} + +// Wrap error with this sentinel error. Adds stack frame. +func (s *Sentinel) Wrap(err error) error { + if err == nil { + panic("tried to wrap a nil error") + } + + return &sentinelWrapper{ + err: s, + wrapped: err, + stacktrace: newStackTrace(1, err), + } +} + +type sentinelWithStackTrace struct { + err error + stacktrace *xruntime.StackTrace +} + +func (e *sentinelWithStackTrace) Error() string { + return e.err.Error() +} + +func (e *sentinelWithStackTrace) Format(s fmt.State, v rune) { + switch v { + case 'v': + if s.Flag('+') && e.stacktrace != nil { + msg := e.err.Error() + _, _ = io.WriteString(s, msg) + writeMsgAndStackTraceSeparator(s, msg) + writeStackTrace(s, e.stacktrace) + return + } + fallthrough + case 's': + _, _ = io.WriteString(s, e.err.Error()) + case 'q': + _, _ = fmt.Fprintf(s, "%q", e.err.Error()) + } +} + +func writeMsgAndStackTraceSeparator(w io.Writer, msg string) { + separator := "\n" + if !strings.HasSuffix(msg, ":") { + separator = ":\n" + } + + _, _ = io.WriteString(w, separator) +} + +// Is checks if e holds the specified error. Checks only immediate error. +func (e *sentinelWithStackTrace) Is(target error) bool { + return e.err == target +} + +// As checks if ew holds the specified error type. Checks only immediate error. +// It does NOT perform target checks as it relies on errors.As to do it +func (e *sentinelWithStackTrace) As(target interface{}) bool { + return xreflect.Assign(e.err, target) +} + +type sentinelWrapper struct { + err error + wrapped error + stacktrace *xruntime.StackTrace +} + +func (e *sentinelWrapper) Error() string { + return fmt.Sprintf("%s", e) +} + +func (e *sentinelWrapper) Format(s fmt.State, v rune) { + switch v { + case 'v': + if s.Flag('+') { + if e.stacktrace != nil { + msg := e.err.Error() + _, _ = io.WriteString(s, msg) + writeMsgAndStackTraceSeparator(s, msg) + writeStackTrace(s, e.stacktrace) + _, _ = fmt.Fprintf(s, "%+v", e.wrapped) + } else { + _, _ = io.WriteString(s, e.err.Error()) + _, _ = io.WriteString(s, ": ") + _, _ = fmt.Fprintf(s, "%+v", e.wrapped) + } + + return + } + fallthrough + case 's': + _, _ = io.WriteString(s, e.err.Error()) + _, _ = io.WriteString(s, ": ") + _, _ = io.WriteString(s, e.wrapped.Error()) + case 'q': + _, _ = fmt.Fprintf(s, "%q", fmt.Sprintf("%s: %s", e.err.Error(), e.wrapped.Error())) + } +} + +// Unwrap implements Wrapper interface +func (e *sentinelWrapper) Unwrap() error { + return e.wrapped +} + +// Is checks if ew holds the specified error. Checks only immediate error. +func (e *sentinelWrapper) Is(target error) bool { + return e.err == target +} + +// As checks if error holds the specified error type. Checks only immediate error. +// It does NOT perform target checks as it relies on errors.As to do it +func (e *sentinelWrapper) As(target interface{}) bool { + return xreflect.Assign(e.err, target) +} diff --git a/library/go/core/xerrors/stacktrace.go b/library/go/core/xerrors/stacktrace.go new file mode 100644 index 0000000000..fab7dc28a8 --- /dev/null +++ b/library/go/core/xerrors/stacktrace.go @@ -0,0 +1,80 @@ +package xerrors + +import ( + "errors" + "fmt" + "io" + + "a.yandex-team.ru/library/go/x/xruntime" +) + +func writeStackTrace(w io.Writer, stacktrace *xruntime.StackTrace) { + for _, frame := range stacktrace.Frames() { + if frame.Function != "" { + _, _ = fmt.Fprintf(w, " %s\n ", frame.Function) + } + + if frame.File != "" { + _, _ = fmt.Fprintf(w, " %s:%d\n", frame.File, frame.Line) + } + } +} + +type ErrorStackTrace interface { + StackTrace() *xruntime.StackTrace +} + +// StackTraceOfEffect returns last stacktrace that was added to error chain (furthest from the root error). +// Guarantees that returned value has valid StackTrace object (but not that there are any frames). +func StackTraceOfEffect(err error) ErrorStackTrace { + var st ErrorStackTrace + for { + if !As(err, &st) { + return nil + } + + if st.StackTrace() != nil { + return st + } + + err = st.(error) + err = errors.Unwrap(err) + } +} + +// StackTraceOfCause returns first stacktrace that was added to error chain (closest to the root error). +// Guarantees that returned value has valid StackTrace object (but not that there are any frames). +func StackTraceOfCause(err error) ErrorStackTrace { + var res ErrorStackTrace + var st ErrorStackTrace + for { + if !As(err, &st) { + return res + } + + if st.StackTrace() != nil { + res = st + } + + err = st.(error) + err = errors.Unwrap(err) + } +} + +// NextStackTracer returns next error with stack trace. +// Guarantees that returned value has valid StackTrace object (but not that there are any frames). +func NextStackTrace(st ErrorStackTrace) ErrorStackTrace { + var res ErrorStackTrace + for { + err := st.(error) + err = errors.Unwrap(err) + + if !As(err, &res) { + return nil + } + + if res.StackTrace() != nil { + return res + } + } +} |