stacktraces can now show custom runtime msgs per frame (#13351)

* stacktraces can now show custom runtime msgs
* improve tests/stdlib/tstackframes.nim
* fix test for --gc:arc
* test --stacktraceMsgs:on and --stacktraceMsgs:off
* --stacktracemsgs:off by default
This commit is contained in:
Timothee Cour
2020-03-30 04:45:32 -07:00
committed by GitHub
parent 8088633250
commit 19cab9fa51
14 changed files with 171 additions and 21 deletions

View File

@@ -489,6 +489,7 @@ struct TFrame_ {
NCSTRING filename;
NI16 len;
NI16 calldepth;
NI frameMsgLen;
};
#define NIM_POSIX_INIT __attribute__((constructor))

30
lib/std/stackframes.nim Normal file
View File

@@ -0,0 +1,30 @@
const NimStackTrace = compileOption("stacktrace")
const NimStackTraceMsgs = compileOption("stacktraceMsgs")
template procName*(): string =
## returns current C/C++ function name
when defined(c) or defined(cpp):
var name {.inject.}: cstring
{.emit: "`name` = __func__;".}
$name
template getPFrame*(): PFrame =
## avoids a function call (unlike `getFrame()`)
block:
when NimStackTrace:
var framePtr {.inject.}: PFrame
{.emit: "`framePtr` = &FR_;".}
framePtr
template setFrameMsg*(msg: string, prefix = " ") =
## attach a msg to current `PFrame`. This can be called multiple times
## in a given PFrame. Noop unless passing --stacktraceMsgs and --stacktrace
when NimStackTrace and NimStackTraceMsgs:
block:
var fr {.inject.}: PFrame
{.emit: "`fr` = &FR_;".}
# consider setting a custom upper limit on size (analog to stack overflow)
frameMsgBuf.setLen fr.frameMsgLen
frameMsgBuf.add prefix
frameMsgBuf.add msg
fr.frameMsgLen += prefix.len + msg.len

View File

@@ -54,6 +54,23 @@ type
include "system/basic_types"
proc compileOption*(option: string): bool {.
magic: "CompileOption", noSideEffect.}
## Can be used to determine an `on|off` compile-time option. Example:
##
## .. code-block:: Nim
## when compileOption("floatchecks"):
## echo "compiled with floating point NaN and Inf checks"
proc compileOption*(option, arg: string): bool {.
magic: "CompileOptionArg", noSideEffect.}
## Can be used to determine an enum compile-time option. Example:
##
## .. code-block:: Nim
## when compileOption("opt", "size") and compileOption("gc", "boehm"):
## echo "compiled with optimization for size and uses Boehm's GC"
{.push warning[GcMem]: off, warning[Uninit]: off.}
{.push hints: off.}
@@ -1040,22 +1057,6 @@ const
# emit this flag
# for string literals, it allows for some optimizations.
proc compileOption*(option: string): bool {.
magic: "CompileOption", noSideEffect.}
## Can be used to determine an `on|off` compile-time option. Example:
##
## .. code-block:: Nim
## when compileOption("floatchecks"):
## echo "compiled with floating point NaN and Inf checks"
proc compileOption*(option, arg: string): bool {.
magic: "CompileOptionArg", noSideEffect.}
## Can be used to determine an enum compile-time option. Example:
##
## .. code-block:: Nim
## when compileOption("opt", "size") and compileOption("gc", "boehm"):
## echo "compiled with optimization for size and uses Boehm's GC"
const
hasThreadSupport = compileOption("threads") and not defined(nimscript)
hasSharedHeap = defined(boehmgc) or defined(gogc) # don't share heaps; every thread has its own
@@ -1891,6 +1892,7 @@ var
type
PFrame* = ptr TFrame ## Represents a runtime frame of the call stack;
## part of the debugger API.
# keep in sync with nimbase.h `struct TFrame_`
TFrame* {.importc, nodecl, final.} = object ## The frame itself.
prev*: PFrame ## Previous frame; used for chaining the call stack.
procname*: cstring ## Name of the proc that is currently executing.
@@ -1898,6 +1900,8 @@ type
filename*: cstring ## Filename of the proc that is currently executing.
len*: int16 ## Length of the inspectable slots.
calldepth*: int16 ## Used for max call depth checking.
when NimStackTraceMsgs:
frameMsgLen*: int ## end position in frameMsgBuf for this frame.
when defined(js):
proc add*(x: var string, y: cstring) {.asmNoStackFrame.} =

View File

@@ -1,3 +1,7 @@
const NimStackTraceMsgs =
when defined(nimHasStacktraceMsgs): compileOption("stacktraceMsgs")
else: false
type
RootEffect* {.compilerproc.} = object of RootObj ## \
## Base effect class.
@@ -16,6 +20,11 @@ type
procname*: cstring ## Name of the proc that is currently executing.
line*: int ## Line number of the proc that is currently executing.
filename*: cstring ## Filename of the proc that is currently executing.
when NimStackTraceMsgs:
frameMsg*: string ## When a stacktrace is generated in a given frame and
## rendered at a later time, we should ensure the stacktrace
## data isn't invalidated; any pointer into PFrame is
## subject to being invalidated so shouldn't be stored.
Exception* {.compilerproc, magic: "Exception".} = object of RootObj ## \
## Base exception class.

View File

@@ -53,6 +53,8 @@ type
len: int
prev: ptr GcFrameHeader
when NimStackTraceMsgs:
var frameMsgBuf* {.threadvar.}: string
var
framePtr {.threadvar.}: PFrame
excHandler {.threadvar.}: PSafePoint
@@ -224,10 +226,17 @@ proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) =
s[last] = StackTraceEntry(procname: it.procname,
line: it.line,
filename: it.filename)
when NimStackTraceMsgs:
let first = if it.prev == nil: 0 else: it.prev.frameMsgLen
if it.frameMsgLen > first:
s[last].frameMsg.setLen(it.frameMsgLen - first)
# somehow string slicing not available here
for i in first .. it.frameMsgLen-1:
s[last].frameMsg[i-first] = frameMsgBuf[i]
it = it.prev
dec last
template addFrameEntry(s, f: untyped) =
template addFrameEntry(s: var string, f: StackTraceEntry|PFrame) =
var oldLen = s.len
add(s, f.filename)
if f.line > 0:
@@ -236,6 +245,12 @@ template addFrameEntry(s, f: untyped) =
add(s, ')')
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
add(s, f.procname)
when NimStackTraceMsgs:
when type(f) is StackTraceEntry:
add(s, f.frameMsg)
else:
var first = if f.prev == nil: 0 else: f.prev.frameMsgLen
for i in first..<f.frameMsgLen: add(s, frameMsgBuf[i])
add(s, "\n")
proc `$`(s: seq[StackTraceEntry]): string =
@@ -519,7 +534,12 @@ proc callDepthLimitReached() {.noinline.} =
quit(1)
proc nimFrame(s: PFrame) {.compilerRtl, inl, raises: [].} =
s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1
if framePtr == nil:
s.calldepth = 0
when NimStackTraceMsgs: s.frameMsgLen = 0
else:
s.calldepth = framePtr.calldepth+1
when NimStackTraceMsgs: s.frameMsgLen = framePtr.frameMsgLen
s.prev = framePtr
framePtr = s
if s.calldepth == nimCallDepthLimit: callDepthLimitReached()