github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/throw/chain.go (about) 1 // Copyright 2020 Insolar Network Ltd. 2 // All rights reserved. 3 // This material is licensed under the Insolar License version 1.0, 4 // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md. 5 6 package throw 7 8 import ( 9 "errors" 10 "reflect" 11 "strings" 12 ) 13 14 type DeepestStackMode uint8 15 16 const ( 17 _ DeepestStackMode = iota 18 InheritedTrace 19 SupersededTrace 20 ) 21 22 type StackTraceHolder interface { 23 // Cause returns a cause for this stack trace. It can NOT be used like Unwrap() as it may return self. 24 Cause() error 25 // ShallowStackTrace returns a stack trace registered for this cause 26 ShallowStackTrace() StackTrace 27 // DeepestStackTrace returns this or the deepest stack trace from cause's error chain that embeds ShallowStackTrace. 28 // Bool value is true when the stack trace was inherited from the cause. 29 DeepestStackTrace() (StackTrace, DeepestStackMode) 30 } 31 32 // StackOf returns a target-matched entry with a wrapping StackTraceHolder (optional) 33 // When (target) is nil - returns the last error of the chain 34 // nolint 35 func StackOf(errChain, target error) (error, StackTraceHolder, bool) { 36 isComparable := target == nil || reflect.TypeOf(target).Comparable() 37 38 for errChain != nil { 39 nextErr := errors.Unwrap(errChain) 40 if sw, ok := errChain.(StackTraceHolder); ok { 41 if target == nil && nextErr == nil || isThis(isComparable, sw.Cause(), target) { 42 return errChain, sw, true 43 } 44 } else if target == nil && nextErr == nil || isThis(isComparable, errChain, target) { 45 return errChain, nil, true 46 } 47 errChain = nextErr 48 } 49 50 return nil, nil, false 51 } 52 53 // NearestStackOf returns a target-matched entry with a last encountered StackTraceHolder with a either shallow or deep non-nil stack (optional) 54 // When (target) is nil - returns the last error of the chain 55 // When (target) is not found, returns only the last StackTraceHolder. 56 // nolint 57 func NearestStackOf(errChain, target error) (error, StackTraceHolder, bool) { 58 isComparable := target == nil || reflect.TypeOf(target).Comparable() 59 60 var sth StackTraceHolder 61 for errChain != nil { 62 nextErr := errors.Unwrap(errChain) 63 if sw, ok := errChain.(StackTraceHolder); ok { 64 if st, _ := sw.DeepestStackTrace(); st != nil { 65 sth = sw 66 } 67 if target == nil && nextErr == nil || isThis(isComparable, sw.Cause(), target) { 68 return errChain, sth, true 69 } 70 } else if target == nil && nextErr == nil || isThis(isComparable, errChain, target) { 71 return errChain, sth, true 72 } 73 errChain = nextErr 74 } 75 76 return nil, sth, false 77 } 78 79 func DeepestStackTraceOf(errChain error) StackTrace { 80 for errChain != nil { 81 if sw, ok := errChain.(StackTraceHolder); ok { 82 if st, _ := sw.DeepestStackTrace(); st != nil { 83 return st 84 } 85 } 86 errChain = errors.Unwrap(errChain) 87 } 88 return nil 89 } 90 91 func ErrorWithStack(errChain error) string { 92 return JoinStackText(errChain.Error(), DeepestStackTraceOf(errChain)) 93 } 94 95 func ErrorWithTopOrFullStack(errChain error) (string, StackTrace) { 96 st := DeepestStackTraceOf(errChain) 97 if st == nil || st.IsFullStack() { 98 return errChain.Error(), st 99 } 100 return JoinStackText(errChain.Error(), st), nil 101 } 102 103 func ErrorWithTopOrMinimizedStack(errChain error, boundPackage string, includeBoundary bool) (string, StackTrace) { 104 st := DeepestStackTraceOf(errChain) 105 switch { 106 case st == nil: 107 return errChain.Error(), nil 108 case !st.IsFullStack(): 109 return JoinStackText(errChain.Error(), st), nil 110 } 111 if boundPackage != "" { 112 st = MinimizeStackTrace(st, boundPackage, includeBoundary) 113 } 114 min := ExtractStackTop(st, 0) 115 return JoinStackText(errChain.Error(), min), st 116 } 117 118 // OutermostStack returns the closest StackTraceHolder with non-nil ShallowStackTrace from errChain 119 func OutermostStack(errChain error) StackTraceHolder { 120 for errChain != nil { 121 if sw, ok := errChain.(StackTraceHolder); ok && sw.ShallowStackTrace() != nil { 122 return sw 123 } 124 errChain = errors.Unwrap(errChain) 125 } 126 return nil 127 } 128 129 // InnermostStack returns the most distant StackTraceHolder with non-nil ShallowStackTrace from errChain 130 func InnermostStack(errChain error) (sth StackTraceHolder) { 131 for errChain != nil { 132 if sw, ok := errChain.(StackTraceHolder); ok && sw.ShallowStackTrace() != nil { 133 sth = sw 134 } 135 errChain = errors.Unwrap(errChain) 136 } 137 return 138 } 139 140 // InnermostFullStack returns the most distant StackTraceHolder with non-nil ShallowStackTrace and IsFullStack from errChain 141 func InnermostFullStack(errChain error) (sth StackTraceHolder) { 142 for errChain != nil { 143 if sw, ok := errChain.(StackTraceHolder); ok && sw.ShallowStackTrace() != nil && sw.ShallowStackTrace().IsFullStack() { 144 sth = sw 145 } 146 errChain = errors.Unwrap(errChain) 147 } 148 return 149 } 150 151 // Walks through the given error chain and call (fn) for each entry, does unwrapping for stack trace data. 152 func Walk(errChain error, fn func(error, StackTraceHolder) bool) bool { 153 if fn == nil { 154 panic(IllegalValue()) 155 } 156 157 for errChain != nil { 158 if sw, ok := errChain.(StackTraceHolder); ok { 159 if fn(sw.Cause(), sw) { 160 return true 161 } 162 errChain = errors.Unwrap(errChain) 163 } else if fn(errChain, nil) { 164 return true 165 } 166 errChain = errors.Unwrap(errChain) 167 } 168 return false 169 } 170 171 // PrintTo calls Walk() and builds a full list of errors and corresponding stacks in the chain. 172 func PrintTo(errChain error, includeStack bool, b *strings.Builder) { 173 Walk(errChain, func(err error, traceHolder StackTraceHolder) bool { 174 var trace StackTrace 175 if traceHolder != nil { 176 trace = traceHolder.ShallowStackTrace() 177 } 178 switch { 179 case err != nil: 180 b.WriteString(err.Error()) 181 b.WriteByte('\n') 182 if trace == nil || !includeStack { 183 return false 184 } 185 b.WriteString(stackTracePrintPrefix) 186 case trace != nil && includeStack: 187 b.WriteString("<nil>\n" + stackTracePrintPrefix) 188 default: 189 b.WriteString("<nil>\n") 190 return false 191 } 192 193 if err := trace.WriteStackTraceTo(b); err != nil { 194 panic(err) 195 } 196 b.WriteByte('\n') 197 return false 198 }) 199 } 200 201 // Print is a convenience wrapper for PrintTo() 202 func Print(errChain error) string { 203 if errChain == nil { 204 return "" 205 } 206 b := strings.Builder{} 207 PrintTo(errChain, true, &b) 208 return b.String() 209 } 210 211 // IsEqual does panic-safe comparison of error values. Incomparable values will always return false. 212 // It uses Is() on both sides 213 func IsEqual(err0, err1 error) bool { 214 if err0 == nil || err1 == nil { 215 return err0 == err1 216 } 217 if reflect.TypeOf(err1).Comparable() && err0 == err1 { 218 return true 219 } 220 if x, ok := err0.(interface{ Is(error) bool }); ok && x.Is(err1) { 221 return true 222 } 223 if x, ok := err1.(interface{ Is(error) bool }); ok && x.Is(err0) { 224 return true 225 } 226 return false 227 } 228 229 type iser interface { 230 Is(error) bool 231 } 232 233 func isThis(isComparable bool, err, target error) bool { 234 if isComparable && err == target { 235 return true 236 } 237 if x, ok := err.(iser); ok && x.Is(target) { 238 return true 239 } 240 return false 241 }