Skip to content

Commit 193366e

Browse files
pablogsalmiss-islington
authored andcommitted
[3.7] bpo-38395: Fix ownership in weakref.proxy methods (GH-16632) (GH-16663)
The implementation of weakref.proxy's methods call back into the Python API using a borrowed references of the weakly referenced object (acquired via PyWeakref_GET_OBJECT). This API call may delete the last reference to the object (either directly or via GC), leaving a dangling pointer, which can be subsequently dereferenced. To fix this, claim a temporary ownership of the referenced object when calling the appropriate method. Some functions because at the moment they do not need to access the borrowed referent, but to protect against future changes to these functions, ownership need to be fixed in all potentially affected methods.. (cherry picked from commit 10cd00a) Co-authored-by: Pablo Galindo <[email protected]> https://bugs.python.org/issue38395
1 parent 1845997 commit 193366e

File tree

3 files changed

+114
-31
lines changed

3 files changed

+114
-31
lines changed

Lib/test/test_weakref.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,26 @@ class List(list): pass
376376
lyst = List()
377377
self.assertEqual(bool(weakref.proxy(lyst)), bool(lyst))
378378

379+
def test_proxy_iter(self):
380+
# Test fails with a debug build of the interpreter
381+
# (see bpo-38395).
382+
383+
obj = None
384+
385+
class MyObj:
386+
def __iter__(self):
387+
nonlocal obj
388+
del obj
389+
return NotImplemented
390+
391+
obj = MyObj()
392+
p = weakref.proxy(obj)
393+
with self.assertRaises(TypeError):
394+
# "blech" in p calls MyObj.__iter__ through the proxy,
395+
# without keeping a reference to the real object, so it
396+
# can be killed in the middle of the call
397+
"blech" in p
398+
379399
def test_getweakrefcount(self):
380400
o = C()
381401
ref1 = weakref.ref(o)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash in :class:`weakref.proxy` objects due to incorrect lifetime
2+
management when calling some associated methods that may delete the last
3+
reference to object being referenced by the proxy. Patch by Pablo Galindo.

Objects/weakrefobject.c

Lines changed: 91 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,14 @@ weakref_hash(PyWeakReference *self)
145145
{
146146
if (self->hash != -1)
147147
return self->hash;
148-
if (PyWeakref_GET_OBJECT(self) == Py_None) {
148+
PyObject* obj = PyWeakref_GET_OBJECT(self);
149+
if (obj == Py_None) {
149150
PyErr_SetString(PyExc_TypeError, "weak object has gone away");
150151
return -1;
151152
}
152-
self->hash = PyObject_Hash(PyWeakref_GET_OBJECT(self));
153+
Py_INCREF(obj);
154+
self->hash = PyObject_Hash(obj);
155+
Py_DECREF(obj);
153156
return self->hash;
154157
}
155158

@@ -159,28 +162,36 @@ weakref_repr(PyWeakReference *self)
159162
{
160163
PyObject *name, *repr;
161164
_Py_IDENTIFIER(__name__);
165+
PyObject* obj = PyWeakref_GET_OBJECT(self);
162166

163-
if (PyWeakref_GET_OBJECT(self) == Py_None)
167+
if (obj == Py_None) {
164168
return PyUnicode_FromFormat("<weakref at %p; dead>", self);
169+
}
170+
171+
Py_INCREF(obj);
172+
if (_PyObject_LookupAttrId(obj, &PyId___name__, &name) < 0) {
173+
Py_DECREF(obj);
174+
return NULL;
175+
}
165176

166-
name = _PyObject_GetAttrId(PyWeakref_GET_OBJECT(self), &PyId___name__);
167177
if (name == NULL || !PyUnicode_Check(name)) {
168178
if (name == NULL)
169179
PyErr_Clear();
170180
repr = PyUnicode_FromFormat(
171181
"<weakref at %p; to '%s' at %p>",
172182
self,
173183
Py_TYPE(PyWeakref_GET_OBJECT(self))->tp_name,
174-
PyWeakref_GET_OBJECT(self));
184+
obj);
175185
}
176186
else {
177187
repr = PyUnicode_FromFormat(
178188
"<weakref at %p; to '%s' at %p (%U)>",
179189
self,
180190
Py_TYPE(PyWeakref_GET_OBJECT(self))->tp_name,
181-
PyWeakref_GET_OBJECT(self),
191+
obj,
182192
name);
183193
}
194+
Py_DECREF(obj);
184195
Py_XDECREF(name);
185196
return repr;
186197
}
@@ -207,8 +218,14 @@ weakref_richcompare(PyWeakReference* self, PyWeakReference* other, int op)
207218
else
208219
Py_RETURN_FALSE;
209220
}
210-
return PyObject_RichCompare(PyWeakref_GET_OBJECT(self),
211-
PyWeakref_GET_OBJECT(other), op);
221+
PyObject* obj = PyWeakref_GET_OBJECT(self);
222+
PyObject* other_obj = PyWeakref_GET_OBJECT(other);
223+
Py_INCREF(obj);
224+
Py_INCREF(other_obj);
225+
PyObject* res = PyObject_RichCompare(obj, other_obj, op);
226+
Py_DECREF(obj);
227+
Py_DECREF(other_obj);
228+
return res;
212229
}
213230

