Skip to content

Commit 83d6bbb

Browse files
committed
Fixed weakref leak in calling functions with kwargs by nulling out after call (in compiler). Fixes #1586. Bumped bytecode magic
1 parent e4dc602 commit 83d6bbb

File tree

4 files changed

+45
-6
lines changed

4 files changed

+45
-6
lines changed

Lib/test/test_weakref_jy.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import unittest
66
import weakref
77
from test import test_support
8+
from test_weakref import extra_collect
89

910
class ReferencesTestCase(unittest.TestCase):
1011

@@ -38,8 +39,45 @@ def __hash__(self):
3839
self.assertEqual(len(hash_called), 2)
3940

4041

42+
class ArgsTestCase(unittest.TestCase):
43+
44+
# XXX consider adding other tests for dict, list, etc
45+
46+
def test_python_fn_kwargs(self):
47+
48+
weakrefs = []
49+
sentinel = []
50+
51+
def watch(obj, kwarg=True):
52+
self.assertEqual(kwarg, True)
53+
# log the death of the reference by appending to the sentinel
54+
ref = weakref.ref(obj, sentinel.append)
55+
weakrefs.append(ref)
56+
57+
self.assert_(not sentinel)
58+
59+
thunk1 = lambda: None
60+
watch(thunk1)
61+
self.assert_(not sentinel)
62+
63+
del thunk1
64+
extra_collect()
65+
self.assert_(sentinel)
66+
67+
del sentinel[:]
68+
69+
thunk2 = lambda: None
70+
watch(thunk2, kwarg=True) # <--- only difference: called with a kwarg
71+
self.assert_(not sentinel)
72+
73+
del thunk2
74+
extra_collect()
75+
self.assert_(sentinel)
76+
77+
78+
4179
def test_main():
42-
test_support.run_unittest(ReferencesTestCase)
80+
test_support.run_unittest(ReferencesTestCase, ArgsTestCase)
4381

4482

4583
if __name__ == '__main__':

src/org/python/compiler/CodeCompiler.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,27 +1804,26 @@ public Object visitCall(Call node) throws Exception {
18041804

18051805
code.aload(argArray);
18061806
code.aload(strArray);
1807-
code.freeLocal(argArray);
18081807
code.freeLocal(strArray);
18091808
code.dup2_x2();
18101809
code.pop2();
18111810

18121811
stackConsume(3); // target + starargs + kwargs
1813-
18141812
code.invokevirtual(p(PyObject.class), "_callextra", sig(PyObject.class,
18151813
PyObject[].class, String[].class, PyObject.class, PyObject.class));
1814+
freeArray(argArray);
18161815
} else if (keys.size() > 0) {
18171816
loadThreadState();
18181817
stackProduce(p(ThreadState.class));
18191818
int argArray = makeArray(values);
18201819
int strArray = makeStrings(code, keys);
18211820
code.aload(argArray);
18221821
code.aload(strArray);
1823-
code.freeLocal(argArray);
18241822
code.freeLocal(strArray);
18251823
stackConsume(2); // target + ts
18261824
code.invokevirtual(p(PyObject.class), "__call__", sig(PyObject.class, ThreadState.class,
18271825
PyObject[].class, String[].class));
1826+
freeArray(argArray);
18281827
} else {
18291828
loadThreadState();
18301829
stackProduce(p(ThreadState.class));

src/org/python/core/imp.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class imp {
2121

2222
private static final String UNKNOWN_SOURCEFILE = "<unknown>";
2323

24-
private static final int APIVersion = 28;
24+
private static final int APIVersion = 29;
2525

2626
public static final int NO_MTIME = -1;
2727

src/org/python/modules/thread/PyLocal.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ final void _local___init__(PyObject[] args, String[] keywords) {
5959
if (where[0] == TYPE && args.length > 0) {
6060
throw Py.TypeError("Initialization arguments are not supported");
6161
}
62-
this.args = args;
62+
// caller zeros out `args`
63+
this.args = new PyObject[args.length];
64+
System.arraycopy(args, 0, this.args, 0, args.length);
6365
this.keywords = keywords;
6466
}
6567

0 commit comments

Comments
 (0)