diff --git a/compiler/commands.nim b/compiler/commands.nim index cbf915ca67421..b4fa21f21b80b 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -919,6 +919,12 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; discard parseSaturatedNatural(arg, value) if not value > 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero") conf.maxLoopIterationsVM = value + of "maxcalldepthvm": + expectArg(conf, switch, arg, pass, info) + var value: int = 2_000 + discard parseSaturatedNatural(arg, value) + if not value > 0: localError(conf, info, "maxCallDepthVM must be a positive integer greater than zero") + conf.maxCallDepthVM = value of "errormax": expectArg(conf, switch, arg, pass, info) # Note: `nim check` (etc) can overwrite this. diff --git a/compiler/options.nim b/compiler/options.nim index 22af4bbd00dd4..0ef65a14048e9 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -385,6 +385,7 @@ type warnCounter*: int errorMax*: int maxLoopIterationsVM*: int ## VM: max iterations of all loops + maxCallDepthVM*: int ## VM: max call depth isVmTrace*: bool configVars*: StringTableRef symbols*: StringTableRef ## We need to use a StringTableRef here as defined @@ -600,6 +601,7 @@ proc newConfigRef*(): ConfigRef = arguments: "", suggestMaxResults: 10_000, maxLoopIterationsVM: 10_000_000, + maxCallDepthVM: 2_000, vmProfileData: newProfileData(), spellSuggestMax: spellSuggestSecretSauce, currentConfigDir: "" diff --git a/compiler/vm.nim b/compiler/vm.nim index 64242de578806..778583a31d273 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -516,6 +516,8 @@ const errIllegalConvFromXtoY = "illegal conversion from '$1' to '$2'" errTooManyIterations = "interpretation requires too many iterations; " & "if you are sure this is not a bug in your code, compile with `--maxLoopIterationsVM:number` (current value: $1)" + errCallDepthExceeded = "maximum call depth for the VM exceeded; " & + "if you are sure this is not a bug in your code, compile with `--maxCallDepthVM:number` (current value: $1)" errFieldXNotFound = "node lacks field: " @@ -590,7 +592,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let newPc = c.cleanUpOnReturn(tos) # Perform any cleanup action before returning if newPc < 0: - dec c.callDepth + inc(c.callDepth) pc = tos.comesFrom let retVal = regs[0] tos = tos.next @@ -1446,12 +1448,14 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = newFrame.slots[i] = regs[rb+i] if isClosure: newFrame.slots[rc] = TFullReg(kind: rkNode, node: regs[rb].node[1]) - if c.callDepth > 2000: - msgWriteln(c.config, "stack trace: (most recent call last)", {msgNoUnitSep}) - stackTraceAux(c, tos, pc) - globalError(c.config, c.debug[pc], "maximum call depth exceeded (2000)") - else: - inc c.callDepth + if c.callDepth <= 0: + if allowInfiniteRecursion in c.features: + c.callDepth = c.config.maxCallDepthVM + else: + msgWriteln(c.config, "stack trace: (most recent call last)", {msgNoUnitSep}) + stackTraceAux(c, tos, pc) + globalError(c.config, c.debug[pc], errCallDepthExceeded % $c.config.maxCallDepthVM) + dec(c.callDepth) tos = newFrame updateRegsAlias # -1 for the following 'inc pc' @@ -2318,6 +2322,7 @@ proc execute(c: PCtx, start: int): PNode = proc execProc*(c: PCtx; sym: PSym; args: openArray[PNode]): PNode = c.loopIterations = c.config.maxLoopIterationsVM + c.callDepth = c.config.maxCallDepthVM if sym.kind in routineKinds: if sym.typ.paramsLen != args.len: result = nil diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index 16278d051738b..3c39661127c7a 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -201,6 +201,7 @@ type TSandboxFlag* = enum ## what the evaluation engine should allow allowCast, ## allow unsafe language feature: 'cast' allowInfiniteLoops ## allow endless loops + allowInfiniteRecursion ## allow infinite recursion TSandboxFlags* = set[TSandboxFlag] TSlotKind* = enum # We try to re-use slots in a smart way to @@ -293,7 +294,8 @@ type proc newCtx*(module: PSym; cache: IdentCache; g: ModuleGraph; idgen: IdGenerator): PCtx = PCtx(code: @[], debug: @[], globals: newNode(nkStmtListExpr), constants: newNode(nkStmtList), types: @[], - prc: PProc(blocks: @[]), module: module, loopIterations: g.config.maxLoopIterationsVM, callDepth: 0, + prc: PProc(blocks: @[]), module: module, loopIterations: g.config.maxLoopIterationsVM, + callDepth: g.config.maxCallDepthVM, comesFromHeuristic: unknownLineInfo, callbacks: @[], callbackIndex: initTable[string, int](), errorFlag: "", cache: cache, config: g.config, graph: g, idgen: idgen) @@ -301,6 +303,7 @@ proc refresh*(c: PCtx, module: PSym; idgen: IdGenerator) = c.module = module c.prc = PProc(blocks: @[]) c.loopIterations = c.config.maxLoopIterationsVM + c.callDepth = c.config.maxCallDepthVM c.idgen = idgen proc reverseName(s: string): string = diff --git a/doc/advopt.txt b/doc/advopt.txt index 7c739ca7232bf..042c3879892a0 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -165,6 +165,7 @@ Advanced options: --verbosity:0|1|2|3 set Nim's verbosity level (1 is default) --errorMax:N stop compilation after N errors; 0 means unlimited --maxLoopIterationsVM:N set max iterations for all VM loops + --maxCallDepthVM:N set max call depth in the VM --experimental:$1 enable experimental language feature --legacy:$2 diff --git a/tests/vm/tinfiniterecursion.nim b/tests/vm/tinfiniterecursion.nim index 47920c7f43692..6ccdc107d2ffd 100644 --- a/tests/vm/tinfiniterecursion.nim +++ b/tests/vm/tinfiniterecursion.nim @@ -3,7 +3,7 @@ proc foo(x: int) = echo "done" else: foo(x + 1) #[tt.Error - ^ maximum call depth exceeded (2000)]# + ^ maximum call depth for the VM exceeded; if you are sure this is not a bug in your code, compile with `--maxCallDepthVM:number` (current value: 2000)]# static: foo(1)