214231
/* Given the head of an object's list of weak references, extract the
@@ -415,26 +432,27 @@ proxy_checkref(PyWeakReference *proxy)
415432
o = PyWeakref_GET_OBJECT(o); \
416433
}
417434

418-
#define UNWRAP_I(o) \
419-
if (PyWeakref_CheckProxy(o)) { \
420-
if (!proxy_checkref((PyWeakReference *)o)) \
421-
return -1; \
422-
o = PyWeakref_GET_OBJECT(o); \
423-
}
424-
425435
#define WRAP_UNARY(method, generic) \
426436
static PyObject * \
427437
method(PyObject *proxy) { \
428438
UNWRAP(proxy); \
429-
return generic(proxy); \
439+
Py_INCREF(proxy); \
440+
PyObject* res = generic(proxy); \
441+
Py_DECREF(proxy); \
442+
return res; \
430443
}
431444

432445
#define WRAP_BINARY(method, generic) \
433446
static PyObject * \
434447
method(PyObject *x, PyObject *y) { \
435448
UNWRAP(x); \
436449
UNWRAP(y); \
437-
return generic(x, y); \
450+
Py_INCREF(x); \
451+
Py_INCREF(y); \
452+
PyObject* res = generic(x, y); \
453+
Py_DECREF(x); \
454+
Py_DECREF(y); \
455+
return res; \
438456
}
439457

440458
/* Note that the third arg needs to be checked for NULL since the tp_call
@@ -447,15 +465,25 @@ proxy_checkref(PyWeakReference *proxy)
447465
UNWRAP(v); \
448466
if (w != NULL) \
449467
UNWRAP(w); \
450-
return generic(proxy, v, w); \
468+
Py_INCREF(proxy); \
469+
Py_INCREF(v); \
470+
Py_XINCREF(w); \
471+
PyObject* res = generic(proxy, v, w); \
472+
Py_DECREF(proxy); \
473+
Py_DECREF(v); \
474+
Py_XDECREF(w); \
475+
return res; \
451476
}
452477

453478
#define WRAP_METHOD(method, special) \
454479
static PyObject * \
455480
method(PyObject *proxy) { \
456481
_Py_IDENTIFIER(special); \
457482
UNWRAP(proxy); \
458-
return _PyObject_CallMethodId(proxy, &PyId_##special, NULL); \
483+
Py_INCREF(proxy); \
484+
PyObject* res = _PyObject_CallMethodId(proxy, &PyId_##special, NULL); \
485+
Py_DECREF(proxy); \
486+
return res; \
459487
}
460488

461489

@@ -481,7 +509,11 @@ proxy_setattr(PyWeakReference *proxy, PyObject *name, PyObject *value)
481509
{
482510
if (!proxy_checkref(proxy))
483511
return -1;
484-
return PyObject_SetAttr(PyWeakref_GET_OBJECT(proxy), name, value);
512+
PyObject *obj = PyWeakref_GET_OBJECT(proxy);
513+
Py_INCREF(obj);
514+
int res = PyObject_SetAttr(obj, name, value);
515+
Py_DECREF(obj);
516+
return res;
485517
}
486518

487519
static PyObject *
@@ -530,9 +562,13 @@ static int
530562
proxy_bool(PyWeakReference *proxy)
531563
{
532564
PyObject *o = PyWeakref_GET_OBJECT(proxy);
533-
if (!proxy_checkref(proxy))
565+
if (!proxy_checkref(proxy)) {
534566
return -1;
535-
return PyObject_IsTrue(o);
567+
}
568+
Py_INCREF(o);
569+
int res = PyObject_IsTrue(o);
570+
Py_DECREF(o);
571+
return res;
536572
}
537573

538574
static void
@@ -551,9 +587,13 @@ proxy_contains(PyWeakReference *proxy, PyObject *value)
551587
{
552588
if (!proxy_checkref(proxy))
553589
return -1;
554-
return PySequence_Contains(PyWeakref_GET_OBJECT(proxy), value);
555-
}
556590

591+
PyObject *obj = PyWeakref_GET_OBJECT(proxy);
592+
Py_INCREF(obj);
593+
int res = PySequence_Contains(obj, value);
594+
Py_DECREF(obj);
595+
return res;
596+
}
557597

558598
/* mapping slots */
559599

