github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/throw/panicwrap.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  	"math"
    11  )
    12  
    13  const NoStackTrace int = math.MaxInt32
    14  
    15  // PanicHolder is a marker interface for a recovered panic
    16  type PanicHolder interface {
    17  	StackTraceHolder
    18  	Recovered() interface{}
    19  }
    20  
    21  const recoverSkipFrames = 2 // defer() + runtime.panic()
    22  
    23  // WrapPanic returns an error for the given recovered panic with a stack trace.
    24  // The error is also marked as recovered panic. Will return nil for a nil value
    25  // NB! Should be used inside a defer.
    26  func WrapPanic(recovered interface{}) error {
    27  	return WrapPanicExt(recovered, recoverSkipFrames+1) // WrapPanic() + defer
    28  }
    29  
    30  // WrapPanicNoStack returns an error for the given recovered panic without a stack trace.
    31  // The error is also marked as recovered panic. Will return nil for a nil value
    32  // NB! Should be used inside a defer.
    33  func WrapPanicNoStack(recovered interface{}) error {
    34  	return WrapPanicExt(recovered, NoStackTrace)
    35  }
    36  
    37  // WrapPanicNoStack returns an error for the given recovered panic with or without a stack trace.
    38  // When (skipFrames) = NoStackTrace then no stack trace is included.
    39  // The error is also marked as recovered panic. Will return nil for a nil value
    40  func WrapPanicExt(recovered interface{}, skipFrames int) error {
    41  	if recovered == nil {
    42  		return nil
    43  	}
    44  
    45  	var st StackTrace
    46  	if skipFrames < NoStackTrace {
    47  		st = CaptureStack(skipFrames + 1)
    48  	}
    49  
    50  	var stDeepest StackTrace
    51  	var stDeepMod DeepestStackMode
    52  
    53  	switch vv := recovered.(type) {
    54  	case panicWrap:
    55  		if st == nil {
    56  			return vv
    57  		}
    58  		switch CompareStackTrace(vv.st, st) {
    59  		case EqualStack, SupersetStack:
    60  			return vv
    61  		}
    62  	case stackWrap:
    63  		stDeepest, stDeepMod = reuseSupersetTrace(st, vv.stDeepest)
    64  	case fmtWrap:
    65  		return panicWrap{st: st, stDeepest: stDeepest, fmtWrap: vv}
    66  	case error:
    67  		sth := OutermostStack(vv)
    68  		if sth != nil {
    69  			stDeep, _ := sth.DeepestStackTrace()
    70  			stDeepest, stDeepMod = reuseSupersetTrace(st, stDeep)
    71  		}
    72  	}
    73  	return panicWrap{st, recovered, stDeepest, wrapInternal(recovered), stDeepMod}
    74  }
    75  
    76  // InnermostPanicWithStack returns the most distant panic holder from the chain.
    77  // The value can be either error or PanicHolder. Otherwise will return nil.
    78  func InnermostPanicWithStack(errorOrHolder interface{}) PanicHolder {
    79  	switch vv := errorOrHolder.(type) {
    80  	case PanicHolder:
    81  		st := vv.ShallowStackTrace()
    82  		if st != nil {
    83  			return vv
    84  		}
    85  		return innermostWithStack(vv.Cause())
    86  	case error:
    87  		return innermostWithStack(vv)
    88  	default:
    89  		return nil
    90  	}
    91  }
    92  
    93  func innermostWithStack(errChain error) PanicHolder {
    94  	for errChain != nil {
    95  		if sw, ok := errChain.(panicWrap); ok {
    96  			nextErr := sw.Unwrap()
    97  			if sw.ShallowStackTrace() != nil && nextErr != nil {
    98  				return sw
    99  			}
   100  			errChain = nextErr
   101  			continue
   102  		}
   103  		errChain = errors.Unwrap(errChain)
   104  	}
   105  	return nil
   106  }