Skip to content

Commit f6a50cf

Browse files
committed
Issue python#16453: Fix equality testing of dead weakref objects.
Also add tests for ordering and hashing.
2 parents 6ff262e + e11fecb commit f6a50cf

3 files changed

Lines changed: 93 additions & 24 deletions

File tree

Lib/test/test_weakref.py

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@ def create_bound_method():
3232
return C().method
3333

3434

35+
class Object:
36+
def __init__(self, arg):
37+
self.arg = arg
38+
def __repr__(self):
39+
return "<Object %r>" % self.arg
40+
def __eq__(self, other):
41+
if isinstance(other, Object):
42+
return self.arg == other.arg
43+
return NotImplemented
44+
def __lt__(self, other):
45+
if isinstance(other, Object):
46+
return self.arg < other.arg
47+
return NotImplemented
48+
def __hash__(self):
49+
return hash(self.arg)
50+
51+
class RefCycle:
52+
def __init__(self):
53+
self.cycle = self
54+
55+
3556
class TestBase(unittest.TestCase):
3657

3758
def setUp(self):
@@ -692,6 +713,69 @@ class A(object):
692713
self.assertEqual(a(), None)
693714
self.assertEqual(l, [a])
694715

716+
def test_equality(self):
717+
# Alive weakrefs defer equality testing to their underlying object.
718+
x = Object(1)
719+
y = Object(1)
720+
z = Object(2)
721+
a = weakref.ref(x)
722+
b = weakref.ref(y)
723+
c = weakref.ref(z)
724+
d = weakref.ref(x)
725+
# Note how we directly test the operators here, to stress both
726+
# __eq__ and __ne__.
727+
self.assertTrue(a == b)
728+
self.assertFalse(a != b)
729+
self.assertFalse(a == c)
730+
self.assertTrue(a != c)
731+
self.assertTrue(a == d)
732+
self.assertFalse(a != d)
733+
del x, y, z
734+
gc.collect()
735+
for r in a, b, c:
736+
# Sanity check
737+
self.assertIs(r(), None)
738+
# Dead weakrefs compare by identity: whether `a` and `d` are the
739+
# same weakref object is an implementation detail, since they pointed
740+
# to the same original object and didn't have a callback.
741+
# (see issue #16453).
742+
self.assertFalse(a == b)
743+
self.assertTrue(a != b)
744+
self.assertFalse(a == c)
745+
self.assertTrue(a != c)
746+
self.assertEqual(a == d, a is d)
747+
self.assertEqual(a != d, a is not d)
748+
749+
def test_ordering(self):
750+
# weakrefs cannot be ordered, even if the underlying objects can.
751+
ops = [operator.lt, operator.gt, operator.le, operator.ge]
752+
x = Object(1)
753+
y = Object(1)
754+
a = weakref.ref(x)
755+
b = weakref.ref(y)
756+
for op in ops:
757+
self.assertRaises(TypeError, op, a, b)
758+
# Same when dead.
759+
del x, y
760+
gc.collect()
761+
for op in ops:
762+
self.assertRaises(TypeError, op, a, b)
763+
764+
def test_hashing(self):
765+
# Alive weakrefs hash the same as the underlying object
766+
x = Object(42)
767+
y = Object(42)
768+
a = weakref.ref(x)
769+
b = weakref.ref(y)
770+
self.assertEqual(hash(a), hash(42))
771+
del x, y
772+
gc.collect()
773+
# Dead weakrefs:
774+
# - retain their hash is they were hashed when alive;
775+
# - otherwise, cannot be hashed.
776+
self.assertEqual(hash(a), hash(42))
777+
self.assertRaises(TypeError, hash, b)
778+
695779

696780
class SubclassableWeakrefTestCase(TestBase):
697781

@@ -796,27 +880,6 @@ def callback(w):
796880
self.assertEqual(self.cbcalled, 0)
797881

798882

799-
class Object:
800-
def __init__(self, arg):
801-
self.arg = arg
802-
def __repr__(self):
803-
return "<Object %r>" % self.arg
804-
def __eq__(self, other):
805-
if isinstance(other, Object):
806-
return self.arg == other.arg
807-
return NotImplemented
808-
def __lt__(self, other):
809-
if isinstance(other, Object):
810-
return self.arg < other.arg
811-
return NotImplemented
812-
def __hash__(self):
813-
return hash(self.arg)
814-
815-
class RefCycle:
816-
def __init__(self):
817-
self.cycle = self
818-
819-
820883
class MappingTestCase(TestBase):
821884

822885
COUNT = 10

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ What's New in Python 3.3.1?
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #16453: Fix equality testing of dead weakref objects.
16+
1517
- Issue #9535: Fix pending signals that have been received but not yet
1618
handled by Python to not persist after os.fork() in the child process.
1719

Objects/weakrefobject.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,13 @@ weakref_richcompare(PyWeakReference* self, PyWeakReference* other, int op)
198198
}
199199
if (PyWeakref_GET_OBJECT(self) == Py_None
200200
|| PyWeakref_GET_OBJECT(other) == Py_None) {
201-
PyObject *res = self==other ? Py_True : Py_False;
202-
Py_INCREF(res);
203-
return res;
201+
int res = (self == other);
202+
if (op == Py_NE)
203+
res = !res;
204+
if (res)
205+
Py_RETURN_TRUE;
206+
else
207+
Py_RETURN_FALSE;
204208
}
205209
return PyObject_RichCompare(PyWeakref_GET_OBJECT(self),
206210
PyWeakref_GET_OBJECT(other), op);

0 commit comments

Comments
 (0)