#
#
# Nimrod's Runtime Library
# (c) Copyright 2011 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# Exception handling code. This is difficult because it has
# to work if there is no more memory (but it doesn't yet!).
var
stackTraceNewLine* = "\n" ## undocumented feature; it is replaced by ``
``
## for CGI applications
isMultiThreaded: bool # true when prog created at least 1 thread
when not defined(windows) or not defined(guiapp):
proc writeToStdErr(msg: CString) = write(stdout, msg)
else:
proc MessageBoxA(hWnd: cint, lpText, lpCaption: cstring, uType: int): int32 {.
header: "", nodecl.}
proc writeToStdErr(msg: CString) =
discard MessageBoxA(0, msg, nil, 0)
proc registerSignalHandler() {.compilerproc.}
proc chckIndx(i, a, b: int): int {.inline, compilerproc.}
proc chckRange(i, a, b: int): int {.inline, compilerproc.}
proc chckRangeF(x, a, b: float): float {.inline, compilerproc.}
proc chckNil(p: pointer) {.inline, compilerproc.}
type
PSafePoint = ptr TSafePoint
TSafePoint {.compilerproc, final.} = object
prev: PSafePoint # points to next safe point ON THE STACK
status: int
context: C_JmpBuf
when hasThreadSupport:
# Support for thread local storage:
when defined(windows):
type
TThreadVarSlot {.compilerproc.} = distinct int32
proc TlsAlloc(): TThreadVarSlot {.
importc: "TlsAlloc", stdcall, dynlib: "kernel32".}
proc TlsSetValue(dwTlsIndex: TThreadVarSlot, lpTlsValue: pointer) {.
importc: "TlsSetValue", stdcall, dynlib: "kernel32".}
proc TlsGetValue(dwTlsIndex: TThreadVarSlot): pointer {.
importc: "TlsGetValue", stdcall, dynlib: "kernel32".}
proc ThreadVarAlloc(): TThreadVarSlot {.compilerproc, inline.} =
result = TlsAlloc()
proc ThreadVarSetValue(s: TThreadVarSlot, value: pointer) {.
compilerproc, inline.} =
TlsSetValue(s, value)
proc ThreadVarGetValue(s: TThreadVarSlot): pointer {.
compilerproc, inline.} =
result = TlsGetValue(s)
else:
{.passL: "-pthread".}
{.passC: "-pthread".}
type
Tpthread_key {.importc: "pthread_key_t",
header: "".} = distinct int32
TThreadVarSlot {.compilerproc.} = Tpthread_key
proc pthread_getspecific(a1: Tpthread_key): pointer {.
importc: "pthread_getspecific", header: "".}
proc pthread_key_create(a1: ptr Tpthread_key,
destruct: proc (x: pointer) {.noconv.}): int32 {.
importc: "pthread_key_create", header: "".}
proc pthread_key_delete(a1: Tpthread_key): int32 {.
importc: "pthread_key_delete", header: "".}
proc pthread_setspecific(a1: Tpthread_key, a2: pointer): int32 {.
importc: "pthread_setspecific", header: "".}
proc specificDestroy(mem: pointer) {.noconv.} =
#aquireSys(heapLock)
#dealloc(mem)
#releaseSys(heapLock)
#c_free(mem)
proc ThreadVarAlloc(): TThreadVarSlot {.compilerproc, inline.} =
discard pthread_key_create(addr(result), specificDestroy)
proc ThreadVarSetValue(s: TThreadVarSlot, value: pointer) {.
compilerproc, inline.} =
discard pthread_setspecific(s, value)
proc ThreadVarGetValue(s: TThreadVarSlot): pointer {.compilerproc, inline.} =
result = pthread_getspecific(s)
type
TGlobals {.final, pure.} = object
excHandler: PSafePoint
currException: ref E_Base
framePtr: PFrame
buf: string # cannot be allocated on the stack!
assertBuf: string # we need a different buffer for
# assert, as it raises an exception and
# exception handler needs the buffer too
gAssertionFailed: ref EAssertionFailed
tempFrames: array [0..127, PFrame] # cannot be allocated on the stack!
data: float # compiler should add thread local variables here!
PGlobals = ptr TGlobals
# it's more efficient to not use a global variable for the thread storage
# slot, but to rely on the implementation to assign slot 0 for us... ;-)
var globalsSlot = ThreadVarAlloc()
#const globalsSlot = TThreadVarSlot(0)
#assert checkSlot.int == globalsSlot.int
proc AtomicAlloc0(size: int): pointer =
#AquireSys(heapLock)
result = c_malloc(size)
zeroMem(result, size)
#ReleaseSys(heapLock)
proc NewGlobals(): PGlobals =
result = cast[PGlobals](AtomicAlloc0(sizeof(TGlobals)))
new(result.gAssertionFailed)
result.buf = newStringOfCap(2000)
result.assertBuf = newStringOfCap(2000)
proc AllocThreadLocalStorage*(): pointer {.inl.} =
isMultiThreaded = true
result = NewGlobals()
proc SetThreadLocalStorage*(p: pointer) {.inl.} =
ThreadVarSetValue(globalsSlot, p)
proc GetGlobals(): PGlobals {.compilerRtl, inl.} =
result = cast[PGlobals](ThreadVarGetValue(globalsSlot))
# create for the main thread:
ThreadVarSetValue(globalsSlot, NewGlobals())
when hasThreadSupport:
template ThreadGlobals =
var globals = GetGlobals()
template `||`(varname: expr): expr = globals.varname
else:
template ThreadGlobals = nil # nothing
template `||`(varname: expr): expr = varname
var
framePtr: PFrame
excHandler: PSafePoint = nil
# list of exception handlers
# a global variable for the root of all try blocks
currException: ref E_Base
buf: string # cannot be allocated on the stack!
assertBuf: string # we need a different buffer for
# assert, as it raises an exception and
# exception handler needs the buffer too
tempFrames: array [0..127, PFrame] # cannot be allocated on the stack!
gAssertionFailed: ref EAssertionFailed
new(||gAssertionFailed)
||buf = newStringOfCap(2000)
||assertBuf = newStringOfCap(2000)
proc pushFrame(s: PFrame) {.compilerRtl, inl.} =
ThreadGlobals()
s.prev = ||framePtr
||framePtr = s
proc popFrame {.compilerRtl, inl.} =
ThreadGlobals()
||framePtr = (||framePtr).prev
proc setFrame(s: PFrame) {.compilerRtl, inl.} =
ThreadGlobals()
||framePtr = s
proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} =
ThreadGlobals()
s.prev = ||excHandler
||excHandler = s
proc popSafePoint {.compilerRtl, inl.} =
ThreadGlobals()
||excHandler = (||excHandler).prev
proc pushCurrentException(e: ref E_Base) {.compilerRtl, inl.} =
ThreadGlobals()
e.parent = ||currException
||currException = e
proc popCurrentException {.compilerRtl, inl.} =
ThreadGlobals()
||currException = (||currException).parent
# some platforms have native support for stack traces:
const
nativeStackTraceSupported = (defined(macosx) or defined(linux)) and
not nimrodStackTrace
when defined(nativeStacktrace) and nativeStackTraceSupported:
type
TDl_info {.importc: "Dl_info", header: "",
final, pure.} = object
dli_fname: CString
dli_fbase: pointer
dli_sname: CString
dli_saddr: pointer
proc backtrace(symbols: ptr pointer, size: int): int {.
importc: "backtrace", header: "".}
proc dladdr(addr1: pointer, info: ptr TDl_info): int {.
importc: "dladdr", header: "".}
when not hasThreadSupport:
var
tempAddresses: array [0..127, pointer] # should not be alloc'd on stack
tempDlInfo: TDl_info
proc auxWriteStackTraceWithBacktrace(s: var string) =
when hasThreadSupport:
var
tempAddresses: array [0..127, pointer] # but better than a threadvar
tempDlInfo: TDl_info
# This is allowed to be expensive since it only happens during crashes
# (but this way you don't need manual stack tracing)
var size = backtrace(cast[ptr pointer](addr(tempAddresses)),
len(tempAddresses))
var enabled = false
for i in 0..size-1:
var dlresult = dladdr(tempAddresses[i], addr(tempDlInfo))
if enabled:
if dlresult != 0:
var oldLen = s.len
add(s, tempDlInfo.dli_fname)
if tempDlInfo.dli_sname != nil:
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
add(s, tempDlInfo.dli_sname)
else:
add(s, '?')
add(s, stackTraceNewLine)
else:
if dlresult != 0 and tempDlInfo.dli_sname != nil and
c_strcmp(tempDlInfo.dli_sname, "signalHandler") == 0'i32:
# Once we're past signalHandler, we're at what the user is
# interested in
enabled = true
proc auxWriteStackTrace(f: PFrame, s: var string) =
const
firstCalls = 32
ThreadGlobals()
var
it = f
i = 0
total = 0
while it != nil and i <= high(||tempFrames)-(firstCalls-1):
# the (-1) is for a nil entry that marks where the '...' should occur
(||tempFrames)[i] = it
inc(i)
inc(total)
it = it.prev
var b = it
while it != nil:
inc(total)
it = it.prev
for j in 1..total-i-(firstCalls-1):
if b != nil: b = b.prev
if total != i:
(||tempFrames)[i] = nil
inc(i)
while b != nil and i <= high(||tempFrames):
(||tempFrames)[i] = b
inc(i)
b = b.prev
for j in countdown(i-1, 0):
if (||tempFrames)[j] == nil:
add(s, "(")
add(s, $(total-i-1))
add(s, " calls omitted) ...")
else:
var oldLen = s.len
add(s, (||tempFrames)[j].filename)
if (||tempFrames)[j].line > 0:
add(s, '(')
add(s, $(||tempFrames)[j].line)
add(s, ')')
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
add(s, (||tempFrames)[j].procname)
add(s, stackTraceNewLine)
proc rawWriteStackTrace(s: var string) =
when nimrodStackTrace:
ThreadGlobals()
if ||framePtr == nil:
add(s, "No stack traceback available")
add(s, stackTraceNewLine)
else:
add(s, "Traceback (most recent call last)")
add(s, stackTraceNewLine)
auxWriteStackTrace(||framePtr, s)
elif defined(nativeStackTrace) and nativeStackTraceSupported:
add(s, "Traceback from system (most recent call last)")
add(s, stackTraceNewLine)
auxWriteStackTraceWithBacktrace(s)
else:
add(s, "No stack traceback available")
add(s, stackTraceNewLine)
proc quitOrDebug() {.inline.} =
when not defined(endb):
quit(1)
else:
endbStep() # call the debugger
proc raiseException(e: ref E_Base, ename: CString) {.compilerRtl.} =
GC_disable() # a bad thing is an error in the GC while raising an exception
e.name = ename
ThreadGlobals()
if ||excHandler != nil:
pushCurrentException(e)
c_longjmp((||excHandler).context, 1)
else:
if not isNil(||buf):
setLen(||buf, 0)
rawWriteStackTrace(||buf)
if e.msg != nil and e.msg[0] != '\0':
add(||buf, "Error: unhandled exception: ")
add(||buf, $e.msg)
else:
add(||buf, "Error: unhandled exception")
add(||buf, " [")
add(||buf, $ename)
add(||buf, "]\n")
writeToStdErr(||buf)
else:
writeToStdErr(ename)
quitOrDebug()
GC_enable()
proc reraiseException() {.compilerRtl.} =
ThreadGlobals()
if ||currException == nil:
raise newException(ENoExceptionToReraise, "no exception to reraise")
else:
raiseException(||currException, (||currException).name)
proc internalAssert(file: cstring, line: int, cond: bool) {.compilerproc.} =
if not cond:
ThreadGlobals()
#c_fprintf(c_stdout, "Assertion failure: file %s line %ld\n", file, line)
#quit(1)
GC_disable() # BUGFIX: `$` allocates a new string object!
if not isNil(||assertBuf):
# BUGFIX: when debugging the GC, assertBuf may be nil
setLen(||assertBuf, 0)
add(||assertBuf, "[Assertion failure] file: ")
add(||assertBuf, file)
add(||assertBuf, " line: ")
add(||assertBuf, $line)
add(||assertBuf, "\n")
(||gAssertionFailed).msg = ||assertBuf
GC_enable()
if ||gAssertionFailed != nil:
raise ||gAssertionFailed
else:
c_fprintf(c_stdout, "Assertion failure: file %s line %ld\n", file, line)
quit(1)
proc WriteStackTrace() =
var s = ""
rawWriteStackTrace(s)
writeToStdErr(s)
var
dbgAborting: bool # whether the debugger wants to abort
proc signalHandler(sig: cint) {.exportc: "signalHandler", noconv.} =
# print stack trace and quit
ThreadGlobals()
var s = sig
GC_disable()
setLen(||buf, 0)
rawWriteStackTrace(||buf)
if s == SIGINT: add(||buf, "SIGINT: Interrupted by Ctrl-C.\n")
elif s == SIGSEGV:
add(||buf, "SIGSEGV: Illegal storage access. (Attempt to read from nil?)\n")
elif s == SIGABRT:
if dbgAborting: return # the debugger wants to abort
add(||buf, "SIGABRT: Abnormal termination.\n")
elif s == SIGFPE: add(||buf, "SIGFPE: Arithmetic error.\n")
elif s == SIGILL: add(||buf, "SIGILL: Illegal operation.\n")
elif s == SIGBUS:
add(||buf, "SIGBUS: Illegal storage access. (Attempt to read from nil?)\n")
else: add(||buf, "unknown signal\n")
writeToStdErr(||buf)
dbgAborting = True # play safe here...
GC_enable()
quit(1) # always quit when SIGABRT
proc registerSignalHandler() =
c_signal(SIGINT, signalHandler)
c_signal(SIGSEGV, signalHandler)
c_signal(SIGABRT, signalHandler)
c_signal(SIGFPE, signalHandler)
c_signal(SIGILL, signalHandler)
c_signal(SIGBUS, signalHandler)
when not defined(noSignalHandler):
registerSignalHandler() # call it in initialization section
proc raiseRangeError(val: biggestInt) {.compilerproc, noreturn, noinline.} =
raise newException(EOutOfRange, "value " & $val & " out of range")
proc raiseIndexError() {.compilerproc, noreturn, noinline.} =
raise newException(EInvalidIndex, "index out of bounds")
proc raiseFieldError(f: string) {.compilerproc, noreturn, noinline.} =
raise newException(EInvalidField, f & " is not accessible")
proc chckIndx(i, a, b: int): int =
if i >= a and i <= b:
return i
else:
raiseIndexError()
proc chckRange(i, a, b: int): int =
if i >= a and i <= b:
return i
else:
raiseRangeError(i)
proc chckRange64(i, a, b: int64): int64 {.compilerproc.} =
if i >= a and i <= b:
return i
else:
raiseRangeError(i)
proc chckRangeF(x, a, b: float): float =
if x >= a and x <= b:
return x
else:
raise newException(EOutOfRange, "value " & $x & " out of range")
proc chckNil(p: pointer) =
if p == nil: c_raise(SIGSEGV)
proc chckObj(obj, subclass: PNimType) {.compilerproc.} =
# checks if obj is of type subclass:
var x = obj
if x == subclass: return # optimized fast path
while x != subclass:
if x == nil:
raise newException(EInvalidObjectConversion, "invalid object conversion")
x = x.base
proc chckObjAsgn(a, b: PNimType) {.compilerproc, inline.} =
if a != b:
raise newException(EInvalidObjectAssignment, "invalid object assignment")
proc isObj(obj, subclass: PNimType): bool {.compilerproc.} =
# checks if obj is of type subclass:
var x = obj
if x == subclass: return true # optimized fast path
while x != subclass:
if x == nil: return false
x = x.base
return true