exception.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package exception
  2. import (
  3. "fmt"
  4. "io"
  5. "runtime"
  6. )
  7. // Exception implements Throwable.
  8. type Exception struct {
  9. cause Throwable
  10. message string
  11. stackTrace []uintptr
  12. }
  13. var _ Throwable = (*Exception)(nil)
  14. func (e *Exception) Cause() Throwable {
  15. return e.cause
  16. }
  17. func (e *Exception) Message() string {
  18. return e.message
  19. }
  20. func (e *Exception) StackTrace() []uintptr {
  21. return e.stackTrace
  22. }
  23. func (e *Exception) SetStackTrace(stackTrace []uintptr) {
  24. e.stackTrace = stackTrace
  25. }
  26. func (e *Exception) PrintStackTrace(w io.Writer) {
  27. var frames *runtime.Frames = runtime.CallersFrames(e.StackTrace())
  28. fmt.Fprint(w, frames)
  29. }
  30. func (e *Exception) Error() string {
  31. return "Exception: " + e.Message()
  32. }
  33. func (e *Exception) EliminatedStackTrace() []uintptr {
  34. var st []uintptr = e.StackTrace()
  35. var stDepth int = len(st)
  36. eliminated := make([]uintptr, 0, stDepth)
  37. for i := 0; i < stDepth-1; i++ {
  38. j := i+1
  39. for j < stDepth && st[i] == st[j] {
  40. j++
  41. }
  42. if j == stDepth {
  43. eliminated = append(eliminated, st[:i+1]...)
  44. } else if j < stDepth {
  45. if j > i+1 {
  46. eliminated = append(eliminated, st[:i+1]...)
  47. eliminated = append(eliminated, st[j:]...)
  48. }
  49. } else {
  50. panic(fmt.Sprintf(
  51. "BUG: EliminatedStackTrace(): i (%d) j (%d) exceed bounds (stDepth %d)",
  52. i, j, stDepth))
  53. }
  54. }
  55. return eliminated
  56. }
  57. // New constructs a new Exception with the specified detail message and cause.
  58. func New(message string, cause Throwable) *Exception {
  59. var pc []uintptr = callers()
  60. return &Exception{
  61. cause: cause,
  62. message: message,
  63. stackTrace: pc,
  64. }
  65. }
  66. // NewWithStackTraceDepthLimit is like New, but with a specified depth limit of stacktrace.
  67. // It is recommended only using NewWithStackTraceDepthLimit
  68. // when
  69. // 0. the stacktrace will be very deep but only first levels are interested, or
  70. // 1. the needed depth of callers is known or can be estimated, and
  71. // performance issues with New are encountered.
  72. func NewWithStackTraceDepthLimit(message string, cause Throwable, limit int) *Exception {
  73. var pc []uintptr
  74. _, pc = implorer(limit, -1)
  75. return &Exception{
  76. cause: cause,
  77. message: message,
  78. stackTrace: pc,
  79. }
  80. }
  81. func callers() []uintptr {
  82. var pc []uintptr
  83. // 16 is deep enough under most conditions, not considering recursive calls.
  84. // JavaScript V8 has a default stacktrace limit of 10.
  85. // See also comments in implorer definition
  86. // (in else branch, above recursive calls of implorer).
  87. _, pc = implorer(16, 0)
  88. return pc
  89. }
  90. // If recursion is negative, then implorer will call runtime.Callers just once,
  91. // not taking care of depth limit exceeding.
  92. func implorer(depthLimit int, recursion int) (int, []uintptr) {
  93. // 10 is the stacktrace depth limit of JavaScript V8.
  94. // We use it as the initial capacity of the slice.
  95. // The stacktrace depth is unlimited though.
  96. var pc = make([]uintptr, depthLimit)
  97. // Suppose a function f throw an exception, i.e. calling exception.New,
  98. // then the implorer are (assuming depthLimit not exceeded)
  99. //
  100. // 0: runtime.Callers
  101. // 1: implorer (this function)
  102. // 2: callers (wraps implorer to expose a simple api)
  103. // 3: newException (returns an exception)
  104. // 4: exception.New (returns a Throwable)
  105. // 5: f
  106. //
  107. // Thus we skip first five implorer.
  108. //
  109. // When depth exceeds depthLimit,
  110. // we increase depthLimit and call implorer recursively,
  111. // thus we skip additional implorer recursion
  112. depth := runtime.Callers(5+recursion, pc)
  113. if recursion < 0 {
  114. return depth, pc[0:depth]
  115. } else if isFull(depth, depthLimit) {
  116. // uintptr size on 64-bit machine is 8.
  117. // Given the initial depthLimit 16,
  118. // the second call will allocate an a 16 KiB array,
  119. // and the third call will allocate a 2 MiB array,
  120. // within the size of L1 and L3 cache of today's commodity machines.
  121. const depth_limit_times = 1024/8
  122. return implorer(depthLimit*(depth_limit_times), recursion+1)
  123. } else {
  124. // Let gc free up the underlying array filled up with zero values.
  125. var result = make([]uintptr, depth)
  126. copy(result, pc)
  127. return depth, result
  128. }
  129. }
  130. func isFull(length int, limit int) bool {
  131. if length < limit {
  132. return false
  133. } else if length == limit {
  134. return true
  135. } else {
  136. message := fmt.Sprintf(
  137. "isFull(length: %d, limit: %d): length should not exceed limit",
  138. length, limit)
  139. panic(message)
  140. }
  141. }
  142. // FromError converts an error (err) to an exception,
  143. // only utilizing the `err.Error()` method.
  144. func FromError(err error) *Exception {
  145. return New(err.Error(), nil)
  146. }