First pass at int overflow checking.
authorRobert Bradshaw <robertwb@gmail.com>
Tue, 31 Jul 2012 07:14:56 +0000 (00:14 -0700)
committerRobert Bradshaw <robertwb@gmail.com>
Thu, 25 Oct 2012 05:49:43 +0000 (22:49 -0700)
Cython/Compiler/ExprNodes.py
Cython/Compiler/PyrexTypes.py
Cython/Utility/Overflow.c [new file with mode: 0644]

index c476ac2..6cfc19e 100755 (executable)
@@ -7997,6 +7997,7 @@ class NumBinopNode(BinopNode):
     #  Binary operation taking numeric arguments.
 
     infix = True
+    overflow_check = False
 
     def analyse_c_operation(self, env):
         type1 = self.operand1.type
@@ -8007,6 +8008,11 @@ class NumBinopNode(BinopNode):
             return
         if self.type.is_complex:
             self.infix = False
+        if self.type.is_int and self.operator in ('+', '-', '*', '/'):
+            self.overflow_check = True
+            binop = {'+': 'add', '-': 'sub', '*': 'mul', '/': 'div'}[self.operator]
+            self.func = self.type.overflow_check_binop(binop, env)
+            self.is_temp = True
         if not self.infix or (type1.is_numeric and type2.is_numeric):
             self.operand1 = self.operand1.coerce_to(self.type, env)
             self.operand2 = self.operand2.coerce_to(self.type, env)
@@ -8048,8 +8054,26 @@ class NumBinopNode(BinopNode):
         return (type1.is_numeric  or type1.is_enum) \
             and (type2.is_numeric  or type2.is_enum)
 
+    def generate_result_code(self, code):
+        super(NumBinopNode, self).generate_result_code(code)
+        if self.overflow_check:
+            self.overflow_bit = code.funcstate.allocate_temp(PyrexTypes.c_int_type, manage_ref=False)
+            code.putln("%s = 0;" % self.overflow_bit);
+            code.putln("%s = %s;" % (self.result(), self.calculate_result_code()))
+            code.putln("if (unlikely(%s)) {" % self.overflow_bit)
+            code.putln('PyErr_Format(PyExc_OverflowError, "value too large");')
+            code.putln(code.error_goto(self.pos))
+            code.putln("}")
+            code.funcstate.release_temp(self.overflow_bit)
+
     def calculate_result_code(self):
