diff options
author | Nick Coghlan <ncoghlan@gmail.com> | 2008-12-30 01:18:48 (GMT) |
---|---|---|
committer | Nick Coghlan <ncoghlan@gmail.com> | 2008-12-30 01:18:48 (GMT) |
commit | 180e4007661f4480c3be50289748526c2647d28e (patch) | |
tree | 9c1da39d98c91ebb08054ddb19d608596b19b498 | |
parent | c13acb18bc2db9d8824ba94b86d8e4c8909d6b6c (diff) | |
download | cpython-180e4007661f4480c3be50289748526c2647d28e.zip cpython-180e4007661f4480c3be50289748526c2647d28e.tar.gz cpython-180e4007661f4480c3be50289748526c2647d28e.tar.bz2 |
Issue #4701: implicitly call PyType_Ready from PyObject_Hash
-rw-r--r-- | Lib/test/test_hash.py | 25 | ||||
-rw-r--r-- | Misc/NEWS | 3 | ||||
-rw-r--r-- | Modules/_testcapimodule.c | 101 | ||||
-rw-r--r-- | Objects/object.c | 11 |
4 files changed, 139 insertions, 1 deletions
diff --git a/Lib/test/test_hash.py b/Lib/test/test_hash.py index 47c66d1..7ce40b9 100644 --- a/Lib/test/test_hash.py +++ b/Lib/test/test_hash.py @@ -111,9 +111,32 @@ class HashInheritanceTestCase(unittest.TestCase): self.assertFalse(isinstance(obj, Hashable), repr(obj)) +# Issue #4701: Check that some builtin types are correctly hashable +# (This test only used to fail in Python 3.0, but has been included +# in 2.x along with the lazy call to PyType_Ready in PyObject_Hash) +class DefaultIterSeq(object): + seq = range(10) + def __len__(self): + return len(self.seq) + def __getitem__(self, index): + return self.seq[index] + +class HashBuiltinsTestCase(unittest.TestCase): + hashes_to_check = [xrange(10), + enumerate(xrange(10)), + iter(DefaultIterSeq()), + iter(lambda: 0, 0), + ] + + def test_hashes(self): + _default_hash = object.__hash__ + for obj in self.hashes_to_check: + self.assertEqual(hash(obj), _default_hash(obj)) + def test_main(): test_support.run_unittest(HashEqualityTestCase, - HashInheritanceTestCase) + HashInheritanceTestCase, + HashBuiltinsTestCase) if __name__ == "__main__": @@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1 Core and Builtins ----------------- +- Issue #4701: PyObject_Hash now implicitly calls PyType_Ready on types + where the tp_hash and tp_dict slots are both NULL. + - Issue #4764: With io.open, IOError.filename is set when trying to open a directory on POSIX systems. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index d640c39..bd2f211 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -173,6 +173,106 @@ test_dict_iteration(PyObject* self) } +/* Issue #4701: Check that PyObject_Hash implicitly calls + * PyType_Ready if it hasn't already been called + */ +static PyTypeObject _HashInheritanceTester_Type = { + PyObject_HEAD_INIT(&PyType_Type) + 0, /* Number of items for varobject */ + "hashinheritancetester", /* Name of this type */ + sizeof(PyObject), /* Basic object size */ + 0, /* Item size for varobject */ + (destructor)PyObject_Del, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyType_GenericNew, /* tp_new */ +}; + +static PyObject* +test_lazy_hash_inheritance(PyObject* self) +{ + PyTypeObject *type; + PyObject *obj; + long hash; + + type = &_HashInheritanceTester_Type; + obj = PyObject_New(PyObject, type); + if (obj == NULL) { + PyErr_Clear(); + PyErr_SetString( + TestError, + "test_lazy_hash_inheritance: failed to create object"); + return NULL; + } + + if (type->tp_dict != NULL) { + PyErr_SetString( + TestError, + "test_lazy_hash_inheritance: type initialised too soon"); + Py_DECREF(obj); + return NULL; + } + + hash = PyObject_Hash(obj); + if ((hash == -1) && PyErr_Occurred()) { + PyErr_Clear(); + PyErr_SetString( + TestError, + "test_lazy_hash_inheritance: could not hash object"); + Py_DECREF(obj); + return NULL; + } + + if (type->tp_dict == NULL) { + PyErr_SetString( + TestError, + "test_lazy_hash_inheritance: type not initialised by hash()"); + Py_DECREF(obj); + return NULL; + } + + if (type->tp_hash != PyType_Type.tp_hash) { + PyErr_SetString( + TestError, + "test_lazy_hash_inheritance: unexpected hash function"); + Py_DECREF(obj); + return NULL; + } + + Py_RETURN_NONE; +} + + /* Tests of PyLong_{As, From}{Unsigned,}Long(), and (#ifdef HAVE_LONG_LONG) PyLong_{As, From}{Unsigned,}LongLong(). @@ -805,6 +905,7 @@ static PyMethodDef TestMethods[] = { {"test_config", (PyCFunction)test_config, METH_NOARGS}, {"test_list_api", (PyCFunction)test_list_api, METH_NOARGS}, {"test_dict_iteration", (PyCFunction)test_dict_iteration,METH_NOARGS}, + {"test_lazy_hash_inheritance", (PyCFunction)test_lazy_hash_inheritance,METH_NOARGS}, {"test_long_api", (PyCFunction)test_long_api, METH_NOARGS}, {"test_long_numbits", (PyCFunction)test_long_numbits, METH_NOARGS}, {"test_k_code", (PyCFunction)test_k_code, METH_NOARGS}, diff --git a/Objects/object.c b/Objects/object.c index c882cf2..7b82db9 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1100,6 +1100,17 @@ PyObject_Hash(PyObject *v) PyTypeObject *tp = v->ob_type; if (tp->tp_hash != NULL) return (*tp->tp_hash)(v); + /* To keep to the general practice that inheriting + * solely from object in C code should work without + * an explicit call to PyType_Ready, we implicitly call + * PyType_Ready here and then check the tp_hash slot again + */ + if (tp->tp_dict == NULL) { + if (PyType_Ready(tp) < 0) + return -1; + if (tp->tp_hash != NULL) + return (*tp->tp_hash)(v); + } if (tp->tp_compare == NULL && RICHCOMPARE(tp) == NULL) { return _Py_HashPointer(v); /* Use address as hash value */ } |