From d2ef0c3afc1a45d1b373b6284390d92ff51ff6f9 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 27 May 2025 07:58:18 +0200 Subject: [PATCH 1/4] gh-134160: Block multiple module initialization --- Doc/extending/extending.rst | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 918c751b009761..c541d721d1277c 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -204,17 +204,32 @@ value must be in a particular range or must satisfy other conditions, :c:data:`PyExc_ValueError` is appropriate. You can also define a new exception that is unique to your module. -For this, you can declare a static global object variable at the beginning -of the file:: +The simplest way to do this is to declare a static global object variable at +the beginning of the file:: - static PyObject *SpamError; + static PyObject *SpamError = NULL; -and initialize it with an exception object in the module's +and initialize it by calling :c:func:`PyErr_NewException` in the module's :c:data:`Py_mod_exec` function (:c:func:`!spam_module_exec`):: + SpamError = PyErr_NewException("spam.error", NULL, NULL); + +Since :c:data:`!SpamError` is a global variable, each call to the +:c:data:`Py_mod_exec` function would overwrite it. +It is not very common to load multiple copies of a module, so for now, +we will block repeated initialization by raising an +:py:exc:`ImportError`:: + + static PyObject *SpamError = NULL; + static int spam_module_exec(PyObject *m) { + if (SpamError != NULL) { + PyErr_SetString(PyExc_ImportError, + "cannot initialize spam module more than once"); + return -1; + } SpamError = PyErr_NewException("spam.error", NULL, NULL); if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) { return -1; @@ -253,6 +268,11 @@ needed to ensure that it will not be discarded, causing :c:data:`!SpamError` to become a dangling pointer. Should it become a dangling pointer, C code which raises the exception could cause a core dump or other unintended side effects. +Also note that there is no :c:func:`Py_DECREF` call to remove this reference. +Even when the Python interpreter shuts down, :c:data:`!SpamError` will not be +garbage-collected. It will "leak". +We did, however, ensure that this will happen at most once per process. + We discuss the use of :c:macro:`PyMODINIT_FUNC` as a function return type later in this sample. From c53b208e16c8c221ad3247c63cc218cb58d64191 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 27 May 2025 17:38:02 +0200 Subject: [PATCH 2/4] Update Doc/extending/extending.rst --- Doc/extending/extending.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index c541d721d1277c..7be010977ddc56 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -268,7 +268,7 @@ needed to ensure that it will not be discarded, causing :c:data:`!SpamError` to become a dangling pointer. Should it become a dangling pointer, C code which raises the exception could cause a core dump or other unintended side effects. -Also note that there is no :c:func:`Py_DECREF` call to remove this reference. +For now, the :c:func:`Py_DECREF` call to remove this reference is missing. Even when the Python interpreter shuts down, :c:data:`!SpamError` will not be garbage-collected. It will "leak". We did, however, ensure that this will happen at most once per process. From e961facf469d8c4fffa5c090f7e112904f4184b1 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Tue, 27 May 2025 22:17:00 +0100 Subject: [PATCH 3/4] Update Doc/extending/extending.rst Co-authored-by: Petr Viktorin --- Doc/extending/extending.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 7be010977ddc56..1f02bae9f249ad 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -216,7 +216,7 @@ and initialize it by calling :c:func:`PyErr_NewException` in the module's Since :c:data:`!SpamError` is a global variable, each call to the :c:data:`Py_mod_exec` function would overwrite it. -It is not very common to load multiple copies of a module, so for now, +For now, let's avoid the issue: we will block repeated initialization by raising an :py:exc:`ImportError`:: From c562e2e8fc2945fd51d9147878906f9b908e8658 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 28 May 2025 09:36:58 +0200 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/extending/extending.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 1f02bae9f249ad..fd63495674651b 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -214,10 +214,10 @@ and initialize it by calling :c:func:`PyErr_NewException` in the module's SpamError = PyErr_NewException("spam.error", NULL, NULL); -Since :c:data:`!SpamError` is a global variable, each call to the -:c:data:`Py_mod_exec` function would overwrite it. -For now, let's avoid the issue: -we will block repeated initialization by raising an +Since :c:data:`!SpamError` is a global variable, it will be overwitten every time +the module is reinitialized, when the :c:data:`Py_mod_exec` function is called. + +For now, let's avoid the issue: we will block repeated initialization by raising an :py:exc:`ImportError`:: static PyObject *SpamError = NULL; @@ -269,8 +269,8 @@ become a dangling pointer. Should it become a dangling pointer, C code which raises the exception could cause a core dump or other unintended side effects. For now, the :c:func:`Py_DECREF` call to remove this reference is missing. -Even when the Python interpreter shuts down, :c:data:`!SpamError` will not be -garbage-collected. It will "leak". +Even when the Python interpreter shuts down, the global :c:data:`!SpamError` +variable will not be garbage-collected. It will "leak". We did, however, ensure that this will happen at most once per process. We discuss the use of :c:macro:`PyMODINIT_FUNC` as a function return type later in this