mirror of
https://github.com/python/cpython.git
synced 2025-08-02 16:13:13 +00:00
[3.13] gh-118908: Limit exposed globals from internal imports and definitions on new REPL startup (GH-119547) (#120362)
This commit is contained in:
parent
51bcb67405
commit
f5289c450a
4 changed files with 83 additions and 8 deletions
|
@ -27,6 +27,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import _sitebuiltins
|
import _sitebuiltins
|
||||||
import linecache
|
import linecache
|
||||||
|
import builtins
|
||||||
import sys
|
import sys
|
||||||
import code
|
import code
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
@ -34,6 +35,12 @@ from types import ModuleType
|
||||||
from .console import InteractiveColoredConsole
|
from .console import InteractiveColoredConsole
|
||||||
from .readline import _get_reader, multiline_input
|
from .readline import _get_reader, multiline_input
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
_error: tuple[type[Exception], ...] | type[Exception]
|
_error: tuple[type[Exception], ...] | type[Exception]
|
||||||
try:
|
try:
|
||||||
from .unix_console import _error
|
from .unix_console import _error
|
||||||
|
@ -73,20 +80,28 @@ REPL_COMMANDS = {
|
||||||
"clear": _clear_screen,
|
"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(
|
def run_multiline_interactive_console(
|
||||||
mainmodule: ModuleType | None = None,
|
mainmodule: ModuleType | None = None,
|
||||||
future_flags: int = 0,
|
future_flags: int = 0,
|
||||||
console: code.InteractiveConsole | None = None,
|
console: code.InteractiveConsole | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
import __main__
|
|
||||||
from .readline import _setup
|
from .readline import _setup
|
||||||
_setup()
|
_setup()
|
||||||
|
|
||||||
mainmodule = mainmodule or __main__
|
namespace = mainmodule.__dict__ if mainmodule else DEFAULT_NAMESPACE
|
||||||
if console is None:
|
if console is None:
|
||||||
console = InteractiveColoredConsole(
|
console = InteractiveColoredConsole(
|
||||||
mainmodule.__dict__, filename="<stdin>"
|
namespace, filename="<stdin>"
|
||||||
)
|
)
|
||||||
if future_flags:
|
if future_flags:
|
||||||
console.compile.compiler.flags |= future_flags
|
console.compile.compiler.flags |= future_flags
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import itertools
|
|
||||||
import io
|
import io
|
||||||
|
import itertools
|
||||||
import os
|
import os
|
||||||
import rlcompleter
|
import rlcompleter
|
||||||
from unittest import TestCase
|
import select
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from unittest import TestCase, skipUnless
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from test.support import force_not_colorized
|
||||||
|
|
||||||
from .support import (
|
from .support import (
|
||||||
FakeConsole,
|
FakeConsole,
|
||||||
|
@ -17,6 +21,10 @@ from _pyrepl.console import Event
|
||||||
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
|
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
|
||||||
from _pyrepl.readline import multiline_input as readline_multiline_input
|
from _pyrepl.readline import multiline_input as readline_multiline_input
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pty
|
||||||
|
except ImportError:
|
||||||
|
pty = None
|
||||||
|
|
||||||
class TestCursorPosition(TestCase):
|
class TestCursorPosition(TestCase):
|
||||||
def prepare_reader(self, events):
|
def prepare_reader(self, events):
|
||||||
|
@ -828,3 +836,54 @@ class TestPasteEvent(TestCase):
|
||||||
reader = self.prepare_reader(events)
|
reader = self.prepare_reader(events)
|
||||||
output = multiline_input(reader)
|
output = multiline_input(reader)
|
||||||
self.assertEqual(output, input_code)
|
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."""
|
"""Test the interactive interpreter."""
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
import unittest
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import cpython_only, has_subprocess_support, SuppressCrashReport
|
from test.support import cpython_only, has_subprocess_support, SuppressCrashReport
|
||||||
|
@ -199,7 +199,6 @@ class TestInteractiveInterpreter(unittest.TestCase):
|
||||||
assert_python_ok("-m", "asyncio")
|
assert_python_ok("-m", "asyncio")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestInteractiveModeSyntaxErrors(unittest.TestCase):
|
class TestInteractiveModeSyntaxErrors(unittest.TestCase):
|
||||||
|
|
||||||
def test_interactive_syntax_error_correct_line(self):
|
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…
Add table
Add a link
Reference in a new issue