Skip to content

Commit f3cffd2

Browse files
Dino Viehland1st1
authored andcommitted
bpo-30604: clean up co_extra support (#2144)
bpo-30604: port fix from 3.6 dropping binary compatibility tweaks
1 parent c90e960 commit f3cffd2

File tree

6 files changed

+130
-23
lines changed

6 files changed

+130
-23
lines changed

Include/pystate.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ typedef struct _is {
7474
PyObject *import_func;
7575
/* Initialized to PyEval_EvalFrameDefault(). */
7676
_PyFrameEvalFunction eval_frame;
77+
78+
Py_ssize_t co_extra_user_count;
79+
freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
80+
7781
#ifdef HAVE_FORK
7882
PyObject *before_forkers;
7983
PyObject *after_forkers_parent;
@@ -173,9 +177,6 @@ typedef struct _ts {
173177
PyObject *coroutine_wrapper;
174178
int in_coroutine_wrapper;
175179

176-
Py_ssize_t co_extra_user_count;
177-
freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
178-
179180
PyObject *async_gen_firstiter;
180181
PyObject *async_gen_finalizer;
181182

Lib/test/test_code.py

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,11 @@
103103
"""
104104

105105
import sys
106+
import threading
106107
import unittest
107108
import weakref
108-
from test.support import run_doctest, run_unittest, cpython_only
109+
from test.support import (run_doctest, run_unittest, cpython_only,
110+
check_impl_detail)
109111

110112

111113
def consts(t):
@@ -212,11 +214,106 @@ def callback(code):
212214
self.assertTrue(self.called)
213215

214216

217+
if check_impl_detail(cpython=True):
218+
import ctypes
219+
py = ctypes.pythonapi
220+
freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp)
221+
222+
RequestCodeExtraIndex = py._PyEval_RequestCodeExtraIndex
223+
RequestCodeExtraIndex.argtypes = (freefunc,)
224+
RequestCodeExtraIndex.restype = ctypes.c_ssize_t
225+
226+
SetExtra = py._PyCode_SetExtra
227+
SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp)
228+
SetExtra.restype = ctypes.c_int
229+
230+
GetExtra = py._PyCode_GetExtra
231+
GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t,
232+
ctypes.POINTER(ctypes.c_voidp))
233+
GetExtra.restype = ctypes.c_int
234+
235+
LAST_FREED = None
236+
def myfree(ptr):
237+
global LAST_FREED
238+
LAST_FREED = ptr
239+
240+
FREE_FUNC = freefunc(myfree)
241+
FREE_INDEX = RequestCodeExtraIndex(FREE_FUNC)
242+
243+
class CoExtra(unittest.TestCase):
244+
def get_func(self):
245+
# Defining a function causes the containing function to have a
246+
# reference to the code object. We need the code objects to go
247+
# away, so we eval a lambda.
248+
return eval('lambda:42')
249+
250+
def test_get_non_code(self):
251+
f = self.get_func()
252+
253+
self.assertRaises(SystemError, SetExtra, 42, FREE_INDEX,
254+
ctypes.c_voidp(100))
255+
self.assertRaises(SystemError, GetExtra, 42, FREE_INDEX,
256+
ctypes.c_voidp(100))
257+
258+
def test_bad_index(self):
259+
f = self.get_func()
260+
self.assertRaises(SystemError, SetExtra, f.__code__,
261+
FREE_INDEX+100, ctypes.c_voidp(100))
262+
self.assertEqual(GetExtra(f.__code__, FREE_INDEX+100,
263+
ctypes.c_voidp(100)), 0)
264+
265+
def test_free_called(self):
266+
# Verify that the provided free function gets invoked
267+
# when the code object is cleaned up.
268+
f = self.get_func()
269+
270+
SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(100))
271+
del f
272+
self.assertEqual(LAST_FREED, 100)
273+
274+
def test_get_set(self):
275+
# Test basic get/set round tripping.
276+
f = self.get_func()
277+
278+
extra = ctypes.c_voidp()
279+
280+
SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(200))
281+
# reset should free...
282+
SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(300))
283+
self.assertEqual(LAST_FREED, 200)
284+
285+
extra = ctypes.c_voidp()
286+
GetExtra(f.__code__, FREE_INDEX, extra)
287+
self.assertEqual(extra.value, 300)
288+
del f
289+
290+
def test_free_different_thread(self):
291+
# Freeing a code object on a different thread then
292+
# where the co_extra was set should be safe.
293+
f = self.get_func()
294+
class ThreadTest(threading.Thread):
295+
def __init__(self, f, test):
296+
super().__init__()
297+
self.f = f
298+
self.test = test
299+
def run(self):
300+
del self.f
301+
self.test.assertEqual(LAST_FREED, 500)
302+
303+
SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500))
304+
tt = ThreadTest(f, self)
305+
del f
306+
tt.start()
307+
tt.join()
308+
self.assertEqual(LAST_FREED, 500)
309+
215310
def test_main(verbose=None):
216311
from test import test_code
217312
run_doctest(test_code, verbose)
218-
run_unittest(CodeTest, CodeConstsTest, CodeWeakRefTest)
219-
313+
tests = [CodeTest, CodeConstsTest, CodeWeakRefTest]
314+
if check_impl_detail(cpython=True):
315+
tests.append(CoExtra)
316+
run_unittest(*tests)
220317

221318
if __name__ == "__main__":
222319
test_main()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.7.0 alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- bpo-30604: Move co_extra_freefuncs from per-thread to per-interpreter to
14+
avoid crashes.
15+
1316
- bpo-30597: ``print`` now shows expected input in custom error message when
1417
used as a Python 2 statement. Patch by Sanyam Khurana.
1518

Objects/codeobject.c

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -416,11 +416,11 @@ static void
416416
code_dealloc(PyCodeObject *co)
417417
{
418418
if (co->co_extra != NULL) {
419-
PyThreadState *tstate = PyThreadState_Get();
419+
PyInterpreterState *interp = PyThreadState_Get()->interp;
420420
_PyCodeObjectExtra *co_extra = co->co_extra;
421421

422422
for (Py_ssize_t i = 0; i < co_extra->ce_size; i++) {
423-
freefunc free_extra = tstate->co_extra_freefuncs[i];
423+
freefunc free_extra = interp->co_extra_freefuncs[i];
424424

425425
if (free_extra != NULL) {
426426
free_extra(co_extra->ce_extras[i]);
@@ -830,8 +830,6 @@ _PyCode_CheckLineNumber(PyCodeObject* co, int lasti, PyAddrPair *bounds)
830830
int
831831
_PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
832832
{
833-
assert(*extra == NULL);
834-
835833
if (!PyCode_Check(code)) {
836834
PyErr_BadInternalCall();
837835
return -1;
@@ -840,8 +838,8 @@ _PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
840838
PyCodeObject *o = (PyCodeObject*) code;
841839
_PyCodeObjectExtra *co_extra = (_PyCodeObjectExtra*) o->co_extra;
842840

843-
844841
if (co_extra == NULL || co_extra->ce_size <= index) {
842+
*extra = NULL;
845843
return 0;
846844
}
847845

@@ -853,10 +851,10 @@ _PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra)
853851
int
854852
_PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
855853
{
856-
PyThreadState *tstate = PyThreadState_Get();
854+
PyInterpreterState *interp = PyThreadState_Get()->interp;
857855

858856
if (!PyCode_Check(code) || index < 0 ||
859-
index >= tstate->co_extra_user_count) {
857+
index >= interp->co_extra_user_count) {
860858
PyErr_BadInternalCall();
861859
return -1;
862860
}
@@ -871,13 +869,13 @@ _PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
871869
}
872870

873871
co_extra->ce_extras = PyMem_Malloc(
874-
tstate->co_extra_user_count * sizeof(void*));
872+
interp->co_extra_user_count * sizeof(void*));
875873
if (co_extra->ce_extras == NULL) {
876874
PyMem_Free(co_extra);
877875
return -1;
878876
}
879877

880-
co_extra->ce_size = tstate->co_extra_user_count;
878+
co_extra->ce_size = interp->co_extra_user_count;
881879

882880
for (Py_ssize_t i = 0; i < co_extra->ce_size; i++) {
883881
co_extra->ce_extras[i] = NULL;
@@ -887,20 +885,28 @@ _PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra)
887885
}
888886
else if (co_extra->ce_size <= index) {
889887
void** ce_extras = PyMem_Realloc(
890-
co_extra->ce_extras, tstate->co_extra_user_count * sizeof(void*));
888+
co_extra->ce_extras, interp->co_extra_user_count * sizeof(void*));
891889

892890
if (ce_extras == NULL) {
893891
return -1;
894892
}
895893

896894
for (Py_ssize_t i = co_extra->ce_size;
897-
i < tstate->co_extra_user_count;
895+
i < interp->co_extra_user_count;
898896
i++) {
899897
ce_extras[i] = NULL;
900898
}
901899

902900
co_extra->ce_extras = ce_extras;
903-
co_extra->ce_size = tstate->co_extra_user_count;
901+
co_extra->ce_size = interp->co_extra_user_count;
902+
}
903+
904+
if (co_extra->ce_extras[index] != NULL) {
905+
freefunc free = interp->co_extra_freefuncs[index];
906+
907+
if (free != NULL) {
908+
free(co_extra->ce_extras[index]);
909+
}
904910
}
905911

906912
co_extra->ce_extras[index] = extra;

Python/ceval.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5287,14 +5287,14 @@ _Py_GetDXProfile(PyObject *self, PyObject *args)
52875287
Py_ssize_t
52885288
_PyEval_RequestCodeExtraIndex(freefunc free)
52895289
{
5290-
PyThreadState *tstate = PyThreadState_Get();
5290+
PyInterpreterState *interp = PyThreadState_Get()->interp;
52915291
Py_ssize_t new_index;
52925292

5293-
if (tstate->co_extra_user_count == MAX_CO_EXTRA_USERS - 1) {
5293+
if (interp->co_extra_user_count == MAX_CO_EXTRA_USERS - 1) {
52945294
return -1;
52955295
}
5296-
new_index = tstate->co_extra_user_count++;
5297-
tstate->co_extra_freefuncs[new_index] = free;
5296+
new_index = interp->co_extra_user_count++;
5297+
interp->co_extra_freefuncs[new_index] = free;
52985298
return new_index;
52995299
}
53005300

Python/pystate.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ PyInterpreterState_New(void)
111111
interp->importlib = NULL;
112112
interp->import_func = NULL;
113113
interp->eval_frame = _PyEval_EvalFrameDefault;
114+
interp->co_extra_user_count = 0;
114115
#ifdef HAVE_DLOPEN
115116
#if HAVE_DECL_RTLD_NOW
116117
interp->dlopenflags = RTLD_NOW;
@@ -281,7 +282,6 @@ new_threadstate(PyInterpreterState *interp, int init)
281282

282283
tstate->coroutine_wrapper = NULL;
283284
tstate->in_coroutine_wrapper = 0;
284-
tstate->co_extra_user_count = 0;
285285

286286
tstate->async_gen_firstiter = NULL;
287287
tstate->async_gen_finalizer = NULL;

0 commit comments

Comments
 (0)