diff --git a/compiler/ast.nim b/compiler/ast.nim index 7bcb7570d0064..0daa1f11f29d4 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1763,7 +1763,7 @@ proc toObject*(typ: PType): PType = proc isImportedException*(t: PType; conf: ConfigRef): bool = assert t != nil - if optNoCppExceptions in conf.globalOptions: + if conf.exc != excCpp: return false let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst}) @@ -1834,7 +1834,7 @@ template assignment*(t: PType): PSym = t.attachedOps[attachedAsgn] template asink*(t: PType): PSym = t.attachedOps[attachedSink] const magicsThatCanRaise = { - mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst} + mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst, mEcho} proc canRaiseConservative*(fn: PNode): bool = if fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise: @@ -1844,9 +1844,12 @@ proc canRaiseConservative*(fn: PNode): bool = proc canRaise*(fn: PNode): bool = if fn.kind == nkSym and (fn.sym.magic notin magicsThatCanRaise or - {sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc}): + {sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc} or + sfGeneratedOp in fn.sym.flags): result = false + elif fn.kind == nkSym and fn.sym.magic == mEcho: + result = true else: - result = fn.typ != nil and ((fn.typ.n[0].len < effectListLen) or + result = fn.typ != nil and fn.typ.n != nil and ((fn.typ.n[0].len < effectListLen) or (fn.typ.n[0][exceptionEffects] != nil and fn.typ.n[0][exceptionEffects].safeLen > 0)) diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index b5fffaddb3cc0..da5dd9b766d40 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -567,6 +567,8 @@ proc genCall(p: BProc, e: PNode, d: var TLoc) = else: genPrefixCall(p, nil, e, d) postStmtActions(p) + if p.config.exc == excGoto and canRaise(e[0]): + raiseExit(p) proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) = if ri[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure: @@ -578,3 +580,5 @@ proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) = else: genPrefixCall(p, le, ri, d) postStmtActions(p) + if p.config.exc == excGoto and canRaise(ri[0]): + raiseExit(p) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index e1f4d180d4da4..8c41fb370fc56 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -2675,10 +2675,13 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = line(p, cpsStmts, "(void)(" & a.r & ");\L") of nkAsmStmt: genAsmStmt(p, n) of nkTryStmt, nkHiddenTryStmt: - if p.module.compileToCpp and optNoCppExceptions notin p.config.globalOptions: + case p.config.exc + of excGoto: + genTryGoto(p, n, d) + of excCpp: genTryCpp(p, n, d) else: - genTry(p, n, d) + genTrySetjmp(p, n, d) of nkRaiseStmt: genRaiseStmt(p, n) of nkTypeSection: # we have to emit the type information for object types here to support diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 86125af66bdaf..4c342aaf690fd 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -196,13 +196,13 @@ proc genState(p: BProc, n: PNode) = proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = # Called by return and break stmts. - # Deals with issues faced when jumping out of try/except/finally stmts, + # Deals with issues faced when jumping out of try/except/finally stmts. - var stack = newSeq[tuple[fin: PNode, inExcept: bool]](0) + var stack = newSeq[tuple[fin: PNode, inExcept: bool, label: Natural]](0) for i in 1..howManyTrys: let tryStmt = p.nestedTryStmts.pop - if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions: + if p.config.exc == excSetjmp: # Pop safe points generated by try if not tryStmt.inExcept: linefmt(p, cpsStmts, "#popSafePoint();$n", []) @@ -221,10 +221,10 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = for i in countdown(howManyTrys-1, 0): p.nestedTryStmts.add(stack[i]) - if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions: + if p.config.exc != excCpp: # Pop exceptions that was handled by the # except-blocks we are in - if not p.noSafePoints: + if noSafePoints notin p.flags: for i in countdown(howManyExcepts-1, 0): linefmt(p, cpsStmts, "#popCurrentException();$n", []) @@ -237,7 +237,7 @@ proc genGotoState(p: BProc, n: PNode) = var a: TLoc initLocExpr(p, n[0], a) lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)]) - p.beforeRetNeeded = true + p.flags.incl beforeRetNeeded lineF(p, cpsStmts, "case -1:$n", []) blockLeaveActions(p, howManyTrys = p.nestedTryStmts.len, @@ -454,13 +454,13 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) = proc genReturnStmt(p: BProc, t: PNode) = if nfPreventCg in t.flags: return - p.beforeRetNeeded = true + p.flags.incl beforeRetNeeded genLineDir(p, t) if (t[0].kind != nkEmpty): genStmts(p, t[0]) blockLeaveActions(p, howManyTrys = p.nestedTryStmts.len, howManyExcepts = p.inExceptBlockLen) - if (p.finallySafePoints.len > 0) and not p.noSafePoints: + if (p.finallySafePoints.len > 0) and noSafePoints notin p.flags: # If we're in a finally block, and we came here by exception # consume it before we return. var safePoint = p.finallySafePoints[^1] @@ -687,15 +687,27 @@ proc genBreakStmt(p: BProc, t: PNode) = genLineDir(p, t) lineF(p, cpsStmts, "goto $1;$n", [label]) +proc raiseExit(p: BProc) = + assert p.config.exc == excGoto + p.flags.incl nimErrorFlagAccessed + if p.nestedTryStmts.len == 0: + p.flags.incl beforeRetNeeded + # easy case, simply goto 'ret': + lineCg(p, cpsStmts, "if (NIM_UNLIKELY(*nimErr_)) goto BeforeRet_;$n", []) + else: + lineCg(p, cpsStmts, "if (NIM_UNLIKELY(*nimErr_)) goto LA$1_;$n", + [p.nestedTryStmts[^1].label]) + proc genRaiseStmt(p: BProc, t: PNode) = - if p.module.compileToCpp: - discard cgsym(p.module, "popCurrentExceptionEx") - if p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept: - # if the current try stmt have a finally block, - # we must execute it before reraising - let finallyBlock = p.nestedTryStmts[^1].fin - if finallyBlock != nil: - genSimpleBlock(p, finallyBlock[0]) + if p.config.exc != excGoto: + if p.config.exc == excCpp: + discard cgsym(p.module, "popCurrentExceptionEx") + if p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept: + # if the current try stmt have a finally block, + # we must execute it before reraising + let finallyBlock = p.nestedTryStmts[^1].fin + if finallyBlock != nil: + genSimpleBlock(p, finallyBlock[0]) if t[0].kind != nkEmpty: var a: TLoc initLocExprSingleUse(p, t[0], a) @@ -714,10 +726,22 @@ proc genRaiseStmt(p: BProc, t: PNode) = else: genLineDir(p, t) # reraise the last exception: - if p.module.compileToCpp and optNoCppExceptions notin p.config.globalOptions: + if p.config.exc == excCpp: line(p, cpsStmts, ~"throw;$n") else: linefmt(p, cpsStmts, "#reraiseException();$n", []) + if p.config.exc == excGoto: + let L = p.nestedTryStmts.len + if L == 0: + p.flags.incl beforeRetNeeded + # easy case, simply goto 'ret': + lineCg(p, cpsStmts, "goto BeforeRet_;$n", []) + else: + # raise inside an 'except' must go to the finally block, + # raise outside an 'except' block must go to the 'except' list. + lineCg(p, cpsStmts, "goto LA$1_;$n", + [p.nestedTryStmts[L-1].label]) + # + ord(p.nestedTryStmts[L-1].inExcept)]) template genCaseGenericBranch(p: BProc, b: PNode, e: TLoc, rangeFormat, eqFormat: FormatStr, labl: TLabel) = @@ -906,8 +930,8 @@ proc genCase(p: BProc, t: PNode, d: var TLoc) = proc genRestoreFrameAfterException(p: BProc) = if optStackTrace in p.module.config.options: - if not p.hasCurFramePointer: - p.hasCurFramePointer = true + if hasCurFramePointer notin p.flags: + p.flags.incl hasCurFramePointer p.procSec(cpsLocals).add(ropecg(p.module, "\tTFrame* _nimCurFrame;$n", [])) p.procSec(cpsInit).add(ropecg(p.module, "\t_nimCurFrame = #getFrame();$n", [])) linefmt(p, cpsStmts, "#setFrame(_nimCurFrame);$n", []) @@ -938,7 +962,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = genLineDir(p, t) discard cgsym(p.module, "popCurrentExceptionEx") let fin = if t[^1].kind == nkFinally: t[^1] else: nil - p.nestedTryStmts.add((fin, false)) + p.nestedTryStmts.add((fin, false, 0.Natural)) startBlock(p, "try {$n") expr(p, t[0], d) endBlock(p) @@ -982,7 +1006,116 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = genSimpleBlock(p, t[^1][0]) -proc genTry(p: BProc, t: PNode, d: var TLoc) = +proc bodyCanRaise(n: PNode): bool = + case n.kind + of nkCallKinds: + result = canRaise(n[0]) + if not result: + # also check the arguments: + for i in 1 ..< n.len: + if bodyCanRaise(n[i]): return true + of nkRaiseStmt: + result = true + of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, + nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef: + result = false + else: + for i in 0 ..< safeLen(n): + if bodyCanRaise(n[i]): return true + result = false + +proc genTryGoto(p: BProc; t: PNode; d: var TLoc) = + if not bodyCanRaise(t): + # optimize away the 'try' block: + expr(p, t[0], d) + if t.len > 1 and t[^1].kind == nkFinally: + genStmts(p, t[^1][0]) + return + + let fin = if t[^1].kind == nkFinally: t[^1] else: nil + inc p.labels, 2 + let lab = p.labels-1 + p.nestedTryStmts.add((fin, false, Natural lab)) + + p.flags.incl nimErrorFlagAccessed + linefmt(p, cpsStmts, "NI oldNimErr$1_ = *nimErr_; *nimErr_ = 0;;$n", [lab]) + + expr(p, t[0], d) + + if 1 < t.len and t[1].kind == nkExceptBranch: + startBlock(p, "if (NIM_UNLIKELY(*nimErr_)) {$n") + else: + startBlock(p) + # pretend we did handle the error for the safe execution of the sections: + linefmt(p, cpsStmts, "LA$1_: oldNimErr$1_ = *nimErr_; *nimErr_ = 0;$n", [lab]) + + p.nestedTryStmts[^1].inExcept = true + var i = 1 + while (i < t.len) and (t[i].kind == nkExceptBranch): + + inc p.labels + let nextExcept = p.labels + p.nestedTryStmts[^1].label = nextExcept + + # bug #4230: avoid false sharing between branches: + if d.k == locTemp and isEmptyType(t.typ): d.k = locNone + if t[i].len == 1: + # general except section: + if i > 1: lineF(p, cpsStmts, "else", []) + startBlock(p) + # we handled the exception, remember this: + linefmt(p, cpsStmts, "--oldNimErr$1_;$n", [lab]) + expr(p, t[i][0], d) + else: + var orExpr: Rope = nil + for j in 0..$1, $2)", [memberName, checkFor]) + + if i > 1: line(p, cpsStmts, "else ") + startBlock(p, "if ($1) {$n", [orExpr]) + # we handled the exception, remember this: + linefmt(p, cpsStmts, "--oldNimErr$1_;$n", [lab]) + expr(p, t[i][^1], d) + + linefmt(p, cpsStmts, "#popCurrentException();$n", []) + linefmt(p, cpsStmts, "LA$1_:;$n", [nextExcept]) + endBlock(p) + + inc(i) + discard pop(p.nestedTryStmts) + endBlock(p) + + #linefmt(p, cpsStmts, "LA$1_:;$n", [lab+1]) + if i < t.len and t[i].kind == nkFinally: + startBlock(p) + if not bodyCanRaise(t[i][0]): + # this is an important optimization; most destroy blocks are detected not to raise an + # exception and so we help the C optimizer by not mutating nimErr_ pointlessly: + genStmts(p, t[i][0]) + else: + # pretend we did handle the error for the safe execution of the 'finally' section: + linefmt(p, cpsStmts, "NI oldNimErrFin$1_ = *nimErr_; *nimErr_ = 0;$n", [lab]) + genStmts(p, t[i][0]) + # this is correct for all these cases: + # 1. finally is run during ordinary control flow + # 2. finally is run after 'except' block handling: these however set the + # error back to nil. + # 3. finally is run for exception handling code without any 'except' + # handler present or only handlers that did not match. + linefmt(p, cpsStmts, "*nimErr_ += oldNimErr$1_ + (*nimErr_ - oldNimErrFin$1_); oldNimErr$1_ = 0;$n", [lab]) + raiseExit(p) + endBlock(p) + # restore the real error value: + linefmt(p, cpsStmts, "*nimErr_ += oldNimErr$1_;$n", [lab]) + +proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) = # code to generate: # # XXX: There should be a standard dispatch algorithm @@ -1013,12 +1146,12 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = # if not isEmptyType(t.typ) and d.k == locNone: getTemp(p, t.typ, d) - let quirkyExceptions = isDefined(p.config, "nimQuirky") or + let quirkyExceptions = p.config.exc == excQuirky or (t.kind == nkHiddenTryStmt and sfSystemModule in p.module.module.flags) if not quirkyExceptions: p.module.includeHeader("") else: - p.noSafePoints = true + p.flags.incl noSafePoints genLineDir(p, t) discard cgsym(p.module, "Exception") var safePoint: Rope @@ -1036,7 +1169,7 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", [safePoint]) startBlock(p, "if ($1.status == 0) {$n", [safePoint]) let fin = if t[^1].kind == nkFinally: t[^1] else: nil - p.nestedTryStmts.add((fin, quirkyExceptions)) + p.nestedTryStmts.add((fin, quirkyExceptions, 0.Natural)) expr(p, t[0], d) if not quirkyExceptions: linefmt(p, cpsStmts, "#popSafePoint();$n", []) diff --git a/compiler/ccgthreadvars.nim b/compiler/ccgthreadvars.nim index 7586aa865dc9a..cc72e3d37e430 100644 --- a/compiler/ccgthreadvars.nim +++ b/compiler/ccgthreadvars.nim @@ -16,8 +16,8 @@ proc emulatedThreadVars(conf: ConfigRef): bool = result = {optThreads, optTlsEmulation} <= conf.globalOptions proc accessThreadLocalVar(p: BProc, s: PSym) = - if emulatedThreadVars(p.config) and not p.threadVarAccessed: - p.threadVarAccessed = true + if emulatedThreadVars(p.config) and threadVarAccessed notin p.flags: + p.flags.incl threadVarAccessed incl p.module.flags, usesThreadVars p.procSec(cpsLocals).addf("\tNimThreadVars* NimTV_;$n", []) p.procSec(cpsInit).add( diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 1e454f25b5e5f..f91f66933b902 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -481,14 +481,6 @@ proc getIntTemp(p: BProc, result: var TLoc) = result.lode = lodeTyp getSysType(p.module.g.graph, unknownLineInfo(), tyInt) result.flags = {} -proc initGCFrame(p: BProc): Rope = - if p.gcFrameId > 0: result = "struct {$1} GCFRAME_;$n" % [p.gcFrameType] - -proc deinitGCFrame(p: BProc): Rope = - if p.gcFrameId > 0: - result = ropecg(p.module, - "if (((NU)&GCFRAME_) < 4096) #nimGCFrame(&GCFRAME_);$n", []) - proc localVarDecl(p: BProc; n: PNode): Rope = let s = n.sym if s.loc.k == locNone: @@ -599,6 +591,7 @@ proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc) proc intLiteral(i: BiggestInt): Rope proc genLiteral(p: BProc, n: PNode): Rope proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope +proc raiseExit(p: BProc) proc initLocExpr(p: BProc, e: PNode, result: var TLoc) = initLoc(result, locNone, e, OnUnknown) @@ -636,13 +629,7 @@ proc initFrame(p: BProc, procname, filename: Rope): Rope = appcg(p.module, p.module.s[cfsFrameDefines], frameDefines, ["#"]) discard cgsym(p.module, "nimFrame") - if p.maxFrameLen > 0: - discard cgsym(p.module, "VarSlot") - result = ropecg(p.module, "\tnimfrs_($1, $2, $3, $4);$n", - [procname, filename, p.maxFrameLen, - p.blocks[0].frameLen]) - else: - result = ropecg(p.module, "\tnimfr_($1, $2);$n", [procname, filename]) + result = ropecg(p.module, "\tnimfr_($1, $2);$n", [procname, filename]) proc initFrameNoDebug(p: BProc; frame, procname, filename: Rope; line: int): Rope = discard cgsym(p.module, "nimFrame") @@ -979,6 +966,13 @@ proc getProcTypeCast(m: BModule, prc: PSym): Rope = genProcParams(m, prc.typ, rettype, params, check) result = "$1(*)$2" % [rettype, params] +proc genProcBody(p: BProc; procBody: PNode) = + genStmts(p, procBody) # modifies p.locals, p.init, etc. + if {nimErrorFlagAccessed, nimErrorFlagDeclared} * p.flags == {nimErrorFlagAccessed}: + p.flags.incl nimErrorFlagDeclared + p.blocks[0].sections[cpsLocals].add(ropecg(p.module, "NI* nimErr_;$n", [])) + p.blocks[0].sections[cpsInit].add(ropecg(p.module, "nimErr_ = #nimErrorFlag();$n", [])) + proc genProcAux(m: BModule, prc: PSym) = var p = newProc(prc, m) var header = genProcHeader(m, prc) @@ -1029,7 +1023,8 @@ proc genProcAux(m: BModule, prc: PSym) = if param.typ.isCompileTimeOnly: continue assignParam(p, param, prc.typ[0]) closureSetup(p, prc) - genStmts(p, procBody) # modifies p.locals, p.init, etc. + genProcBody(p, procBody) + var generatedProc: Rope generatedProc.genCLineDir prc.info, m.config if sfNoReturn in prc.flags: @@ -1046,8 +1041,7 @@ proc genProcAux(m: BModule, prc: PSym) = # This fixes the use of methods and also the case when 2 functions within the same module # call each other using directly the "_actual" versions (an optimization) - see issue #11608 m.s[cfsProcHeaders].addf("$1;\n", [header]) - generatedProc.add ropecg(p.module, "$1 {", [header]) - generatedProc.add(initGCFrame(p)) + generatedProc.add ropecg(p.module, "$1 {$n", [header]) if optStackTrace in prc.options: generatedProc.add(p.s(cpsLocals)) var procname = makeCString(prc.name.s) @@ -1057,11 +1051,12 @@ proc genProcAux(m: BModule, prc: PSym) = if optProfiler in prc.options: # invoke at proc entry for recursion: appcg(p, cpsInit, "\t#nimProfile();$n", []) - if p.beforeRetNeeded: generatedProc.add("{") + # this pair of {} is required for C++ (C++ is weird with its + # control flow integrity checks): + if beforeRetNeeded in p.flags: generatedProc.add("{") generatedProc.add(p.s(cpsInit)) generatedProc.add(p.s(cpsStmts)) - if p.beforeRetNeeded: generatedProc.add(~"\t}BeforeRet_: ;$n") - generatedProc.add(deinitGCFrame(p)) + if beforeRetNeeded in p.flags: generatedProc.add(~"\t}BeforeRet_: ;$n") if optStackTrace in prc.options: generatedProc.add(deinitFrame(p)) generatedProc.add(returnStmt) generatedProc.add(~"}$N") @@ -1645,10 +1640,6 @@ proc genInitCode(m: BModule) = # add new scope for following code, because old vcc compiler need variable # be defined at the top of the block prc.addf("{$N", []) - if m.initProc.gcFrameId > 0: - moduleInitRequired = true - prc.add(initGCFrame(m.initProc)) - writeSection(initProc, cpsLocals) if m.initProc.s(cpsInit).len > 0 or m.initProc.s(cpsStmts).len > 0: @@ -1666,12 +1657,11 @@ proc genInitCode(m: BModule) = writeSection(initProc, cpsInit, m.hcrOn) writeSection(initProc, cpsStmts) + if beforeRetNeeded in m.initProc.flags: + prc.add(~"\tBeforeRet_: ;$n") if optStackTrace in m.initProc.options and preventStackTrace notin m.flags: prc.add(deinitFrame(m.initProc)) - if m.initProc.gcFrameId > 0: - moduleInitRequired = true - prc.add(deinitGCFrame(m.initProc)) prc.addf("}$N", []) prc.addf("}$N$N", []) @@ -1894,7 +1884,7 @@ proc myProcess(b: PPassContext, n: PNode): PNode = if m.hcrOn: addHcrInitGuards(m.initProc, transformedN, m.inHcrInitGuard) else: - genStmts(m.initProc, transformedN) + genProcBody(m.initProc, transformedN) proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool = if optForceFullMake notin m.config.globalOptions: @@ -1996,6 +1986,10 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = if b == nil: return var m = BModule(b) if sfMainModule in m.module.flags: + let testForError = getCompilerProc(graph, "nimTestErrorFlag") + if testForError != nil and graph.config.exc == excGoto: + n.add newTree(nkCall, testForError.newSymNode) + for i in countdown(high(graph.globalDestructors), 0): n.add graph.globalDestructors[i] if passes.skipCodegen(m.config, n): return @@ -2005,7 +1999,7 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = # XXX emit the dispatchers into its own .c file? if n != nil: m.initProc.options = initProcOptions(m) - genStmts(m.initProc, n) + genProcBody(m.initProc, n) if m.hcrOn: # make sure this is pulled in (meaning hcrGetGlobal() is called for it during init) diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 9255e82a5ff50..326bdeb453321 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -64,16 +64,20 @@ type nestedExceptStmts*: int16 # how many except statements is it nested into frameLen*: int16 + TCProcFlag* = enum + beforeRetNeeded, + threadVarAccessed, + hasCurFramePointer, + noSafePoints, + nimErrorFlagAccessed, + nimErrorFlagDeclared + TCProc = object # represents C proc that is currently generated prc*: PSym # the Nim proc that this C proc belongs to - beforeRetNeeded*: bool # true iff 'BeforeRet' label for proc is needed - threadVarAccessed*: bool # true if the proc already accessed some threadvar - hasCurFramePointer*: bool # true if _nimCurFrame var needed to recover after - # exception is generated - noSafePoints*: bool # the proc doesn't use safe points in exception handling + flags*: set[TCProcFlag] lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements currLineInfo*: TLineInfo # AST codegen will make this superfluous - nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool]] + nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, label: Natural]] # in how many nested try statements we are # (the vars must be volatile then) # bool is true when are in the except part of a try block @@ -86,14 +90,11 @@ type options*: TOptions # options that should be used for code # generation; this is the same as prc.options # unless prc == nil - maxFrameLen*: int # max length of frame descriptor module*: BModule # used to prevent excessive parameter passing withinLoop*: int # > 0 if we are within a loop splitDecls*: int # > 0 if we are in some context for C++ that # requires 'T x = T()' to become 'T x; x = T()' # (yes, C++ is weird like that) - gcFrameId*: Natural # for the GC stack marking - gcFrameType*: Rope # the struct {} we put the GC markers into sigConflicts*: CountTable[string] TTypeSeq* = seq[PType] diff --git a/compiler/commands.nim b/compiler/commands.nim index fa94d36e3406c..4e258f8107dd4 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -219,6 +219,7 @@ const errNoneBoehmRefcExpectedButXFound = "'none', 'boehm' or 'refc' expected, but '$1' found" errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' expected, but '$1' found" errGuiConsoleOrLibExpectedButXFound = "'gui', 'console' or 'lib' expected, but '$1' found" + errInvalidExceptionSystem = "'goto', 'setjump', 'cpp' or 'quirky' expected, but '$1' found" proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo): bool = case switch.normalize @@ -254,6 +255,13 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo else: localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg) of "dynliboverride": result = isDynlibOverride(conf, arg) + of "exceptions": + case arg.normalize + of "cpp": result = conf.exc == excCpp + of "setjmp": result = conf.exc == excSetjmp + of "quirky": result = conf.exc == excQuirky + of "goto": result = conf.exc == excGoto + else: localError(conf, info, errInvalidExceptionSystem % arg) else: invalidCmdLineOption(conf, passCmd1, switch, info) proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool = @@ -411,8 +419,12 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; expectArg(conf, switch, arg, pass, info) if {':', '='} in arg: splitSwitch(conf, arg, key, val, pass, info) + if cmpIgnoreStyle(key, "nimQuirky") == 0: + conf.exc = excQuirky defineSymbol(conf.symbols, key, val) else: + if cmpIgnoreStyle(arg, "nimQuirky") == 0: + conf.exc = excQuirky defineSymbol(conf.symbols, arg) of "undef", "u": expectArg(conf, switch, arg, pass, info) @@ -457,6 +469,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; defineSymbol(conf.symbols, "gcmarkandsweep") of "destructors", "arc": conf.selectedGC = gcArc + when true: + if conf.cmd != cmdCompileToCpp: + conf.exc = excGoto defineSymbol(conf.symbols, "gcdestructors") defineSymbol(conf.symbols, "gcarc") incl conf.globalOptions, optSeqDestructors @@ -466,6 +481,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; defineSymbol(conf.symbols, "nimV2") of "orc": conf.selectedGC = gcOrc + when true: + if conf.cmd != cmdCompileToCpp: + conf.exc = excGoto defineSymbol(conf.symbols, "gcdestructors") defineSymbol(conf.symbols, "gcorc") incl conf.globalOptions, optSeqDestructors @@ -775,8 +793,15 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; localError(conf, info, "unknown obsolete feature") of "nocppexceptions": expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optNoCppExceptions) + conf.exc = low(ExceptionSystem) defineSymbol(conf.symbols, "noCppExceptions") + of "exceptions": + case arg.normalize + of "cpp": conf.exc = excCpp + of "setjmp": conf.exc = excSetjmp + of "quirky": conf.exc = excQuirky + of "goto": conf.exc = excGoto + else: localError(conf, info, errInvalidExceptionSystem % arg) of "cppdefine": expectArg(conf, switch, arg, pass, info) if conf != nil: diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index ae23fec3f8da6..ec9b4a9f4e047 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -102,3 +102,4 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimnomagic64") defineSymbol("nimNewShiftOps") defineSymbol("nimHasCursor") + defineSymbol("nimHasExceptionsQuery") diff --git a/compiler/main.nim b/compiler/main.nim index 877b82dd93ce4..7a78c66bae126 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -108,6 +108,7 @@ proc commandJsonScript(graph: ModuleGraph) = when not defined(leanCompiler): proc commandCompileToJS(graph: ModuleGraph) = let conf = graph.config + conf.exc = excCpp if conf.outDir.isEmpty: conf.outDir = conf.projectPath @@ -188,6 +189,7 @@ proc mainCommand*(graph: ModuleGraph) = commandCompileToC(graph) of "cpp", "compiletocpp": conf.cmd = cmdCompileToCpp + conf.exc = excCpp defineSymbol(graph.config.symbols, "cpp") commandCompileToC(graph) of "objc", "compiletooc": diff --git a/compiler/options.nim b/compiler/options.nim index 443b2a1b6b7b1..9c17ea1e6d835 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -71,7 +71,6 @@ type # please make sure we have under 32 options # also: generate header file optIdeDebug # idetools: debug mode optIdeTerse # idetools: use terse descriptions - optNoCppExceptions # use C exception handling even with CPP optExcessiveStackTrace # fully qualified module filenames optShowAllMismatches # show all overloading resolution candidates optWholeProject # for 'doc2': output any dependency @@ -159,6 +158,12 @@ type ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc, ccTcc, ccPcc, ccUcc, ccIcl, ccIcc, ccClangCl + ExceptionSystem* = enum + excSetjmp, # setjmp based exception handling + excCpp, # use C++'s native exception handling + excGoto, # exception handling based on goto (should become the new default for C) + excQuirky # quirky exception handling + CfileFlag* {.pure.} = enum Cached, ## no need to recompile this time External ## file was introduced via .compile pragma @@ -204,6 +209,7 @@ type exitcode*: int8 cmd*: TCommands # the command selectedGC*: TGCMode # the selected GC (+) + exc*: ExceptionSystem verbosity*: int # how verbose the compiler is numberOfProcessors*: int # number of processors evalExpr*: string # expression for idetools --eval diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 6596cfba6cbf2..c8b13ab6aef74 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -880,7 +880,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, of wNoreturn: noVal(c, it) # Disable the 'noreturn' annotation when in the "Quirky Exceptions" mode! - if not isDefined(c.config, "nimQuirky"): + if c.config.exc notin {excQuirky, excGoto}: incl(sym.flags, sfNoReturn) if sym.typ[0] != nil: localError(c.config, sym.ast[paramsPos][0].info, diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 6a61956003364..6f83fac05a012 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -167,9 +167,7 @@ proc guardDotAccess(a: PEffects; n: PNode) = guardGlobal(a, n, g) proc makeVolatile(a: PEffects; s: PSym) {.inline.} = - template compileToCpp(a): untyped = - a.config.cmd == cmdCompileToCpp or sfCompileToCpp in getModule(a.owner).flags - if a.inTryStmt > 0 and not compileToCpp(a): + if a.inTryStmt > 0 and a.config.exc == excSetjmp: incl(s.flags, sfVolatile) proc initVar(a: PEffects, n: PNode; volatileCheck: bool) = diff --git a/doc/advopt.txt b/doc/advopt.txt index fef9f6895bef7..e7626bd89f08f 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -98,12 +98,13 @@ Advanced options: --skipProjCfg:on|off do not read the project's configuration file --gc:refc|markAndSweep|boehm|go|none|regions select the GC to use; default is 'refc' + --exceptions:setjmp|cpp|goto|quirky + select the exception handling implementation --index:on|off turn index file generation on|off --putenv:key=value set an environment variable --NimblePath:PATH add a path for Nimble support --noNimblePath deactivate the Nimble path --clearNimblePath empty the list of Nimble package search paths - --noCppExceptions use default exception handling with C++ backend --cppCompileToNamespace:namespace use the provided namespace for the generated C++ code, if no namespace is provided "Nim" will be used diff --git a/lib/system.nim b/lib/system.nim index 58250db3f230d..838205c57f513 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -3105,7 +3105,7 @@ when not defined(js): when defined(nimV2): type TNimNode {.compilerproc.} = object # to keep the code generator simple - DestructorProc = proc (p: pointer) {.nimcall, benign.} + DestructorProc = proc (p: pointer) {.nimcall, benign, raises: [].} TNimType {.compilerproc.} = object destructor: pointer size: int @@ -3120,6 +3120,12 @@ when not defined(js): {.pop.} +when not defined(js) and not defined(nimscript): + proc writeStackTrace*() {.tags: [], gcsafe, raises: [].} + ## Writes the current stack trace to ``stderr``. This is only works + ## for debug builds. Since it's usually used for debugging, this + ## is proclaimed to have no IO effect! + when not declared(sysFatal): include "system/fatal" @@ -3693,10 +3699,6 @@ when not defined(JS): #and not defined(nimscript): proc unsetControlCHook*() ## Reverts a call to setControlCHook. - proc writeStackTrace*() {.tags: [], gcsafe.} - ## Writes the current stack trace to ``stderr``. This is only works - ## for debug builds. Since it's usually used for debugging, this - ## is proclaimed to have no IO effect! when hostOS != "standalone": proc getStackTrace*(): string {.gcsafe.} ## Gets the current stack trace. This only works for debug builds. diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 6e06b10f8ea57..36e39cfc0dcd9 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -27,19 +27,21 @@ else: proc writeToStdErr(msg: cstring) = discard MessageBoxA(nil, msg, nil, 0) -proc showErrorMessage(data: cstring) {.gcsafe.} = +proc showErrorMessage(data: cstring) {.gcsafe, raises: [].} = + var toWrite = true if errorMessageWriter != nil: - errorMessageWriter($data) - else: + try: + errorMessageWriter($data) + toWrite = false + except: + discard + if toWrite: when defined(genode): # stderr not available by default, use the LOG session echo data else: writeToStdErr(data) -proc quitOrDebug() {.inline.} = - quit(1) - proc chckIndx(i, a, b: int): int {.inline, compilerproc, benign.} proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.} proc chckRangeF(x, a, b: float): float {.inline, compilerproc, benign.} @@ -57,10 +59,12 @@ var # list of exception handlers # a global variable for the root of all try blocks currException {.threadvar.}: ref Exception - raiseCounter {.threadvar.}: uint - gcFramePtr {.threadvar.}: GcFrame +when defined(cpp) and not defined(noCppExceptions): + var + raiseCounter {.threadvar.}: uint + type FrameState = tuple[gcFramePtr: GcFrame, framePtr: PFrame, excHandler: PSafePoint, currException: ref Exception] @@ -130,7 +134,7 @@ proc popCurrentExceptionEx(id: uint) {.compilerRtl.} = cur = cur.up if cur == nil: showErrorMessage("popCurrentExceptionEx() exception was not found in the exception stack. Aborting...") - quitOrDebug() + quit(1) prev.up = cur.up proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} = @@ -327,57 +331,87 @@ var onUnhandledException*: (proc (errorMsg: string) {. ## The default is to write a stacktrace to ``stderr`` and then call ``quit(1)``. ## Unstable API. -template unhandled(buf, body) = - if onUnhandledException != nil: - onUnhandledException($buf) +proc reportUnhandledError(e: ref Exception) {.nodestroy.} = + when hasSomeStackTrace: + var buf = newStringOfCap(2000) + if e.trace.len == 0: + rawWriteStackTrace(buf) + else: + var trace = $e.trace + add(buf, trace) + `=destroy`(trace) + add(buf, "Error: unhandled exception: ") + add(buf, e.msg) + add(buf, " [") + add(buf, $e.name) + add(buf, "]\n") + + if onUnhandledException != nil: + onUnhandledException(buf) + else: + showErrorMessage(buf) + `=destroy`(buf) else: - body + # ugly, but avoids heap allocations :-) + template xadd(buf, s, slen) = + if L + slen < high(buf): + copyMem(addr(buf[L]), cstring(s), slen) + inc L, slen + template add(buf, s) = + xadd(buf, s, s.len) + var buf: array[0..2000, char] + var L = 0 + if e.trace.len != 0: + var trace = $e.trace + add(buf, trace) + `=destroy`(trace) + add(buf, "Error: unhandled exception: ") + add(buf, e.msg) + add(buf, " [") + xadd(buf, e.name, e.name.len) + add(buf, "]\n") + when defined(nimNoArrayToCstringConversion): + template tbuf(): untyped = addr buf + else: + template tbuf(): untyped = buf + + if onUnhandledException != nil: + onUnhandledException($tbuf()) + else: + showErrorMessage(tbuf()) proc nimLeaveFinally() {.compilerRtl.} = when defined(cpp) and not defined(noCppExceptions): {.emit: "throw;".} else: - template e: untyped = currException if excHandler != nil: c_longjmp(excHandler.context, 1) else: - when hasSomeStackTrace: - var buf = newStringOfCap(2000) - if e.trace.len == 0: rawWriteStackTrace(buf) - else: add(buf, $e.trace) - add(buf, "Error: unhandled exception: ") - add(buf, e.msg) - add(buf, " [") - add(buf, $e.name) - add(buf, "]\n") - unhandled(buf): - showErrorMessage(buf) - quitOrDebug() - `=destroy`(buf) - else: - # ugly, but avoids heap allocations :-) - template xadd(buf, s, slen) = - if L + slen < high(buf): - copyMem(addr(buf[L]), cstring(s), slen) - inc L, slen - template add(buf, s) = - xadd(buf, s, s.len) - var buf: array[0..2000, char] - var L = 0 - if e.trace.len != 0: - add(buf, $e.trace) # gc allocation - add(buf, "Error: unhandled exception: ") - add(buf, e.msg) - add(buf, " [") - xadd(buf, e.name, e.name.len) - add(buf, "]\n") - when defined(nimNoArrayToCstringConversion): - template tbuf(): untyped = addr buf - else: - template tbuf(): untyped = buf - unhandled(tbuf()): - showErrorMessage(tbuf()) - quitOrDebug() + reportUnhandledError(currException) + quit(1) + +when gotoBasedExceptions: + var nimInErrorMode {.threadvar.}: int + + proc nimErrorFlag(): ptr int {.compilerRtl, inl.} = + result = addr(nimInErrorMode) + + proc nimTestErrorFlag() {.compilerRtl.} = + ## This proc must be called before ``currException`` is destroyed. + ## It also must be called at the end of every thread to ensure no + ## error is swallowed. + if currException != nil: + reportUnhandledError(currException) + currException = nil + quit(1) + + addQuitProc(proc () {.noconv.} = + if currException != nil: + reportUnhandledError(currException) + # emulate: ``programResult = 1`` via abort() and a nop signal handler. + c_signal(SIGABRT, (proc (sign: cint) {.noconv, benign.} = discard)) + c_abort() + ) proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} = if localRaiseHook != nil: @@ -394,50 +428,19 @@ proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} = raiseCounter.inc # skip zero at overflow e.raiseId = raiseCounter {.emit: "`e`->raise();".} - elif defined(nimQuirky): - pushCurrentException(e) + elif defined(nimQuirky) or gotoBasedExceptions: + # XXX This check should likely also be done in the setjmp case below. + if e != currException: + pushCurrentException(e) + when gotoBasedExceptions: + inc nimInErrorMode else: if excHandler != nil: pushCurrentException(e) c_longjmp(excHandler.context, 1) else: - when hasSomeStackTrace: - var buf = newStringOfCap(2000) - if e.trace.len == 0: rawWriteStackTrace(buf) - else: add(buf, $e.trace) - add(buf, "Error: unhandled exception: ") - add(buf, e.msg) - add(buf, " [") - add(buf, $e.name) - add(buf, "]\n") - unhandled(buf): - showErrorMessage(buf) - quitOrDebug() - `=destroy`(buf) - else: - # ugly, but avoids heap allocations :-) - template xadd(buf, s, slen) = - if L + slen < high(buf): - copyMem(addr(buf[L]), cstring(s), slen) - inc L, slen - template add(buf, s) = - xadd(buf, s, s.len) - var buf: array[0..2000, char] - var L = 0 - if e.trace.len != 0: - add(buf, $e.trace) # gc allocation - add(buf, "Error: unhandled exception: ") - add(buf, e.msg) - add(buf, " [") - xadd(buf, e.name, e.name.len) - add(buf, "]\n") - when defined(nimNoArrayToCstringConversion): - template tbuf(): untyped = addr buf - else: - template tbuf(): untyped = buf - unhandled(tbuf()): - showErrorMessage(tbuf()) - quitOrDebug() + reportUnhandledError(e) + quit(1) proc raiseExceptionEx(e: sink(ref Exception), ename, procname, filename: cstring, line: int) {.compilerRtl, nodestroy.} = @@ -461,15 +464,18 @@ proc reraiseException() {.compilerRtl.} = if currException == nil: sysFatal(ReraiseError, "no exception to reraise") else: - raiseExceptionAux(currException) + when gotoBasedExceptions: + inc nimInErrorMode + else: + raiseExceptionAux(currException) proc writeStackTrace() = when hasSomeStackTrace: var s = "" rawWriteStackTrace(s) - cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall.}](showErrorMessage)(s) + cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall, raises: [].}](showErrorMessage)(s) else: - cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall.}](showErrorMessage)("No stack traceback available\n") + cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall, raises: [].}](showErrorMessage)("No stack traceback available\n") proc getStackTrace(): string = when hasSomeStackTrace: @@ -506,9 +512,9 @@ proc callDepthLimitReached() {.noinline.} = $nimCallDepthLimit & " function calls). You can change it with " & "-d:nimCallDepthLimit= but really try to avoid deep " & "recursions instead.\n") - quitOrDebug() + quit(1) -proc nimFrame(s: PFrame) {.compilerRtl, inl.} = +proc nimFrame(s: PFrame) {.compilerRtl, inl, raises: [].} = s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1 s.prev = framePtr framePtr = s diff --git a/lib/system/fatal.nim b/lib/system/fatal.nim index 087753d3d75b3..d68d067125b56 100644 --- a/lib/system/fatal.nim +++ b/lib/system/fatal.nim @@ -1,4 +1,19 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2019 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + {.push profiler: off.} + +when defined(nimHasExceptionsQuery): + const gotoBasedExceptions = compileOption("exceptions", "goto") +else: + const gotoBasedExceptions = false + when hostOS == "standalone": include "$projectpath/panicoverride" @@ -9,19 +24,20 @@ when hostOS == "standalone": rawoutput(message) panic(arg) -elif defined(nimQuirky) and not defined(nimscript): +elif (defined(nimQuirky) or gotoBasedExceptions) and not defined(nimscript): import ansi_c proc name(t: typedesc): string {.magic: "TypeTrait".} proc sysFatal(exceptn: typedesc, message, arg: string) {.inline, noreturn.} = + writeStackTrace() var buf = newStringOfCap(200) add(buf, "Error: unhandled exception: ") add(buf, message) add(buf, arg) add(buf, " [") add(buf, name exceptn) - add(buf, "]") + add(buf, "]\n") cstderr.rawWrite buf quit 1 diff --git a/lib/system/refs_v2.nim b/lib/system/refs_v2.nim index 6fd34fca6dec3..e07c33086f62d 100644 --- a/lib/system/refs_v2.nim +++ b/lib/system/refs_v2.nim @@ -111,7 +111,7 @@ proc nimRawDispose(p: pointer) {.compilerRtl.} = template dispose*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x)) #proc dispose*(x: pointer) = nimRawDispose(x) -proc nimDestroyAndDispose(p: pointer) {.compilerRtl.} = +proc nimDestroyAndDispose(p: pointer) {.compilerRtl, raises: [].} = let d = cast[ptr PNimType](p)[].destructor if d != nil: cast[DestructorProc](d)(p) when false: diff --git a/tests/assert/tassert_c.nim b/tests/assert/tassert_c.nim index 877b3aead38d5..84ccea8230a29 100644 --- a/tests/assert/tassert_c.nim +++ b/tests/assert/tassert_c.nim @@ -8,7 +8,7 @@ tassert_c.nim(35) tassert_c tassert_c.nim(34) foo assertions.nim(27) failedAssertImpl assertions.nim(20) raiseAssert -fatal.nim(39) sysFatal""" +fatal.nim(55) sysFatal""" proc tmatch(x, p: string): bool = var i = 0 diff --git a/tests/destructor/tgotoexceptions.nim b/tests/destructor/tgotoexceptions.nim new file mode 100755 index 0000000000000..f765922700903 --- /dev/null +++ b/tests/destructor/tgotoexceptions.nim @@ -0,0 +1,117 @@ +discard """ + output: ''' +msg1 +msg2 +finally2 +finally1 +begin +one iteration! +caught! +except1 +finally1 +caught! 2 +BEFORE +FINALLY +BEFORE +EXCEPT +FINALLY +RECOVER +BEFORE +EXCEPT: IOError: hi +FINALLY +''' + cmd: "nim c --gc:arc --exceptions:goto $file" +""" + +#bug 7204 +proc nested_finally = + try: + raise newException(KeyError, "msg1") + except KeyError as ex: + echo ex.msg + try: + raise newException(ValueError, "msg2") + except: + echo getCurrentExceptionMsg() + finally: + echo "finally2" + finally: + echo "finally1" + +nested_finally() + +proc doraise = + raise newException(ValueError, "gah") + +proc main = + while true: + try: + echo "begin" + doraise() + finally: + echo "one ", "iteration!" + +try: + main() +except: + echo "caught!" + +when true: + proc p = + try: + raise newException(Exception, "Hello") + except: + echo "except1" + raise + finally: + echo "finally1" + + try: + p() + except: + echo "caught! 2" + + +proc noException = + try: + echo "BEFORE" + + except: + echo "EXCEPT" + raise + + finally: + echo "FINALLY" + +try: noException() +except: echo "RECOVER" + +proc reraise_in_except = + try: + echo "BEFORE" + raise newException(IOError, "") + + except IOError: + echo "EXCEPT" + raise + + finally: + echo "FINALLY" + +try: reraise_in_except() +except: echo "RECOVER" + +proc return_in_except = + try: + echo "BEFORE" + raise newException(IOError, "hi") + + except: + echo "EXCEPT: ", getCurrentException().name, ": ", getCurrentExceptionMsg() + return + + finally: + echo "FINALLY" + +try: return_in_except() +except: echo "RECOVER" diff --git a/tests/destructor/tgotoexceptions2.nim b/tests/destructor/tgotoexceptions2.nim new file mode 100644 index 0000000000000..057caf7b7024e --- /dev/null +++ b/tests/destructor/tgotoexceptions2.nim @@ -0,0 +1,104 @@ +discard """ + cmd: "nim c --gc:arc --exceptions:goto $file" + output: ''' +B1 +B2 +catch +A1 +1 +B1 +B2 +catch +A1 +A2 +0 +B1 +B2 +A1 +1 +B1 +B2 +A1 +A2 +3 +A +B +C +''' +""" + +# More thorough test of return-in-finaly + +var raiseEx = true +var returnA = true +var returnB = false + +proc main: int = + try: #A + try: #B + if raiseEx: + raise newException(OSError, "") + return 3 + finally: #B + echo "B1" + if returnB: + return 2 + echo "B2" + except OSError: #A + echo "catch" + finally: #A + echo "A1" + if returnA: + return 1 + echo "A2" + +for x in [true, false]: + for y in [true, false]: + # echo "raiseEx: " & $x + # echo "returnA: " & $y + # echo "returnB: " & $z + # in the original test returnB was set to true too and + # this leads to swallowing the OSError exception. This is + # somewhat compatible with Python but it's non-sense, 'finally' + # should not be allowed to swallow exceptions. The goto based + # implementation does something sane so we don't "correct" its + # behavior just to be compatible with v1. + raiseEx = x + returnA = y + echo main() + +# Various tests of return nested in double try/except statements + +proc test1() = + + defer: echo "A" + + try: + raise newException(OSError, "Problem") + except OSError: + return + +test1() + + +proc test2() = + + defer: echo "B" + + try: + return + except OSError: + discard + +test2() + +proc test3() = + try: + try: + raise newException(OSError, "Problem") + except OSError: + return + finally: + echo "C" + +test3() diff --git a/tests/destructor/tgotoexceptions3.nim b/tests/destructor/tgotoexceptions3.nim new file mode 100644 index 0000000000000..308d288b251b0 --- /dev/null +++ b/tests/destructor/tgotoexceptions3.nim @@ -0,0 +1,7 @@ +discard """ + cmd: "nim c --gc:arc --exceptions:goto $file" + outputsub: "Error: unhandled exception: Problem [OSError]" + exitcode: "1" +""" + +raise newException(OSError, "Problem") diff --git a/tests/exception/tfinally4.nim b/tests/exception/tfinally4.nim index feaf1bc96720e..a7dbbffefd595 100644 --- a/tests/exception/tfinally4.nim +++ b/tests/exception/tfinally4.nim @@ -1,5 +1,39 @@ discard """ - output: "B1\nA1\n1\nB1\nB2\ncatch\nA1\n1\nB1\nA1\nA2\n2\nB1\nB2\ncatch\nA1\nA2\n0\nB1\nA1\n1\nB1\nB2\nA1\n1\nB1\nA1\nA2\n2\nB1\nB2\nA1\nA2\n3" + output: ''' +B1 +A1 +1 +B1 +B2 +catch +A1 +1 +B1 +A1 +A2 +2 +B1 +B2 +catch +A1 +A2 +0 +B1 +A1 +1 +B1 +B2 +A1 +1 +B1 +A1 +A2 +2 +B1 +B2 +A1 +A2 +3''' """ # More thorough test of return-in-finaly