Skip to content

Commit 5b1ef20

Browse files
vstinnerencukou
andauthored
bpo-39824: module_traverse() don't call m_traverse if md_state=NULL (GH-18738)
Extension modules: m_traverse, m_clear and m_free functions of PyModuleDef are no longer called if the module state was requested but is not allocated yet. This is the case immediately after the module is created and before the module is executed (Py_mod_exec function). More precisely, these functions are not called if m_size is greater than 0 and the module state (as returned by PyModule_GetState()) is NULL. Extension modules without module state (m_size <= 0) are not affected. Co-Authored-By: Petr Viktorin <[email protected]>
1 parent 5226894 commit 5b1ef20

File tree

10 files changed

+91
-144
lines changed

10 files changed

+91
-144
lines changed

Doc/c-api/module.rst

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,23 +196,47 @@ or request "multi-phase initialization" by returning the definition struct itsel
196196
.. c:member:: traverseproc m_traverse
197197
198198
A traversal function to call during GC traversal of the module object, or
199-
``NULL`` if not needed. This function may be called before module state
200-
is allocated (:c:func:`PyModule_GetState()` may return `NULL`),
201-
and before the :c:member:`Py_mod_exec` function is executed.
199+
``NULL`` if not needed.
200+
201+
This function is not called if the module state was requested but is not
202+
allocated yet. This is the case immediately after the module is created
203+
and before the module is executed (:c:data:`Py_mod_exec` function). More
204+
precisely, this function is not called if :c:member:`m_size` is greater
205+
than 0 and the module state (as returned by :c:func:`PyModule_GetState`)
206+
is ``NULL``.
207+
208+
.. versionchanged:: 3.9
209+
No longer called before the module state is allocated.
202210
203211
.. c:member:: inquiry m_clear
204212
205213
A clear function to call during GC clearing of the module object, or
206-
``NULL`` if not needed. This function may be called before module state
207-
is allocated (:c:func:`PyModule_GetState()` may return `NULL`),
208-
and before the :c:member:`Py_mod_exec` function is executed.
214+
``NULL`` if not needed.
215+
216+
This function is not called if the module state was requested but is not
217+
allocated yet. This is the case immediately after the module is created
218+
and before the module is executed (:c:data:`Py_mod_exec` function). More
219+
precisely, this function is not called if :c:member:`m_size` is greater
220+
than 0 and the module state (as returned by :c:func:`PyModule_GetState`)
221+
is ``NULL``.
222+
223+
.. versionchanged:: 3.9
224+
No longer called before the module state is allocated.
209225
210226
.. c:member:: freefunc m_free
211227
212-
A function to call during deallocation of the module object, or ``NULL`` if
213-
not needed. This function may be called before module state
214-
is allocated (:c:func:`PyModule_GetState()` may return `NULL`),
215-
and before the :c:member:`Py_mod_exec` function is executed.
228+
A function to call during deallocation of the module object, or ``NULL``
229+
if not needed.
230+
231+
This function is not called if the module state was requested but is not
232+
allocated yet. This is the case immediately after the module is created
233+
and before the module is executed (:c:data:`Py_mod_exec` function). More
234+
precisely, this function is not called if :c:member:`m_size` is greater
235+
than 0 and the module state (as returned by :c:func:`PyModule_GetState`)
236+
is ``NULL``.
237+
238+
.. versionchanged:: 3.9
239+
No longer called before the module state is allocated.
216240
217241
Single-phase initialization
218242
...........................

Doc/whatsnew/3.9.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,17 @@ Build and C API Changes
503503
*tstate* parameter (``PyThreadState*``).
504504
(Contributed by Victor Stinner in :issue:`38500`.)
505505

506+
* Extension modules: :c:member:`~PyModuleDef.m_traverse`,
507+
:c:member:`~PyModuleDef.m_clear` and :c:member:`~PyModuleDef.m_free`
508+
functions of :c:type:`PyModuleDef` are no longer called if the module state
509+
was requested but is not allocated yet. This is the case immediately after
510+
the module is created and before the module is executed
511+
(:c:data:`Py_mod_exec` function). More precisely, these functions are not called
512+
if :c:member:`~PyModuleDef.m_size` is greater than 0 and the module state (as
513+
returned by :c:func:`PyModule_GetState`) is ``NULL``.
514+
515+
Extension modules without module state (``m_size <= 0``) are not affected.
516+
506517

507518
Deprecated
508519
==========

