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:
parent
8088633250
commit
19cab9fa51
14 changed files with 171 additions and 21 deletions
|
|
@ -150,6 +150,9 @@ echo f
|
||||||
- `net.newContext` now performs SSL Certificate checking on Linux and OSX.
|
- `net.newContext` now performs SSL Certificate checking on Linux and OSX.
|
||||||
Define `nimDisableCertificateValidation` to disable it globally.
|
Define `nimDisableCertificateValidation` to disable it globally.
|
||||||
- new syntax for lvalue references: `var b {.byaddr.} = expr` enabled by `import pragmas`
|
- new syntax for lvalue references: `var b {.byaddr.} = expr` enabled by `import pragmas`
|
||||||
|
- new module `std/stackframes`, in particular `setFrameMsg` which enables
|
||||||
|
custom runtime annotation of stackframes, see #13351 for examples. Turn on/off via
|
||||||
|
`--stackTraceMsgs:on/off`
|
||||||
|
|
||||||
## Language additions
|
## Language additions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -281,6 +281,7 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool
|
||||||
of "hints": result = contains(conf.options, optHints)
|
of "hints": result = contains(conf.options, optHints)
|
||||||
of "threadanalysis": result = contains(conf.globalOptions, optThreadAnalysis)
|
of "threadanalysis": result = contains(conf.globalOptions, optThreadAnalysis)
|
||||||
of "stacktrace": result = contains(conf.options, optStackTrace)
|
of "stacktrace": result = contains(conf.options, optStackTrace)
|
||||||
|
of "stacktracemsgs": result = contains(conf.options, optStackTraceMsgs)
|
||||||
of "linetrace": result = contains(conf.options, optLineTrace)
|
of "linetrace": result = contains(conf.options, optLineTrace)
|
||||||
of "debugger": result = contains(conf.globalOptions, optCDebug)
|
of "debugger": result = contains(conf.globalOptions, optCDebug)
|
||||||
of "profiler": result = contains(conf.options, optProfiler)
|
of "profiler": result = contains(conf.options, optProfiler)
|
||||||
|
|
@ -531,6 +532,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||||
if processOnOffSwitchOrList(conf, {optHints}, arg, pass, info): listHints(conf)
|
if processOnOffSwitchOrList(conf, {optHints}, arg, pass, info): listHints(conf)
|
||||||
of "threadanalysis": processOnOffSwitchG(conf, {optThreadAnalysis}, arg, pass, info)
|
of "threadanalysis": processOnOffSwitchG(conf, {optThreadAnalysis}, arg, pass, info)
|
||||||
of "stacktrace": processOnOffSwitch(conf, {optStackTrace}, arg, pass, info)
|
of "stacktrace": processOnOffSwitch(conf, {optStackTrace}, arg, pass, info)
|
||||||
|
of "stacktracemsgs": processOnOffSwitch(conf, {optStackTraceMsgs}, arg, pass, info)
|
||||||
of "excessivestacktrace": processOnOffSwitchG(conf, {optExcessiveStackTrace}, arg, pass, info)
|
of "excessivestacktrace": processOnOffSwitchG(conf, {optExcessiveStackTrace}, arg, pass, info)
|
||||||
of "linetrace": processOnOffSwitch(conf, {optLineTrace}, arg, pass, info)
|
of "linetrace": processOnOffSwitch(conf, {optLineTrace}, arg, pass, info)
|
||||||
of "debugger":
|
of "debugger":
|
||||||
|
|
|
||||||
|
|
@ -114,3 +114,4 @@ proc initDefines*(symbols: StringTableRef) =
|
||||||
|
|
||||||
defineSymbol("nimHasSinkInference")
|
defineSymbol("nimHasSinkInference")
|
||||||
defineSymbol("nimNewIntegerOps")
|
defineSymbol("nimNewIntegerOps")
|
||||||
|
defineSymbol("nimHasStacktraceMsgs")
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@ type # please make sure we have under 32 options
|
||||||
optOverflowCheck, optNilCheck, optRefCheck,
|
optOverflowCheck, optNilCheck, optRefCheck,
|
||||||
optNaNCheck, optInfCheck, optStaticBoundsCheck, optStyleCheck,
|
optNaNCheck, optInfCheck, optStaticBoundsCheck, optStyleCheck,
|
||||||
optAssert, optLineDir, optWarns, optHints,
|
optAssert, optLineDir, optWarns, optHints,
|
||||||
optOptimizeSpeed, optOptimizeSize, optStackTrace, # stack tracing support
|
optOptimizeSpeed, optOptimizeSize,
|
||||||
|
optStackTrace, # stack tracing support
|
||||||
|
optStackTraceMsgs, # enable custom runtime msgs via `setFrameMsg`
|
||||||
optLineTrace, # line tracing support (includes stack tracing)
|
optLineTrace, # line tracing support (includes stack tracing)
|
||||||
optByRef, # use pass by ref for objects
|
optByRef, # use pass by ref for objects
|
||||||
# (for interfacing with C)
|
# (for interfacing with C)
|
||||||
|
|
@ -325,7 +327,7 @@ const
|
||||||
|
|
||||||
DefaultOptions* = {optObjCheck, optFieldCheck, optRangeCheck,
|
DefaultOptions* = {optObjCheck, optFieldCheck, optRangeCheck,
|
||||||
optBoundsCheck, optOverflowCheck, optAssert, optWarns, optRefCheck,
|
optBoundsCheck, optOverflowCheck, optAssert, optWarns, optRefCheck,
|
||||||
optHints, optStackTrace, optLineTrace,
|
optHints, optStackTrace, optLineTrace, # consider adding `optStackTraceMsgs`
|
||||||
optTrMacros, optNilCheck, optStyleCheck, optSinkInference}
|
optTrMacros, optNilCheck, optStyleCheck, optSinkInference}
|
||||||
DefaultGlobalOptions* = {optThreadAnalysis,
|
DefaultGlobalOptions* = {optThreadAnalysis,
|
||||||
optExcessiveStackTrace, optListFullPaths}
|
optExcessiveStackTrace, optListFullPaths}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@
|
||||||
# this module does the semantic checking for expressions
|
# this module does the semantic checking for expressions
|
||||||
# included from sem.nim
|
# included from sem.nim
|
||||||
|
|
||||||
|
when defined(nimCompilerStackraceHints):
|
||||||
|
import std/stackframes
|
||||||
|
|
||||||
const
|
const
|
||||||
errExprXHasNoType = "expression '$1' has no type (or is ambiguous)"
|
errExprXHasNoType = "expression '$1' has no type (or is ambiguous)"
|
||||||
errXExpectsTypeOrValue = "'$1' expects a type or value"
|
errXExpectsTypeOrValue = "'$1' expects a type or value"
|
||||||
|
|
@ -2557,6 +2560,8 @@ proc shouldBeBracketExpr(n: PNode): bool =
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
|
proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
|
||||||
|
when defined(nimCompilerStackraceHints):
|
||||||
|
setFrameMsg c.config$n.info & " " & $n.kind
|
||||||
result = n
|
result = n
|
||||||
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
|
if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
|
||||||
if nfSem in n.flags: return
|
if nfSem in n.flags: return
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ Advanced options:
|
||||||
turn support for hot code reloading on|off
|
turn support for hot code reloading on|off
|
||||||
--excessiveStackTrace:on|off
|
--excessiveStackTrace:on|off
|
||||||
stack traces use full file paths
|
stack traces use full file paths
|
||||||
|
--stackTraceMsgs:on|off enable user defined stack frame msgs via `setFrameMsg`
|
||||||
--oldNewlines:on|off turn on|off the old behaviour of "\n"
|
--oldNewlines:on|off turn on|off the old behaviour of "\n"
|
||||||
--laxStrings:on|off when turned on, accessing the zero terminator in
|
--laxStrings:on|off when turned on, accessing the zero terminator in
|
||||||
strings is allowed; only for backwards compatibility
|
strings is allowed; only for backwards compatibility
|
||||||
|
|
|
||||||
|
|
@ -489,6 +489,7 @@ struct TFrame_ {
|
||||||
NCSTRING filename;
|
NCSTRING filename;
|
||||||
NI16 len;
|
NI16 len;
|
||||||
NI16 calldepth;
|
NI16 calldepth;
|
||||||
|
NI frameMsgLen;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define NIM_POSIX_INIT __attribute__((constructor))
|
#define NIM_POSIX_INIT __attribute__((constructor))
|
||||||
|
|
|
||||||
30
lib/std/stackframes.nim
Normal file
30
lib/std/stackframes.nim
Normal 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
|
||||||
|
|
@ -54,6 +54,23 @@ type
|
||||||
|
|
||||||
include "system/basic_types"
|
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 warning[GcMem]: off, warning[Uninit]: off.}
|
||||||
{.push hints: off.}
|
{.push hints: off.}
|
||||||
|
|
||||||
|
|
@ -1040,22 +1057,6 @@ const
|
||||||
# emit this flag
|
# emit this flag
|
||||||
# for string literals, it allows for some optimizations.
|
# 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
|
const
|
||||||
hasThreadSupport = compileOption("threads") and not defined(nimscript)
|
hasThreadSupport = compileOption("threads") and not defined(nimscript)
|
||||||
hasSharedHeap = defined(boehmgc) or defined(gogc) # don't share heaps; every thread has its own
|
hasSharedHeap = defined(boehmgc) or defined(gogc) # don't share heaps; every thread has its own
|
||||||
|
|
@ -1891,6 +1892,7 @@ var
|
||||||
type
|
type
|
||||||
PFrame* = ptr TFrame ## Represents a runtime frame of the call stack;
|
PFrame* = ptr TFrame ## Represents a runtime frame of the call stack;
|
||||||
## part of the debugger API.
|
## part of the debugger API.
|
||||||
|
# keep in sync with nimbase.h `struct TFrame_`
|
||||||
TFrame* {.importc, nodecl, final.} = object ## The frame itself.
|
TFrame* {.importc, nodecl, final.} = object ## The frame itself.
|
||||||
prev*: PFrame ## Previous frame; used for chaining the call stack.
|
prev*: PFrame ## Previous frame; used for chaining the call stack.
|
||||||
procname*: cstring ## Name of the proc that is currently executing.
|
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.
|
filename*: cstring ## Filename of the proc that is currently executing.
|
||||||
len*: int16 ## Length of the inspectable slots.
|
len*: int16 ## Length of the inspectable slots.
|
||||||
calldepth*: int16 ## Used for max call depth checking.
|
calldepth*: int16 ## Used for max call depth checking.
|
||||||
|
when NimStackTraceMsgs:
|
||||||
|
frameMsgLen*: int ## end position in frameMsgBuf for this frame.
|
||||||
|
|
||||||
when defined(js):
|
when defined(js):
|
||||||
proc add*(x: var string, y: cstring) {.asmNoStackFrame.} =
|
proc add*(x: var string, y: cstring) {.asmNoStackFrame.} =
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
const NimStackTraceMsgs =
|
||||||
|
when defined(nimHasStacktraceMsgs): compileOption("stacktraceMsgs")
|
||||||
|
else: false
|
||||||
|
|
||||||
type
|
type
|
||||||
RootEffect* {.compilerproc.} = object of RootObj ## \
|
RootEffect* {.compilerproc.} = object of RootObj ## \
|
||||||
## Base effect class.
|
## Base effect class.
|
||||||
|
|
@ -16,6 +20,11 @@ type
|
||||||
procname*: cstring ## Name of the proc that is currently executing.
|
procname*: cstring ## Name of the proc that is currently executing.
|
||||||
line*: int ## Line number 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.
|
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 ## \
|
Exception* {.compilerproc, magic: "Exception".} = object of RootObj ## \
|
||||||
## Base exception class.
|
## Base exception class.
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ type
|
||||||
len: int
|
len: int
|
||||||
prev: ptr GcFrameHeader
|
prev: ptr GcFrameHeader
|
||||||
|
|
||||||
|
when NimStackTraceMsgs:
|
||||||
|
var frameMsgBuf* {.threadvar.}: string
|
||||||
var
|
var
|
||||||
framePtr {.threadvar.}: PFrame
|
framePtr {.threadvar.}: PFrame
|
||||||
excHandler {.threadvar.}: PSafePoint
|
excHandler {.threadvar.}: PSafePoint
|
||||||
|
|
@ -224,10 +226,17 @@ proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) =
|
||||||
s[last] = StackTraceEntry(procname: it.procname,
|
s[last] = StackTraceEntry(procname: it.procname,
|
||||||
line: it.line,
|
line: it.line,
|
||||||
filename: it.filename)
|
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
|
it = it.prev
|
||||||
dec last
|
dec last
|
||||||
|
|
||||||
template addFrameEntry(s, f: untyped) =
|
template addFrameEntry(s: var string, f: StackTraceEntry|PFrame) =
|
||||||
var oldLen = s.len
|
var oldLen = s.len
|
||||||
add(s, f.filename)
|
add(s, f.filename)
|
||||||
if f.line > 0:
|
if f.line > 0:
|
||||||
|
|
@ -236,6 +245,12 @@ template addFrameEntry(s, f: untyped) =
|
||||||
add(s, ')')
|
add(s, ')')
|
||||||
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
|
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
|
||||||
add(s, f.procname)
|
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")
|
add(s, "\n")
|
||||||
|
|
||||||
proc `$`(s: seq[StackTraceEntry]): string =
|
proc `$`(s: seq[StackTraceEntry]): string =
|
||||||
|
|
@ -519,7 +534,12 @@ proc callDepthLimitReached() {.noinline.} =
|
||||||
quit(1)
|
quit(1)
|
||||||
|
|
||||||
proc nimFrame(s: PFrame) {.compilerRtl, inl, raises: [].} =
|
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
|
s.prev = framePtr
|
||||||
framePtr = s
|
framePtr = s
|
||||||
if s.calldepth == nimCallDepthLimit: callDepthLimitReached()
|
if s.calldepth == nimCallDepthLimit: callDepthLimitReached()
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,4 @@ try:
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
let e = getCurrentException()
|
let e = getCurrentException()
|
||||||
let trace = e.getStackTrace
|
let trace = e.getStackTrace
|
||||||
echo tmatch(trace, expected)
|
if tmatch(trace, expected): echo true else: echo "wrong trace:\n" & trace
|
||||||
|
|
|
||||||
38
tests/stdlib/mstackframes.nim
Normal file
38
tests/stdlib/mstackframes.nim
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import std/stackframes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# line 5
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
proc main1(n: int) =
|
||||||
|
setFrameMsg $("main1", n)
|
||||||
|
if n > 0:
|
||||||
|
main1(n-1)
|
||||||
|
|
||||||
|
proc main2(n: int) =
|
||||||
|
count.inc
|
||||||
|
setFrameMsg $("main2", n, count)
|
||||||
|
proc bar() =
|
||||||
|
setFrameMsg $("bar ",)
|
||||||
|
if n < 3: raise newException(CatchableError, "on purpose")
|
||||||
|
bar()
|
||||||
|
main2(n-1)
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
var z = 0
|
||||||
|
setFrameMsg "\n z: " & $z, prefix = ""
|
||||||
|
# multiple calls inside a frame are possible
|
||||||
|
z.inc
|
||||||
|
setFrameMsg "\n z: " & $z, prefix = ""
|
||||||
|
try:
|
||||||
|
main2(5)
|
||||||
|
except CatchableError:
|
||||||
|
main1(10) # goes deep and then unwinds; sanity check to ensure `setFrameMsg` from inside
|
||||||
|
# `main1` won't invalidate the stacktrace; if StackTraceEntry.frameMsg
|
||||||
|
# were a reference instead of a copy, this would fail.
|
||||||
|
let e = getCurrentException()
|
||||||
|
let trace = e.getStackTrace
|
||||||
|
echo trace
|
||||||
|
|
||||||
|
main()
|
||||||
34
tests/stdlib/tstackframes.nim
Normal file
34
tests/stdlib/tstackframes.nim
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import std/[strformat,os,osproc]
|
||||||
|
import "$nim/compiler/unittest_light" # works even if moved by megatest
|
||||||
|
|
||||||
|
proc main(opt: string, expected: string) =
|
||||||
|
const nim = getCurrentCompilerExe()
|
||||||
|
const file = currentSourcePath().parentDir / "mstackframes.nim"
|
||||||
|
let cmd = fmt"{nim} c -r --excessiveStackTrace:off --stacktraceMsgs:{opt} --hints:off {file}"
|
||||||
|
let (output, exitCode) = execCmdEx(cmd)
|
||||||
|
assertEquals output, expected
|
||||||
|
doAssert exitCode == 0
|
||||||
|
|
||||||
|
main("on"): """
|
||||||
|
mstackframes.nim(38) mstackframes
|
||||||
|
mstackframes.nim(29) main
|
||||||
|
z: 0
|
||||||
|
z: 1
|
||||||
|
mstackframes.nim(20) main2 ("main2", 5, 1)
|
||||||
|
mstackframes.nim(20) main2 ("main2", 4, 2)
|
||||||
|
mstackframes.nim(20) main2 ("main2", 3, 3)
|
||||||
|
mstackframes.nim(19) main2 ("main2", 2, 4)
|
||||||
|
mstackframes.nim(18) bar ("bar ",)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
main("off"): """
|
||||||
|
mstackframes.nim(38) mstackframes
|
||||||
|
mstackframes.nim(29) main
|
||||||
|
mstackframes.nim(20) main2
|
||||||
|
mstackframes.nim(20) main2
|
||||||
|
mstackframes.nim(20) main2
|
||||||
|
mstackframes.nim(19) main2
|
||||||
|
mstackframes.nim(18) bar
|
||||||
|
|
||||||
|
"""
|
||||||
Loading…
Add table
Add a link
Reference in a new issue