excpt.nim 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. # Exception handling code. Carefully coded so that tiny programs which do not
  10. # use the heap (and nor exceptions) do not include the GC or memory allocator.
  11. var
  12. errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], benign,
  13. nimcall.})
  14. ## Function that will be called
  15. ## instead of `stdmsg.write` when printing stacktrace.
  16. ## Unstable API.
  17. when not defined(windows) or not defined(guiapp):
  18. proc writeToStdErr(msg: cstring) = rawWrite(cstderr, msg)
  19. else:
  20. proc MessageBoxA(hWnd: pointer, lpText, lpCaption: cstring, uType: int): int32 {.
  21. header: "<windows.h>", nodecl.}
  22. proc writeToStdErr(msg: cstring) =
  23. discard MessageBoxA(nil, msg, nil, 0)
  24. proc showErrorMessage(data: cstring) {.gcsafe.} =
  25. if errorMessageWriter != nil:
  26. errorMessageWriter($data)
  27. else:
  28. when defined(genode):
  29. # stderr not available by default, use the LOG session
  30. echo data
  31. else:
  32. writeToStdErr(data)
  33. proc quitOrDebug() {.inline.} =
  34. when defined(endb):
  35. endbStep() # call the debugger
  36. else:
  37. quit(1)
  38. proc chckIndx(i, a, b: int): int {.inline, compilerproc, benign.}
  39. proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.}
  40. proc chckRangeF(x, a, b: float): float {.inline, compilerproc, benign.}
  41. proc chckNil(p: pointer) {.noinline, compilerproc, benign.}
  42. type
  43. GcFrame = ptr GcFrameHeader
  44. GcFrameHeader {.compilerproc.} = object
  45. len: int
  46. prev: ptr GcFrameHeader
  47. var
  48. framePtr {.threadvar.}: PFrame
  49. excHandler {.threadvar.}: PSafePoint
  50. # list of exception handlers
  51. # a global variable for the root of all try blocks
  52. currException {.threadvar.}: ref Exception
  53. raiseCounter {.threadvar.}: uint
  54. gcFramePtr {.threadvar.}: GcFrame
  55. type
  56. FrameState = tuple[gcFramePtr: GcFrame, framePtr: PFrame,
  57. excHandler: PSafePoint, currException: ref Exception]
  58. proc getFrameState*(): FrameState {.compilerRtl, inl.} =
  59. return (gcFramePtr, framePtr, excHandler, currException)
  60. proc setFrameState*(state: FrameState) {.compilerRtl, inl.} =
  61. gcFramePtr = state.gcFramePtr
  62. framePtr = state.framePtr
  63. excHandler = state.excHandler
  64. currException = state.currException
  65. proc getFrame*(): PFrame {.compilerRtl, inl.} = framePtr
  66. proc popFrame {.compilerRtl, inl.} =
  67. framePtr = framePtr.prev
  68. when false:
  69. proc popFrameOfAddr(s: PFrame) {.compilerRtl.} =
  70. var it = framePtr
  71. if it == s:
  72. framePtr = framePtr.prev
  73. else:
  74. while it != nil:
  75. if it == s:
  76. framePtr = it.prev
  77. break
  78. it = it.prev
  79. proc setFrame*(s: PFrame) {.compilerRtl, inl.} =
  80. framePtr = s
  81. proc getGcFrame*(): GcFrame {.compilerRtl, inl.} = gcFramePtr
  82. proc popGcFrame*() {.compilerRtl, inl.} = gcFramePtr = gcFramePtr.prev
  83. proc setGcFrame*(s: GcFrame) {.compilerRtl, inl.} = gcFramePtr = s
  84. proc pushGcFrame*(s: GcFrame) {.compilerRtl, inl.} =
  85. s.prev = gcFramePtr
  86. zeroMem(cast[pointer](cast[int](s)+%sizeof(GcFrameHeader)), s.len*sizeof(pointer))
  87. gcFramePtr = s
  88. proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} =
  89. s.hasRaiseAction = false
  90. s.prev = excHandler
  91. excHandler = s
  92. proc popSafePoint {.compilerRtl, inl.} =
  93. excHandler = excHandler.prev
  94. proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} =
  95. e.up = currException
  96. currException = e
  97. proc popCurrentException {.compilerRtl, inl.} =
  98. currException = currException.up
  99. proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
  100. # in cpp backend exceptions can pop-up in the different order they were raised, example #5628
  101. if currException.raiseId == id:
  102. currException = currException.up
  103. else:
  104. var cur = currException.up
  105. var prev = currException
  106. while cur != nil and cur.raiseId != id:
  107. prev = cur
  108. cur = cur.up
  109. if cur == nil:
  110. showErrorMessage("popCurrentExceptionEx() exception was not found in the exception stack. Aborting...")
  111. quitOrDebug()
  112. prev.up = cur.up
  113. proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} =
  114. if not e.isNil:
  115. currException = e
  116. # some platforms have native support for stack traces:
  117. const
  118. nativeStackTraceSupported* = (defined(macosx) or defined(linux)) and
  119. not NimStackTrace
  120. hasSomeStackTrace = NimStackTrace or
  121. defined(nativeStackTrace) and nativeStackTraceSupported
  122. when defined(nativeStacktrace) and nativeStackTraceSupported:
  123. type
  124. TDl_info {.importc: "Dl_info", header: "<dlfcn.h>",
  125. final, pure.} = object
  126. dli_fname: cstring
  127. dli_fbase: pointer
  128. dli_sname: cstring
  129. dli_saddr: pointer
  130. proc backtrace(symbols: ptr pointer, size: int): int {.
  131. importc: "backtrace", header: "<execinfo.h>".}
  132. proc dladdr(addr1: pointer, info: ptr TDl_info): int {.
  133. importc: "dladdr", header: "<dlfcn.h>".}
  134. when not hasThreadSupport:
  135. var
  136. tempAddresses: array[0..127, pointer] # should not be alloc'd on stack
  137. tempDlInfo: TDl_info
  138. proc auxWriteStackTraceWithBacktrace(s: var string) =
  139. when hasThreadSupport:
  140. var
  141. tempAddresses: array[0..127, pointer] # but better than a threadvar
  142. tempDlInfo: TDl_info
  143. # This is allowed to be expensive since it only happens during crashes
  144. # (but this way you don't need manual stack tracing)
  145. var size = backtrace(cast[ptr pointer](addr(tempAddresses)),
  146. len(tempAddresses))
  147. var enabled = false
  148. for i in 0..size-1:
  149. var dlresult = dladdr(tempAddresses[i], addr(tempDlInfo))
  150. if enabled:
  151. if dlresult != 0:
  152. var oldLen = s.len
  153. add(s, tempDlInfo.dli_fname)
  154. if tempDlInfo.dli_sname != nil:
  155. for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
  156. add(s, tempDlInfo.dli_sname)
  157. else:
  158. add(s, '?')
  159. add(s, "\n")
  160. else:
  161. if dlresult != 0 and tempDlInfo.dli_sname != nil and
  162. c_strcmp(tempDlInfo.dli_sname, "signalHandler") == 0'i32:
  163. # Once we're past signalHandler, we're at what the user is
  164. # interested in
  165. enabled = true
  166. when not hasThreadSupport:
  167. var
  168. tempFrames: array[0..127, PFrame] # should not be alloc'd on stack
  169. const
  170. reraisedFromBegin = -10
  171. reraisedFromEnd = -100
  172. template reraisedFrom(z): untyped =
  173. StackTraceEntry(procname: nil, line: z, filename: nil)
  174. proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) =
  175. var
  176. it = f
  177. i = 0
  178. while it != nil:
  179. inc(i)
  180. it = it.prev
  181. var last = i-1
  182. when true: # not defined(gcDestructors):
  183. if s.len == 0:
  184. s = newSeq[StackTraceEntry](i)
  185. else:
  186. last = s.len + i - 1
  187. s.setLen(last+1)
  188. it = f
  189. while it != nil:
  190. s[last] = StackTraceEntry(procname: it.procname,
  191. line: it.line,
  192. filename: it.filename)
  193. it = it.prev
  194. dec last
  195. template addFrameEntry(s, f: untyped) =
  196. var oldLen = s.len
  197. add(s, f.filename)
  198. if f.line > 0:
  199. add(s, '(')
  200. add(s, $f.line)
  201. add(s, ')')
  202. for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
  203. add(s, f.procname)
  204. add(s, "\n")
  205. proc `$`(s: seq[StackTraceEntry]): string =
  206. result = newStringOfCap(2000)
  207. for i in 0 .. s.len-1:
  208. if s[i].line == reraisedFromBegin: result.add "[[reraised from:\n"
  209. elif s[i].line == reraisedFromEnd: result.add "]]\n"
  210. else: addFrameEntry(result, s[i])
  211. proc auxWriteStackTrace(f: PFrame, s: var string) =
  212. when hasThreadSupport:
  213. var
  214. tempFrames: array[0..127, PFrame] # but better than a threadvar
  215. const
  216. firstCalls = 32
  217. var
  218. it = f
  219. i = 0
  220. total = 0
  221. # setup long head:
  222. while it != nil and i <= high(tempFrames)-firstCalls:
  223. tempFrames[i] = it
  224. inc(i)
  225. inc(total)
  226. it = it.prev
  227. # go up the stack to count 'total':
  228. var b = it
  229. while it != nil:
  230. inc(total)
  231. it = it.prev
  232. var skipped = 0
  233. if total > len(tempFrames):
  234. # skip N
  235. skipped = total-i-firstCalls+1
  236. for j in 1..skipped:
  237. if b != nil: b = b.prev
  238. # create '...' entry:
  239. tempFrames[i] = nil
  240. inc(i)
  241. # setup short tail:
  242. while b != nil and i <= high(tempFrames):
  243. tempFrames[i] = b
  244. inc(i)
  245. b = b.prev
  246. for j in countdown(i-1, 0):
  247. if tempFrames[j] == nil:
  248. add(s, "(")
  249. add(s, $skipped)
  250. add(s, " calls omitted) ...\n")
  251. else:
  252. addFrameEntry(s, tempFrames[j])
  253. proc stackTraceAvailable*(): bool
  254. when hasSomeStackTrace:
  255. proc rawWriteStackTrace(s: var string) =
  256. when NimStackTrace:
  257. if framePtr == nil:
  258. add(s, "No stack traceback available\n")
  259. else:
  260. add(s, "Traceback (most recent call last)\n")
  261. auxWriteStackTrace(framePtr, s)
  262. elif defined(nativeStackTrace) and nativeStackTraceSupported:
  263. add(s, "Traceback from system (most recent call last)\n")
  264. auxWriteStackTraceWithBacktrace(s)
  265. else:
  266. add(s, "No stack traceback available\n")
  267. proc rawWriteStackTrace(s: var seq[StackTraceEntry]) =
  268. when NimStackTrace:
  269. auxWriteStackTrace(framePtr, s)
  270. else:
  271. s = @[]
  272. proc stackTraceAvailable(): bool =
  273. when NimStackTrace:
  274. if framePtr == nil:
  275. result = false
  276. else:
  277. result = true
  278. elif defined(nativeStackTrace) and nativeStackTraceSupported:
  279. result = true
  280. else:
  281. result = false
  282. else:
  283. proc stackTraceAvailable*(): bool = result = false
  284. var onUnhandledException*: (proc (errorMsg: string) {.
  285. nimcall.}) ## Set this error \
  286. ## handler to override the existing behaviour on an unhandled exception.
  287. ##
  288. ## The default is to write a stacktrace to ``stderr`` and then call ``quit(1)``.
  289. ## Unstable API.
  290. template unhandled(buf, body) =
  291. if onUnhandledException != nil:
  292. onUnhandledException($buf)
  293. else:
  294. body
  295. proc raiseExceptionAux(e: ref Exception) =
  296. if localRaiseHook != nil:
  297. if not localRaiseHook(e): return
  298. if globalRaiseHook != nil:
  299. if not globalRaiseHook(e): return
  300. when defined(cpp) and not defined(noCppExceptions):
  301. pushCurrentException(e)
  302. raiseCounter.inc
  303. if raiseCounter == 0:
  304. raiseCounter.inc # skip zero at overflow
  305. e.raiseId = raiseCounter
  306. {.emit: "`e`->raise();".}
  307. elif defined(nimQuirky):
  308. pushCurrentException(e)
  309. else:
  310. if excHandler != nil:
  311. if not excHandler.hasRaiseAction or excHandler.raiseAction(e):
  312. pushCurrentException(e)
  313. c_longjmp(excHandler.context, 1)
  314. else:
  315. when hasSomeStackTrace:
  316. var buf = newStringOfCap(2000)
  317. if e.trace.len == 0: rawWriteStackTrace(buf)
  318. else: add(buf, $e.trace)
  319. add(buf, "Error: unhandled exception: ")
  320. add(buf, e.msg)
  321. add(buf, " [")
  322. add(buf, $e.name)
  323. add(buf, "]\n")
  324. unhandled(buf):
  325. showErrorMessage(buf)
  326. quitOrDebug()
  327. else:
  328. # ugly, but avoids heap allocations :-)
  329. template xadd(buf, s, slen) =
  330. if L + slen < high(buf):
  331. copyMem(addr(buf[L]), cstring(s), slen)
  332. inc L, slen
  333. template add(buf, s) =
  334. xadd(buf, s, s.len)
  335. var buf: array[0..2000, char]
  336. var L = 0
  337. if e.trace.len != 0:
  338. add(buf, $e.trace) # gc allocation
  339. add(buf, "Error: unhandled exception: ")
  340. add(buf, e.msg)
  341. add(buf, " [")
  342. xadd(buf, e.name, e.name.len)
  343. add(buf, "]\n")
  344. when defined(nimNoArrayToCstringConversion):
  345. template tbuf(): untyped = addr buf
  346. else:
  347. template tbuf(): untyped = buf
  348. unhandled(tbuf()):
  349. showErrorMessage(tbuf())
  350. quitOrDebug()
  351. proc raiseExceptionEx(e: ref Exception, ename, procname, filename: cstring, line: int) {.compilerRtl.} =
  352. if e.name.isNil: e.name = ename
  353. when hasSomeStackTrace:
  354. if e.trace.len == 0:
  355. rawWriteStackTrace(e.trace)
  356. elif framePtr != nil:
  357. e.trace.add reraisedFrom(reraisedFromBegin)
  358. auxWriteStackTrace(framePtr, e.trace)
  359. e.trace.add reraisedFrom(reraisedFromEnd)
  360. else:
  361. if procname != nil and filename != nil:
  362. e.trace.add StackTraceEntry(procname: procname, filename: filename, line: line)
  363. raiseExceptionAux(e)
  364. proc raiseException(e: ref Exception, ename: cstring) {.compilerRtl.} =
  365. raiseExceptionEx(e, ename, nil, nil, 0)
  366. proc reraiseException() {.compilerRtl.} =
  367. if currException == nil:
  368. sysFatal(ReraiseError, "no exception to reraise")
  369. else:
  370. raiseExceptionAux(currException)
  371. proc writeStackTrace() =
  372. when hasSomeStackTrace:
  373. var s = ""
  374. rawWriteStackTrace(s)
  375. cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall.}](showErrorMessage)(s)
  376. else:
  377. cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall.}](showErrorMessage)("No stack traceback available\n")
  378. proc getStackTrace(): string =
  379. when hasSomeStackTrace:
  380. result = ""
  381. rawWriteStackTrace(result)
  382. else:
  383. result = "No stack traceback available\n"
  384. proc getStackTrace(e: ref Exception): string =
  385. if not isNil(e):
  386. result = $e.trace
  387. else:
  388. result = ""
  389. proc getStackTraceEntries*(e: ref Exception): seq[StackTraceEntry] =
  390. ## Returns the attached stack trace to the exception ``e`` as
  391. ## a ``seq``. This is not yet available for the JS backend.
  392. when not defined(gcDestructors):
  393. shallowCopy(result, e.trace)
  394. else:
  395. result = move(e.trace)
  396. proc getStackTraceEntries*(): seq[StackTraceEntry] =
  397. ## Returns the stack trace entries for the current stack trace.
  398. ## This is not yet available for the JS backend.
  399. when hasSomeStackTrace:
  400. rawWriteStackTrace(result)
  401. const nimCallDepthLimit {.intdefine.} = 2000
  402. proc callDepthLimitReached() {.noinline.} =
  403. writeStackTrace()
  404. showErrorMessage("Error: call depth limit reached in a debug build (" &
  405. $nimCallDepthLimit & " function calls). You can change it with " &
  406. "-d:nimCallDepthLimit=<int> but really try to avoid deep " &
  407. "recursions instead.\n")
  408. quitOrDebug()
  409. proc nimFrame(s: PFrame) {.compilerRtl, inl.} =
  410. s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1
  411. s.prev = framePtr
  412. framePtr = s
  413. if s.calldepth == nimCallDepthLimit: callDepthLimitReached()
  414. when defined(endb):
  415. var
  416. dbgAborting: bool # whether the debugger wants to abort
  417. when defined(cpp) and appType != "lib" and
  418. not defined(js) and not defined(nimscript) and
  419. hostOS != "standalone" and not defined(noCppExceptions):
  420. type
  421. StdException {.importcpp: "std::exception", header: "<exception>".} = object
  422. proc what(ex: StdException): cstring {.importcpp: "((char *)#.what())".}
  423. proc setTerminate(handler: proc() {.noconv.})
  424. {.importc: "std::set_terminate", header: "<exception>".}
  425. setTerminate proc() {.noconv.} =
  426. # Remove ourself as a handler, reinstalling the default handler.
  427. setTerminate(nil)
  428. var msg = "Unknown error in unexpected exception handler"
  429. try:
  430. raise
  431. except Exception:
  432. msg = currException.getStackTrace() & "Error: unhandled exception: " &
  433. currException.msg & " [" & $currException.name & "]"
  434. except StdException as e:
  435. msg = "Error: unhandled cpp exception: " & $e.what()
  436. except:
  437. msg = "Error: unhandled unknown cpp exception"
  438. when defined(genode):
  439. # stderr not available by default, use the LOG session
  440. echo msg
  441. else:
  442. writeToStdErr msg & "\n"
  443. quit 1
  444. when not defined(noSignalHandler) and not defined(useNimRtl):
  445. proc signalHandler(sign: cint) {.exportc: "signalHandler", noconv.} =
  446. template processSignal(s, action: untyped) {.dirty.} =
  447. if s == SIGINT: action("SIGINT: Interrupted by Ctrl-C.\n")
  448. elif s == SIGSEGV:
  449. action("SIGSEGV: Illegal storage access. (Attempt to read from nil?)\n")
  450. elif s == SIGABRT:
  451. when defined(endb):
  452. if dbgAborting: return # the debugger wants to abort
  453. action("SIGABRT: Abnormal termination.\n")
  454. elif s == SIGFPE: action("SIGFPE: Arithmetic error.\n")
  455. elif s == SIGILL: action("SIGILL: Illegal operation.\n")
  456. elif (when declared(SIGBUS): s == SIGBUS else: false):
  457. action("SIGBUS: Illegal storage access. (Attempt to read from nil?)\n")
  458. else:
  459. block platformSpecificSignal:
  460. when declared(SIGPIPE):
  461. if s == SIGPIPE:
  462. action("SIGPIPE: Pipe closed.\n")
  463. break platformSpecificSignal
  464. action("unknown signal\n")
  465. # print stack trace and quit
  466. when defined(memtracker):
  467. logPendingOps()
  468. when hasSomeStackTrace:
  469. GC_disable()
  470. var buf = newStringOfCap(2000)
  471. rawWriteStackTrace(buf)
  472. processSignal(sign, buf.add) # nice hu? currying a la Nim :-)
  473. showErrorMessage(buf)
  474. GC_enable()
  475. else:
  476. var msg: cstring
  477. template asgn(y) =
  478. msg = y
  479. processSignal(sign, asgn)
  480. showErrorMessage(msg)
  481. when defined(endb): dbgAborting = true
  482. quit(1) # always quit when SIGABRT
  483. proc registerSignalHandler() =
  484. c_signal(SIGINT, signalHandler)
  485. c_signal(SIGSEGV, signalHandler)
  486. c_signal(SIGABRT, signalHandler)
  487. c_signal(SIGFPE, signalHandler)
  488. c_signal(SIGILL, signalHandler)
  489. when declared(SIGBUS):
  490. c_signal(SIGBUS, signalHandler)
  491. when declared(SIGPIPE):
  492. c_signal(SIGPIPE, signalHandler)
  493. registerSignalHandler() # call it in initialization section
  494. proc setControlCHook(hook: proc () {.noconv.}) =
  495. # ugly cast, but should work on all architectures:
  496. type SignalHandler = proc (sign: cint) {.noconv, benign.}
  497. c_signal(SIGINT, cast[SignalHandler](hook))
  498. when not defined(noSignalHandler) and not defined(useNimRtl):
  499. proc unsetControlCHook() =
  500. # proc to unset a hook set by setControlCHook
  501. c_signal(SIGINT, signalHandler)