From 1c901860e621615ecf9b77400d6cfae2c975d93a Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:10:53 +0200 Subject: [PATCH] [3.14] gh-128636: Fix crash in PyREPL when os.environ is overwritten with an invalid value for macOS (GH-138089) (#138938) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Co-authored-by: yihong Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/_colorize.py | 21 +++++++++++++------ Lib/_pyrepl/unix_console.py | 8 +++++-- Lib/test/support/__init__.py | 2 +- Lib/test/test_pyrepl/test_unix_console.py | 9 ++++++++ ...-09-10-10-02-59.gh-issue-128636.ldRKGZ.rst | 2 ++ 5 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-09-10-10-02-59.gh-issue-128636.ldRKGZ.rst diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 7c09bb4564c2..4f510a7141b3 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -273,21 +273,29 @@ def decolor(text: str) -> str: def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool: + + def _safe_getenv(k: str, fallback: str | None = None) -> str | None: + """Exception-safe environment retrieval. See gh-128636.""" + try: + return os.environ.get(k, fallback) + except Exception: + return fallback + if file is None: file = sys.stdout if not sys.flags.ignore_environment: - if os.environ.get("PYTHON_COLORS") == "0": + if _safe_getenv("PYTHON_COLORS") == "0": return False - if os.environ.get("PYTHON_COLORS") == "1": + if _safe_getenv("PYTHON_COLORS") == "1": return True - if os.environ.get("NO_COLOR"): + if _safe_getenv("NO_COLOR"): return False if not COLORIZE: return False - if os.environ.get("FORCE_COLOR"): + if _safe_getenv("FORCE_COLOR"): return True - if os.environ.get("TERM") == "dumb": + if _safe_getenv("TERM") == "dumb": return False if not hasattr(file, "fileno"): @@ -330,7 +338,8 @@ def get_theme( environment (including environment variable state and console configuration on Windows) can also change in the course of the application life cycle. """ - if force_color or (not force_no_color and can_colorize(file=tty_file)): + if force_color or (not force_no_color and + can_colorize(file=tty_file)): return _theme return theme_no_color diff --git a/Lib/_pyrepl/unix_console.py b/Lib/_pyrepl/unix_console.py index a7e49923191c..9953051bf7c4 100644 --- a/Lib/_pyrepl/unix_console.py +++ b/Lib/_pyrepl/unix_console.py @@ -159,6 +159,10 @@ class UnixConsole(Console): self.pollob.register(self.input_fd, select.POLLIN) self.terminfo = terminfo.TermInfo(term or None) self.term = term + self.is_apple_terminal = ( + platform.system() == "Darwin" + and os.getenv("TERM_PROGRAM") == "Apple_Terminal" + ) @overload def _my_getstr(cap: str, optional: Literal[False] = False) -> bytes: ... @@ -339,7 +343,7 @@ class UnixConsole(Console): tcsetattr(self.input_fd, termios.TCSADRAIN, raw) # In macOS terminal we need to deactivate line wrap via ANSI escape code - if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal": + if self.is_apple_terminal: os.write(self.output_fd, b"\033[?7l") self.screen = [] @@ -370,7 +374,7 @@ class UnixConsole(Console): self.flushoutput() tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate) - if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal": + if self.is_apple_terminal: os.write(self.output_fd, b"\033[?7h") if hasattr(self, "old_sigwinch"): diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 65c1da29936b..a719e49ef377 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2899,7 +2899,7 @@ def force_color(color: bool): from .os_helper import EnvironmentVarGuard with ( - swap_attr(_colorize, "can_colorize", lambda file=None: color), + swap_attr(_colorize, "can_colorize", lambda *, file=None: color), EnvironmentVarGuard() as env, ): env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS") diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py index ab1236768cfb..6185c7e3c794 100644 --- a/Lib/test/test_pyrepl/test_unix_console.py +++ b/Lib/test/test_pyrepl/test_unix_console.py @@ -303,3 +303,12 @@ class TestConsole(TestCase): self.assertIsInstance(console.getheightwidth(), tuple) os.environ = [] self.assertIsInstance(console.getheightwidth(), tuple) + + @unittest.skipUnless(sys.platform == "darwin", "requires macOS") + def test_restore_with_invalid_environ_on_macos(self, _os_write): + # gh-128636 for macOS + console = UnixConsole(term="xterm") + with os_helper.EnvironmentVarGuard(): + os.environ = [] + console.prepare() # needed to call restore() + console.restore() # this should succeed diff --git a/Misc/NEWS.d/next/Library/2025-09-10-10-02-59.gh-issue-128636.ldRKGZ.rst b/Misc/NEWS.d/next/Library/2025-09-10-10-02-59.gh-issue-128636.ldRKGZ.rst new file mode 100644 index 000000000000..54eae0a46016 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-10-10-02-59.gh-issue-128636.ldRKGZ.rst @@ -0,0 +1,2 @@ +Fix crash in PyREPL when os.environ is overwritten with an invalid value for +mac -- 2.47.3