]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-112292 : Catch import error conditions with readline hooks (gh-112313)
authorAnthony Shaw <anthony.p.shaw@gmail.com>
Tue, 28 Nov 2023 01:58:53 +0000 (12:58 +1100)
committerGitHub <noreply@github.com>
Tue, 28 Nov 2023 01:58:53 +0000 (18:58 -0700)
Prevents a segmentation fault in registered hooks for the readline library, but only when the readline module is loaded inside an isolated sub interpreter.  The module is single-phase init so loading it fails, but not until the module init function has already run, where the readline hooks get registered.

The readlinestate_global macro was error-prone to PyImport_FindModule returning NULL and crashing in about 18 places.  I could reproduce 1 easily, but this PR replaces the macro with a function and adds error conditions to the other functions.

Misc/NEWS.d/next/Library/2023-11-22-19-43-54.gh-issue-112292.5nDU87.rst [new file with mode: 0644]
Modules/readline.c

diff --git a/Misc/NEWS.d/next/Library/2023-11-22-19-43-54.gh-issue-112292.5nDU87.rst b/Misc/NEWS.d/next/Library/2023-11-22-19-43-54.gh-issue-112292.5nDU87.rst
new file mode 100644 (file)
index 0000000..8345e33
--- /dev/null
@@ -0,0 +1,2 @@
+Fix a crash in :mod:`readline` when imported from a sub interpreter. Patch
+by Anthony Shaw
index fde552d124bc777cfdc6a1ac973373680ad32c7d..209ac8bbcfbe7841f89b329a77d094486e15209f 100644 (file)
@@ -147,8 +147,19 @@ readline_free(void *m)
 
 static PyModuleDef readlinemodule;
 
-#define readlinestate_global ((readlinestate *)PyModule_GetState(PyState_FindModule(&readlinemodule)))
-
+static inline readlinestate*
+get_hook_module_state(void)
+{
+    PyObject *mod = PyState_FindModule(&readlinemodule);
+    if (mod == NULL){
+        PyErr_Clear();
+        return NULL;
+    }
+    Py_INCREF(mod);
+    readlinestate *state = get_readline_state(mod);
+    Py_DECREF(mod);
+    return state;
+}
 
 /* Convert to/from multibyte C strings */
 
