diff --git a/changelog.md b/changelog.md index 0fb99c6f4e1c..fa753e7c472c 100644 --- a/changelog.md +++ b/changelog.md @@ -161,6 +161,14 @@ echo f - The compiler now inferes "sink parameters". To disable this for a specific routine, annotate it with `.nosinks`. To disable it for a section of code, use `{.push sinkInference: off.}`...`{.pop.}`. +- The compiler now supports a new switch `--panics:on` that turns runtime + errors like `IndexError` or `OverflowError` into fatal errors that **cannot** + be caught via Nim's `try` statement. `--panics:on` can improve the + runtime efficiency and code size of your program significantly. +- The compiler now warns about inheriting directly from `system.Exception` as + this is **very bad** style. You should inherit from `ValueError`, `IOError`, + `OSError` or from a different specific exception type that inherits from + `CatchableError` and cannot be confused with a `Defect`. ## Bugfixes diff --git a/compiler/ast.nim b/compiler/ast.nim index 62c301a43adb..55ad2ba4bccc 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -229,7 +229,7 @@ type TNodeKinds* = set[TNodeKind] type - TSymFlag* = enum # 41 flags! + TSymFlag* = enum # 42 flags! sfUsed, # read access of sym (for warnings) or simply used sfExported, # symbol is exported from module sfFromGeneric, # symbol is instantiation of a generic; this is needed @@ -289,6 +289,8 @@ type sfTemplateParam # symbol is a template parameter sfCursor # variable/field is a cursor, see RFC 177 for details sfInjectDestructors # whether the proc needs the 'injectdestructors' transformation + sfAlwaysReturn # proc can never raise an exception, not even OverflowError + # or out-of-memory TSymFlags* = set[TSymFlag] diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index da5dd9b766d4..3e2cf22718d7 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -557,6 +557,18 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) = pl.add(~"];$n") line(p, cpsStmts, pl) +proc canRaiseDisp(p: BProc; n: PNode): bool = + # we assume things like sysFatal cannot raise themselves + if n.kind == nkSym and sfAlwaysReturn in n.sym.flags: + result = false + elif optPanics in p.config.globalOptions or + (n.kind == nkSym and sfSystemModule in getModule(n.sym).flags): + # we know we can be strict: + result = canRaise(n) + else: + # we have to be *very* conservative: + result = canRaiseConservative(n) + proc genCall(p: BProc, e: PNode, d: var TLoc) = if e[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure: genClosureCall(p, nil, e, d) @@ -567,7 +579,7 @@ 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]): + if p.config.exc == excGoto and canRaiseDisp(p, e[0]): raiseExit(p) proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) = @@ -580,5 +592,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]): + if p.config.exc == excGoto and canRaiseDisp(p, ri[0]): raiseExit(p) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 1bed4bc6cc5e..4aad1a4f8a55 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -506,23 +506,25 @@ template binaryArithOverflowRaw(p: BProc, t: PType, a, b: TLoc; else: getTypeDesc(p.module, t) var result = getTempName(p.module) linefmt(p, cpsLocals, "$1 $2;$n", [storage, result]) - lineCg(p, cpsStmts, "$1 = #$2($3, $4);$n", [result, cpname, rdCharLoc(a), rdCharLoc(b)]) + lineCg(p, cpsStmts, "if (#$2($3, $4, &$1)) { #raiseOverflow(); $5};$n", + [result, cpname, rdCharLoc(a), rdCharLoc(b), raiseInstr(p)]) if size < p.config.target.intSize or t.kind in {tyRange, tyEnum}: - linefmt(p, cpsStmts, "if ($1 < $2 || $1 > $3) #raiseOverflow();$n", - [result, intLiteral(firstOrd(p.config, t)), intLiteral(lastOrd(p.config, t))]) + linefmt(p, cpsStmts, "if ($1 < $2 || $1 > $3){ #raiseOverflow(); $4}$n", + [result, intLiteral(firstOrd(p.config, t)), intLiteral(lastOrd(p.config, t)), + raiseInstr(p)]) result proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = const prc: array[mAddI..mPred, string] = [ - "addInt", "subInt", - "mulInt", "divInt", "modInt", - "addInt", "subInt" + "nimAddInt", "nimSubInt", + "nimMulInt", "nimDivInt", "nimModInt", + "nimAddInt", "nimSubInt" ] prc64: array[mAddI..mPred, string] = [ - "addInt64", "subInt64", - "mulInt64", "divInt64", "modInt64", - "addInt64", "subInt64" + "nimAddInt64", "nimSubInt64", + "nimMulInt64", "nimDivInt64", "nimModInt64", + "nimAddInt64", "nimSubInt64" ] opr: array[mAddI..mPred, string] = ["+", "-", "*", "/", "%", "+", "-"] var a, b: TLoc @@ -537,6 +539,12 @@ proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = let res = "($1)($2 $3 $4)" % [getTypeDesc(p.module, e.typ), rdLoc(a), rope(opr[m]), rdLoc(b)] putIntoDest(p, d, e, res) else: + # we handle div by zero here so that we know that the compilerproc's + # result is only for overflows. + if m in {mDivI, mModI}: + linefmt(p, cpsStmts, "if ($1 == 0){ #raiseDivByZero(); $2}$n", + [rdLoc(b), raiseInstr(p)]) + let res = binaryArithOverflowRaw(p, t, a, b, if t.kind == tyInt64: prc64[m] else: prc[m]) putIntoDest(p, d, e, "($#)($#)" % [getTypeDesc(p.module, e.typ), res]) @@ -549,8 +557,8 @@ proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = initLocExpr(p, e[1], a) t = skipTypes(e.typ, abstractRange) if optOverflowCheck in p.options: - linefmt(p, cpsStmts, "if ($1 == $2) #raiseOverflow();$n", - [rdLoc(a), intLiteral(firstOrd(p.config, t))]) + linefmt(p, cpsStmts, "if ($1 == $2){ #raiseOverflow(); $3}$n", + [rdLoc(a), intLiteral(firstOrd(p.config, t)), raiseInstr(p)]) case m of mUnaryMinusI: putIntoDest(p, d, e, "((NI$2)-($1))" % [rdLoc(a), rope(getSize(p.config, t) * 8)]) @@ -817,12 +825,12 @@ proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym) = let strLit = genStringLiteral(p.module, newStrNode(nkStrLit, msg)) if op.magic == mNot: linefmt(p, cpsStmts, - "if ($1) #raiseFieldError($2);$n", - [rdLoc(test), strLit]) + "if ($1){ #raiseFieldError($2); $3}$n", + [rdLoc(test), strLit, raiseInstr(p)]) else: linefmt(p, cpsStmts, - "if (!($1)) #raiseFieldError($2);$n", - [rdLoc(test), strLit]) + "if (!($1)){ #raiseFieldError($2); $3}$n", + [rdLoc(test), strLit, raiseInstr(p)]) proc genCheckedRecordField(p: BProc, e: PNode, d: var TLoc) = if optFieldCheck in p.options: @@ -861,11 +869,11 @@ proc genArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = # semantic pass has already checked for const index expressions if firstOrd(p.config, ty) == 0: if (firstOrd(p.config, b.t) < firstOrd(p.config, ty)) or (lastOrd(p.config, b.t) > lastOrd(p.config, ty)): - linefmt(p, cpsStmts, "if ((NU)($1) > (NU)($2)) #raiseIndexError2($1, $2);$n", - [rdCharLoc(b), intLiteral(lastOrd(p.config, ty))]) + linefmt(p, cpsStmts, "if ((NU)($1) > (NU)($2)){ #raiseIndexError2($1, $2); $3}$n", + [rdCharLoc(b), intLiteral(lastOrd(p.config, ty)), raiseInstr(p)]) else: - linefmt(p, cpsStmts, "if ($1 < $2 || $1 > $3) #raiseIndexError3($1, $2, $3);$n", - [rdCharLoc(b), first, intLiteral(lastOrd(p.config, ty))]) + linefmt(p, cpsStmts, "if ($1 < $2 || $1 > $3){ #raiseIndexError3($1, $2, $3); $4}$n", + [rdCharLoc(b), first, intLiteral(lastOrd(p.config, ty)), raiseInstr(p)]) else: let idx = getOrdValue(y) if idx < firstOrd(p.config, ty) or idx > lastOrd(p.config, ty): @@ -888,19 +896,19 @@ proc genBoundsCheck(p: BProc; arr, a, b: TLoc) = of tyOpenArray, tyVarargs: linefmt(p, cpsStmts, "if ($2-$1 != -1 && " & - "((NU)($1) >= (NU)($3Len_0) || (NU)($2) >= (NU)($3Len_0))) #raiseIndexError();$n", - [rdLoc(a), rdLoc(b), rdLoc(arr)]) + "((NU)($1) >= (NU)($3Len_0) || (NU)($2) >= (NU)($3Len_0))){ #raiseIndexError(); $4}$n", + [rdLoc(a), rdLoc(b), rdLoc(arr), raiseInstr(p)]) of tyArray: let first = intLiteral(firstOrd(p.config, ty)) linefmt(p, cpsStmts, "if ($2-$1 != -1 && " & - "($2-$1 < -1 || $1 < $3 || $1 > $4 || $2 < $3 || $2 > $4)) #raiseIndexError();$n", - [rdCharLoc(a), rdCharLoc(b), first, intLiteral(lastOrd(p.config, ty))]) + "($2-$1 < -1 || $1 < $3 || $1 > $4 || $2 < $3 || $2 > $4)){ #raiseIndexError(); $5}$n", + [rdCharLoc(a), rdCharLoc(b), first, intLiteral(lastOrd(p.config, ty)), raiseInstr(p)]) of tySequence, tyString: linefmt(p, cpsStmts, "if ($2-$1 != -1 && " & - "((NU)($1) >= (NU)$3 || (NU)($2) >= (NU)$3)) #raiseIndexError();$n", - [rdLoc(a), rdLoc(b), lenExpr(p, arr)]) + "((NU)($1) >= (NU)$3 || (NU)($2) >= (NU)$3)){ #raiseIndexError(); $4}$n", + [rdLoc(a), rdLoc(b), lenExpr(p, arr), raiseInstr(p)]) else: discard proc genOpenArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = @@ -908,8 +916,8 @@ proc genOpenArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = initLocExpr(p, x, a) initLocExpr(p, y, b) # emit range check: if optBoundsCheck in p.options: - linefmt(p, cpsStmts, "if ((NU)($1) >= (NU)($2Len_0)) #raiseIndexError2($1,$2Len_0-1);$n", - [rdLoc(b), rdLoc(a)]) # BUGFIX: ``>=`` and not ``>``! + linefmt(p, cpsStmts, "if ((NU)($1) >= (NU)($2Len_0)){ #raiseIndexError2($1,$2Len_0-1); $3}$n", + [rdLoc(b), rdLoc(a), raiseInstr(p)]) # BUGFIX: ``>=`` and not ``>``! inheritLocation(d, a) putIntoDest(p, d, n, ropecg(p.module, "$1[$2]", [rdLoc(a), rdCharLoc(b)]), a.storage) @@ -924,12 +932,12 @@ proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) = if optBoundsCheck in p.options: if ty.kind == tyString and (not defined(nimNoZeroTerminator) or optLaxStrings in p.options): linefmt(p, cpsStmts, - "if ((NU)($1) > (NU)$2) #raiseIndexError2($1,$2);$n", - [rdLoc(b), lenExpr(p, a)]) + "if ((NU)($1) > (NU)$2){ #raiseIndexError2($1,$2); $3}$n", + [rdLoc(b), lenExpr(p, a), raiseInstr(p)]) else: linefmt(p, cpsStmts, - "if ((NU)($1) >= (NU)$2) #raiseIndexError2($1,$2-1);$n", - [rdLoc(b), lenExpr(p, a)]) + "if ((NU)($1) >= (NU)$2){ #raiseIndexError2($1,$2-1); $3}$n", + [rdLoc(b), lenExpr(p, a), raiseInstr(p)]) if d.k == locNone: d.storage = OnHeap if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}: a.r = ropecg(p.module, "(*$1)", [a.r]) @@ -1938,21 +1946,33 @@ proc genCast(p: BProc, e: PNode, d: var TLoc) = # C code; plus it's the right thing to do for closures: genSomeCast(p, e, d) -proc genRangeChck(p: BProc, n: PNode, d: var TLoc, magic: string) = +proc genRangeChck(p: BProc, n: PNode, d: var TLoc) = var a: TLoc var dest = skipTypes(n.typ, abstractVar) + initLocExpr(p, n[0], a) if optRangeCheck notin p.options or (dest.kind in {tyUInt..tyUInt64} and checkUnsignedConversions notin p.config.legacyFeatures): - initLocExpr(p, n[0], a) - putIntoDest(p, d, n, "(($1) ($2))" % - [getTypeDesc(p.module, dest), rdCharLoc(a)], a.storage) + discard "no need to generate a check because it was disabled" else: - let mm = if dest.kind in {tyUInt32, tyUInt64, tyUInt}: "chckRangeU" else: magic - initLocExpr(p, n[0], a) - putIntoDest(p, d, lodeTyp dest, ropecg(p.module, "(($1)#$5($2, $3, $4))", [ - getTypeDesc(p.module, dest), rdCharLoc(a), - genLiteral(p, n[1], dest), genLiteral(p, n[2], dest), - mm]), a.storage) + let raiser = + case skipTypes(n.typ, abstractVarRange).kind + of tyUInt..tyUInt64, tyChar: "raiseRangeErrorU" + of tyFloat..tyFloat128: "raiseRangeErrorF" + else: "raiseRangeErrorI" + discard cgsym(p.module, raiser) + # This seems to be bug-compatible with Nim version 1 but what we + # should really do here is to check if uint64Value < high(int) + let boundaryCast = + if n[0].typ.skipTypes(abstractVarRange).kind in {tyUInt, tyUInt32, tyUInt64}: + "(NI64)" + else: + "" + # emit range check: + linefmt(p, cpsStmts, "if ($6($1) < $2 || $6($1) > $3){ $4($1, $2, $3); $5}$n", + [rdCharLoc(a), genLiteral(p, n[1], dest), genLiteral(p, n[2], dest), + raiser, raiseInstr(p), boundaryCast]) + putIntoDest(p, d, n, "(($1) ($2))" % + [getTypeDesc(p.module, dest), rdCharLoc(a)], a.storage) proc genConv(p: BProc, e: PNode, d: var TLoc) = let destType = e.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) @@ -2004,9 +2024,9 @@ proc binaryFloatArith(p: BProc, e: PNode, d: var TLoc, m: TMagic) = [opr[m], rdLoc(a), rdLoc(b), getSimpleTypeDesc(p.module, e[1].typ)])) if optNaNCheck in p.options: - linefmt(p, cpsStmts, "#nanCheck($1);$n", [rdLoc(d)]) + linefmt(p, cpsStmts, "if ($1 != $1){ #raiseFloatInvalidOp(); $2}$n", [rdLoc(d), raiseInstr(p)]) if optInfCheck in p.options: - linefmt(p, cpsStmts, "#infCheck($1);$n", [rdLoc(d)]) + linefmt(p, cpsStmts, "if ($1 != 0.0 && $1*0.5 == $1) { #raiseFloatOverflow($1); $2}$n", [rdLoc(d), raiseInstr(p)]) else: binaryArith(p, e, d, m) @@ -2122,10 +2142,8 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mSwap: genSwap(p, e, d) of mInc, mDec: const opr: array[mInc..mDec, string] = ["+=", "-="] - const fun64: array[mInc..mDec, string] = ["addInt64", - "subInt64"] - const fun: array[mInc..mDec, string] = ["addInt", - "subInt"] + const fun64: array[mInc..mDec, string] = ["nimAddInt64", "nimSubInt64"] + const fun: array[mInc..mDec, string] = ["nimAddInt","nimSubInt"] let underlying = skipTypes(e[1].typ, {tyGenericInst, tyAlias, tySink, tyVar, tyLent, tyRange}) if optOverflowCheck notin p.options or underlying.kind in {tyUInt..tyUInt64}: binaryStmt(p, e, d, opr[op]) @@ -2432,11 +2450,11 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) = else: genTypeInfo(p.module, dest, n.info) if nilCheck != nil: - linefmt(p, cpsStmts, "if ($1) #chckObj($2, $3);$n", - [nilCheck, r, checkFor]) + linefmt(p, cpsStmts, "if ($1 && !#isObj($2, $3)){ #raiseObjectConversionError(); $4}$n", + [nilCheck, r, checkFor, raiseInstr(p)]) else: - linefmt(p, cpsStmts, "#chckObj($1, $2);$n", - [r, checkFor]) + linefmt(p, cpsStmts, "if (!#isObj($1, $2)){ #raiseObjectConversionError(); $3}$n", + [r, checkFor, raiseInstr(p)]) if n[0].typ.kind != tyObject: putIntoDest(p, d, n, "(($1) ($2))" % [getTypeDesc(p.module, n.typ), rdLoc(a)], a.storage) @@ -2629,9 +2647,9 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = expr(p, n[1][0], d) of nkObjDownConv: downConv(p, n, d) of nkObjUpConv: upConv(p, n, d) - of nkChckRangeF: genRangeChck(p, n, d, "chckRangeF") - of nkChckRange64: genRangeChck(p, n, d, "chckRange64") - of nkChckRange: genRangeChck(p, n, d, "chckRange") + of nkChckRangeF: genRangeChck(p, n, d) + of nkChckRange64: genRangeChck(p, n, d) + of nkChckRange: genRangeChck(p, n, d) of nkStringToCString: convStrToCStr(p, n, d) of nkCStringToString: convCStrToStr(p, n, d) of nkLambdaKinds: diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 9c067a339e81..1c63ebaeac83 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -706,6 +706,22 @@ proc finallyActions(p: BProc) = if finallyBlock != nil: genSimpleBlock(p, finallyBlock[0]) +proc raiseInstr(p: BProc): Rope = + if p.config.exc == excGoto: + let L = p.nestedTryStmts.len + if L == 0: + p.flags.incl beforeRetNeeded + # easy case, simply goto 'ret': + result = ropecg(p.module, "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. + result = ropecg(p.module, "goto LA$1_;$n", + [p.nestedTryStmts[L-1].label]) + # + ord(p.nestedTryStmts[L-1].inExcept)]) + else: + result = nil + proc genRaiseStmt(p: BProc, t: PNode) = if p.config.exc == excCpp: discard cgsym(p.module, "popCurrentExceptionEx") @@ -733,18 +749,9 @@ proc genRaiseStmt(p: BProc, t: PNode) = 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)]) + let gotoInstr = raiseInstr(p) + if gotoInstr != nil: + line(p, cpsStmts, gotoInstr) template genCaseGenericBranch(p: BProc, b: PNode, e: TLoc, rangeFormat, eqFormat: FormatStr, labl: TLabel) = @@ -1009,14 +1016,14 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = genSimpleBlock(p, t[^1][0]) -proc bodyCanRaise(n: PNode): bool = +proc bodyCanRaise(p: BProc; n: PNode): bool = case n.kind of nkCallKinds: - result = canRaise(n[0]) + result = canRaiseDisp(p, n[0]) if not result: # also check the arguments: for i in 1 ..< n.len: - if bodyCanRaise(n[i]): return true + if bodyCanRaise(p, n[i]): return true of nkRaiseStmt: result = true of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, @@ -1024,22 +1031,24 @@ proc bodyCanRaise(n: PNode): bool = result = false else: for i in 0 ..< safeLen(n): - if bodyCanRaise(n[i]): return true + if bodyCanRaise(p, n[i]): return true result = false proc genTryGoto(p: BProc; t: PNode; d: var TLoc) = - if not bodyCanRaise(t): + let fin = if t[^1].kind == nkFinally: t[^1] else: nil + inc p.labels + let lab = p.labels + p.nestedTryStmts.add((fin, false, Natural lab)) + + if not bodyCanRaise(p, 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]) + linefmt(p, cpsStmts, "LA$1_: ;$n", [lab]) + if fin != nil: + genStmts(p, fin[0]) + discard pop(p.nestedTryStmts) 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 p.procSec(cpsLocals).add(ropecg(p.module, "NI oldNimErr$1_;$n", [lab])) linefmt(p, cpsStmts, "oldNimErr$1_ = *nimErr_; *nimErr_ = 0;;$n", [lab]) @@ -1099,7 +1108,7 @@ proc genTryGoto(p: BProc; t: PNode; d: var TLoc) = #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]): + if not bodyCanRaise(p, 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]) @@ -1380,8 +1389,8 @@ proc asgnFieldDiscriminant(p: BProc, e: PNode) = genCaseObjDiscMapping(p, e[0], t, field, oldVal) genCaseObjDiscMapping(p, e[1], t, field, newVal) lineCg(p, cpsStmts, - "#nimFieldDiscriminantCheckV2($1, $2);$n", - [rdLoc(oldVal), rdLoc(newVal)]) + "if ($1 != $2) { #raiseObjectCaseTransition(); $3}$n", + [rdLoc(oldVal), rdLoc(newVal), raiseInstr(p)]) else: genDiscriminantCheck(p, a, tmp, dotExpr[0].typ, field) genAssignment(p, a, tmp, {}) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 57424ec9548c..06507b6e6ea0 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -276,6 +276,7 @@ proc postStmtActions(p: BProc) {.inline.} = proc accessThreadLocalVar(p: BProc, s: PSym) proc emulatedThreadVars(conf: ConfigRef): bool {.inline.} proc genProc(m: BModule, prc: PSym) +proc raiseInstr(p: BProc): Rope template compileToCpp(m: BModule): untyped = m.config.cmd == cmdCompileToCpp or sfCompileToCpp in m.module.flags diff --git a/compiler/commands.nim b/compiler/commands.nim index 0c93654da287..62d4d75dd1c7 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -881,6 +881,10 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; processOnOffSwitchG(conf, {optBenchmarkVM}, arg, pass, info) of "sinkinference": processOnOffSwitch(conf, {optSinkInference}, arg, pass, info) + of "panics": + processOnOffSwitchG(conf, {optPanics}, arg, pass, info) + if optPanics in conf.globalOptions: + defineSymbol(conf.symbols, "nimPanics") of "": # comes from "-" in for example: `nim c -r -` (gets stripped from -) handleStdinInput(conf) else: diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index a84b0d8780ba..da40dd2b7d79 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -113,3 +113,4 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimHasLibFFIEnabled") defineSymbol("nimHasSinkInference") + defineSymbol("nimNewIntegerOps") diff --git a/compiler/enumtostr.nim b/compiler/enumtostr.nim index 42d844609bb4..614190ac3164 100644 --- a/compiler/enumtostr.nim +++ b/compiler/enumtostr.nim @@ -40,6 +40,7 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph): PSym = n[resultPos] = newSymNode(res) result.ast = n incl result.flags, sfFromGeneric + incl result.flags, sfAlwaysReturn proc searchObjCaseImpl(obj: PNode; field: PSym): PNode = case obj.kind @@ -101,3 +102,4 @@ proc genCaseObjDiscMapping*(t: PType; field: PSym; info: TLineInfo; g: ModuleGra n[resultPos] = newSymNode(res) result.ast = n incl result.flags, sfFromGeneric + incl result.flags, sfAlwaysReturn diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index 5ac65201ebb0..6eac5bf1be13 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -34,6 +34,7 @@ type warnTypelessParam, warnUseBase, warnWriteToForeignHeap, warnUnsafeCode, warnUnusedImportX, + warnInheritFromException, warnEachIdentIsTuple, warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnGcUnsafe2, warnUninit, warnGcMem, warnDestructor, warnLockLevel, warnResultShadowed, @@ -81,6 +82,7 @@ const warnWriteToForeignHeap: "write to foreign heap", warnUnsafeCode: "unsafe code: '$1'", warnUnusedImportX: "imported and not used: '$1'", + warnInheritFromException: "inherit from a more precise exception type like ValueError, IOError or OSError", warnEachIdentIsTuple: "each identifier is a tuple", warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future.", warnProveField: "cannot prove that field '$1' is accessible", @@ -138,7 +140,8 @@ const "LanguageXNotSupported", "FieldXNotSupported", "CommentXIgnored", "TypelessParam", "UseBase", "WriteToForeignHeap", - "UnsafeCode", "UnusedImport", "EachIdentIsTuple", + "UnsafeCode", "UnusedImport", "InheritFromException", + "EachIdentIsTuple", "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit", "GcMem", "Destructor", "LockLevel", "ResultShadowed", "Spacing", "CaseTransition", "CycleCreated", "User"] @@ -227,7 +230,7 @@ type TErrorOutputs* = set[TErrorOutput] ERecoverableError* = object of ValueError - ESuggestDone* = object of Exception + ESuggestDone* = object of ValueError proc `==`*(a, b: FileIndex): bool {.borrow.} diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index 3a6d287083b6..d3be4b3b6046 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -165,6 +165,7 @@ proc stopCompile*(g: ModuleGraph): bool {.inline.} = proc createMagic*(g: ModuleGraph; name: string, m: TMagic): PSym = result = newSym(skProc, getIdent(g.cache, name), nil, unknownLineInfo, {}) result.magic = m + result.flags = {sfAlwaysReturn} proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph = result = ModuleGraph() diff --git a/compiler/options.nim b/compiler/options.nim index 42406272cc20..88251a42e724 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -90,6 +90,7 @@ type # please make sure we have under 32 options optNimV019 optBenchmarkVM # Enables cpuTime() in the VM optProduceAsm # produce assembler code + optPanics # turn panics (sysFatal) into a process termination TGlobalOptions* = set[TGlobalOption] @@ -509,15 +510,17 @@ proc getOutFile*(conf: ConfigRef; filename: RelativeFile, ext: string): Absolute proc absOutFile*(conf: ConfigRef): AbsoluteFile = result = conf.outDir / conf.outFile - if dirExists(result.string): - result.string.add ".out" + when defined(posix): + if dirExists(result.string): + result.string.add ".out" proc prepareToWriteOutput*(conf: ConfigRef): AbsoluteFile = ## Create the output directory and returns a full path to the output file createDir conf.outDir result = conf.outDir / conf.outFile - if dirExists(result.string): - result.string.add ".out" + when defined(posix): + if dirExists(result.string): + result.string.add ".out" proc getPrefixDir*(conf: ConfigRef): AbsoluteDir = ## Gets the prefix dir, usually the parent directory where the binary resides. diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index b91e93025c40..cdb47d2a45ca 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1283,7 +1283,7 @@ proc typeSectionFinalPass(c: PContext, n: PNode) = while x.kind in {nkStmtList, nkStmtListExpr} and x.len > 0: x = x.lastSon if x.kind notin {nkObjectTy, nkDistinctTy, nkEnumTy, nkEmpty} and - s.typ.kind notin {tyObject, tyEnum}: + s.typ.skipTypes(abstractPtrs-{tyAlias}).kind notin {tyObject, tyEnum}: # type aliases are hard: var t = semTypeNode(c, x, nil) assert t != nil diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index f05affc6b56b..e0be620c3fe5 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -847,6 +847,9 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; isInheritable: bool): PTy # specialized object, there will be second check after instantiation # located in semGeneric. if concreteBase.kind == tyObject: + if concreteBase.sym != nil and concreteBase.sym.magic == mException and + sfSystemModule notin c.module.flags: + message(c.config, n.info, warnInheritFromException, "") addInheritedFields(c, check, pos, concreteBase) else: if concreteBase.kind != tyError: diff --git a/compiler/sizealignoffsetimpl.nim b/compiler/sizealignoffsetimpl.nim index ee88e276f2ef..81d2f5fafffc 100644 --- a/compiler/sizealignoffsetimpl.nim +++ b/compiler/sizealignoffsetimpl.nim @@ -23,7 +23,7 @@ const szUncomputedSize* = -1 szTooBigSize* = -4 -type IllegalTypeRecursionError = object of Exception +type IllegalTypeRecursionError = object of ValueError proc raiseIllegalTypeRecursion() = raise newException(IllegalTypeRecursionError, "illegal type recursion") diff --git a/doc/advopt.txt b/doc/advopt.txt index 517edab5b198..1ebec0f492b6 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -146,3 +146,4 @@ Advanced options: see also https://nim-lang.github.io/Nim/estp.html --benchmarkVM:on|off enable benchmarking of VM code with cpuTime() --sinkInference:on|off en-/disable sink parameter inference (default: on) + --panics:on|off turn panics into process terminations (default: off) diff --git a/doc/manual.rst b/doc/manual.rst index 516be24eaaed..3a0faed2e7ec 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -105,13 +105,13 @@ identifier meanings, and in some cases expression values. An error detected during semantic analysis is called a `static error`:idx:. Errors described in this manual are static errors when not otherwise specified. -A `checked runtime error`:idx: is an error that the implementation detects +A `panic`:idx: is an error that the implementation detects and reports at runtime. The method for reporting such errors is via *raising exceptions* or *dying with a fatal error*. However, the implementation provides a means to disable these `runtime checks`:idx:. See the section pragmas_ for details. -Whether a checked runtime error results in an exception or in a fatal error is +Whether a panic results in an exception or in a fatal error is implementation specific. Thus the following program is invalid; even though the code purports to catch the `IndexError` from an out-of-bounds array access, the compiler may instead choose to allow the program to die with a fatal error. @@ -124,6 +124,12 @@ compiler may instead choose to allow the program to die with a fatal error. except IndexError: echo "invalid index" +The current implementation allows to switch between these different behaviors +via ``--panics:on|off``. When panics are turned on, the program dies on a +panic, if they are turned off the runtime errors are turned into +exceptions. The benefit of ``--panics:on`` is that it produces smaller binary +code and the compiler has more freedom to optimize the code. + An `unchecked runtime error`:idx: is an error that is not guaranteed to be detected, and can cause the subsequent behavior of the computation to be arbitrary. Unchecked runtime errors cannot occur if only `safe`:idx: @@ -869,9 +875,9 @@ Ordinal types have the following characteristics: the operation of functions as ``inc``, ``ord``, ``dec`` on ordinal types to be defined. - Ordinal values have a smallest possible value. Trying to count further - down than the smallest value gives a checked runtime or static error. + down than the smallest value produces a panic or a static error. - Ordinal values have a largest possible value. Trying to count further - than the largest value gives a checked runtime or static error. + than the largest value produces a panic or a static error. Integers, bool, characters and enumeration types (and subranges of these types) belong to ordinal types. For reasons of simplicity of implementation @@ -982,7 +988,7 @@ lowest and highest value of the type. For example: to 5. ``PositiveFloat`` defines a subrange of all positive floating point values. NaN does not belong to any subrange of floating point types. Assigning any other value to a variable of type ``Subrange`` is a -checked runtime error (or static error if it can be determined during +panic (or a static error if it can be determined during semantic analysis). Assignments from the base type to one of its subrange types (and vice versa) are allowed. @@ -1763,9 +1769,9 @@ Nil If a reference points to *nothing*, it has the value ``nil``. ``nil`` is also the default value for all ``ref`` and ``ptr`` types. Dereferencing ``nil`` -is an unrecoverable fatal runtime error. A dereferencing operation ``p[]`` -implies that ``p`` is not nil. This can be exploited by the implementation to -optimize code like: +is an unrecoverable fatal runtime error (and not a panic). +A dereferencing operation ``p[]`` implies that ``p`` is not nil. This can be +exploited by the implementation to optimize code like: .. code-block:: nim @@ -4107,7 +4113,7 @@ branch always has to be ``void``: .. code-block:: nim from strutils import parseInt - + let x = try: parseInt("133a") except: -1 finally: echo "hi" @@ -4242,9 +4248,11 @@ The exception tree is defined in the `system `_ module. Every exception inherits from ``system.Exception``. Exceptions that indicate programming bugs inherit from ``system.Defect`` (which is a subtype of ``Exception``) and are stricly speaking not catchable as they can also be mapped to an operation -that terminates the whole process. Exceptions that indicate any other runtime -error that can be caught inherit from ``system.CatchableError`` -(which is a subtype of ``Exception``). +that terminates the whole process. If panics are turned into exceptions, these +exceptions inherit from `Defect`. + +Exceptions that indicate any other runtime error that can be caught inherit from +``system.CatchableError`` (which is a subtype of ``Exception``). Imported exceptions @@ -5667,7 +5675,7 @@ The ``include`` statement can be used outside of the top level, as such: # Module B proc main() = include A - + main() # => Hello World! diff --git a/koch.nim b/koch.nim index c2f28f2e034f..60d0e3544ecb 100644 --- a/koch.nim +++ b/koch.nim @@ -475,7 +475,7 @@ proc hostInfo(): string = proc runCI(cmd: string) = doAssert cmd.len == 0, cmd # avoid silently ignoring - echo "runCI:", cmd + echo "runCI: ", cmd echo hostInfo() # note(@araq): Do not replace these commands with direct calls (eg boot()) # as that would weaken our testing efforts. diff --git a/lib/nimbase.h b/lib/nimbase.h index 004ba170b1b5..d25c4dad070e 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -545,4 +545,26 @@ typedef int Nim_and_C_compiler_disagree_on_target_architecture[sizeof(NI) == siz #define NIM_CHECK_SIZE(typ, sz) \ _Static_assert(sizeof(typ) == sz, "Nim & C disagree on type size") +/* these exist to make the codegen logic simpler */ +#define nimModInt(a, b, res) (((*res) = (a) % (b)), 0) +#define nimModInt64(a, b, res) (((*res) = (a) % (b)), 0) + +/* these exist because we cannot have .compilerProcs that are importc'ed + by a different name */ + +#define nimAddInt64(a, b, res) __builtin_saddll_overflow(a, b, (long long int*)res) +#define nimSubInt64(a, b, res) __builtin_ssubll_overflow(a, b, (long long int*)res) +#define nimMulInt64(a, b, res) __builtin_smulll_overflow(a, b, (long long int*)res) + +#if NIM_INTBITS == 32 + #define nimAddInt(a, b, res) __builtin_sadd_overflow(a, b, res) + #define nimSubInt(a, b, res) __builtin_ssub_overflow(a, b, res) + #define nimMulInt(a, b, res) __builtin_smul_overflow(a, b, res) +#else + /* map it to the 'long long' variant */ + #define nimAddInt(a, b, res) __builtin_saddll_overflow(a, b, (long long int*)res) + #define nimSubInt(a, b, res) __builtin_ssubll_overflow(a, b, (long long int*)res) + #define nimMulInt(a, b, res) __builtin_smulll_overflow(a, b, (long long int*)res) +#endif + #endif /* NIMBASE_H */ diff --git a/lib/system.nim b/lib/system.nim index 5dff62806ee7..602fbc1f11cf 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2221,7 +2221,10 @@ when notJSnotNims: # we cannot compile this with stack tracing on # as it would recurse endlessly! - include "system/arithm" + when defined(nimNewIntegerOps): + include "system/integerops" + else: + include "system/arithm" {.pop.} diff --git a/lib/system/arithm.nim b/lib/system/arithm.nim index 16ac8affea5e..9e3ec79c4ab5 100644 --- a/lib/system/arithm.nim +++ b/lib/system/arithm.nim @@ -409,13 +409,13 @@ when not declared(mulInt): # We avoid setting the FPU control word here for compatibility with libraries # written in other languages. -proc raiseFloatInvalidOp {.noinline.} = +proc raiseFloatInvalidOp {.compilerproc, noinline.} = sysFatal(FloatInvalidOpError, "FPU operation caused a NaN result") proc nanCheck(x: float64) {.compilerproc, inline.} = if x != x: raiseFloatInvalidOp() -proc raiseFloatOverflow(x: float64) {.noinline.} = +proc raiseFloatOverflow(x: float64) {.compilerproc, noinline.} = if x > 0.0: sysFatal(FloatOverflowError, "FPU operation caused an overflow") else: diff --git a/lib/system/chcks.nim b/lib/system/chcks.nim index 52642bbf9178..117543fcda98 100644 --- a/lib/system/chcks.nim +++ b/lib/system/chcks.nim @@ -28,6 +28,19 @@ proc raiseIndexError() {.compilerproc, noinline.} = proc raiseFieldError(f: string) {.compilerproc, noinline.} = sysFatal(FieldError, f) +proc raiseRangeErrorI(i, a, b: BiggestInt) {.compilerproc, noinline.} = + sysFatal(RangeError, "value out of range: " & $i & " notin " & $a & " .. " & $b) + +proc raiseRangeErrorF(i, a, b: float) {.compilerproc, noinline.} = + sysFatal(RangeError, "value out of range: " & $i & " notin " & $a & " .. " & $b) + +proc raiseRangeErrorU(i, a, b: uint64) {.compilerproc, noinline.} = + # todo: better error reporting + sysFatal(RangeError, "value out of range") + +proc raiseObjectConversionError() {.compilerproc, noinline.} = + sysFatal(ObjectConversionError, "invalid object conversion") + proc chckIndx(i, a, b: int): int = if i >= a and i <= b: return i @@ -116,6 +129,5 @@ when not defined(nimV2): return true when defined(nimV2): - proc nimFieldDiscriminantCheckV2(oldDiscVal, newDiscVal: uint8) {.compilerproc.} = - if oldDiscVal != newDiscVal: - sysFatal(FieldError, "assignment to discriminant changes object branch") + proc raiseObjectCaseTransition() {.compilerproc.} = + sysFatal(FieldError, "assignment to discriminant changes object branch") diff --git a/lib/system/fatal.nim b/lib/system/fatal.nim index d68d067125b5..761e0dd69b2c 100644 --- a/lib/system/fatal.nim +++ b/lib/system/fatal.nim @@ -24,7 +24,7 @@ when hostOS == "standalone": rawoutput(message) panic(arg) -elif (defined(nimQuirky) or gotoBasedExceptions) and not defined(nimscript): +elif (defined(nimQuirky) or defined(nimPanics)) and not defined(nimscript): import ansi_c proc name(t: typedesc): string {.magic: "TypeTrait".} @@ -46,20 +46,9 @@ elif (defined(nimQuirky) or gotoBasedExceptions) and not defined(nimscript): else: proc sysFatal(exceptn: typedesc, message: string) {.inline, noreturn.} = - when declared(owned): - var e: owned(ref exceptn) - else: - var e: ref exceptn - new(e) - e.msg = message - raise e + raise (ref exceptn)(msg: message) proc sysFatal(exceptn: typedesc, message, arg: string) {.inline, noreturn.} = - when declared(owned): - var e: owned(ref exceptn) - else: - var e: ref exceptn - new(e) - e.msg = message & arg - raise e + raise (ref exceptn)(msg: message & arg) + {.pop.} diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 7be9f4b1fc4b..15f316cccbd1 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -441,13 +441,15 @@ proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} = zeroMem(result, size) when defined(memProfiler): nimProfile(size) +{.push overflowChecks: on.} proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = # `newObj` already uses locks, so no need for them here. - let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) + let size = len * typ.base.size + GenericSeqSize result = newObj(typ, size) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len when defined(memProfiler): nimProfile(size) +{.pop.} proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = # generates a new object and sets its reference counter to 1 @@ -476,12 +478,14 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = sysAssert(allocInv(gch.region), "newObjRC1 end") when defined(memProfiler): nimProfile(size) +{.push overflowChecks: on.} proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} = - let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) + let size = len * typ.base.size + GenericSeqSize result = newObjRC1(typ, size) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len when defined(memProfiler): nimProfile(size) +{.pop.} proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = collectCT(gch) diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index fe07766d9288..ff2b6ad6a6c0 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -79,7 +79,7 @@ template decTypeSize(cell, t) = if t.kind in {tyString, tySequence}: let cap = cast[PGenericSeq](cellToUsr(cell)).space let size = if t.kind == tyString: cap+1+GenericSeqSize - else: addInt(mulInt(cap, t.base.size), GenericSeqSize) + else: cap * t.base.size + GenericSeqSize atomicDec t.sizes, size+sizeof(Cell) else: atomicDec t.sizes, t.base.size+sizeof(Cell) diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index 271543445f06..a026b404baef 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -305,20 +305,22 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = when defined(memProfiler): nimProfile(size) when not defined(nimSeqsV2): + {.push overflowChecks: on.} proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = # `newObj` already uses locks, so no need for them here. - let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) + let size = len * typ.base.size + GenericSeqSize result = newObj(typ, size) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len when defined(memProfiler): nimProfile(size) proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} = - let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) + let size = len * typ.base.size + GenericSeqSize result = newObj(typ, size) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len when defined(memProfiler): nimProfile(size) + {.pop.} proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = collectCT(gch, newsize + sizeof(Cell)) diff --git a/lib/system/gc_regions.nim b/lib/system/gc_regions.nim index 6b1640ab1448..38365469edf5 100644 --- a/lib/system/gc_regions.nim +++ b/lib/system/gc_regions.nim @@ -334,20 +334,21 @@ proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} = result = rawNewObj(tlRegion, typ, size) when defined(memProfiler): nimProfile(size) +{.push overflowChecks: on.} proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = - let size = roundup(addInt(mulInt(len, typ.base.size), GenericSeqSize), - MemAlign) + let size = roundup(len * typ.base.size + GenericSeqSize, MemAlign) result = rawNewSeq(tlRegion, typ, size) zeroMem(result, size) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len proc newStr(typ: PNimType, len: int; init: bool): pointer {.compilerRtl.} = - let size = roundup(addInt(len, GenericSeqSize), MemAlign) + let size = roundup(len + GenericSeqSize, MemAlign) result = rawNewSeq(tlRegion, typ, size) if init: zeroMem(result, size) cast[PGenericSeq](result).len = 0 cast[PGenericSeq](result).reserved = len +{.pop.} proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = result = rawNewObj(tlRegion, typ, size) diff --git a/lib/system/integerops.nim b/lib/system/integerops.nim new file mode 100644 index 000000000000..dc0197f14c46 --- /dev/null +++ b/lib/system/integerops.nim @@ -0,0 +1,132 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2020 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# Integer arithmetic with overflow checking. Uses +# intrinsics or inline assembler. + +proc raiseOverflow {.compilerproc, noinline.} = + # a single proc to reduce code size to a minimum + sysFatal(OverflowError, "over- or underflow") + +proc raiseDivByZero {.compilerproc, noinline.} = + sysFatal(DivByZeroError, "division by zero") + +{.pragma: nimbaseH, importc, nodecl, noSideEffect, compilerproc.} + +when defined(gcc) or defined(clang): + # take the #define from nimbase.h + + proc nimAddInt(a, b: int, res: ptr int): bool {.nimbaseH.} + proc nimSubInt(a, b: int, res: ptr int): bool {.nimbaseH.} + proc nimMulInt(a, b: int, res: ptr int): bool {.nimbaseH.} + + proc nimAddInt64(a, b: int64; res: ptr int64): bool {.nimbaseH.} + proc nimSubInt64(a, b: int64; res: ptr int64): bool {.nimbaseH.} + proc nimMulInt64(a, b: int64; res: ptr int64): bool {.nimbaseH.} + +# unary minus and 'abs' not required here anymore and are directly handled +# in the code generator. +# 'nimModInt' does exist in nimbase.h without check as we moved the +# check for 0 to the codgen. +proc nimModInt(a, b: int; res: ptr int): bool {.nimbaseH.} + +proc nimModInt64(a, b: int64; res: ptr int64): bool {.nimbaseH.} + +# Platform independent versions. + +template addImplFallback(name, T, U) {.dirty.} = + when not declared(name): + proc name(a, b: T; res: ptr T): bool {.compilerproc, inline.} = + let r = cast[T](cast[U](a) + cast[U](b)) + if (r xor a) >= T(0) or (r xor b) >= T(0): + res[] = r + else: + result = true + +addImplFallback(nimAddInt, int, uint) +addImplFallback(nimAddInt64, int64, uint64) + +template subImplFallback(name, T, U) {.dirty.} = + when not declared(name): + proc name(a, b: T; res: ptr T): bool {.compilerproc, inline.} = + let r = cast[T](cast[U](a) - cast[U](b)) + if (r xor a) >= 0 or (r xor not b) >= 0: + res[] = r + else: + result = true + +subImplFallback(nimSubInt, int, uint) +subImplFallback(nimSubInt64, int64, uint64) + +template mulImplFallback(name, T, U, conv) {.dirty.} = + # + # This code has been inspired by Python's source code. + # The native int product x*y is either exactly right or *way* off, being + # just the last n bits of the true product, where n is the number of bits + # in an int (the delivered product is the true product plus i*2**n for + # some integer i). + # + # The native float64 product x*y is subject to three + # rounding errors: on a sizeof(int)==8 box, each cast to double can lose + # info, and even on a sizeof(int)==4 box, the multiplication can lose info. + # But, unlike the native int product, it's not in *range* trouble: even + # if sizeof(int)==32 (256-bit ints), the product easily fits in the + # dynamic range of a float64. So the leading 50 (or so) bits of the float64 + # product are correct. + # + # We check these two ways against each other, and declare victory if + # they're approximately the same. Else, because the native int product is + # the only one that can lose catastrophic amounts of information, it's the + # native int product that must have overflowed. + # + when not declared(name): + proc name(a, b: T; res: ptr T): bool {.compilerproc, inline.} = + let r = cast[T](cast[U](a) * cast[U](b)) + let floatProd = conv(a) * conv(b) + let resAsFloat = conv(r) + # Fast path for normal case: small multiplicands, and no info + # is lost in either method. + if resAsFloat == floatProd: + res[] = r + else: + # Somebody somewhere lost info. Close enough, or way off? Note + # that a != 0 and b != 0 (else resAsFloat == floatProd == 0). + # The difference either is or isn't significant compared to the + # true value (of which floatProd is a good approximation). + + # abs(diff)/abs(prod) <= 1/32 iff + # 32 * abs(diff) <= abs(prod) -- 5 good bits is "close enough" + if 32.0 * abs(resAsFloat - floatProd) <= abs(floatProd): + res[] = r + else: + result = true + +mulImplFallback(nimMulInt, int, uint, toFloat) +mulImplFallback(nimMulInt64, int64, uint64, toBiggestFloat) + + +template divImplFallback(name, T) {.dirty.} = + proc name(a, b: T; res: ptr T): bool {.compilerproc, inline.} = + # we moved the b == 0 case out into the codegen. + if a == low(T) and b == T(-1): + result = true + else: + res[] = a div b + +divImplFallback(nimDivInt, int) +divImplFallback(nimDivInt64, int64) + +proc raiseFloatInvalidOp {.compilerproc, noinline.} = + sysFatal(FloatInvalidOpError, "FPU operation caused a NaN result") + +proc raiseFloatOverflow(x: float64) {.compilerproc, noinline.} = + if x > 0.0: + sysFatal(FloatOverflowError, "FPU operation caused an overflow") + else: + sysFatal(FloatUnderflowError, "FPU operations caused an underflow") diff --git a/lib/system/mm/boehm.nim b/lib/system/mm/boehm.nim index d02d52b525f5..a8b0e17b409e 100644 --- a/lib/system/mm/boehm.nim +++ b/lib/system/mm/boehm.nim @@ -101,10 +101,12 @@ proc newObj(typ: PNimType, size: int): pointer {.compilerproc.} = else: result = alloc(size) if typ.finalizer != nil: boehmRegisterFinalizer(result, boehmgc_finalizer, typ.finalizer, nil, nil) +{.push overflowChecks: on.} proc newSeq(typ: PNimType, len: int): pointer {.compilerproc.} = - result = newObj(typ, addInt(mulInt(len, typ.base.size), GenericSeqSize)) + result = newObj(typ, len * typ.base.size + GenericSeqSize) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len +{.pop.} proc growObj(old: pointer, newsize: int): pointer = result = realloc(old, newsize) diff --git a/lib/system/mm/none.nim b/lib/system/mm/none.nim index 3572b062c825..0079daac3983 100644 --- a/lib/system/mm/none.nim +++ b/lib/system/mm/none.nim @@ -19,10 +19,12 @@ proc newObj(typ: PNimType, size: int): pointer {.compilerproc.} = proc newObjNoInit(typ: PNimType, size: int): pointer = result = alloc(size) +{.push overflowChecks: on.} proc newSeq(typ: PNimType, len: int): pointer {.compilerproc.} = - result = newObj(typ, addInt(mulInt(len, typ.base.size), GenericSeqSize)) + result = newObj(typ, len * typ.base.size + GenericSeqSize) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len +{.pop.} proc growObj(old: pointer, newsize: int): pointer = result = realloc(old, newsize) diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index d2d0d576fbf3..2c3394df945b 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -102,18 +102,20 @@ else: include "system/gc" when not declared(nimNewSeqOfCap) and not defined(nimSeqsV2): + {.push overflowChecks: on.} proc nimNewSeqOfCap(typ: PNimType, cap: int): pointer {.compilerproc.} = when defined(gcRegions): - let s = mulInt(cap, typ.base.size) # newStr already adds GenericSeqSize + let s = cap * typ.base.size # newStr already adds GenericSeqSize result = newStr(typ, s, ntfNoRefs notin typ.base.flags) else: - let s = addInt(mulInt(cap, typ.base.size), GenericSeqSize) + let s = cap * typ.base.size + GenericSeqSize when declared(newObjNoInit): result = if ntfNoRefs in typ.base.flags: newObjNoInit(typ, s) else: newObj(typ, s) else: result = newObj(typ, s) cast[PGenericSeq](result).len = 0 cast[PGenericSeq](result).reserved = cap + {.pop.} {.pop.} diff --git a/tests/assert/tassert_c.nim b/tests/assert/tassert_c.nim index 98b859c1cf08..e9e6b519256b 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(29) failedAssertImpl assertions.nim(22) raiseAssert -fatal.nim(55) sysFatal""" +fatal.nim(49) sysFatal""" proc tmatch(x, p: string): bool = var i = 0 diff --git a/tests/destructor/tgotoexceptions7.nim b/tests/destructor/tgotoexceptions7.nim new file mode 100644 index 000000000000..30fe28e7c415 --- /dev/null +++ b/tests/destructor/tgotoexceptions7.nim @@ -0,0 +1,52 @@ +discard """ + cmd: "nim c --gc:arc --exceptions:goto --panics:off $file" + output: '''field error prevented +prevented! +caught +AssertionError +900''' +""" + +type + E = enum + kindA, kindB + Obj = object + case kind: E + of kindA: s: string + of kindB: i: int + + ObjA = ref object of RootObj + ObjB = ref object of ObjA + +proc takeRange(x: range[0..4]) = discard + +proc bplease(x: ObjB) = discard + +proc helper = doAssert(false) + +proc main(i: int) = + var obj = Obj(kind: kindA, s: "abc") + try: + obj.kind = kindB + except FieldError: + echo "field error prevented" + + try: + var objA = ObjA() + bplease(ObjB(objA)) + except ObjectConversionError: + echo "prevented!" + + try: + takeRange(i) + except RangeError: + echo "caught" + + try: + helper() + except AssertionError: + echo "AssertionError" + + echo i * i + +main(30) diff --git a/tests/range/tsubrange2.nim b/tests/range/tsubrange2.nim index e0fb71c5f425..1b90b52c791d 100644 --- a/tests/range/tsubrange2.nim +++ b/tests/range/tsubrange2.nim @@ -1,5 +1,5 @@ discard """ - outputsub: "value out of range: 50 [RangeError]" + outputsub: "value out of range: 50 notin 0 .. 40 [RangeError]" exitcode: "1" """ diff --git a/tests/range/tsubrange3.nim b/tests/range/tsubrange3.nim index d3aae87dfda0..ba9e6a143ec0 100644 --- a/tests/range/tsubrange3.nim +++ b/tests/range/tsubrange3.nim @@ -1,5 +1,5 @@ discard """ - outputsub: "value out of range: 50 [RangeError]" + outputsub: "value out of range: 50 notin 0 .. 40 [RangeError]" exitcode: "1" """