Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Jul 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions Lib/test/test_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,16 @@ class I:
else:
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):
# Test comparison and hash of methods
class A:
Expand All @@ -544,24 +554,30 @@ def f(self):
def g(self):
pass
def __eq__(self, other):
return self.x == other.x
return True
def __hash__(self):
return self.x
raise TypeError
class B(A):
pass

a1 = A(1)
a2 = A(2)
self.assertEqual(a1.f, a1.f)
self.assertNotEqual(a1.f, a2.f)
self.assertNotEqual(a1.f, a1.g)
self.assertEqual(a1.f, A(1).f)
a2 = A(1)
self.assertTrue(a1.f == a1.f)
self.assertFalse(a1.f != a1.f)
self.assertFalse(a1.f == a2.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(A(1).f))

self.assertNotEqual(A.f, a1.f)
self.assertNotEqual(A.f, A.g)
self.assertEqual(B.f, A.f)
self.assertFalse(A.f == a1.f)
self.assertTrue(A.f != a1.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))

# the following triggers a SystemError in 2.4
Expand Down
83 changes: 58 additions & 25 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4350,38 +4350,71 @@ def __init__(self):
else:
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):
# Testing method-wrapper objects...
# <type 'method-wrapper'> did not support any reflection before 2.5

# XXX should methods really support __eq__?

l = []
self.assertEqual(l.__add__, l.__add__)
self.assertEqual(l.__add__, [].__add__)
self.assertNotEqual(l.__add__, [5].__add__)
self.assertNotEqual(l.__add__, l.__mul__)
self.assertTrue(l.__add__ == l.__add__)
self.assertFalse(l.__add__ != l.__add__)
self.assertFalse(l.__add__ == [].__add__)
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__')
if hasattr(l.__add__, '__self__'):
# CPython
self.assertIs(l.__add__.__self__, l)
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.assertIs(l.__add__.__self__, l)
self.assertIs(l.__add__.__objclass__, list)
self.assertEqual(l.__add__.__doc__, list.__add__.__doc__)
try:
hash(l.__add__)
except TypeError:
pass
else:
self.fail("no TypeError from hash([].__add__)")
# hash([].__add__) should not be based on hash([])
hash(l.__add__)

t = ()
t += (7,)
self.assertEqual(t.__add__, (7,).__add__)
self.assertEqual(hash(t.__add__), hash((7,).__add__))
def test_builtin_function_or_method(self):
# Not really belonging to test_descr, but introspection and
# comparison on <type 'builtin_function_or_method'> seems not
# 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):
# Testing NotImplemented...
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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.
14 changes: 4 additions & 10 deletions Objects/classobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,9 @@ method_richcompare(PyObject *self, PyObject *other, int op)
b = (PyMethodObject *)other;
eq = PyObject_RichCompareBool(a->im_func, b->im_func, Py_EQ);
if (eq == 1) {
if (a->im_self == NULL || b->im_self == NULL)
eq = a->im_self == b->im_self;
else
eq = PyObject_RichCompareBool(a->im_self, b->im_self,
Py_EQ);
eq = (a->im_self == b->im_self);
}
if (eq < 0)
else if (eq < 0)
return NULL;
if (op == Py_EQ)
res = eq ? Py_True : Py_False;
Expand Down Expand Up @@ -274,11 +270,9 @@ method_hash(PyMethodObject *a)
{
Py_hash_t x, y;
if (a->im_self == NULL)
x = PyObject_Hash(Py_None);
x = _Py_HashPointer(Py_None);
else
x = PyObject_Hash(a->im_self);
if (x == -1)
return -1;
x = _Py_HashPointer(a->im_self);
y = PyObject_Hash(a->im_func);
if (y == -1)
return -1;
Expand Down
33 changes: 15 additions & 18 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1038,38 +1038,35 @@ wrapper_dealloc(wrapperobject *wp)
static PyObject *
wrapper_richcompare(PyObject *a, PyObject *b, int op)
{
PyWrapperDescrObject *a_descr, *b_descr;
wrapperobject *wa, *wb;
int eq;

assert(a != NULL && b != NULL);

/* 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;
}

/* compare by descriptor address; if the descriptors are the same,
compare by the objects they're bound to */
a_descr = ((wrapperobject *)a)->descr;
b_descr = ((wrapperobject *)b)->descr;
if (a_descr == b_descr) {
a = ((wrapperobject *)a)->self;
b = ((wrapperobject *)b)->self;
return PyObject_RichCompare(a, b, op);
wa = (wrapperobject *)a;
wb = (wrapperobject *)b;
eq = (wa->descr == wb->descr && wa->self == wb->self);
if (eq == (op == Py_EQ)) {
Py_RETURN_TRUE;
}
else {
Py_RETURN_FALSE;
}

Py_RETURN_RICHCOMPARE(a_descr, b_descr, op);
}

static Py_hash_t
wrapper_hash(wrapperobject *wp)
{
Py_hash_t x, y;
x = _Py_HashPointer(wp->descr);
if (x == -1)
return -1;
y = PyObject_Hash(wp->self);
if (y == -1)
return -1;
x = _Py_HashPointer(wp->self);
y = _Py_HashPointer(wp->descr);
x = x ^ y;
if (x == -1)
x = -2;
Expand Down
10 changes: 1 addition & 9 deletions Objects/methodobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -251,16 +251,8 @@ static Py_hash_t
meth_hash(PyCFunctionObject *a)
{
Py_hash_t x, y;
if (a->m_self == NULL)
x = 0;
else {
x = PyObject_Hash(a->m_self);
if (x == -1)
return -1;
}
x = _Py_HashPointer(a->m_self);
y = _Py_HashPointer((void*)(a->m_ml->ml_meth));
if (y == -1)
return -1;
x ^= y;
if (x == -1)
x = -2;
Expand Down