Commit ac20e0f9 authored by Serhiy Storchaka's avatar Serhiy Storchaka Committed by GitHub

bpo-1617161: Make the hash and equality of methods not depending on the value of self. (GH-7848)

* The hash of BuiltinMethodType instances no longer depends on the hash
  of __self__. It depends now on the hash of id(__self__).
* The hash and equality of ModuleType and MethodWrapperType instances no
  longer depend on the hash and equality of __self__. They depend now on
  the hash and equality of id(__self__).
* MethodWrapperType instances no longer support ordering.
parent c48e26dc
...@@ -534,6 +534,16 @@ class ClassTests(unittest.TestCase): ...@@ -534,6 +534,16 @@ class ClassTests(unittest.TestCase):
else: else:
self.fail("attribute error for I.__init__ got masked") self.fail("attribute error for I.__init__ got masked")
def assertNotOrderable(self, a, b):
with self.assertRaises(TypeError):
a < b
with self.assertRaises(TypeError):
a > b
with self.assertRaises(TypeError):
a <= b
with self.assertRaises(TypeError):
a >= b
def testHashComparisonOfMethods(self): def testHashComparisonOfMethods(self):
# Test comparison and hash of methods # Test comparison and hash of methods
class A: class A:
...@@ -544,24 +554,30 @@ class ClassTests(unittest.TestCase): ...@@ -544,24 +554,30 @@ class ClassTests(unittest.TestCase):
def g(self): def g(self):
pass pass
def __eq__(self, other): def __eq__(self, other):
return self.x == other.x return True
def __hash__(self): def __hash__(self):
return self.x raise TypeError
class B(A): class B(A):
pass pass
a1 = A(1) a1 = A(1)
a2 = A(2) a2 = A(1)
self.assertEqual(a1.f, a1.f) self.assertTrue(a1.f == a1.f)
self.assertNotEqual(a1.f, a2.f) self.assertFalse(a1.f != a1.f)
self.assertNotEqual(a1.f, a1.g) self.assertFalse(a1.f == a2.f)
self.assertEqual(a1.f, A(1).f) self.assertTrue(a1.f != a2.f)
self.assertFalse(a1.f == a1.g)
self.assertTrue(a1.f != a1.g)
self.assertNotOrderable(a1.f, a1.f)
self.assertEqual(hash(a1.f), hash(a1.f)) self.assertEqual(hash(a1.f), hash(a1.f))
self.assertEqual(hash(a1.f), hash(A(1).f))
self.assertNotEqual(A.f, a1.f) self.assertFalse(A.f == a1.f)
self.assertNotEqual(A.f, A.g) self.assertTrue(A.f != a1.f)
self.assertEqual(B.f, A.f) self.assertFalse(A.f == A.g)
self.assertTrue(A.f != A.g)
self.assertTrue(B.f == A.f)
self.assertFalse(B.f != A.f)
self.assertNotOrderable(A.f, A.f)
self.assertEqual(hash(B.f), hash(A.f)) self.assertEqual(hash(B.f), hash(A.f))
# the following triggers a SystemError in 2.4 # the following triggers a SystemError in 2.4
......
...@@ -4350,38 +4350,71 @@ order (MRO) for bases """ ...@@ -4350,38 +4350,71 @@ order (MRO) for bases """
else: else:
self.fail("did not test __init__() for None return") self.fail("did not test __init__() for None return")
def assertNotOrderable(self, a, b):
with self.assertRaises(TypeError):
a < b
with self.assertRaises(TypeError):
a > b
with self.assertRaises(TypeError):
a <= b
with self.assertRaises(TypeError):
a >= b
def test_method_wrapper(self): def test_method_wrapper(self):
# Testing method-wrapper objects... # Testing method-wrapper objects...
# <type 'method-wrapper'> did not support any reflection before 2.5 # <type 'method-wrapper'> did not support any reflection before 2.5
# XXX should methods really support __eq__?
l = [] l = []
self.assertEqual(l.__add__, l.__add__) self.assertTrue(l.__add__ == l.__add__)
self.assertEqual(l.__add__, [].__add__) self.assertFalse(l.__add__ != l.__add__)
self.assertNotEqual(l.__add__, [5].__add__) self.assertFalse(l.__add__ == [].__add__)
self.assertNotEqual(l.__add__, l.__mul__) self.assertTrue(l.__add__ != [].__add__)
self.assertFalse(l.__add__ == l.__mul__)
self.assertTrue(l.__add__ != l.__mul__)
self.assertNotOrderable(l.__add__, l.__add__)
self.assertEqual(l.__add__.__name__, '__add__') self.assertEqual(l.__add__.__name__, '__add__')
if hasattr(l.__add__, '__self__'):
# CPython
self.assertIs(l.__add__.__self__, l) self.assertIs(l.__add__.__self__, l)
self.assertIs(l.__add__.__objclass__, list) self.assertIs(l.__add__.__objclass__, list)
else:
# Python implementations where [].__add__ is a normal bound method
self.assertIs(l.__add__.im_self, l)
self.assertIs(l.__add__.im_class, list)
self.assertEqual(l.__add__.__doc__, list.__add__.__doc__) self.assertEqual(l.__add__.__doc__, list.__add__.__doc__)
try: # hash([].__add__) should not be based on hash([])
hash(l.__add__) hash(l.__add__)
except TypeError:
pass
else:
self.fail("no TypeError from hash([].__add__)")
t = () def test_builtin_function_or_method(self):
t += (7,) # Not really belonging to test_descr, but introspection and
self.assertEqual(t.__add__, (7,).__add__) # comparison on <type 'builtin_function_or_method'> seems not
self.assertEqual(hash(t.__add__), hash((7,).__add__)) # to be tested elsewhere
l = []
self.assertTrue(l.append == l.append)
self.assertFalse(l.append != l.append)
self.assertFalse(l.append == [].append)
self.assertTrue(l.append != [].append)
self.assertFalse(l.append == l.pop)
self.assertTrue(l.append != l.pop)
self.assertNotOrderable(l.append, l.append)
self.assertEqual(l.append.__name__, 'append')
self.assertIs(l.append.__self__, l)
# self.assertIs(l.append.__objclass__, list) --- could be added?
self.assertEqual(l.append.__doc__, list.append.__doc__)
# hash([].append) should not be based on hash([])
hash(l.append)
def test_special_unbound_method_types(self):
# Testing objects of <type 'wrapper_descriptor'>...
self.assertTrue(list.__add__ == list.__add__)
self.assertFalse(list.__add__ != list.__add__)
self.assertFalse(list.__add__ == list.__mul__)
self.assertTrue(list.__add__ != list.__mul__)
self.assertNotOrderable(list.__add__, list.__add__)
self.assertEqual(list.__add__.__name__, '__add__')
self.assertIs(list.__add__.__objclass__, list)
# Testing objects of <type 'method_descriptor'>...
self.assertTrue(list.append == list.append)
self.assertFalse(list.append != list.append)
self.assertFalse(list.append == list.pop)
self.assertTrue(list.append != list.pop)
self.assertNotOrderable(list.append, list.append)
self.assertEqual(list.append.__name__, 'append')
self.assertIs(list.append.__objclass__, list)
def test_not_implemented(self): def test_not_implemented(self):
# Testing NotImplemented... # Testing NotImplemented...
......
The hash of :class:`BuiltinMethodType` instances (methods of built-in classes)
now depends on the hash of the identity of *__self__* instead of its value.
The hash and equality of :class:`ModuleType` and :class:`MethodWrapperType`
instances (methods of user-defined classes and some methods of built-in classes
like ``str.__add__``) now depend on the hash and equality of the identity
of *__self__* instead of its value. :class:`MethodWrapperType` instances no
longer support ordering.
...@@ -225,13 +225,9 @@ method_richcompare(PyObject *self, PyObject *other, int op) ...@@ -225,13 +225,9 @@ method_richcompare(PyObject *self, PyObject *other, int op)
b = (PyMethodObject *)other; b = (PyMethodObject *)other;
eq = PyObject_RichCompareBool(a->im_func, b->im_func, Py_EQ); eq = PyObject_RichCompareBool(a->im_func, b->im_func, Py_EQ);
if (eq == 1) { if (eq == 1) {
if (a->im_self == NULL || b->im_self == NULL) eq = (a->im_self == b->im_self);
eq = a->im_self == b->im_self;
else
eq = PyObject_RichCompareBool(a->im_self, b->im_self,
Py_EQ);
} }
if (eq < 0) else if (eq < 0)
return NULL; return NULL;
if (op == Py_EQ) if (op == Py_EQ)
res = eq ? Py_True : Py_False; res = eq ? Py_True : Py_False;
...@@ -274,11 +270,9 @@ method_hash(PyMethodObject *a) ...@@ -274,11 +270,9 @@ method_hash(PyMethodObject *a)
{ {
Py_hash_t x, y; Py_hash_t x, y;
if (a->im_self == NULL) if (a->im_self == NULL)
x = PyObject_Hash(Py_None); x = _Py_HashPointer(Py_None);
else else
x = PyObject_Hash(a->im_self); x = _Py_HashPointer(a->im_self);
if (x == -1)
return -1;
y = PyObject_Hash(a->im_func); y = PyObject_Hash(a->im_func);
if (y == -1) if (y == -1)
return -1; return -1;
......
...@@ -1038,38 +1038,35 @@ wrapper_dealloc(wrapperobject *wp) ...@@ -1038,38 +1038,35 @@ wrapper_dealloc(wrapperobject *wp)
static PyObject * static PyObject *
wrapper_richcompare(PyObject *a, PyObject *b, int op) wrapper_richcompare(PyObject *a, PyObject *b, int op)
{ {
PyWrapperDescrObject *a_descr, *b_descr; wrapperobject *wa, *wb;
int eq;
assert(a != NULL && b != NULL); assert(a != NULL && b != NULL);
/* both arguments should be wrapperobjects */ /* both arguments should be wrapperobjects */
if (!Wrapper_Check(a) || !Wrapper_Check(b)) { if ((op != Py_EQ && op != Py_NE)
|| !Wrapper_Check(a) || !Wrapper_Check(b))
{
Py_RETURN_NOTIMPLEMENTED; Py_RETURN_NOTIMPLEMENTED;
} }
/* compare by descriptor address; if the descriptors are the same, wa = (wrapperobject *)a;
compare by the objects they're bound to */ wb = (wrapperobject *)b;
a_descr = ((wrapperobject *)a)->descr; eq = (wa->descr == wb->descr && wa->self == wb->self);
b_descr = ((wrapperobject *)b)->descr; if (eq == (op == Py_EQ)) {
if (a_descr == b_descr) { Py_RETURN_TRUE;
a = ((wrapperobject *)a)->self; }
b = ((wrapperobject *)b)->self; else {
return PyObject_RichCompare(a, b, op); Py_RETURN_FALSE;
} }
Py_RETURN_RICHCOMPARE(a_descr, b_descr, op);
} }
static Py_hash_t static Py_hash_t
wrapper_hash(wrapperobject *wp) wrapper_hash(wrapperobject *wp)
{ {
Py_hash_t x, y; Py_hash_t x, y;
x = _Py_HashPointer(wp->descr); x = _Py_HashPointer(wp->self);
if (x == -1) y = _Py_HashPointer(wp->descr);
return -1;
y = PyObject_Hash(wp->self);
if (y == -1)
return -1;
x = x ^ y; x = x ^ y;
if (x == -1) if (x == -1)
x = -2; x = -2;
......
...@@ -251,16 +251,8 @@ static Py_hash_t ...@@ -251,16 +251,8 @@ static Py_hash_t
meth_hash(PyCFunctionObject *a) meth_hash(PyCFunctionObject *a)
{ {
Py_hash_t x, y; Py_hash_t x, y;
if (a->m_self == NULL) x = _Py_HashPointer(a->m_self);
x = 0;
else {
x = PyObject_Hash(a->m_self);
if (x == -1)
return -1;
}
y = _Py_HashPointer((void*)(a->m_ml->ml_meth)); y = _Py_HashPointer((void*)(a->m_ml->ml_meth));
if (y == -1)
return -1;
x ^= y; x ^= y;
if (x == -1) if (x == -1)
x = -2; x = -2;
......
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