Lib/test/test_importlib/extension/test_loader.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -267,29 +267,6 @@ def test_nonascii(self):
267267
self.assertEqual(module.__name__, name)
268268
self.assertEqual(module.__doc__, "Module named in %s" % lang)
269269

270-
@unittest.skipIf(not hasattr(sys, 'gettotalrefcount'),
271-
'--with-pydebug has to be enabled for this test')
272-
def test_bad_traverse(self):
273-
''' Issue #32374: Test that traverse fails when accessing per-module
274-
state before Py_mod_exec was executed.
275-
(Multiphase initialization modules only)
276-
'''
277-
script = """if True:
278-
try:
279-
from test import support
280-
import importlib.util as util
281-
spec = util.find_spec('_testmultiphase')
282-
spec.name = '_testmultiphase_with_bad_traverse'
283-
284-
with support.SuppressCrashReport():
285-
m = spec.loader.create_module(spec)
286-
except:
287-
# Prevent Python-level exceptions from
288-
# ending the process with non-zero status
289-
# (We are testing for a crash in C-code)
290-
pass"""
291-
assert_python_failure("-c", script)
292-
293270

294271
(Frozen_MultiPhaseExtensionModuleTests,
295272
Source_MultiPhaseExtensionModuleTests
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Extension modules: :c:member:`~PyModuleDef.m_traverse`,
2+
:c:member:`~PyModuleDef.m_clear` and :c:member:`~PyModuleDef.m_free` functions
3+
of :c:type:`PyModuleDef` are no longer called if the module state was requested
4+
but is not allocated yet. This is the case immediately after the module is
5+
created and before the module is executed (:c:data:`Py_mod_exec` function). More
6+
precisely, these functions are not called if :c:member:`~PyModuleDef.m_size` is
7+
greater than 0 and the module state (as returned by
8+
:c:func:`PyModule_GetState`) is ``NULL``.
9+
10+
Extension modules without module state (``m_size <= 0``) are not affected.

Modules/_localemodule.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -778,19 +778,15 @@ static int
778778
locale_traverse(PyObject *m, visitproc visit, void *arg)
779779
{
780780
_locale_state *state = (_locale_state*)PyModule_GetState(m);
781-
if (state) {
782-
Py_VISIT(state->Error);
783-
}
781+
Py_VISIT(state->Error);
784782
return 0;
785783
}
786784

787785
static int
788786
locale_clear(PyObject *m)
789787
{
790788
_locale_state *state = (_locale_state*)PyModule_GetState(m);
791-
if (state) {
792-
Py_CLEAR(state->Error);
793-
}
789+
Py_CLEAR(state->Error);
794790
return 0;
795791
}
796792

Modules/_testmultiphase.c

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -225,20 +225,18 @@ static int execfunc(PyObject *m)
225225

226226
/* Helper for module definitions; there'll be a lot of them */
227227

228-
#define TEST_MODULE_DEF_EX(name, slots, methods, statesize, traversefunc) { \
228+
#define TEST_MODULE_DEF(name, slots, methods) { \
229229
PyModuleDef_HEAD_INIT, /* m_base */ \
230230
name, /* m_name */ \
231231
PyDoc_STR("Test module " name), /* m_doc */ \
232-
statesize, /* m_size */ \
232+
0, /* m_size */ \
233233
methods, /* m_methods */ \
234234
slots, /* m_slots */ \
235-
traversefunc, /* m_traverse */ \
235+
NULL, /* m_traverse */ \
236236
NULL, /* m_clear */ \
237237
NULL, /* m_free */ \
238238
}
239239

240-
#define TEST_MODULE_DEF(name, slots, methods) TEST_MODULE_DEF_EX(name, slots, methods, 0, NULL)
241-
242240
static PyModuleDef_Slot main_slots[] = {
243241
{Py_mod_exec, execfunc},
244242
{0, NULL},
@@ -622,52 +620,6 @@ PyInit__testmultiphase_exec_unreported_exception(PyObject *spec)
622620
return PyModuleDef_Init(&def_exec_unreported_exception);
623621
}
624622

