From 3a313e36555a3416799f3c049b8ebb41e9edeb8a Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Mon, 19 Jul 2004 16:29:17 +0000 Subject: Check the type of values returned by __int__, __float__, __long__, __oct__, and __hex__. Raise TypeError if an invalid type is returned. Note that PyNumber_Int and PyNumber_Long can still return ints or longs. Fixes SF bug #966618. --- Lib/test/test_class.py | 107 ++++++++++++++++++++++++++++++++++++------------- Misc/NEWS | 5 +++ Objects/abstract.c | 39 +++++++++++++++--- Objects/intobject.c | 6 --- Python/bltinmodule.c | 22 +++++++++- 5 files changed, 138 insertions(+), 41 deletions(-) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index a37f2a4..a564f73 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -43,11 +43,6 @@ testmeths = [ "neg", "pos", "abs", - "int", - "long", - "float", - "oct", - "hex", # generic operations "init", @@ -58,6 +53,11 @@ testmeths = [ # "hash", # "str", # "repr", +# "int", +# "long", +# "float", +# "oct", +# "hex", # These are separate because they can influence the test of other methods. # "getattr", @@ -81,6 +81,26 @@ class AllTests: print "__repr__:", args return "AllTests" + def __int__(self, *args): + print "__int__:", args + return 1 + + def __float__(self, *args): + print "__float__:", args + return 1.0 + + def __long__(self, *args): + print "__long__:", args + return 1L + + def __oct__(self, *args): + print "__oct__:", args + return '01' + + def __hex__(self, *args): + print "__hex__:", args + return '0x1' + def __cmp__(self, *args): print "__cmp__:", args return 0 @@ -195,21 +215,11 @@ else: -testme +testme abs(testme) -if sys.platform[:4] != 'java': - int(testme) - long(testme) - float(testme) - oct(testme) - hex(testme) -else: - # Jython enforced that these methods return - # a value of the expected type. - print "__int__: ()" - print "__long__: ()" - print "__float__: ()" - print "__oct__: ()" - print "__hex__: ()" - +int(testme) +long(testme) +float(testme) +oct(testme) +hex(testme) # And the rest... @@ -254,6 +264,54 @@ testme.eggs = "spam, spam, spam and ham" del testme.cardinal +# return values of some method are type-checked +class BadTypeClass: + def __int__(self): + return None + __float__ = __int__ + __long__ = __int__ + __str__ = __int__ + __repr__ = __int__ + __oct__ = __int__ + __hex__ = __int__ + +def check_exc(stmt, exception): + """Raise TestFailed if executing 'stmt' does not raise 'exception' + """ + try: + exec stmt + except exception: + pass + else: + raise TestFailed, "%s should raise %s" % (stmt, exception) + +check_exc("int(BadTypeClass())", TypeError) +check_exc("float(BadTypeClass())", TypeError) +check_exc("long(BadTypeClass())", TypeError) +check_exc("str(BadTypeClass())", TypeError) +check_exc("repr(BadTypeClass())", TypeError) +check_exc("oct(BadTypeClass())", TypeError) +check_exc("hex(BadTypeClass())", TypeError) + +# mixing up ints and longs is okay +class IntLongMixClass: + def __int__(self): + return 0L + + def __long__(self): + return 0 + +try: + int(IntLongMixClass()) +except TypeError: + raise TestFailed, "TypeError should not be raised" + +try: + long(IntLongMixClass()) +except TypeError: + raise TestFailed, "TypeError should not be raised" + + # Test correct errors from hash() on objects with comparisons but no __hash__ class C0: @@ -264,17 +322,12 @@ hash(C0()) # This should work; the next two should raise TypeError class C1: def __cmp__(self, other): return 0 -try: hash(C1()) -except TypeError: pass -else: raise TestFailed, "hash(C1()) should raise an exception" +check_exc("hash(C1())", TypeError) class C2: def __eq__(self, other): return 1 -try: hash(C2()) -except TypeError: pass -else: raise TestFailed, "hash(C2()) should raise an exception" - +check_exc("hash(C2())", TypeError) # Test for SF bug 532646 diff --git a/Misc/NEWS b/Misc/NEWS index 29adb45..c395576 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -28,6 +28,11 @@ Core and builtins - Compiler now treats None as a constant. +- The type of values returned by __int__, __float__, __long__, + __oct__, and __hex__ are now checked. Returning an invalid type + will cause a TypeError to be raised. This matches the behavior of + Jython. + Extension modules ----------------- diff --git a/Objects/abstract.c b/Objects/abstract.c index 01e3e4c..bc36c6f 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -965,8 +965,17 @@ PyNumber_Int(PyObject *o) 10); #endif m = o->ob_type->tp_as_number; - if (m && m->nb_int) - return m->nb_int(o); + if (m && m->nb_int) { + PyObject *res = m->nb_int(o); + if (res && (!PyInt_Check(res) && !PyLong_Check(res))) { + PyErr_Format(PyExc_TypeError, + "__int__ returned non-int (type %.200s)", + res->ob_type->tp_name); + Py_DECREF(res); + return NULL; + } + return res; + } if (!PyObject_AsCharBuffer(o, &buffer, &buffer_len)) return int_from_string((char*)buffer, buffer_len); @@ -1022,8 +1031,17 @@ PyNumber_Long(PyObject *o) 10); #endif m = o->ob_type->tp_as_number; - if (m && m->nb_long) - return m->nb_long(o); + if (m && m->nb_long) { + PyObject *res = m->nb_long(o); + if (res && (!PyInt_Check(res) && !PyLong_Check(res))) { + PyErr_Format(PyExc_TypeError, + "__long__ returned non-long (type %.200s)", + res->ob_type->tp_name); + Py_DECREF(res); + return NULL; + } + return res; + } if (!PyObject_AsCharBuffer(o, &buffer, &buffer_len)) return long_from_string(buffer, buffer_len); @@ -1047,8 +1065,17 @@ PyNumber_Float(PyObject *o) } if (!PyString_Check(o)) { m = o->ob_type->tp_as_number; - if (m && m->nb_float) - return m->nb_float(o); + if (m && m->nb_float) { + PyObject *res = m->nb_float(o); + if (res && !PyFloat_Check(res)) { + PyErr_Format(PyExc_TypeError, + "__float__ returned non-float (type %.200s)", + res->ob_type->tp_name); + Py_DECREF(res); + return NULL; + } + return res; + } } return PyFloat_FromString(o, NULL); } diff --git a/Objects/intobject.c b/Objects/intobject.c index bdf7da3..f52ef07 100644 --- a/Objects/intobject.c +++ b/Objects/intobject.c @@ -948,12 +948,6 @@ int_subtype_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (tmp == NULL) return NULL; if (!PyInt_Check(tmp)) { - if (!PyLong_Check(tmp)) { - PyErr_SetString(PyExc_ValueError, - "value can't be converted to int"); - Py_DECREF(tmp); - return NULL; - } ival = PyLong_AsLong(tmp); if (ival == -1 && PyErr_Occurred()) { Py_DECREF(tmp); diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 10e59c9..4143681 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -962,6 +962,7 @@ static PyObject * builtin_hex(PyObject *self, PyObject *v) { PyNumberMethods *nb; + PyObject *res; if ((nb = v->ob_type->tp_as_number) == NULL || nb->nb_hex == NULL) { @@ -969,7 +970,15 @@ builtin_hex(PyObject *self, PyObject *v) "hex() argument can't be converted to hex"); return NULL; } - return (*nb->nb_hex)(v); + res = (*nb->nb_hex)(v); + if (res && !PyString_Check(res)) { + PyErr_Format(PyExc_TypeError, + "__hex__ returned non-string (type %.200s)", + res->ob_type->tp_name); + Py_DECREF(res); + return NULL; + } + return res; } PyDoc_STRVAR(hex_doc, @@ -1178,6 +1187,7 @@ static PyObject * builtin_oct(PyObject *self, PyObject *v) { PyNumberMethods *nb; + PyObject *res; if (v == NULL || (nb = v->ob_type->tp_as_number) == NULL || nb->nb_oct == NULL) { @@ -1185,7 +1195,15 @@ builtin_oct(PyObject *self, PyObject *v) "oct() argument can't be converted to oct"); return NULL; } - return (*nb->nb_oct)(v); + res = (*nb->nb_oct)(v); + if (res && !PyString_Check(res)) { + PyErr_Format(PyExc_TypeError, + "__oct__ returned non-string (type %.200s)", + res->ob_type->tp_name); + Py_DECREF(res); + return NULL; + } + return res; } PyDoc_STRVAR(oct_doc, -- cgit v0.12