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
This commit is contained in:
Andreas Rumpf 2020-03-12 23:44:33 +01:00 • committed by GitHub
commit a6682de004
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 441 additions and 142 deletions

View file

@ -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 */

View file

@ -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.}

View file

@ -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:

View file

@ -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")

View file

@ -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.}

View file

@ -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)

View file

@ -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)

View file

@ -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))

View file

@ -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)

132
lib/system/integerops.nim Normal file
View file

@ -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")

View file

@ -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)

View file

@ -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)

View file

@ -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.}