From 274c3326d9d57f55d12e1811d287d83d46273ade Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 25 Apr 2023 10:57:30 -0400 Subject: [PATCH 1/3] GH-103929: handle long input lines with PyOS_InputHook If: - stdin is in line buffer mode - the readline module is not loaded - not on windows - a GUI toolkit has installd PyOS_InputHook Then, if the user enters lines longer than 98 characters into `input`: - user calls `input(...)` - user types/pastes a line longer than 98 characters + 1 newline - PyOS_InputHook returns - the first 99 characters are returned - the last character is not a new line so we go back to my_fgets - the PyOS_InputHook blocks because despite there being value in the buffer, stdin does not flag itself as ready to be read again - user hits enter again and to finish reading the input - the extra new line comes out the next time the user calls input This fixes this bug by passing the currently read number of characters to `my_fgets` to skip the input hook when reading out a long buffer. closes #103929 --- Doc/c-api/veryhigh.rst | 2 ++ Parser/myreadline.c | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/veryhigh.rst b/Doc/c-api/veryhigh.rst index 7eb9f0b54abd4e..f3ee37c15b8e03 100644 --- a/Doc/c-api/veryhigh.rst +++ b/Doc/c-api/veryhigh.rst @@ -151,6 +151,8 @@ the same library that the Python runtime is using. event loops, as done in :file:`Modules/_tkinter.c` in the Python source code. + This function should block until stdin is readable. + .. versionchanged:: 3.12 This function is only called from the :ref:`main interpreter `. diff --git a/Parser/myreadline.c b/Parser/myreadline.c index 64e8f5383f0602..3ffe452494e799 100644 --- a/Parser/myreadline.c +++ b/Parser/myreadline.c @@ -38,7 +38,7 @@ int (*PyOS_InputHook)(void) = NULL; except if _PyOS_InterruptOccurred() returns true. */ static int -my_fgets(PyThreadState* tstate, char *buf, int len, FILE *fp) +my_fgets(PyThreadState* tstate, char *buf, int len, FILE *fp, int n) { #ifdef MS_WINDOWS HANDLE handle; @@ -53,7 +53,7 @@ my_fgets(PyThreadState* tstate, char *buf, int len, FILE *fp) #endif while (1) { - if (PyOS_InputHook != NULL && + if (PyOS_InputHook != NULL && n == 0 && // GH-104668: See PyOS_ReadlineFunctionPointer's comment below... _Py_IsMainInterpreter(tstate->interp)) { @@ -333,7 +333,7 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt) return NULL; } p = pr; - int err = my_fgets(tstate, p + n, (int)incr, sys_stdin); + int err = my_fgets(tstate, p + n, (int)incr, sys_stdin, n); if (err == 1) { // Interrupt PyMem_RawFree(p); From 294c7c47197990104cddb5c6449a869be743125f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 17 Oct 2025 13:14:41 -0400 Subject: [PATCH 2/3] DOC: add additional qualifier to expected behavior of PyOS_InputHook --- Doc/c-api/veryhigh.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/veryhigh.rst b/Doc/c-api/veryhigh.rst index f3ee37c15b8e03..a9c8906ef5f3a7 100644 --- a/Doc/c-api/veryhigh.rst +++ b/Doc/c-api/veryhigh.rst @@ -151,7 +151,8 @@ the same library that the Python runtime is using. event loops, as done in :file:`Modules/_tkinter.c` in the Python source code. - This function should block until stdin is readable. + This function should block until stdin is readable but may return + early. .. versionchanged:: 3.12 This function is only called from the From 96fa029507a69c6605cbd75a6db4c5e1a1454005 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 3 Dec 2025 21:55:28 -0500 Subject: [PATCH 3/3] MNT: move the PyOS_InputHook call up a loop level If we have returned from the input hook then there is something to read in the stdin so read it out to the new line before calling the input hook again. --- Doc/c-api/veryhigh.rst | 3 +-- Parser/myreadline.c | 17 +++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/c-api/veryhigh.rst b/Doc/c-api/veryhigh.rst index a9c8906ef5f3a7..f3ee37c15b8e03 100644 --- a/Doc/c-api/veryhigh.rst +++ b/Doc/c-api/veryhigh.rst @@ -151,8 +151,7 @@ the same library that the Python runtime is using. event loops, as done in :file:`Modules/_tkinter.c` in the Python source code. - This function should block until stdin is readable but may return - early. + This function should block until stdin is readable. .. versionchanged:: 3.12 This function is only called from the diff --git a/Parser/myreadline.c b/Parser/myreadline.c index 3ffe452494e799..337c02063da337 100644 --- a/Parser/myreadline.c +++ b/Parser/myreadline.c @@ -38,7 +38,7 @@ int (*PyOS_InputHook)(void) = NULL; except if _PyOS_InterruptOccurred() returns true. */ static int -my_fgets(PyThreadState* tstate, char *buf, int len, FILE *fp, int n) +my_fgets(PyThreadState* tstate, char *buf, int len, FILE *fp) { #ifdef MS_WINDOWS HANDLE handle; @@ -53,12 +53,6 @@ my_fgets(PyThreadState* tstate, char *buf, int len, FILE *fp, int n) #endif while (1) { - if (PyOS_InputHook != NULL && n == 0 && - // GH-104668: See PyOS_ReadlineFunctionPointer's comment below... - _Py_IsMainInterpreter(tstate->interp)) - { - (void)(PyOS_InputHook)(); - } errno = 0; clearerr(fp); @@ -313,6 +307,13 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt) } fflush(stderr); + if (PyOS_InputHook != NULL && + // GH-104668: See PyOS_ReadlineFunctionPointer's comment below... + _Py_IsMainInterpreter(tstate->interp)) + { + (void)(PyOS_InputHook)(); + } + n = 0; p = NULL; do { @@ -333,7 +334,7 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt) return NULL; } p = pr; - int err = my_fgets(tstate, p + n, (int)incr, sys_stdin, n); + int err = my_fgets(tstate, p + n, (int)incr, sys_stdin); if (err == 1) { // Interrupt PyMem_RawFree(p);