625-
static int
626-
bad_traverse(PyObject *self, visitproc visit, void *arg) {
627-
testmultiphase_state *m_state;
628-
629-
m_state = PyModule_GetState(self);
630-
631-
/* The following assertion mimics any traversal function that doesn't correctly handle
632-
* the case during module creation where the module state hasn't been created yet.
633-
*
634-
* The check that it is used to test only runs in debug mode, so it is OK that the
635-
* assert() will get compiled out in fully optimised release builds.
636-
*/
637-
assert(m_state != NULL);
638-
Py_VISIT(m_state->integer);
639-
return 0;
640-
}
641-
642-
static int
643-
execfunc_with_bad_traverse(PyObject *mod) {
644-
testmultiphase_state *m_state;
645-
646-
m_state = PyModule_GetState(mod);
647-
if (m_state == NULL) {
648-
return -1;
649-
}
650-
651-
m_state->integer = PyLong_FromLong(0x7fffffff);
652-
Py_INCREF(m_state->integer);
653-
654-
return 0;
655-
}
656-
657-
static PyModuleDef_Slot slots_with_bad_traverse[] = {
658-
{Py_mod_exec, execfunc_with_bad_traverse},
659-
{0, NULL}
660-
};
661-
662-
static PyModuleDef def_with_bad_traverse = TEST_MODULE_DEF_EX(
663-
"_testmultiphase_with_bad_traverse", slots_with_bad_traverse, NULL,
664-
sizeof(testmultiphase_state), bad_traverse);
665-
666-
PyMODINIT_FUNC
667-
PyInit__testmultiphase_with_bad_traverse(PyObject *spec) {
668-
return PyModuleDef_Init(&def_with_bad_traverse);
669-
}
670-
671623
/*** Helper for imp test ***/
672624

673625
static PyModuleDef imp_dummy_def = TEST_MODULE_DEF("imp_dummy", main_slots, testexport_methods);

Modules/atexitmodule.c

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -229,15 +229,14 @@ atexit_m_traverse(PyObject *self, visitproc visit, void *arg)
229229
atexitmodule_state *modstate;
230230

231231
modstate = (atexitmodule_state *)PyModule_GetState(self);
232-
if (modstate != NULL) {
233-
for (i = 0; i < modstate->ncallbacks; i++) {
234-
atexit_callback *cb = modstate->atexit_callbacks[i];
235-
if (cb == NULL)
236-
continue;
237-
Py_VISIT(cb->func);
238-
Py_VISIT(cb->args);
239-
Py_VISIT(cb->kwargs);
240-
}
232+
233+
for (i = 0; i < modstate->ncallbacks; i++) {
234+
atexit_callback *cb = modstate->atexit_callbacks[i];
235+
if (cb == NULL)
236+
continue;
237+
Py_VISIT(cb->func);
238+
Py_VISIT(cb->args);
239+
Py_VISIT(cb->kwargs);
241240
}
242241
return 0;
243242
}
@@ -247,9 +246,7 @@ atexit_m_clear(PyObject *self)
247246
{
248247
atexitmodule_state *modstate;
249248
modstate = (atexitmodule_state *)PyModule_GetState(self);
250-
if (modstate != NULL) {
251-
atexit_cleanup(modstate);
252-
}
249+
atexit_cleanup(modstate);
253250
return 0;
254251
}
255252

@@ -258,10 +255,8 @@ atexit_free(PyObject *m)
258255
{
259256
atexitmodule_state *modstate;
260257
modstate = (atexitmodule_state *)PyModule_GetState(m);
261-
if (modstate != NULL) {
262-
atexit_cleanup(modstate);
263-
PyMem_Free(modstate->atexit_callbacks);
264-
}
258+
atexit_cleanup(modstate);
259+
PyMem_Free(modstate->atexit_callbacks);
265260
}
266261

