Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stacktraces can now show custom runtime msgs per frame #13351

Merged
merged 11 commits into from
Mar 30, 2020
Next Next commit
stacktraces can now show custom runtime msgs
  • Loading branch information
timotheecour committed Mar 25, 2020
commit 1e85eb038acd3f7a283d58a2f971b65f1015f1a7
3 changes: 3 additions & 0 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2554,8 +2554,11 @@ proc shouldBeBracketExpr(n: PNode): bool =
n[0] = be
return true

import std/stackframes

proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
result = n
setFrameMsg c.config$n.info & " " & $n.kind
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
if nfSem in n.flags: return
case n.kind
Expand Down
1 change: 1 addition & 0 deletions lib/nimbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ struct TFrame_ {
NCSTRING filename;
NI16 len;
NI16 calldepth;
NI frameMsgLen; // should that be int64?
};

#define NIM_POSIX_INIT __attribute__((constructor))
Expand Down
31 changes: 31 additions & 0 deletions lib/std/stackframes.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const NimStackTrace = compileOption("stacktrace")

template procName*(): string =
var name {.inject.}: cstring
{.emit: "`name` = __func__;".}
$name
# import std/strutils
# ($name).rsplit('_', 1)[0]

# TODO:proc getFrame*(): PFrame {.compilerRtl, inl.} = framePtr
template getPFrame*(): PFrame =
block:
# note: PFrame.calldepth (among other fields...) seems useful
when NimStackTrace:
# IMPROVE: this does a double pointer copy (not a huge deal but still)
var framePtr {.inject.}: PFrame
{.emit: "`framePtr` = &FR_;".}
framePtr

template setFrameMsg*(msg: string) =
## attach a msg to current PFrame. This can be called multiple times
## in a given PFrame.
when NimStackTrace:
let fr = getPFrame()
# let fr = getFrame()
# let fr = framePtr
# TODO: instead realloc etc; TODO: also, js
# TODO: set an upper limit? maybe not needed unless stack grows large AND lots of msgs are written
frameMsgBuf.setLen fr.frameMsgLen
frameMsgBuf.add msg
fr.frameMsgLen += msg.len
23 changes: 12 additions & 11 deletions lib/system.nim
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,18 @@ type
RootRef* = ref RootObj ## Reference to `RootObj`.


type
PFrame* = ptr TFrame ## Represents a runtime frame of the call stack;
## part of the debugger API.
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.
line*: int ## Line number of the proc that is currently executing.
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.
frameMsgLen*: int

include "system/exceptions"

when defined(js) or defined(nimdoc):
Expand Down Expand Up @@ -1888,17 +1900,6 @@ var
## writes an error message and terminates the program, except when
## using `--os:any`

type
PFrame* = ptr TFrame ## Represents a runtime frame of the call stack;
## part of the debugger API.
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.
line*: int ## Line number of the proc that is currently executing.
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 defined(js):
proc add*(x: var string, y: cstring) {.asmNoStackFrame.} =
asm """
Expand Down
1 change: 1 addition & 0 deletions lib/system/exceptions.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ 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.
pframe*: PFrame

Exception* {.compilerproc, magic: "Exception".} = object of RootObj ## \
## Base exception class.
Expand Down
18 changes: 16 additions & 2 deletions lib/system/excpt.nim
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,14 @@ proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) =
while it != nil:
s[last] = StackTraceEntry(procname: it.procname,
line: it.line,
filename: it.filename)
filename: it.filename,
pframe: it)
it = it.prev
dec last

var
frameMsgBuf* {.threadvar.}: string # TODO: more lowlevel, like cstring + malloc? eg for gc

template addFrameEntry(s, f: untyped) =
var oldLen = s.len
add(s, f.filename)
Expand All @@ -236,6 +240,11 @@ template addFrameEntry(s, f: untyped) =
add(s, ')')
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
add(s, f.procname)
add(s, " ")
let pframe = when type(f) is StackTraceEntry: f.pframe
else: f
var old = if pframe.prev == nil: 0 else: pframe.prev.frameMsgLen
for i in old..<pframe.frameMsgLen: add(s, frameMsgBuf[i])
add(s, "\n")

proc `$`(s: seq[StackTraceEntry]): string =
Expand Down Expand Up @@ -519,7 +528,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
s.frameMsgLen = 0
else:
s.calldepth = framePtr.calldepth+1
s.frameMsgLen = framePtr.frameMsgLen
s.prev = framePtr
framePtr = s
if s.calldepth == nimCallDepthLimit: callDepthLimitReached()
Expand Down
19 changes: 19 additions & 0 deletions tests/stdlib/tstackframes.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import std/stackframes

proc main2(n: int) =
setFrameMsg $(n,)
if n > 0:
main2(n-1)

proc main(n: int) =
#[
TODO: instead, simply allow setting custom frame msg
]#
setFrameMsg $(n,)
proc bar() =
setFrameMsg "in bar "
doAssert n >= 3
bar()
main(n-1)

main(10)