From 191f8ceda7d7a8b18b61c105d2647d6da93a8fc1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 16 Sep 2026 22:55:20 +0300 Subject: [PATCH 1/2] gh-70331: Protect IDLE's imports from user files in the current directory Start the user process with -P, so that the current directory is not on sys.path while idlelib.run and its dependencies are imported. sys.path is set later by transfer_path(). "python -m idlelib" now removes the current directory from sys.path. --- Lib/idlelib/__main__.py | 7 +++++++ Lib/idlelib/idle_test/test_pyshell.py | 20 ++++++++++++++++++- Lib/idlelib/pyshell.py | 5 ++++- ...6-09-16-22-00-00.gh-issue-70331.shadow.rst | 3 +++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst diff --git a/Lib/idlelib/__main__.py b/Lib/idlelib/__main__.py index ec3915b265f665e..4dde0d6a5acbb51 100644 --- a/Lib/idlelib/__main__.py +++ b/Lib/idlelib/__main__.py @@ -3,5 +3,12 @@ Run IDLE as python -m idlelib """ +import sys + +if not sys.flags.safe_path: + # Remove the current directory, prepended by "python -m", so that + # user files do not shadow IDLE's imports (gh-70331). + del sys.path[0] + import idlelib.pyshell idlelib.pyshell.main() diff --git a/Lib/idlelib/idle_test/test_pyshell.py b/Lib/idlelib/idle_test/test_pyshell.py index dec81bdbbccd67e..e24fbf1b3a589b9 100644 --- a/Lib/idlelib/idle_test/test_pyshell.py +++ b/Lib/idlelib/idle_test/test_pyshell.py @@ -3,8 +3,11 @@ from idlelib import pyshell import os +import sys import unittest -from test.support import requires +from unittest import mock +from test.support import os_helper, requires +from test.support.script_helper import assert_python_ok from tkinter import Tk @@ -37,6 +40,21 @@ def test_fix_user_path(self): eq(pyshell.fix_user_path(['/a', '/b']), ['/a', '/b']) eq(pyshell.fix_user_path([idlelib_dir]), []) + def test_shadowed_stdlib(self): + # gh-70331: user files in the current directory must not shadow + # the stdlib modules imported by IDLE. + with os_helper.temp_dir() as cwd: + for name in ('random', 'tkinter'): + os_helper.create_empty_file(os.path.join(cwd, f'{name}.py')) + assert_python_ok('-m', 'idlelib', '-h', __isolated=False, __cwd=cwd) + + def test_build_subprocess_arglist(self): + interp = mock.Mock(port=1234) + args = pyshell.ModifiedInterpreter.build_subprocess_arglist(interp) + # gh-70331: -P keeps the current directory out of sys.path. + self.assertEqual(args[:2], [sys.executable, '-P']) + self.assertEqual(args[-1], '1234') + class PyShellFileListTest(unittest.TestCase): diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 51568d2a9e950e3..ab1fd6ed69b44c9 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -451,7 +451,10 @@ def build_subprocess_arglist(self): del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc', default=False, type='bool') command = f"__import__('idlelib.run').run.main({del_exitf!r})" - return [sys.executable] + w + ["-c", command, str(self.port)] + # -P keeps the current directory off sys.path, so that user files + # do not shadow run's imports (gh-70331). transfer_path() sets + # sys.path later. + return [sys.executable, '-P'] + w + ["-c", command, str(self.port)] def start_subprocess(self): addr = (HOST, self.port) diff --git a/Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst b/Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst new file mode 100644 index 000000000000000..2b58fbc62c03e42 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst @@ -0,0 +1,3 @@ +IDLE no longer fails to start when the current directory contains user files +with the same names as standard library modules that IDLE imports, such as +``random.py`` or ``tkinter.py``. From bdbf342c6c52a58cbb10f095978d8fca0442dc0f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 23 Sep 2026 12:32:33 +0300 Subject: [PATCH 2/2] Also protect the "python -m idlelib.idle" entry point --- Lib/idlelib/idle.py | 8 +++++++- Lib/idlelib/idle_test/test_pyshell.py | 7 +++++-- .../IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst | 5 +++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/idle.py b/Lib/idlelib/idle.py index 485d5a75a29c1ad..d9faa12cc2ba45e 100644 --- a/Lib/idlelib/idle.py +++ b/Lib/idlelib/idle.py @@ -1,6 +1,12 @@ -import os.path import sys +if __spec__ is not None and not sys.flags.safe_path: + # Remove the current directory, prepended by "python -m", so that + # user files do not shadow IDLE's imports (gh-70331). + del sys.path[0] + +import os.path + # Enable running IDLE with idlelib in a non-standard location. # This was once used to run development versions of IDLE. diff --git a/Lib/idlelib/idle_test/test_pyshell.py b/Lib/idlelib/idle_test/test_pyshell.py index e24fbf1b3a589b9..e0c2af57238f493 100644 --- a/Lib/idlelib/idle_test/test_pyshell.py +++ b/Lib/idlelib/idle_test/test_pyshell.py @@ -44,9 +44,12 @@ def test_shadowed_stdlib(self): # gh-70331: user files in the current directory must not shadow # the stdlib modules imported by IDLE. with os_helper.temp_dir() as cwd: - for name in ('random', 'tkinter'): + for name in ('os', 'random', 'tkinter'): os_helper.create_empty_file(os.path.join(cwd, f'{name}.py')) - assert_python_ok('-m', 'idlelib', '-h', __isolated=False, __cwd=cwd) + for module in 'idlelib', 'idlelib.idle': + with self.subTest(module=module): + assert_python_ok('-m', module, '-h', + __isolated=False, __cwd=cwd) def test_build_subprocess_arglist(self): interp = mock.Mock(port=1234) diff --git a/Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst b/Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst index 2b58fbc62c03e42..1a7124e76cd49f8 100644 --- a/Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst +++ b/Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst @@ -1,3 +1,4 @@ -IDLE no longer fails to start when the current directory contains user files -with the same names as standard library modules that IDLE imports, such as +IDLE no longer fails to start with ``python -m idlelib``, and its user process +no longer fails to start, when the current directory contains user files with +the same names as standard library modules that IDLE imports, such as ``random.py`` or ``tkinter.py``.