]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-118908: Limit exposed globals from internal imports and definitions on...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 11 Jun 2024 18:04:39 +0000 (20:04 +0200)
committerGitHub <noreply@github.com>
Tue, 11 Jun 2024 18:04:39 +0000 (18:04 +0000)
Lib/_pyrepl/simple_interact.py
Lib/test/test_pyrepl/test_pyrepl.py
Lib/test/test_repl.py
Misc/NEWS.d/next/Library/2024-05-25-10-40-38.gh-issue-118908.XcZiq4.rst [new file with mode: 0644]

index 2e5698eb131684f24cd70a0685a7ef32c487121d..620f87b4867073bab1566361daa2df664248faca 100644 (file)
@@ -27,6 +27,7 @@ from __future__ import annotations
 
 import _sitebuiltins
 import linecache
+import builtins
 import sys
 import code
 from types import ModuleType
@@ -34,6 +35,12 @@ from types import ModuleType
 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 @@ REPL_COMMANDS = {
     "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
index 45114e7315749f2c2f5d115a1151d1abfa83d2d1..3167b8473bfe2093613551cf364e4a2cf5e3eb1f 100644 (file)
@@ -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.console import Event
 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 @@ class TestPasteEvent(TestCase):
         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
index 340178366fc13ac91d36f24e7d649ad5b2b78abd..1caf09ceaf10fc4a9e9ec3713e329ab40cab22b3 100644 (file)
@@ -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 @@ class TestInteractiveInterpreter(unittest.TestCase):
         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 (file)
index 0000000..bf58d72
--- /dev/null
@@ -0,0 +1,2 @@
+Limit exposed globals from internal imports and definitions on new REPL
+startup. Patch by Eugene Triguba and Pablo Galindo.