From 02d53ae3080f7128f49be34acd52d731e8253f58 Mon Sep 17 00:00:00 2001 From: Mathieu Dubois-Briand Date: Wed, 7 May 2025 17:26:02 +0200 Subject: [PATCH 1/4] gh-46927: Prevent readline from overriding environment Readline library will set the LINES and COLUMNS environment variables during initialization. One might expect these variables to be updated later on SIGWINCH, but this is not the case with cpython. As a consequence, when the readline module is imported, any process launched from cpython with the default environment will have LINES and COLUMNS variables set, potentially to a wrong value. Use the rl_change_environment global variable to disable this initial setup of the environment variables. --- .../2025-05-07-18-31-31.gh-issue-46927.sF02gj.rst | 2 ++ Modules/readline.c | 7 +++++++ 2 files changed, 9 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-18-31-31.gh-issue-46927.sF02gj.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-18-31-31.gh-issue-46927.sF02gj.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-18-31-31.gh-issue-46927.sF02gj.rst new file mode 100644 index 000000000000000..3ba9757c8acb5de --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-18-31-31.gh-issue-46927.sF02gj.rst @@ -0,0 +1,2 @@ +Prevent :mod:`readline` from overriding the ``COLUMNS`` and ``LINES`` +environment variables, as values are not updated on terminal resize. diff --git a/Modules/readline.c b/Modules/readline.c index 0dd99dc66c08e9d..52c7182e885aaca 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -1323,6 +1323,13 @@ setup_readline(readlinestate *mod_state) /* The name must be defined before initialization */ rl_readline_name = "python"; +#ifndef WITH_EDITLINE + /* Prevent readline from changing environment variables such as LINES and + * COLUMNS. + */ + rl_change_environment = 0; +#endif + /* the libedit readline emulation resets key bindings etc * when calling rl_initialize. So call it upfront */ From 775ae4a00c55f5ccd3d77119b0591b9a928c0ed6 Mon Sep 17 00:00:00 2001 From: Mathieu Dubois-Briand Date: Thu, 8 May 2025 08:55:36 +0200 Subject: [PATCH 2/4] Disable rl_change_environment on apple --- Modules/readline.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/readline.c b/Modules/readline.c index 52c7182e885aaca..f60bdd33b00e2d0 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -1323,7 +1323,7 @@ setup_readline(readlinestate *mod_state) /* The name must be defined before initialization */ rl_readline_name = "python"; -#ifndef WITH_EDITLINE +#if !defined(__APPLE__) /* Prevent readline from changing environment variables such as LINES and * COLUMNS. */ From 8bbc1cf2fc34caa414f9b75d03a913ec3f6927c7 Mon Sep 17 00:00:00 2001 From: Mathieu Dubois-Briand Date: Mon, 12 May 2025 17:49:20 +0200 Subject: [PATCH 3/4] Add test --- Lib/test/test_readline.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/test/test_readline.py b/Lib/test/test_readline.py index b9d082b3597f131..9a8197db7c0fe5e 100644 --- a/Lib/test/test_readline.py +++ b/Lib/test/test_readline.py @@ -405,6 +405,15 @@ def test_write_read_limited_history(self): # So, we've only tested that the read did not fail. # See TestHistoryManipulation for the full test. + def test_environment_is_not_modified(self): + # os.environ contains enviroment at the time "os" module was loaded, so + # before the "readline" module is loaded. + original_env = dict(os.environ) + + # Force refresh of os.environ and make sure it is the same as before the + # refresh. + os.reload_environ() + self.assertEqual(dict(os.environ), original_env) @unittest.skipUnless(support.Py_GIL_DISABLED, 'these tests can only possibly fail with GIL disabled') class FreeThreadingTest(unittest.TestCase): From e3bce0d1810fda69ce2c50ce91b183825726870e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 27 Jun 2026 21:52:42 +0300 Subject: [PATCH 4/4] gh-46927: Detect rl_change_environment with a configure probe Guard the rl_change_environment assignment with HAVE_RL_CHANGE_ENVIRONMENT, set by a configure link test, instead of the __APPLE__ macro. This compiles it out on any libedit build lacking the symbol, not just on macOS. Co-Authored-By: Claude Opus 4.8 --- Modules/readline.c | 8 ++++---- configure | 51 ++++++++++++++++++++++++++++++++++++++++++++++ configure.ac | 11 ++++++++++ pyconfig.h.in | 3 +++ 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/Modules/readline.c b/Modules/readline.c index f60bdd33b00e2d0..1b161fd0b4a05cd 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -1323,10 +1323,10 @@ setup_readline(readlinestate *mod_state) /* The name must be defined before initialization */ rl_readline_name = "python"; -#if !defined(__APPLE__) - /* Prevent readline from changing environment variables such as LINES and - * COLUMNS. - */ +#ifdef HAVE_RL_CHANGE_ENVIRONMENT + /* Prevent readline from setting the LINES and COLUMNS environment + * variables: ncurses prefers them over an ioctl() query, so a stale value + * left after a resize breaks SIGWINCH / KEY_RESIZE handling (gh-46927). */ rl_change_environment = 0; #endif diff --git a/configure b/configure index c51192f12c82230..b4d629f3ea19b68 100755 --- a/configure +++ b/configure @@ -27184,6 +27184,57 @@ then : printf "%s\n" "#define HAVE_RL_RESIZE_TERMINAL 1" >>confdefs.h +fi + + # rl_change_environment is in readline 6.3, but not in editline + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for rl_change_environment in -l$LIBREADLINE" >&5 +printf %s "checking for rl_change_environment in -l$LIBREADLINE... " >&6; } +if test ${ac_cv_readline_rl_change_environment+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include /* Must be first for Gnu Readline */ + #ifdef WITH_EDITLINE + # include + #else + # include + # include + #endif + +int +main (void) +{ +int x = rl_change_environment + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_readline_rl_change_environment=yes +else case e in #( + e) ac_cv_readline_rl_change_environment=no + ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_readline_rl_change_environment" >&5 +printf "%s\n" "$ac_cv_readline_rl_change_environment" >&6; } + if test "x$ac_cv_readline_rl_change_environment" = xyes +then : + + +printf "%s\n" "#define HAVE_RL_CHANGE_ENVIRONMENT 1" >>confdefs.h + + fi # check for readline 4.2 diff --git a/configure.ac b/configure.ac index a7b2f62579b0e96..d233cb9973c8f86 100644 --- a/configure.ac +++ b/configure.ac @@ -6472,6 +6472,17 @@ AS_VAR_IF([with_readline], [no], [ AC_DEFINE([HAVE_RL_RESIZE_TERMINAL], [1], [Define if you have readline 4.0]) ]) + # rl_change_environment is in readline 6.3, but not in editline + AC_CACHE_CHECK([for rl_change_environment in -l$LIBREADLINE], [ac_cv_readline_rl_change_environment], [ + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([readline_includes], [int x = rl_change_environment])], + [ac_cv_readline_rl_change_environment=yes], [ac_cv_readline_rl_change_environment=no] + ) + ]) + AS_VAR_IF([ac_cv_readline_rl_change_environment], [yes], [ + AC_DEFINE([HAVE_RL_CHANGE_ENVIRONMENT], [1], [Define if you have readline 6.3]) + ]) + # check for readline 4.2 AC_CACHE_CHECK([for rl_completion_matches in -l$LIBREADLINE], [ac_cv_readline_rl_completion_matches], [ AC_LINK_IFELSE( diff --git a/pyconfig.h.in b/pyconfig.h.in index 7586ad3f2667054..b7eae653c9cfaf9 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1074,6 +1074,9 @@ /* Define if you can turn off readline's signal handling. */ #undef HAVE_RL_CATCH_SIGNAL +/* Define if you have readline 6.3 */ +#undef HAVE_RL_CHANGE_ENVIRONMENT + /* Define to 1 if the system has the type 'rl_compdisp_func_t'. */ #undef HAVE_RL_COMPDISP_FUNC_T