diff options
author | Eugene Triguba <eugenetriguba@gmail.com> | 2024-06-11 13:40:31 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-11 17:40:31 +0000 |
commit | 86a8a1c57a386fb3330bee0fa44fc3fd6c3042a3 (patch) | |
tree | c57a316b21861cb45af433d74c68e386a76d6326 | |
parent | 0335662fe1f663fe96e3e4acf0f34c5959d06b00 (diff) | |
download | cpython-86a8a1c57a386fb3330bee0fa44fc3fd6c3042a3.tar.gz cpython-86a8a1c57a386fb3330bee0fa44fc3fd6c3042a3.zip |
gh-118908: Limit exposed globals from internal imports and definitions on new REPL startup (#119547)
-rw-r--r-- | Lib/_pyrepl/simple_interact.py | 21 | ||||
-rw-r--r-- | Lib/test/test_pyrepl/test_pyrepl.py | 63 | ||||
-rw-r--r-- | Lib/test/test_repl.py | 5 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2024-05-25-10-40-38.gh-issue-118908.XcZiq4.rst | 2 |
4 files changed, 83 insertions, 8 deletions
diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py index 2e5698eb131..620f87b4867 100644 --- a/Lib/_pyrepl/simple_interact.py +++ b/Lib/_pyrepl/simple_interact.py @@ -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 diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 45114e73157..3167b8473bf 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.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 diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 340178366fc..1caf09ceaf1 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 @@ 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 index 00000000000..bf58d7277fc --- /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. |