summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2024-06-11 18:04:39 (GMT)
committerGitHub <noreply@github.com>2024-06-11 18:04:39 (GMT)
commitf5289c450a324bd560b328ecd42ac9faf578276e (patch)
treecb6e20bb2d656475374b91df402e7952a8bec72b /Lib
parent51bcb67405cceee1f18067fb2ae510dec47191bc (diff)
downloadcpython-f5289c450a324bd560b328ecd42ac9faf578276e.zip
cpython-f5289c450a324bd560b328ecd42ac9faf578276e.tar.gz
cpython-f5289c450a324bd560b328ecd42ac9faf578276e.tar.bz2
[3.13] gh-118908: Limit exposed globals from internal imports and definitions on new REPL startup (GH-119547) (#120362)
Diffstat (limited to 'Lib')
-rw-r--r--Lib/_pyrepl/simple_interact.py21
-rw-r--r--Lib/test/test_pyrepl/test_pyrepl.py63
-rw-r--r--Lib/test/test_repl.py5
3 files changed, 81 insertions, 8 deletions
diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py
index 2e5698e..620f87b 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 45114e7..3167b84 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 3401783..1caf09c 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):