Commit 4278dbf0 authored by Stefan Behnel's avatar Stefan Behnel

Fix various cases where optimised division of constants did not check for division by 0.

Closes #2820.
parent c3be8ff4
......@@ -3166,6 +3166,7 @@ class OptimizeBuiltinCalls(Visitor.NodeRefCleanupMixin,
PyrexTypes.CFuncTypeArg("op2", PyrexTypes.py_object_type, None),
PyrexTypes.CFuncTypeArg("cval", ctype, None),
PyrexTypes.CFuncTypeArg("inplace", PyrexTypes.c_bint_type, None),
PyrexTypes.CFuncTypeArg("zerodiv_check", PyrexTypes.c_bint_type, None),
], exception_value=None if ret_type.is_pyobject else ret_type.exception_value))
for ctype in (PyrexTypes.c_long_type, PyrexTypes.c_double_type)
for ret_type in (PyrexTypes.py_object_type, PyrexTypes.c_bint_type)
......@@ -3297,12 +3298,20 @@ class OptimizeBuiltinCalls(Visitor.NodeRefCleanupMixin,
# Cut off at an integer border that is still safe for all operations.
return node
if operator in ('TrueDivide', 'FloorDivide', 'Divide', 'Remainder'):
if args[1].constant_result == 0:
# Don't optimise division by 0. :)
return node
args = list(args)
args.append((ExprNodes.FloatNode if is_float else ExprNodes.IntNode)(
numval.pos, value=numval.value, constant_result=numval.constant_result,
type=num_type))
inplace = node.inplace if isinstance(node, ExprNodes.NumBinopNode) else False
args.append(ExprNodes.BoolNode(node.pos, value=inplace, constant_result=inplace))
zerodivision_check = arg_order == 'CObj' and (
not node.cdivision if isinstance(node, ExprNodes.DivNode) else False)
args.append(ExprNodes.BoolNode(node.pos, value=zerodivision_check, constant_result=zerodivision_check))
utility_code = TempitaUtilityCode.load_cached(
"PyFloatBinop" if is_float else "PyIntCompare" if operator in ('Eq', 'Ne') else "PyIntBinop",
......
......@@ -787,9 +787,9 @@ static CYTHON_INLINE {{c_ret_type}} __Pyx_PyInt_{{'' if ret_type.is_pyobject els
{{py: c_ret_type = 'PyObject*' if ret_type.is_pyobject else 'int'}}
#if !CYTHON_COMPILING_IN_PYPY
static {{c_ret_type}} __Pyx_PyInt_{{'' if ret_type.is_pyobject else 'Bool'}}{{op}}{{order}}(PyObject *op1, PyObject *op2, long intval, int inplace); /*proto*/
static {{c_ret_type}} __Pyx_PyInt_{{'' if ret_type.is_pyobject else 'Bool'}}{{op}}{{order}}(PyObject *op1, PyObject *op2, long intval, int inplace, int zerodivision_check); /*proto*/
#else
#define __Pyx_PyInt_{{'' if ret_type.is_pyobject else 'Bool'}}{{op}}{{order}}(op1, op2, intval, inplace) \
#define __Pyx_PyInt_{{'' if ret_type.is_pyobject else 'Bool'}}{{op}}{{order}}(op1, op2, intval, inplace, zerodivision_check) \
{{if op in ('Eq', 'Ne')}}{{'' if ret_type.is_pyobject else '__Pyx_PyObject_IsTrueAndDecref'}}(PyObject_RichCompare(op1, op2, Py_{{op.upper()}}))
{{else}}(inplace ? PyNumber_InPlace{{op}}(op1, op2) : PyNumber_{{op}}(op1, op2))
{{endif}}
......@@ -804,6 +804,8 @@ static {{c_ret_type}} __Pyx_PyInt_{{'' if ret_type.is_pyobject else 'Bool'}}{{op
{{py: return_true = 'Py_RETURN_TRUE' if ret_type.is_pyobject else 'return 1'}}
{{py: return_false = 'Py_RETURN_FALSE' if ret_type.is_pyobject else 'return 0'}}
{{py: slot_name = {'TrueDivide': 'true_divide', 'FloorDivide': 'floor_divide'}.get(op, op.lower()) }}
{{py: cfunc_name = '__Pyx_PyInt_%s%s%s' % ('' if ret_type.is_pyobject else 'Bool', op, order)}}
{{py: zerodiv_check = lambda operand, _cfunc_name=cfunc_name: '%s_ZeroDivisionError(%s)' % (_cfunc_name, operand)}}
{{py:
c_op = {
'Add': '+', 'Subtract': '-', 'Remainder': '%', 'TrueDivide': '/', 'FloorDivide': '/',
......@@ -812,7 +814,21 @@ c_op = {
}[op]
}}
static {{c_ret_type}} __Pyx_PyInt_{{'' if ret_type.is_pyobject else 'Bool'}}{{op}}{{order}}(PyObject *op1, PyObject *op2, CYTHON_UNUSED long intval, CYTHON_UNUSED int inplace) {
{{if op in ('TrueDivide', 'FloorDivide', 'Remainder')}}
#if PY_MAJOR_VERSION < 3 || CYTHON_USE_PYLONG_INTERNALS
#define {{zerodiv_check('operand')}} \
if (unlikely(zerodivision_check && ((operand) == 0))) { \
PyErr_SetString(PyExc_ZeroDivisionError, "integer division{{if op == 'Remainder'}} or modulo{{endif}} by zero"); \
return NULL; \
}
#endif
{{endif}}
static {{c_ret_type}} {{cfunc_name}}(PyObject *op1, PyObject *op2, CYTHON_UNUSED long intval, int inplace, int zerodivision_check) {
// Prevent "unused" warnings.
(void)inplace;
(void)zerodivision_check;
{{if op in ('Eq', 'Ne')}}
if (op1 == op2) {
{{return_true if op == 'Eq' else return_false}};
......@@ -841,18 +857,21 @@ static {{c_ret_type}} __Pyx_PyInt_{{'' if ret_type.is_pyobject else 'Bool'}}{{op
return PyInt_FromLong(x);
return PyLong_Type.tp_as_number->nb_{{slot_name}}(op1, op2);
{{elif c_op == '%'}}
{{zerodiv_check('b')}}
// see ExprNodes.py :: mod_int_utility_code
x = a % b;
x += ((x != 0) & ((x ^ b) < 0)) * b;
return PyInt_FromLong(x);
{{elif op == 'TrueDivide'}}
{{zerodiv_check('b')}}
if (8 * sizeof(long) <= 53 || likely(labs({{ival}}) <= ((PY_LONG_LONG)1 << 53))) {
return PyFloat_FromDouble((double)a / (double)b);
}
// let Python do the rounding
return PyInt_Type.tp_as_number->nb_{{slot_name}}(op1, op2);
{{elif op == 'FloorDivide'}}
// INT_MIN / -1 is the only case that overflows
// INT_MIN / -1 is the only case that overflows, b == 0 is an error case
{{zerodiv_check('b')}}
if (unlikely(b == -1 && ((unsigned long)a) == 0-(unsigned long)a))
return PyInt_Type.tp_as_number->nb_{{slot_name}}(op1, op2);
else {
......@@ -934,16 +953,19 @@ static {{c_ret_type}} __Pyx_PyInt_{{'' if ret_type.is_pyobject else 'Bool'}}{{op
}
{{else}}
{{if c_op == '%'}}
{{zerodiv_check('b')}}
// see ExprNodes.py :: mod_int_utility_code
x = a % b;
x += ((x != 0) & ((x ^ b) < 0)) * b;
{{elif op == 'TrueDivide'}}
{{zerodiv_check('b')}}
if ((8 * sizeof(long) <= 53 || likely(labs({{ival}}) <= ((PY_LONG_LONG)1 << 53)))
|| __Pyx_sst_abs(size) <= 52 / PyLong_SHIFT) {
|| __Pyx_sst_abs(size) <= 52 / PyLong_SHIFT) {
return PyFloat_FromDouble((double)a / (double)b);
}
return PyLong_Type.tp_as_number->nb_{{slot_name}}(op1, op2);
{{elif op == 'FloorDivide'}}
{{zerodiv_check('b')}}
{
long q, r;
// see ExprNodes.py :: div_int_utility_code
......@@ -1008,6 +1030,12 @@ static {{c_ret_type}} __Pyx_PyInt_{{'' if ret_type.is_pyobject else 'Bool'}}{{op
}
{{else}}
double result;
{{if op == 'TrueDivide'}}
if (unlikely(zerodivision_check && b == 0)) {
PyErr_SetString(PyExc_ZeroDivisionError, "float division by zero");
return NULL;
}
{{endif}}
// copied from floatobject.c in Py3.5:
PyFPE_START_PROTECT("{{op.lower() if not op.endswith('Divide') else 'divide'}}", return NULL)
result = ((double)a) {{c_op}} (double)b;
......@@ -1030,9 +1058,9 @@ static {{c_ret_type}} __Pyx_PyInt_{{'' if ret_type.is_pyobject else 'Bool'}}{{op
{{py: c_ret_type = 'PyObject*' if ret_type.is_pyobject else 'int'}}
#if !CYTHON_COMPILING_IN_PYPY
static {{c_ret_type}} __Pyx_PyFloat_{{'' if ret_type.is_pyobject else 'Bool'}}{{op}}{{order}}(PyObject *op1, PyObject *op2, double floatval, int inplace); /*proto*/
static {{c_ret_type}} __Pyx_PyFloat_{{'' if ret_type.is_pyobject else 'Bool'}}{{op}}{{order}}(PyObject *op1, PyObject *op2, double floatval, int inplace, int zerodivision_check); /*proto*/
#else
#define __Pyx_PyFloat_{{'' if ret_type.is_pyobject else 'Bool'}}{{op}}{{order}}(op1, op2, floatval, inplace) \
#define __Pyx_PyFloat_{{'' if ret_type.is_pyobject else 'Bool'}}{{op}}{{order}}(op1, op2, floatval, inplace, zerodivision_check) \
{{if op in ('Eq', 'Ne')}}{{'' if ret_type.is_pyobject else '__Pyx_PyObject_IsTrueAndDecref'}}(PyObject_RichCompare(op1, op2, Py_{{op.upper()}}))
{{elif op == 'Divide'}}((inplace ? __Pyx_PyNumber_InPlaceDivide(op1, op2) : __Pyx_PyNumber_Divide(op1, op2)))
{{else}}(inplace ? PyNumber_InPlace{{op}}(op1, op2) : PyNumber_{{op}}(op1, op2))
......@@ -1047,6 +1075,8 @@ static {{c_ret_type}} __Pyx_PyFloat_{{'' if ret_type.is_pyobject else 'Bool'}}{{
{{py: return_true = 'Py_RETURN_TRUE' if ret_type.is_pyobject else 'return 1'}}
{{py: return_false = 'Py_RETURN_FALSE' if ret_type.is_pyobject else 'return 0'}}
{{py: pyval, fval = ('op2', 'b') if order == 'CObj' else ('op1', 'a') }}
{{py: cfunc_name = '__Pyx_PyFloat_%s%s%s' % ('' if ret_type.is_pyobject else 'Bool', op, order) }}
{{py: zerodiv_check = lambda operand, _cfunc_name=cfunc_name: '%s_ZeroDivisionError(%s)' % (_cfunc_name, operand)}}
{{py:
c_op = {
'Add': '+', 'Subtract': '-', 'TrueDivide': '/', 'Divide': '/', 'Remainder': '%',
......@@ -1054,9 +1084,19 @@ c_op = {
}[op]
}}
static {{c_ret_type}} __Pyx_PyFloat_{{'' if ret_type.is_pyobject else 'Bool'}}{{op}}{{order}}(PyObject *op1, PyObject *op2, double floatval, CYTHON_UNUSED int inplace) {
{{if order == 'CObj' and c_op in '%/'}}
#define {{zerodiv_check('operand')}} if (unlikely(zerodivision_check && ((operand) == 0))) { \
PyErr_SetString(PyExc_ZeroDivisionError, "integer division{{if op == 'Remainder'}} or modulo{{endif}} by zero"); \
return NULL; \
}
{{endif}}
static {{c_ret_type}} {{cfunc_name}}(PyObject *op1, PyObject *op2, double floatval, int inplace, int zerodivision_check) {
const double {{'a' if order == 'CObj' else 'b'}} = floatval;
double {{fval}}{{if op not in ('Eq', 'Ne')}}, result{{endif}};
// Prevent "unused" warnings.
(void)inplace;
(void)zerodivision_check;
{{if op in ('Eq', 'Ne')}}
if (op1 == op2) {
......@@ -1066,11 +1106,13 @@ static {{c_ret_type}} __Pyx_PyFloat_{{'' if ret_type.is_pyobject else 'Bool'}}{{
if (likely(PyFloat_CheckExact({{pyval}}))) {
{{fval}} = PyFloat_AS_DOUBLE({{pyval}});
{{if order == 'CObj' and c_op in '%/'}}{{zerodiv_check(fval)}}{{endif}}
} else
#if PY_MAJOR_VERSION < 3
if (likely(PyInt_CheckExact({{pyval}}))) {
{{fval}} = (double) PyInt_AS_LONG({{pyval}});
{{if order == 'CObj' and c_op in '%/'}}{{zerodiv_check(fval)}}{{endif}}
} else
#endif
......@@ -1079,7 +1121,7 @@ static {{c_ret_type}} __Pyx_PyFloat_{{'' if ret_type.is_pyobject else 'Bool'}}{{
const digit* digits = ((PyLongObject*){{pyval}})->ob_digit;
const Py_ssize_t size = Py_SIZE({{pyval}});
switch (size) {
case 0: {{fval}} = 0.0; break;
case 0: {{if order == 'CObj' and c_op in '%/'}}{{zerodiv_check('0')}}{{else}}{{fval}} = 0.0;{{endif}} break;
case -1: {{fval}} = -(double) digits[0]; break;
case 1: {{fval}} = (double) digits[0]; break;
{{for _size in (2, 3, 4)}}
......@@ -1111,6 +1153,7 @@ static {{c_ret_type}} __Pyx_PyFloat_{{'' if ret_type.is_pyobject else 'Bool'}}{{
{{else}}
{{fval}} = PyLong_AsDouble({{pyval}});
if (unlikely({{fval}} == -1.0 && PyErr_Occurred())) return NULL;
{{if order == 'CObj' and c_op in '%/'}}{{zerodiv_check(fval)}}{{endif}}
{{endif}}
}
} else {
......@@ -1132,6 +1175,7 @@ static {{c_ret_type}} __Pyx_PyFloat_{{'' if ret_type.is_pyobject else 'Bool'}}{{
}
{{else}}
// copied from floatobject.c in Py3.5:
{{if order == 'CObj' and c_op in '%/'}}{{zerodiv_check('b')}}{{endif}}
PyFPE_START_PROTECT("{{op.lower() if not op.endswith('Divide') else 'divide'}}", return NULL)
{{if c_op == '%'}}
result = fmod(a, b);
......
......@@ -26,6 +26,39 @@ def float_by_float():
return 3.0 / 2.0
def div_by_0(x):
"""
>>> div_by_0(0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_by_0(0.0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_by_0(1) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_by_0(1.0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> float('inf') / 0.0 # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_by_0(float('inf')) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_by_0(float('-inf')) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> float('nan') / 0.0 # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_by_0(float('nan')) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
"""
return x / 0.0
def div_1_by(x):
"""
>>> div_1_by(1.0)
......@@ -42,6 +75,12 @@ def div_1_by(x):
-0.0
>>> div_1_by(float('nan'))
nan
>>> div_1_by(0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_1_by(0.0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
"""
return 1.0 / x
......@@ -116,6 +155,12 @@ def div_neg_2_by(x):
nan
>>> div_neg_2_by(float('nan'))
nan
>>> div_neg_2_by(0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_neg_2_by(0.0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
"""
return (-2.0) / x
......@@ -148,6 +193,12 @@ def div_nan_by(x):
nan
>>> div_nan_by(float('nan'))
nan
>>> div_nan_by(0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_nan_by(0.0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
"""
return float("nan") / x
......@@ -182,6 +233,15 @@ def div_inf_by(x):
nan
>>> div_inf_by(float('-inf'))
nan
>>> float("inf") / 0.0 # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_inf_by(0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_inf_by(0.0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
"""
return float("inf") / x
......@@ -196,5 +256,14 @@ def div_neg_inf_by(x):
inf
>>> div_neg_inf_by(-1.0)
inf
>>> float("-inf") / 0.0 # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_neg_inf_by(0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
>>> div_neg_inf_by(0.0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division by zero
"""
return float("-inf") / x
......@@ -157,3 +157,73 @@ def int_int(int a, int b):
(0.5, 2.0)
"""
return a/b, b/a
def div_by_0(a):
"""
>>> div_by_0(0)
'OK'
>>> div_by_0(0.0)
'OK'
"""
try:
1/a
except ZeroDivisionError:
pass
else:
return "FAIL 1"
try:
1//a
except ZeroDivisionError:
pass
else:
return "FAIL 2"
try:
5.0/a
except ZeroDivisionError:
pass
else:
return "FAIL 3"
try:
5.0//a
except ZeroDivisionError:
pass
else:
return "FAIL 4"
try:
5/a
except ZeroDivisionError:
pass
else:
return "FAIL 5"
try:
5//a
except ZeroDivisionError:
pass
else:
return "FAIL 6"
try:
(2**15)/a
except ZeroDivisionError:
pass
else:
return "FAIL 7"
try:
(2**15)//a
except ZeroDivisionError:
pass
else:
return "FAIL 8"
try:
(2**30)/a
except ZeroDivisionError:
pass
else:
return "FAIL 9"
try:
(2**30)//a
except ZeroDivisionError:
pass
else:
return "FAIL 10"
return 'OK'
......@@ -7,11 +7,25 @@ def modobj(obj2, obj3):
1
>>> modobj('%d', 5)
'5'
>>> modobj(1, 0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division...by zero
"""
obj1 = obj2 % obj3
return obj1
def mod_10_obj(int2):
"""
>>> mod_10_obj(0) # doctest: +ELLIPSIS
Traceback (most recent call last):
ZeroDivisionError: ...division...by zero
>>> mod_10_obj(3)
1
"""
return 10 % int2
def mod_obj_10(int2):
"""
>>> 0 % 10
......
......@@ -143,3 +143,73 @@ def int_int(int a, int b):
(0, 2)
"""
return a/b, b/a
def div_by_0(a):
"""
>>> div_by_0(0)
'OK'
>>> div_by_0(0.0)
'OK'
"""
try:
1/a
except ZeroDivisionError:
pass
else:
return "FAIL 1"
try:
1//a
except ZeroDivisionError:
pass
else:
return "FAIL 2"
try:
5.0/a
except ZeroDivisionError:
pass
else:
return "FAIL 3"
try:
5.0//a
except ZeroDivisionError:
pass
else:
return "FAIL 4"
try:
5/a
except ZeroDivisionError:
pass
else:
return "FAIL 5"
try:
5//a
except ZeroDivisionError:
pass
else:
return "FAIL 6"
try:
(2**15)/a
except ZeroDivisionError:
pass
else:
return "FAIL 7"
try:
(2**15)//a
except ZeroDivisionError:
pass
else:
return "FAIL 8"
try:
(2**30)/a
except ZeroDivisionError:
pass
else:
return "FAIL 9"
try:
(2**30)//a
except ZeroDivisionError:
pass
else:
return "FAIL 10"
return 'OK'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment