[3.13] gh-118908: Limit exposed globals from internal imports and definitions on new REPL startup (GH-119547) (#120362)

This commit is contained in:
Miss Islington (bot) 2024-06-11 20:04:39 +02:00 committed by GitHub
parent 51bcb67405
commit f5289c450a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 83 additions and 8 deletions

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -0,0 +1,2 @@
Limit exposed globals from internal imports and definitions on new REPL
startup. Patch by Eugene Triguba and Pablo Galindo.