excpt.nim 16 KB

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