diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2020-03-12 23:44:33 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-12 23:44:33 +0100 |
commit | a6682de0045468ae1d15afbd2fd5378960e15eb7 (patch) | |
tree | 85bca8b0a220e6295fe60bfc45487197acaf8b8d /lib | |
parent | 14b2354b7da36041ca046e60e833b5be9a04f1e4 (diff) | |
download | Nim-a6682de0045468ae1d15afbd2fd5378960e15eb7.tar.gz |
catchable defects (#13626)
* allow defects to be caught even for --exceptions:goto (WIP) * implemented the new --panics:on|off switch; refs https://github.com/nim-lang/RFCs/issues/180 * new implementation for integer overflow checking * produce a warning if a user-defined exception type inherits from Exception directly * applied Timothee's suggestions; improved the documentation and replace the term 'checked runtime check' by 'panic' * fixes #13627 * don't inherit from Exception directly
Diffstat (limited to 'lib')
-rw-r--r-- | lib/nimbase.h | 22 | ||||
-rw-r--r-- | lib/system.nim | 5 | ||||
-rw-r--r-- | lib/system/arithm.nim | 4 | ||||
-rw-r--r-- | lib/system/chcks.nim | 18 | ||||
-rw-r--r-- | lib/system/fatal.nim | 19 | ||||
-rw-r--r-- | lib/system/gc.nim | 8 | ||||
-rw-r--r-- | lib/system/gc_common.nim | 2 | ||||
-rw-r--r-- | lib/system/gc_ms.nim | 6 | ||||
-rw-r--r-- | lib/system/gc_regions.nim | 7 | ||||
-rw-r--r-- | lib/system/integerops.nim | 132 | ||||
-rw-r--r-- | lib/system/mm/boehm.nim | 4 | ||||
-rw-r--r-- | lib/system/mm/none.nim | 4 | ||||
-rw-r--r-- | lib/system/mmdisp.nim | 6 |
13 files changed, 204 insertions, 33 deletions
diff --git a/lib/nimbase.h b/lib/nimbase.h index 004ba170b..d25c4dad0 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 5dff62806..602fbc1f1 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 16ac8affe..9e3ec79c4 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 52642bbf9..117543fcd 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 d68d06712..761e0dd69 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 7be9f4b1f..15f316ccc 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 fe07766d9..ff2b6ad6a 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 271543445..a026b404b 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 6b1640ab1..38365469e 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 000000000..dc0197f14 --- /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 d02d52b52..a8b0e17b4 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 3572b062c..0079daac3 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 d2d0d576f..2c3394df9 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.} |