@@ -562,7 +602,12 @@ proxy_length(PyWeakReference *proxy)
562602
{
563603
if (!proxy_checkref(proxy))
564604
return -1;
565-
return PyObject_Length(PyWeakref_GET_OBJECT(proxy));
605+
606+
PyObject *obj = PyWeakref_GET_OBJECT(proxy);
607+
Py_INCREF(obj);
608+
Py_ssize_t res = PyObject_Length(obj);
609+
Py_DECREF(obj);
610+
return res;
566611
}
567612

568613
WRAP_BINARY(proxy_getitem, PyObject_GetItem)
@@ -573,10 +618,16 @@ proxy_setitem(PyWeakReference *proxy, PyObject *key, PyObject *value)
573618
if (!proxy_checkref(proxy))
574619
return -1;
575620

576-
if (value == NULL)
577-
return PyObject_DelItem(PyWeakref_GET_OBJECT(proxy), key);
578-
else
579-
return PyObject_SetItem(PyWeakref_GET_OBJECT(proxy), key, value);
621+
PyObject *obj = PyWeakref_GET_OBJECT(proxy);
622+
Py_INCREF(obj);
623+
int res;
624+
if (value == NULL) {
625+
res = PyObject_DelItem(obj, key);
626+
} else {
627+
res = PyObject_SetItem(obj, key, value);
628+
}
629+
Py_DECREF(obj);
630+
return res;
580631
}
581632

582633
/* iterator slots */
@@ -586,15 +637,24 @@ proxy_iter(PyWeakReference *proxy)
586637
{
587638
if (!proxy_checkref(proxy))
588639
return NULL;
589-
return PyObject_GetIter(PyWeakref_GET_OBJECT(proxy));
640+
PyObject *obj = PyWeakref_GET_OBJECT(proxy);
641+
Py_INCREF(obj);
642+
PyObject* res = PyObject_GetIter(obj);
643+
Py_DECREF(obj);
644+
return res;
590645
}
591646

592647
static PyObject *
593648
proxy_iternext(PyWeakReference *proxy)
594649
{
595650
if (!proxy_checkref(proxy))
596651
return NULL;
597-
return PyIter_Next(PyWeakref_GET_OBJECT(proxy));
652+
653+
PyObject *obj = PyWeakref_GET_OBJECT(proxy);
654+
Py_INCREF(obj);
655+
PyObject* res = PyIter_Next(obj);
656+
Py_DECREF(obj);
657+
return res;
598658
}
599659

600660

0 commit comments

Comments
 (0)