github.com/haraldrudell/parl@v0.4.176/perrors/errorglue/first-panic-stack.go (about) 1 /* 2 © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package errorglue 7 8 import ( 9 "errors" 10 "slices" 11 12 "github.com/haraldrudell/parl/perrors/panicdetector" 13 "github.com/haraldrudell/parl/pruntime" 14 ) 15 16 // FirstPanicStack checks all stack traces, oldest first, for 17 // a panic and returns: 18 // - stack: 19 // - — the panic stack if any 20 // - — otherwise, the oldest stack if any 21 // - — otherwise nil 22 // - isPanic true: a panic stack was found 23 // - — recoveryIndex panicIndex are valid 24 // - numberOfStacks: the number of stacks until the panic stack including it, 25 // or the total number of stacks 26 // - errorWithStack: 27 // - — if panic, the error providing the oldest panic stack 28 // - — otherwise, the oldest error with a stack 29 // - — otherwise, the oldest error 30 // - — otherwise nil 31 // - this enables consumer to print: 32 // - — panic location if any 33 // - — otherwise, oldest error location if any 34 // - — otherwise no location 35 // - err is an error chain that may contain stack traces and be nil 36 // - if any stack trace has a panic: 37 // - — isPanic is true 38 // - — stack is the oldest stack with a panic 39 // - — stack[panicIndex] is the code line causing the oldest panic 40 // - — stack[recoveryIndex] is the first code line in the deferred sequence. 41 // Typically the last line of the executing function 42 // - — numberOfStacks is 1 for the panic stack and added the number of stacks that are older 43 // - — errorWithStack is the error with panic, typically from [parl.Recover] 44 // - if isPanic is false: 45 // - — panicIndex recoveryIndex are zero 46 // - — stack and errorWithStack is the oldest stack if any 47 // -— numberOfStacks is total number of stacks in the error chain 48 // - this must be done beacuse maybe panic() was 49 // provided an error with stack trace 50 // - on err nil: all values false, nil or zero 51 func FirstPanicStack(err error) ( 52 isPanic bool, stack pruntime.Stack, recoveryIndex, panicIndex int, 53 numberOfStacks int, 54 errorWithStack error, 55 ) { 56 57 // any panic stack need to be associated with its exact error 58 // - normally, the returned stack is from any older error in the chain 59 // - therefore, the err’s error chain must be traversed 60 // - the error chain is a directed single-linked list newest error first 61 // - — 62 // - if there is no panic, the oldest stack trace is what will be displayed, ie. the source of the error 63 // - a panic can be in a newer stack trace if an error with a stack trace is provided to panic 64 // - therefore, the oldest panic stack trace should be provided 65 66 // errs is the expanded error chain 67 // - err first that is newest, then older errrors 68 var errs []error 69 for e := err; e != nil; e = errors.Unwrap(e) { 70 errs = append(errs, e) 71 } 72 // errs now begin with oldest error, ending with err 73 slices.Reverse(errs) 74 75 // oldestStack contains the oldest stack trace 76 // - if there is no panic, this should be returned 77 var oldestStack pruntime.Stack 78 // the oldest error, or 79 // if a stack was found, the oldest error with a stack 80 var oldestErr error 81 82 // scan for panic-stack, oldest error first 83 for _, e := range errs { 84 85 // check if e contains a stack trace 86 if errWithStack, ok := e.(ErrorCallStacker); ok { 87 // save stack 88 stack = errWithStack.StackTrace() 89 if oldestStack == nil { 90 // store oldest stack 91 oldestStack = stack 92 // store oldest error or oldest error with stack 93 oldestErr = e 94 } 95 // store the error providing stack 96 errorWithStack = e 97 } else { 98 continue // an error without stack 99 } 100 // increase number of stack until panic, 101 // or total number of stacks 102 numberOfStacks++ 103 104 // check if the stack trace has a panic 105 isPanic, recoveryIndex, panicIndex = panicdetector.Indices(stack) 106 if !isPanic { 107 continue // this error did not have a panic stack-trace 108 } 109 110 return // found panic return 111 } 112 // no panic stack was found 113 stack = oldestStack 114 errorWithStack = oldestErr 115 116 return // no panic-stack return 117 }