mirror of https://github.com/python/cpython
gh-118908: Limit exposed globals from internal imports and definitions on new REPL startup (#119547)
This commit is contained in:
parent
0335662fe1
commit
86a8a1c57a
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Limit exposed globals from internal imports and definitions on new REPL
|
||||
startup. Patch by Eugene Triguba and Pablo Galindo.
|
Loading…
Reference in New Issue