Skip to content

Commit 4731634

Browse files
authored
bpo-31787: Prevent refleaks when calling __init__() more than once (GH-3995)
(cherry picked from commit d019bc8)
1 parent 310b424 commit 4731634

13 files changed

Lines changed: 103 additions & 15 deletions

File tree

Lib/test/test_asyncio/test_tasks.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,6 +2133,20 @@ class CTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase):
21332133
Task = getattr(tasks, '_CTask', None)
21342134
Future = getattr(futures, '_CFuture', None)
21352135

2136+
@support.refcount_test
2137+
def test_refleaks_in_task___init__(self):
2138+
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
2139+
@asyncio.coroutine
2140+
def coro():
2141+
pass
2142+
task = self.new_task(self.loop, coro())
2143+
self.loop.run_until_complete(task)
2144+
refs_before = gettotalrefcount()
2145+
for i in range(100):
2146+
task.__init__(coro(), loop=self.loop)
2147+
self.loop.run_until_complete(task)
2148+
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
2149+
21362150

21372151
@unittest.skipUnless(hasattr(futures, '_CFuture'),
21382152
'requires the C _asyncio module')

Lib/test/test_bz2.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import sys
1414
from test.support import unlink
1515
import _compression
16+
import sys
1617

1718
try:
1819
import threading
@@ -828,6 +829,16 @@ def test_failure(self):
828829
# Previously, a second call could crash due to internal inconsistency
829830
self.assertRaises(Exception, bzd.decompress, self.BAD_DATA * 30)
830831

832+
@support.refcount_test
833+
def test_refleaks_in___init__(self):
834+
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
835+
bzd = BZ2Decompressor()
836+
refs_before = gettotalrefcount()
837+
for i in range(100):
838+
bzd.__init__()
839+
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
840+
841+
831842
class CompressDecompressTest(BaseTest):
832843
def testCompress(self):
833844
data = bz2.compress(self.TEXT)

Lib/test/test_descr.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,15 @@ def f(cls, arg): return (cls, arg)
15191519
del cm.x
15201520
self.assertNotHasAttr(cm, "x")
15211521

1522+
@support.refcount_test
1523+
def test_refleaks_in_classmethod___init__(self):
1524+
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
1525+
cm = classmethod(None)
1526+
refs_before = gettotalrefcount()
1527+
for i in range(100):
1528+
cm.__init__(None)
1529+
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
1530+
15221531
@support.impl_detail("the module 'xxsubtype' is internal")
15231532
def test_classmethods_in_c(self):
15241533
# Testing C-based class methods...
@@ -1574,6 +1583,15 @@ class D(C):
15741583
del sm.x
15751584
self.assertNotHasAttr(sm, "x")
15761585

1586+
@support.refcount_test
1587+
def test_refleaks_in_staticmethod___init__(self):
1588+
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
1589+
sm = staticmethod(None)
1590+
refs_before = gettotalrefcount()
1591+
for i in range(100):
1592+
sm.__init__(None)
1593+
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
1594+
15771595
@support.impl_detail("the module 'xxsubtype' is internal")
15781596
def test_staticmethods_in_c(self):
15791597
# Testing C-based static methods...

Lib/test/test_hashlib.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,15 @@ def hash_constructors(self):
165165
constructors = self.constructors_to_test.values()
166166
return itertools.chain.from_iterable(constructors)
167167

168+
@support.refcount_test
169+
def test_refleaks_in_hash___init__(self):
170+
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
171+
sha1_hash = c_hashlib.new('sha1')
172+
refs_before = gettotalrefcount()
173+
for i in range(100):
174+
sha1_hash.__init__('sha1')
175+
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
176+
168177
def test_hash_array(self):
169178
a = array.array("b", range(10))
170179
for cons in self.hash_constructors:

Lib/test/test_lzma.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import pathlib
55
import pickle
66
import random
7+
import sys
8+
from test import support
79
import unittest
810

