aboutsummaryrefslogtreecommitdiffstats
path: root/library/go/core/xerrors
diff options
context:
space:
mode:
authorqrort <qrort@yandex-team.com>2022-11-30 23:47:12 +0300
committerqrort <qrort@yandex-team.com>2022-11-30 23:47:12 +0300
commit22f8ae0e3f5d68b92aecccdf96c1d841a0334311 (patch)
treebffa27765faf54126ad44bcafa89fadecb7a73d7 /library/go/core/xerrors
parent332b99e2173f0425444abb759eebcb2fafaa9209 (diff)
downloadydb-22f8ae0e3f5d68b92aecccdf96c1d841a0334311.tar.gz
validate canons without yatest_common
Diffstat (limited to 'library/go/core/xerrors')
-rw-r--r--library/go/core/xerrors/doc.go2
-rw-r--r--library/go/core/xerrors/errorf.go88
-rw-r--r--library/go/core/xerrors/forward.go56
-rw-r--r--library/go/core/xerrors/internal/modes/stack_frames_count.go22
-rw-r--r--library/go/core/xerrors/internal/modes/stack_trace_mode.go48
-rw-r--r--library/go/core/xerrors/mode.go93
-rw-r--r--library/go/core/xerrors/new.go48
-rw-r--r--library/go/core/xerrors/sentinel.go150
-rw-r--r--library/go/core/xerrors/stacktrace.go80
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
+ }
+ }
+}