267262
PyDoc_STRVAR(atexit_unregister__doc__,

Modules/audioop.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,19 +1926,15 @@ static int
19261926
audioop_traverse(PyObject *module, visitproc visit, void *arg)
19271927
{
19281928
audioop_state *state = (audioop_state *)PyModule_GetState(module);
1929-
if (state) {
1930-
Py_VISIT(state->AudioopError);
1931-
}
1929+
Py_VISIT(state->AudioopError);
19321930
return 0;
19331931
}
19341932

19351933
static int
19361934
audioop_clear(PyObject *module)
19371935
{
19381936
audioop_state *state = (audioop_state *)PyModule_GetState(module);
1939-
if (state) {
1940-
Py_CLEAR(state->AudioopError);
1941-
}
1937+
Py_CLEAR(state->AudioopError);
19421938
return 0;
19431939
}
19441940

Modules/binascii.c

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,21 +1653,17 @@ static int
16531653
binascii_traverse(PyObject *module, visitproc visit, void *arg)
16541654
{
16551655
binascii_state *state = get_binascii_state(module);
1656-
if (state) {
1657-
Py_VISIT(state->Error);
1658-
Py_VISIT(state->Incomplete);
1659-
}
1656+
Py_VISIT(state->Error);
1657+
Py_VISIT(state->Incomplete);
16601658
return 0;
16611659
}
16621660

16631661
static int
16641662
binascii_clear(PyObject *module)
16651663
{
16661664
binascii_state *state = get_binascii_state(module);
1667-
if (state) {
1668-
Py_CLEAR(state->Error);
1669-
Py_CLEAR(state->Incomplete);
1670-
}
1665+
Py_CLEAR(state->Error);
1666+
Py_CLEAR(state->Incomplete);
16711667
return 0;
16721668
}
16731669

@@ -1686,7 +1682,7 @@ static struct PyModuleDef binasciimodule = {
16861682
binascii_slots,
16871683
binascii_traverse,
16881684
binascii_clear,
1689-
binascii_free
1685+
binascii_free
16901686
};
16911687

16921688
PyMODINIT_FUNC

Objects/moduleobject.c

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,6 @@ static PyMemberDef module_members[] = {
2626
};
2727

2828

29-
/* Helper for sanity check for traverse not handling m_state == NULL
30-
* Issue #32374 */
31-
#ifdef Py_DEBUG
32-
static int
33-
bad_traverse_test(PyObject *self, void *arg) {
34-
assert(self != NULL);
35-
return 0;
36-
}
37-
#endif
38-
3929
PyTypeObject PyModuleDef_Type = {
4030
PyVarObject_HEAD_INIT(&PyType_Type, 0)
4131
"moduledef", /* tp_name */
@@ -360,16 +350,6 @@ PyModule_FromDefAndSpec2(struct PyModuleDef* def, PyObject *spec, int module_api
360350
}
361351
}
362352

363-
/* Sanity check for traverse not handling m_state == NULL
364-
* This doesn't catch all possible cases, but in many cases it should
365-
* make many cases of invalid code crash or raise Valgrind issues
366-
* sooner than they would otherwise.
367-
* Issue #32374 */
368-
#ifdef Py_DEBUG
369-
if (def->m_traverse != NULL) {
370-
def->m_traverse(m, bad_traverse_test, NULL);
371-
}
372-
#endif
373353
Py_DECREF(nameobj);
374354
return m;
375355

@@ -687,8 +667,12 @@ module_dealloc(PyModuleObject *m)
687667
}
688668
if (m->md_weaklist != NULL)
689669
PyObject_ClearWeakRefs((PyObject *) m);
690-
if (m->md_def && m->md_def->m_free)
670+
/* bpo-39824: Don't call m_free() if m_size > 0 and md_state=NULL */
671+
if (m->md_def && m->md_def->m_free
672+
&& (m->md_def->m_size <= 0 || m->md_state != NULL))
673+
{
691674
m->md_def->m_free(m);
675+
}
692676
Py_XDECREF(m->md_dict);
693677
Py_XDECREF(m->md_name);
694678
if (m->md_state != NULL)
@@ -770,7 +754,10 @@ module_getattro(PyModuleObject *m, PyObject *name)
770754
static int
771755
module_traverse(PyModuleObject *m, visitproc visit, void *arg)
772756
{
773-
if (m->md_def && m->md_def->m_traverse) {
757+
/* bpo-39824: Don't call m_traverse() if m_size > 0 and md_state=NULL */
758+
if (m->md_def && m->md_def->m_traverse
759+
&& (m->md_def->m_size <= 0 || m->md_state != NULL))
760+
{
774761
int res = m->md_def->m_traverse((PyObject*)m, visit, arg);
775762
if (res)
776763
return res;
@@ -782,7 +769,10 @@ module_traverse(PyModuleObject *m, visitproc visit, void *arg)
782769
static int
783770
module_clear(PyModuleObject *m)
784771
{
785-
if (m->md_def && m->md_def->m_clear) {
772+
/* bpo-39824: Don't call m_clear() if m_size > 0 and md_state=NULL */
773+
if (m->md_def && m->md_def->m_clear
774+
&& (m->md_def->m_size <= 0 || m->md_state != NULL))
775+
{
786776
int res = m->md_def->m_clear((PyObject*)m);
787777
if (PyErr_Occurred()) {
788778
PySys_FormatStderr("Exception ignored in m_clear of module%s%V\n",

0 commit comments

Comments
 (0)