-        if self.infix:
+        if self.overflow_check:
+            return "%s(%s, %s, &%s)" % (
+                self.func,
+                self.operand1.result(),
+                self.operand2.result(),
+                self.overflow_bit)
+        elif self.infix:
             return "(%s %s %s)" % (
                 self.operand1.result(),
                 self.operator,
index 74f53f1..ef6db92 100755 (executable)
@@ -25,7 +25,7 @@ class BaseType(object):
         # This is not entirely robust.
         safe = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789'
         all = []
-        for c in self.declaration_code("").replace(" ", "__"):
+        for c in self.declaration_code("").replace("unsigned ", "unsigned_").replace("long long", "long_long").replace(" ", "__"):
             if c in safe:
                 all.append(c)
             else:
@@ -1548,6 +1548,30 @@ class CIntType(CNumericType):
             # be negative for signed ints, which is good.
             return "0xbad0bad0";
 
+    def overflow_check_binop(self, binop, env):
+        env.use_utility_code(UtilityCode.load("Common", "Overflow.c"))
+        type = self.declaration_code("")
+        name = self.specialization_name()
+        if type in ('int', 'long', 'long long'):
+            env.use_utility_code(TempitaUtilityCode.load("BaseCaseSigned", "Overflow.c", context={'INT': type, 'NAME': name}))
+        elif type in ('unsigned int', 'unsigned long', 'unsigned long long'):
+            env.use_utility_code(TempitaUtilityCode.load("BaseCaseUnsigned", "Overflow.c", context={'UINT': type, 'NAME': name}))
+        elif self.rank <= 1:
+            # sizeof(short) < sizeof(int)
+            return "__Pyx_%s_%s_no_overflow" % (binop, name)
+        else:
+            _load_overflow_base(env)
+            env.use_utility_code(TempitaUtilityCode.load("Binop", "Overflow.c", context={'TYPE': type, 'NAME': name, 'BINOP': binop}))
+        return "__Pyx_%s_%s_checking_overflow" % (binop, name)
+
+def _load_overflow_base(env):
+    env.use_utility_code(UtilityCode.load("Common", "Overflow.c"))
+    for type in ('int', 'long', 'long long'):
+        env.use_utility_code(TempitaUtilityCode.load("BaseCaseSigned", "Overflow.c", context={'INT': type, 'NAME': type.replace(' ', '_')}))
+    for type in ('unsigned int', 'unsigned long', 'unsigned long long'):
+        env.use_utility_code(TempitaUtilityCode.load("BaseCaseUnsigned", "Overflow.c", context={'UINT': type, 'NAME': type.replace(' ', '_')}))
+
+
 class CAnonEnumType(CIntType):
 
     is_enum = 1
diff --git a/Cython/Utility/Overflow.c b/Cython/Utility/Overflow.c
new file mode 100644 (file)
index 0000000..00d0ff3
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+These functions provide integer arithmetic with integer checking.  They do not
+actually raise an exception when an overflow is detected, but rather set a bit
+in the overflow parameter.  (This parameter may be re-used accross several
+arithmetic operations, so should be or-ed rather than assigned to.)
+
+The implementation is divided into two parts, the signed and unsigned basecases,
+which is where the magic happens, and a generic template matching a specific
+type to an implementation based on its (c-compile-time) size and signedness.
+
+When possible, branching is avoided, and preference is given to speed over
+accuracy (a low rate of falsely "detected" overflows are acceptable,
+undetected overflows are not).
+
+
+TODO: Hook up checking.
+TODO: Conditionally support 128-bit with intmax_t?
+*/
+
+/////////////// Common.proto ///////////////
+
+static int __Pyx_check_twos_complement() {
+    if (-1 != ~0) {
+        PyErr_SetString(PyExc_RuntimeError, "Two's complement required for overflow checks.");
+        return 1;
+    } else if (sizeof(short) == sizeof(int)) {
+        PyErr_SetString(PyExc_RuntimeError, "sizeof(short) < sizeof(int) required for overflow checks.");
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+#define __PYX_IS_UNSIGNED(type) ((type) -1 > 0)
+#define __PYX_SIGN_BIT(type)    ((unsigned type) 1 << (sizeof(type) * 8 - 1))
+#define __PYX_HALF_MAX(type)    (((type) 1) << (sizeof(type) * 8 - 2))
+#define __PYX_MIN(type)         (__PYX_IS_UNSIGNED(type) ? (type) 0 : 0 - __PYX_HALF_MAX(type) - __PYX_HALF_MAX(type))
+#define __PYX_MAX(type)         (~__PYX_MIN(type))
+
+#define __Pyx_add_no_overflow(a, b, overflow) ((a) + (b))
+#define __Pyx_add_const_no_overflow(a, b, overflow) ((a) + (b))
+#define __Pyx_sub_no_overflow(a, b, overflow) ((a) - (b))
+#define __Pyx_sub_no_const_overflow(a, b, overflow) ((a) - (b))
+#define __Pyx_mul_no_overflow(a, b, overflow) ((a) * (b))
+#define __Pyx_mul_const_no_overflow(a, b, overflow) ((a) * (b))
+#define __Pyx_div_no_overflow(a, b, overflow) ((a) / (b))
+#define __Pyx_div_const_no_overflow(a, b, overflow) ((a) / (b))
+
+
+
+/////////////// BaseCaseUnsigned.proto ///////////////
+
+static CYTHON_INLINE {{UINT}} __Pyx_add_{{NAME}}_checking_overflow({{UINT}} a, {{UINT}} b, int *overflow);
+static CYTHON_INLINE {{UINT}} __Pyx_sub_{{NAME}}_checking_overflow({{UINT}} a, {{UINT}} b, int *overflow);
+static CYTHON_INLINE {{UINT}} __Pyx_mul_{{NAME}}_checking_overflow({{UINT}} a, {{UINT}} b, int *overflow);
+static CYTHON_INLINE {{UINT}} __Pyx_div_{{NAME}}_checking_overflow({{UINT}} a, {{UINT}} b, int *overflow);
+
+// Use these when b is known at compile time.
+#define __Pyx_add_const_{{NAME}}_checking_overflow __Pyx_add_{{NAME}}_checking_overflow
+#define __Pyx_sub_const_{{NAME}}_checking_overflow __Pyx_sub_{{NAME}}_checking_overflow
+static CYTHON_INLINE {{UINT}} __Pyx_mul_const_{{NAME}}_checking_overflow({{UINT}} a, {{UINT}} constant, int *overflow);
+#define __Pyx_div_const_{{NAME}}_checking_overflow __Pyx_div_{{NAME}}_checking_overflow
+
+/////////////// BaseCaseUnsigned ///////////////
+
+static CYTHON_INLINE {{UINT}} __Pyx_add_{{NAME}}_checking_overflow({{UINT}} a, {{UINT}} b, int *overflow) {
+    {{UINT}} r = a + b;
+    *overflow |= r < a;
+    return r;
+}
+
+static CYTHON_INLINE {{UINT}} __Pyx_sub_{{NAME}}_checking_overflow({{UINT}} a, {{UINT}} b, int *overflow) {
+    {{UINT}} r = a - b;
+    *overflow |= r > a;
+    return r;
+}
+
+static CYTHON_INLINE {{UINT}} __Pyx_mul_{{NAME}}_checking_overflow({{UINT}} a, {{UINT}} b, int *overflow) {
+    if (sizeof({{UINT}}) < sizeof(long)) {
+        unsigned long big_r = ((unsigned long) a) * ((unsigned long) b);
+        {{UINT}} r = ({{UINT}}) big_r;
+        *overflow |= big_r != r;
+        return ({{UINT}}) r;
+    } else if (sizeof({{UINT}}) < sizeof(long long)) {
+        unsigned long long big_r = ((unsigned long long) a) * ((unsigned long long) b);
+        {{UINT}} r = ({{UINT}}) big_r;
+        *overflow |= big_r != r;
+        return ({{UINT}}) r;
+    } else {
+        {{UINT}} prod = a * b;
+        double dprod = ((double) a) * ((double) b);
+        // False positives.  Yes, the equality is required to avoid false negatives.
+        *overflow |= dprod >= (double) __PYX_MAX({{UINT}});
+        return prod;
+    }
+}
+
+static CYTHON_INLINE {{UINT}} __Pyx_mul_const_{{NAME}}_checking_overflow({{UINT}} a, {{UINT}} b, int *overflow) {
+    if (b > 1) {
+        *overflow |= a > __PYX_MAX({{UINT}}) / b;
+    }
+    return a * b;
+}
+
+
+static CYTHON_INLINE {{UINT}} __Pyx_div_{{NAME}}_checking_overflow({{UINT}} a, {{UINT}} b, int *overflow) {
+    if (b == 0) {
+        *overflow |= 1;
+        return 0;
+    }
+    return a / b;
+}
+
+
+/////////////// BaseCaseSigned.proto ///////////////
+
+static CYTHON_INLINE {{INT}} __Pyx_add_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow);
+static CYTHON_INLINE {{INT}} __Pyx_sub_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow);
+static CYTHON_INLINE {{INT}} __Pyx_mul_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow);
+static CYTHON_INLINE {{INT}} __Pyx_div_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow);
+
+
+// Use when b is known at compile time.
+static CYTHON_INLINE {{INT}} __Pyx_add_const_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow);
+static CYTHON_INLINE {{INT}} __Pyx_sub_const_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow);
+static CYTHON_INLINE {{INT}} __Pyx_mul_const_{{NAME}}_checking_overflow({{INT}} a, {{INT}} constant, int *overflow);
+#define __Pyx_div_const_{{NAME}}_checking_overflow __Pyx_div_{{NAME}}_checking_overflow
+
+/////////////// BaseCaseSigned ///////////////
+
+#define TOP_TWO_BITS(value, type) (value & ((unsigned type)3 << (sizeof(type) * 8 - 2)))
+
+static CYTHON_INLINE {{INT}} __Pyx_add_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow) {
+    if (sizeof({{INT}}) < sizeof(long)) {
+        long big_r = ((long) a) + ((long) b);
+        {{INT}} r = ({{INT}}) big_r;
+        *overflow |= big_r != r;
+        return ({{INT}}) r;
+    } else if (sizeof({{INT}}) < sizeof(long long)) {
+        long long big_r = ((long long) a) + ((long long) b);
+        {{INT}} r = ({{INT}}) big_r;
+        *overflow |= big_r != r;
+        return ({{INT}}) r;
+    } else {
+        // Signed overflow undefined, but unsigned is well defined.
+        {{INT}} r = ({{INT}}) ((unsigned {{INT}}) a + (unsigned {{INT}}) b);
+        // sign(a) == sign(b) != sign(r)
+        {{INT}} sign_a = __PYX_SIGN_BIT({{INT}}) & a;
+        {{INT}} sign_b = __PYX_SIGN_BIT({{INT}}) & b;
+        {{INT}} sign_r = __PYX_SIGN_BIT({{INT}}) & r;
+        *overflow |= (sign_a == sign_b) & (sign_a != sign_r);
+        return r;
+    }
+}
+
+static CYTHON_INLINE {{INT}} __Pyx_add_const_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow) {
+    if (b > 0) {
+        *overflow |= a > __PYX_MAX({{INT}}) - b;
+    } else if (b < 0) {
+        *overflow |= a < __PYX_MIN({{INT}}) - b;
+    }
+    return a + b;
+}
+
+static CYTHON_INLINE {{INT}} __Pyx_sub_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow) {
+    *overflow |= b == __PYX_MIN({{INT}});
+    return __Pyx_add_{{NAME}}_checking_overflow(a, -b, overflow);
+}
+
+static CYTHON_INLINE {{INT}} __Pyx_sub_const_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow) {
+    *overflow |= b == __PYX_MIN({{INT}});
+    return __Pyx_add_const_{{NAME}}_checking_overflow(a, -b, overflow);
+}
+
+static CYTHON_INLINE {{INT}} __Pyx_mul_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow) {
+    if (sizeof({{INT}}) < sizeof(long)) {
+        long big_r = ((long) a) * ((long) b);
+        {{INT}} r = ({{INT}}) big_r;
+        *overflow |= big_r != r;
+        return ({{INT}}) r;
+    } else if (sizeof({{INT}}) < sizeof(long long)) {
+        long long big_r = ((long long) a) * ((long long) b);
+        {{INT}} r = ({{INT}}) big_r;
+        *overflow |= big_r != r;
+        return ({{INT}}) r;
+    } else {
+        {{INT}} prod = a * b;
+        double dprod = ((double) a) * ((double) b);
+        // False positives.
+        *overflow |= fabs(dprod) > (double) __PYX_MAX({{INT}});
+        return prod;
+    }
+}
+
+static CYTHON_INLINE {{INT}} __Pyx_mul_const_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow) {
+    if (b > 1) {
+        *overflow |= a > __PYX_MAX({{INT}}) / b;
+        *overflow |= a < __PYX_MIN({{INT}}) / b;
+    } else if (b == -1) {
+        *overflow |= a == __PYX_MIN({{INT}});
+    } else if (b < -1) {
+        *overflow |= a > __PYX_MIN({{INT}}) / b;
+        *overflow |= a < __PYX_MAX({{INT}}) / b;
+    }
+    return a * b;
+}
+
+static CYTHON_INLINE {{INT}} __Pyx_div_{{NAME}}_checking_overflow({{INT}} a, {{INT}} b, int *overflow) {
+    if (b == 0) {
+        *overflow |= 1;
+        return 0;
+    }
+    *overflow |= (a == __PYX_MIN({{INT}})) & (b == -1);
+    return a / b;
+}
+
+
+/////////////// SizeCheck.proto ///////////////
+
+static int __Pyx_check_sane_{{TYPE}}() {
+    if (sizeof({{TYPE}}) == sizeof(int) ||
+        sizeof({{TYPE}}) == sizeof(long) ||
+        sizeof({{TYPE}}) == sizeof(long long)) {
+        return 0;
+    } else {
+        PyErr_Format(PyExc_RuntimeError, "Bad size for int type %s: %d", "{{TYPE}}", sizeof({{TYPE}}));
+        return 1;
+    }
+}
+
+
+/////////////// Binop.proto ///////////////
+
+static CYTHON_INLINE {{TYPE}} __Pyx_{{BINOP}}_{{NAME}}_checking_overflow({{TYPE}} a, {{TYPE}} b, int *overflow);
+
+/////////////// Binop ///////////////
+
+static CYTHON_INLINE {{TYPE}} __Pyx_{{BINOP}}_{{NAME}}_checking_overflow({{TYPE}} a, {{TYPE}} b, int *overflow) {
+    if (sizeof({{TYPE}}) < sizeof(int)) {
+        return __Pyx_{{BINOP}}_no_overflow(a, b, overflow);
+    } else if (__PYX_IS_UNSIGNED({{TYPE}})) {
+        if (sizeof({{TYPE}}) == sizeof(int)) {
+            return __Pyx_{{BINOP}}_unsigned_int_checking_overflow(a, b, overflow);
+        } else if (sizeof({{TYPE}}) == sizeof(long)) {
+            return __Pyx_{{BINOP}}_unsigned_long_checking_overflow(a, b, overflow);
+        } else if (sizeof({{TYPE}}) == sizeof(long long)) {
+            return __Pyx_{{BINOP}}_unsigned_long_long_checking_overflow(a, b, overflow);
+        } else {
+            abort(); return 0; // handled elsewhere
+        }
+    } else {
+        if (sizeof({{TYPE}}) == sizeof(int)) {
+            return __Pyx_{{BINOP}}_int_checking_overflow(a, b, overflow);
+        } else if (sizeof({{TYPE}}) == sizeof(long)) {
+            return __Pyx_{{BINOP}}_long_checking_overflow(a, b, overflow);
+        } else if (sizeof({{TYPE}}) == sizeof(long long)) {
+            return __Pyx_{{BINOP}}_long_long_checking_overflow(a, b, overflow);
+        } else {
+            abort(); return 0; // handled elsewhere
+        }
+    }
+}