911
from test.support import (
@@ -364,6 +366,15 @@ def test_pickle(self):
364366
with self.assertRaises(TypeError):
365367
pickle.dumps(LZMADecompressor(), proto)
366368

369+
@support.refcount_test
370+
def test_refleaks_in_decompressor___init__(self):
371+
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
372+
lzd = LZMADecompressor()
373+
refs_before = gettotalrefcount()
374+
for i in range(100):
375+
lzd.__init__()
376+
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
377+
367378

368379
class CompressDecompressFunctionTestCase(unittest.TestCase):
369380

Lib/test/test_property.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import sys
55
import unittest
6+
from test import support
67

78
class PropertyBase(Exception):
89
pass
@@ -173,6 +174,16 @@ def spam(self):
173174
sub.__class__.spam.__doc__ = 'Spam'
174175
self.assertEqual(sub.__class__.spam.__doc__, 'Spam')
175176

177+
@support.refcount_test
178+
def test_refleaks_in___init__(self):
179+
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
180+
fake_prop = property('fget', 'fset', 'fdel', 'doc')
181+
refs_before = gettotalrefcount()
182+
for i in range(100):
183+
fake_prop.__init__('fget', 'fset', 'fdel', 'doc')
184+
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
185+
186+
176187
# Issue 5890: subclasses of property do not preserve method __doc__ strings
177188
class PropertySub(property):
178189
"""This is a subclass of property"""
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed refleaks of ``__init__()`` methods in various modules.
2+
(Contributed by Oren Milman)

Modules/_asynciomodule.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,25 @@ future_schedule_callbacks(FutureObj *fut)
132132
return 0;
133133
}
134134

135+
135136
static int
136137
future_init(FutureObj *fut, PyObject *loop)
137138
{
138139
PyObject *res;
139140
int is_true;
140141
_Py_IDENTIFIER(get_debug);
141142

143+
// Same to FutureObj_clear() but not clearing fut->dict
144+
Py_CLEAR(fut->fut_loop);
145+
Py_CLEAR(fut->fut_callbacks);
146+
Py_CLEAR(fut->fut_result);
147+
Py_CLEAR(fut->fut_exception);
148+
Py_CLEAR(fut->fut_source_tb);
149+
150+
fut->fut_state = STATE_PENDING;
151+
fut->fut_log_tb = 0;
152+
fut->fut_blocking = 0;
153+
142154
if (loop == Py_None) {
143155
loop = _PyObject_CallNoArg(asyncio_get_event_loop);
144156
if (loop == NULL) {
@@ -148,7 +160,7 @@ future_init(FutureObj *fut, PyObject *loop)
148160
else {
149161
Py_INCREF(loop);
150162
}
151-
Py_XSETREF(fut->fut_loop, loop);
163+
fut->fut_loop = loop;
152164

153165
res = _PyObject_CallMethodId(fut->fut_loop, &PyId_get_debug, NULL);
154166
if (res == NULL) {
@@ -160,13 +172,13 @@ future_init(FutureObj *fut, PyObject *loop)
160172
return -1;
161173
}
162174
if (is_true) {
163-
Py_XSETREF(fut->fut_source_tb, _PyObject_CallNoArg(traceback_extract_stack));
175+
fut->fut_source_tb = _PyObject_CallNoArg(traceback_extract_stack);
164176
if (fut->fut_source_tb == NULL) {
165177
return -1;
166178
}
167179
}
168180

169-
Py_XSETREF(fut->fut_callbacks, PyList_New(0));
181+
fut->fut_callbacks = PyList_New(0);
170182
if (fut->fut_callbacks == NULL) {
171183
return -1;
172184
}
@@ -1336,12 +1348,12 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop)
13361348
return -1;
13371349
}
13381350

1339-
self->task_fut_waiter = NULL;
1351+
Py_CLEAR(self->task_fut_waiter);
13401352
self->task_must_cancel = 0;
13411353
self->task_log_destroy_pending = 1;
13421354

13431355
Py_INCREF(coro);
1344-
self->task_coro = coro;
1356+
Py_XSETREF(self->task_coro, coro);
13451357

13461358
if (task_call_step_soon(self, NULL)) {
13471359
return -1;

Modules/_bz2module.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ _bz2_BZ2Decompressor___init___impl(BZ2Decompressor *self)
663663
self->bzs_avail_in_real = 0;
664664
self->input_buffer = NULL;
665665
self->input_buffer_size = 0;
666-
self->unused_data = PyBytes_FromStringAndSize(NULL, 0);
666+
Py_XSETREF(self->unused_data, PyBytes_FromStringAndSize(NULL, 0));
667667
if (self->unused_data == NULL)
668668
goto error;
669669

Modules/_hashopenssl.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,8 @@ EVP_tp_init(EVPobject *self, PyObject *args, PyObject *kwds)
378378
return -1;
379379
}
380380

381-
self->name = name_obj;
382-
Py_INCREF(self->name);
381+
Py_INCREF(name_obj);
382+
Py_XSETREF(self->name, name_obj);
383383

384384
if (data_obj) {
385385
if (view.len >= HASHLIB_GIL_MINSIZE) {

0 commit comments

Comments
 (0)