@@ -438,14 +449,15 @@ readline_set_completion_display_matches_hook_impl(PyObject *module,
                                                   PyObject *function)
 /*[clinic end generated code: output=516e5cb8db75a328 input=4f0bfd5ab0179a26]*/
 {
+    readlinestate *state = get_readline_state(module);
     PyObject *result = set_hook("completion_display_matches_hook",
-                    &readlinestate_global->completion_display_matches_hook,
+                    &state->completion_display_matches_hook,
                     function);
 #ifdef HAVE_RL_COMPLETION_DISPLAY_MATCHES_HOOK
     /* We cannot set this hook globally, since it replaces the
        default completion display. */
     rl_completion_display_matches_hook =
-        readlinestate_global->completion_display_matches_hook ?
+        state->completion_display_matches_hook ?
 #if defined(HAVE_RL_COMPDISP_FUNC_T)
         (rl_compdisp_func_t *)on_completion_display_matches_hook : 0;
 #else
@@ -472,7 +484,8 @@ static PyObject *
 readline_set_startup_hook_impl(PyObject *module, PyObject *function)
 /*[clinic end generated code: output=02cd0e0c4fa082ad input=7783b4334b26d16d]*/
 {
-    return set_hook("startup_hook", &readlinestate_global->startup_hook,
+    readlinestate *state = get_readline_state(module);
+    return set_hook("startup_hook", &state->startup_hook,
             function);
 }
 
@@ -497,7 +510,8 @@ static PyObject *
 readline_set_pre_input_hook_impl(PyObject *module, PyObject *function)
 /*[clinic end generated code: output=fe1a96505096f464 input=4f3eaeaf7ce1fdbe]*/
 {
-    return set_hook("pre_input_hook", &readlinestate_global->pre_input_hook,
+    readlinestate *state = get_readline_state(module);
+    return set_hook("pre_input_hook", &state->pre_input_hook,
             function);
 }
 #endif
@@ -530,7 +544,8 @@ static PyObject *
 readline_get_begidx_impl(PyObject *module)
 /*[clinic end generated code: output=362616ee8ed1b2b1 input=e083b81c8eb4bac3]*/
 {
-    return Py_NewRef(readlinestate_global->begidx);
+    readlinestate *state = get_readline_state(module);
+    return Py_NewRef(state->begidx);
 }
 
 /* Get the ending index for the scope of the tab-completion */
@@ -545,7 +560,8 @@ static PyObject *
 readline_get_endidx_impl(PyObject *module)
 /*[clinic end generated code: output=7f763350b12d7517 input=d4c7e34a625fd770]*/
 {
-    return Py_NewRef(readlinestate_global->endidx);
+    readlinestate *state = get_readline_state(module);
+    return Py_NewRef(state->endidx);
 }
 
 /* Set the tab-completion word-delimiters that readline uses */
@@ -772,7 +788,8 @@ static PyObject *
 readline_set_completer_impl(PyObject *module, PyObject *function)
 /*[clinic end generated code: output=171a2a60f81d3204 input=51e81e13118eb877]*/
 {
-    return set_hook("completer", &readlinestate_global->completer, function);
+    readlinestate *state = get_readline_state(module);
+    return set_hook("completer", &state->completer, function);
 }
 
 /*[clinic input]
@@ -785,10 +802,11 @@ static PyObject *
 readline_get_completer_impl(PyObject *module)
 /*[clinic end generated code: output=6e6bbd8226d14475 input=6457522e56d70d13]*/
 {
-    if (readlinestate_global->completer == NULL) {
+    readlinestate *state = get_readline_state(module);
+    if (state->completer == NULL) {
         Py_RETURN_NONE;
     }
-    return Py_NewRef(readlinestate_global->completer);
+    return Py_NewRef(state->completer);
 }
 
 /* Private function to get current length of history.  XXX It may be
@@ -1026,7 +1044,12 @@ on_startup_hook(void)
 {
     int r;
     PyGILState_STATE gilstate = PyGILState_Ensure();
-    r = on_hook(readlinestate_global->startup_hook);
+    readlinestate *state = get_hook_module_state();
+    if (state == NULL) {
+        PyGILState_Release(gilstate);
+        return -1;
+    }
+    r = on_hook(state->startup_hook);
     PyGILState_Release(gilstate);
     return r;
 }
@@ -1043,7 +1066,12 @@ on_pre_input_hook(void)
 {
     int r;
     PyGILState_STATE gilstate = PyGILState_Ensure();
-    r = on_hook(readlinestate_global->pre_input_hook);
+    readlinestate *state = get_hook_module_state();
+    if (state == NULL) {
+        PyGILState_Release(gilstate);
+        return -1;
+    }
+    r = on_hook(state->pre_input_hook);
     PyGILState_Release(gilstate);
     return r;
 }
@@ -1060,6 +1088,11 @@ on_completion_display_matches_hook(char **matches,
     int i;
     PyObject *sub, *m=NULL, *s=NULL, *r=NULL;
     PyGILState_STATE gilstate = PyGILState_Ensure();
+    readlinestate *state = get_hook_module_state();
+    if (state == NULL) {
+        PyGILState_Release(gilstate);
+        return;
+    }
     m = PyList_New(num_matches);
     if (m == NULL)
         goto error;
@@ -1070,7 +1103,7 @@ on_completion_display_matches_hook(char **matches,
         PyList_SET_ITEM(m, i, s);
     }
     sub = decode(matches[0]);
-    r = PyObject_CallFunction(readlinestate_global->completion_display_matches_hook,
+    r = PyObject_CallFunction(state->completion_display_matches_hook,
                               "NNi", sub, m, max_length);
 
     m=NULL;
@@ -1118,12 +1151,17 @@ static char *
 on_completion(const char *text, int state)
 {
     char *result = NULL;
-    if (readlinestate_global->completer != NULL) {
+    PyGILState_STATE gilstate = PyGILState_Ensure();
+    readlinestate *module_state = get_hook_module_state();
+    if (module_state == NULL) {
+        PyGILState_Release(gilstate);
+        return NULL;
+    }
+    if (module_state->completer != NULL) {
         PyObject *r = NULL, *t;
-        PyGILState_STATE gilstate = PyGILState_Ensure();
         rl_attempted_completion_over = 1;
         t = decode(text);
-        r = PyObject_CallFunction(readlinestate_global->completer, "Ni", t, state);
+        r = PyObject_CallFunction(module_state->completer, "Ni", t, state);
         if (r == NULL)
             goto error;
         if (r == Py_None) {
@@ -1145,6 +1183,7 @@ on_completion(const char *text, int state)
         PyGILState_Release(gilstate);
         return result;
     }
+    PyGILState_Release(gilstate);
     return result;
 }
 
@@ -1160,6 +1199,7 @@ flex_complete(const char *text, int start, int end)
     size_t start_size, end_size;
     wchar_t *s;
     PyGILState_STATE gilstate = PyGILState_Ensure();
+    readlinestate *state = get_hook_module_state();
 #ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
     rl_completion_append_character ='\0';
 #endif
@@ -1187,10 +1227,12 @@ flex_complete(const char *text, int start, int end)
     end = start + (int)end_size;
 
 done:
-    Py_XDECREF(readlinestate_global->begidx);
-    Py_XDECREF(readlinestate_global->endidx);
-    readlinestate_global->begidx = PyLong_FromLong((long) start);
-    readlinestate_global->endidx = PyLong_FromLong((long) end);
+    if (state) {
+        Py_XDECREF(state->begidx);
+        Py_XDECREF(state->endidx);
+        state->begidx = PyLong_FromLong((long) start);
+        state->endidx = PyLong_FromLong((long) end);
+    }
     result = completion_matches((char *)text, *on_completion);
     PyGILState_Release(gilstate);
     return result;
@@ -1511,12 +1553,17 @@ PyInit_readline(void)
     }
 
     mod_state = (readlinestate *) PyModule_GetState(m);
+    if (mod_state == NULL){
+        goto error;
+    }
     PyOS_ReadlineFunctionPointer = call_readline;
     if (setup_readline(mod_state) < 0) {
         PyErr_NoMemory();
         goto error;
     }
-
+    if (PyErr_Occurred()){
+        goto error;
+    }
     return m;
 
 error: