https://github.com/python/cpython/commit/86a8a1c57a386fb3330bee0fa44fc3fd6c3042a3
commit: 86a8a1c57a386fb3330bee0fa44fc3fd6c3042a3
branch: main
author: Eugene Triguba <[email protected]>
committer: pablogsal <[email protected]>
date: 2024-06-11T17:40:31Z
summary:

gh-118908: Limit exposed globals from internal imports and definitions on new 
REPL startup (#119547)

files:
A Misc/NEWS.d/next/Library/2024-05-25-10-40-38.gh-issue-118908.XcZiq4.rst
M Lib/_pyrepl/simple_interact.py
M Lib/test/test_pyrepl/test_pyrepl.py
M Lib/test/test_repl.py

diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py
index 2e5698eb131684..620f87b4867073 100644
--- a/Lib/_pyrepl/simple_interact.py
+++ b/Lib/_pyrepl/simple_interact.py
@@ -27,6 +27,7 @@
 
 import _sitebuiltins
 import linecache
+import builtins
 import sys
 import code
 from types import ModuleType
@@ -34,6 +35,12 @@
 from .console import InteractiveColoredConsole
 from .readline import _get_reader, multiline_input
 
+TYPE_CHECKING = False
+
+if TYPE_CHECKING:
+    from typing import Any
+
+
 _error: tuple[type[Exception], ...] | type[Exception]
 try:
     from .unix_console import _error
@@ -73,20 +80,28 @@ def _clear_screen():
     "clear": _clear_screen,
 }
 
+DEFAULT_NAMESPACE: dict[str, Any] = {
+    '__name__': '__main__',
+    '__doc__': None,
+    '__package__': None,
+    '__loader__': None,
+    '__spec__': None,
+    '__annotations__': {},
+    '__builtins__': builtins,
+}
 
 def run_multiline_interactive_console(
     mainmodule: ModuleType | None = None,
     future_flags: int = 0,
     console: code.InteractiveConsole | None = None,
 ) -> None:
-    import __main__
     from .readline import _setup
     _setup()
 
-    mainmodule = mainmodule or __main__
+    namespace = mainmodule.__dict__ if mainmodule else DEFAULT_NAMESPACE
     if console is None:
         console = InteractiveColoredConsole(
-            mainmodule.__dict__, filename="<stdin>"
+            namespace, filename="<stdin>"
         )
     if future_flags:
         console.compile.compiler.flags |= future_flags
diff --git a/Lib/test/test_pyrepl/test_pyrepl.py 
b/Lib/test/test_pyrepl/test_pyrepl.py
index 45114e7315749f..3167b8473bfe20 100644
--- a/Lib/test/test_pyrepl/test_pyrepl.py
+++ b/Lib/test/test_pyrepl/test_pyrepl.py
@@ -1,9 +1,13 @@
-import itertools
 import io
+import itertools
 import os
 import rlcompleter
-from unittest import TestCase
+import select
+import subprocess
+import sys
+from unittest import TestCase, skipUnless
 from unittest.mock import patch
+from test.support import force_not_colorized
 
 from .support import (
     FakeConsole,
@@ -17,6 +21,10 @@
 from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
 from _pyrepl.readline import multiline_input as readline_multiline_input
 
+try:
+    import pty
+except ImportError:
+    pty = None
 
 class TestCursorPosition(TestCase):
     def prepare_reader(self, events):
@@ -828,3 +836,54 @@ def test_bracketed_paste_single_line(self):
         reader = self.prepare_reader(events)
         output = multiline_input(reader)
         self.assertEqual(output, input_code)
+
+
+@skipUnless(pty, "requires pty")
+class TestMain(TestCase):
+    @force_not_colorized
+    def test_exposed_globals_in_repl(self):
+        expected_output = (
+            "[\'__annotations__\', \'__builtins__\', \'__doc__\', 
\'__loader__\', "
+            "\'__name__\', \'__package__\', \'__spec__\']"
+        )
+        output, exit_code = self.run_repl(["sorted(dir())", "exit"])
+        if "can\'t use pyrepl" in output:
+            self.skipTest("pyrepl not available")
+        self.assertEqual(exit_code, 0)
+        self.assertIn(expected_output, output)
+
+    def test_dumb_terminal_exits_cleanly(self):
+        env = os.environ.copy()
+        env.update({"TERM": "dumb"})
+        output, exit_code = self.run_repl("exit()\n", env=env)
+        self.assertEqual(exit_code, 0)
+        self.assertIn("warning: can\'t use pyrepl", output)
+        self.assertNotIn("Exception", output)
+        self.assertNotIn("Traceback", output)
+
+    def run_repl(self, repl_input: str | list[str], env: dict | None = None) 
-> tuple[str, int]:
+        master_fd, slave_fd = pty.openpty()
+        process = subprocess.Popen(
+            [sys.executable, "-i", "-u"],
+            stdin=slave_fd,
+            stdout=slave_fd,
+            stderr=slave_fd,
+            text=True,
+            close_fds=True,
+            env=env if env else os.environ,
+       )
+        if isinstance(repl_input, list):
+            repl_input = "\n".join(repl_input) + "\n"
+        os.write(master_fd, repl_input.encode("utf-8"))
+
+        output = []
+        while select.select([master_fd], [], [], 0.5)[0]:
+            data = os.read(master_fd, 1024).decode("utf-8")
+            if not data:
+                break
+            output.append(data)
+
+        os.close(master_fd)
+        os.close(slave_fd)
+        exit_code = process.wait()
+        return "\n".join(output), exit_code
diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py
index 340178366fc13a..1caf09ceaf10fc 100644
--- a/Lib/test/test_repl.py
+++ b/Lib/test/test_repl.py
@@ -1,9 +1,9 @@
 """Test the interactive interpreter."""
 
-import sys
 import os
-import unittest
 import subprocess
+import sys
+import unittest
 from textwrap import dedent
 from test import support
 from test.support import cpython_only, has_subprocess_support, 
SuppressCrashReport
@@ -199,7 +199,6 @@ def test_asyncio_repl_is_ok(self):
         assert_python_ok("-m", "asyncio")
 
 
-
 class TestInteractiveModeSyntaxErrors(unittest.TestCase):
 
     def test_interactive_syntax_error_correct_line(self):
diff --git 
a/Misc/NEWS.d/next/Library/2024-05-25-10-40-38.gh-issue-118908.XcZiq4.rst 
b/Misc/NEWS.d/next/Library/2024-05-25-10-40-38.gh-issue-118908.XcZiq4.rst
new file mode 100644
index 00000000000000..bf58d7277fcd51
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-05-25-10-40-38.gh-issue-118908.XcZiq4.rst
@@ -0,0 +1,2 @@
+Limit exposed globals from internal imports and definitions on new REPL
+startup. Patch by Eugene Triguba and Pablo Galindo.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to