Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f5289c4

Browse filesBrowse files
[3.13] gh-118908: Limit exposed globals from internal imports and definitions on new REPL startup (GH-119547) (#120362)
1 parent 51bcb67 commit f5289c4
Copy full SHA for f5289c4

File tree

Expand file treeCollapse file tree

4 files changed

+83
-8
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+83
-8
lines changed

‎Lib/_pyrepl/simple_interact.py

Copy file name to clipboardExpand all lines: Lib/_pyrepl/simple_interact.py
+18-3Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,20 @@
2727

2828
import _sitebuiltins
2929
import linecache
30+
import builtins
3031
import sys
3132
import code
3233
from types import ModuleType
3334

3435
from .console import InteractiveColoredConsole
3536
from .readline import _get_reader, multiline_input
3637

38+
TYPE_CHECKING = False
39+
40+
if TYPE_CHECKING:
41+
from typing import Any
42+
43+
3744
_error: tuple[type[Exception], ...] | type[Exception]
3845
try:
3946
from .unix_console import _error
@@ -73,20 +80,28 @@ def _clear_screen():
7380
"clear": _clear_screen,
7481
}
7582

83+
DEFAULT_NAMESPACE: dict[str, Any] = {
84+
'__name__': '__main__',
85+
'__doc__': None,
86+
'__package__': None,
87+
'__loader__': None,
88+
'__spec__': None,
89+
'__annotations__': {},
90+
'__builtins__': builtins,
91+
}
7692

7793
def run_multiline_interactive_console(
7894
mainmodule: ModuleType | None = None,
7995
future_flags: int = 0,
8096
console: code.InteractiveConsole | None = None,
8197
) -> None:
82-
import __main__
8398
from .readline import _setup
8499
_setup()
85100

86-
mainmodule = mainmodule or __main__
101+
namespace = mainmodule.__dict__ if mainmodule else DEFAULT_NAMESPACE
87102
if console is None:
88103
console = InteractiveColoredConsole(
89-
mainmodule.__dict__, filename="<stdin>"
104+
namespace, filename="<stdin>"
90105
)
91106
if future_flags:
92107
console.compile.compiler.flags |= future_flags

‎Lib/test/test_pyrepl/test_pyrepl.py

Copy file name to clipboardExpand all lines: Lib/test/test_pyrepl/test_pyrepl.py
+61-2Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import itertools
21
import io
2+
import itertools
33
import os
44
import rlcompleter
5-
from unittest import TestCase
5+
import select
6+
import subprocess
7+
import sys
8+
from unittest import TestCase, skipUnless
69
from unittest.mock import patch
10+
from test.support import force_not_colorized
711

812
from .support import (
913
FakeConsole,
@@ -17,6 +21,10 @@
1721
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
1822
from _pyrepl.readline import multiline_input as readline_multiline_input
1923

24+
try:
25+
import pty
26+
except ImportError:
27+
pty = None
2028

2129
class TestCursorPosition(TestCase):
2230
def prepare_reader(self, events):
@@ -828,3 +836,54 @@ def test_bracketed_paste_single_line(self):
828836
reader = self.prepare_reader(events)
829837
output = multiline_input(reader)
830838
self.assertEqual(output, input_code)
839+
840+
841+
@skipUnless(pty, "requires pty")
842+
class TestMain(TestCase):
843+
@force_not_colorized
844+
def test_exposed_globals_in_repl(self):
845+
expected_output = (
846+
"[\'__annotations__\', \'__builtins__\', \'__doc__\', \'__loader__\', "
847+
"\'__name__\', \'__package__\', \'__spec__\']"
848+
)
849+
output, exit_code = self.run_repl(["sorted(dir())", "exit"])
850+
if "can\'t use pyrepl" in output:
851+
self.skipTest("pyrepl not available")
852+
self.assertEqual(exit_code, 0)
853+
self.assertIn(expected_output, output)
854+
855+
def test_dumb_terminal_exits_cleanly(self):
856+
env = os.environ.copy()
857+
env.update({"TERM": "dumb"})
858+
output, exit_code = self.run_repl("exit()\n", env=env)
859+
self.assertEqual(exit_code, 0)
860+
self.assertIn("warning: can\'t use pyrepl", output)
861+
self.assertNotIn("Exception", output)
862+
self.assertNotIn("Traceback", output)
863+
864+
def run_repl(self, repl_input: str | list[str], env: dict | None = None) -> tuple[str, int]:
865+
master_fd, slave_fd = pty.openpty()
866+
process = subprocess.Popen(
867+
[sys.executable, "-i", "-u"],
868+
stdin=slave_fd,
869+
stdout=slave_fd,
870+
stderr=slave_fd,
871+
text=True,
872+
close_fds=True,
873+
env=env if env else os.environ,
874+
)
875+
if isinstance(repl_input, list):
876+
repl_input = "\n".join(repl_input) + "\n"
877+
os.write(master_fd, repl_input.encode("utf-8"))
878+
879+
output = []
880+
while select.select([master_fd], [], [], 0.5)[0]:
881+
data = os.read(master_fd, 1024).decode("utf-8")
882+
if not data:
883+
break
884+
output.append(data)
885+
886+
os.close(master_fd)
887+
os.close(slave_fd)
888+
exit_code = process.wait()
889+
return "\n".join(output), exit_code

‎Lib/test/test_repl.py

Copy file name to clipboardExpand all lines: Lib/test/test_repl.py
+2-3Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Test the interactive interpreter."""
22

3-
import sys
43
import os
5-
import unittest
64
import subprocess
5+
import sys
6+
import unittest
77
from textwrap import dedent
88
from test import support
99
from test.support import cpython_only, has_subprocess_support, SuppressCrashReport
@@ -199,7 +199,6 @@ def test_asyncio_repl_is_ok(self):
199199
assert_python_ok("-m", "asyncio")
200200

201201

202-
203202
class TestInteractiveModeSyntaxErrors(unittest.TestCase):
204203

205204
def test_interactive_syntax_error_correct_line(self):
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Limit exposed globals from internal imports and definitions on new REPL
2+
startup. Patch by Eugene Triguba and Pablo Galindo.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.