Skip to content

Commit b79a6f4

Browse files
committed
Merge remote-tracking branch 'origin/main' into gh-159117-profiler-dealloc
2 parents 8f8ca16 + 6af6553 commit b79a6f4

31 files changed

Lines changed: 538 additions & 158 deletions

‎Doc/library/mailbox.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ accessing and manipulating on-disk mailboxes and the messages they contain.
1515
:class:`~email.message.Message` class with format-specific state and behavior.
1616
Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF.
1717

18+
.. warning::
19+
20+
The :mod:`!mailbox` module is designed for use in trusted environments,
21+
where data from untrusted sources is never passed to the module's API
22+
calls and only trusted users have access to the filesystems accessible
23+
to the module.
1824

1925
.. seealso::
2026

‎Include/errcode.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ extern "C" {
3838
#define E_BADSINGLE 27 /* Ill-formed single statement input */
3939
#define E_INTERACT_STOP 28 /* Interactive mode stopped tokenization */
4040
#define E_COLUMNOVERFLOW 29 /* Column offset overflow */
41+
#define E_INCOMPLETE 30 /* An unfinished token in partial input */
4142

4243
#ifdef __cplusplus
4344
}

‎Include/internal/pycore_parser.h‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,17 @@ _Py_DECLARE_STR(empty, "")
2727
}, \
2828
}
2929

30+
/* The caller must initialize *incomplete to NULL if supplied. A returned AST
31+
can also return an owned exception there for a missing line terminator.
32+
Validate the AST before reporting that exception. */
3033
extern struct _mod* _PyParser_ASTFromString(
3134
const char *str,
3235
PyObject* filename,
3336
int mode,
3437
PyCompilerFlags *flags,
3538
PyArena *arena,
36-
PyObject *module);
39+
PyObject *module,
40+
PyObject **incomplete);
3741

3842
extern struct _mod* _PyParser_ASTFromFile(
3943
FILE *fp,

‎Lib/codeop.py‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,10 @@ def _maybe_compile(compiler, source, filename, symbol, flags):
6262
warnings.simplefilter("ignore", (SyntaxWarning, DeprecationWarning))
6363
try:
6464
compiler(source, filename, symbol, flags=flags)
65+
except _IncompleteInputError:
66+
return None
6567
except SyntaxError: # Let other compile() errors propagate.
66-
try:
67-
compiler(source + "\n", filename, symbol, flags=flags)
68-
return None
69-
except _IncompleteInputError:
70-
return None
71-
except SyntaxError:
72-
pass
73-
# fallthrough
68+
pass
7469

7570
return compiler(source, filename, symbol, incomplete_input=False)
7671

‎Lib/test/support/__init__.py‎

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -348,16 +348,17 @@ def requires(resource, msg=None):
348348
if resource == 'gui' and not _is_gui_available():
349349
raise ResourceDenied(_is_gui_available.reason)
350350

351-
def _requires_unix_version(sysname, min_version):
352-
"""Decorator raising SkipTest if the OS is `sysname` and the version is less
351+
352+
def _requires_platform_version(name, sys_platform, get_version_text, min_version):
353+
"""
354+
Decorator raising SkipTest if the OS is `sysname` and the version is less
353355
than `min_version`.
354356
355-
For example, @_requires_unix_version('FreeBSD', (7, 2)) raises SkipTest if
356-
the FreeBSD version is less than 7.2.
357+
For example, @_requires_platform_version('FreeBSD', 'freebsd', (7, 2))
358+
raises SkipTest if the FreeBSD version is less than 7.2.
357359
"""
358-
min_version_txt = '.'.join(map(str, min_version))
359-
version_txt = platform.release().split('-', 1)[0]
360-
if platform.system() == sysname:
360+
version_txt = get_version_text()
361+
if sys.platform == sys_platform:
361362
try:
362363
version = tuple(map(int, version_txt.split('.')))
363364
except ValueError:
@@ -367,21 +368,31 @@ def _requires_unix_version(sysname, min_version):
367368
else:
368369
skip = False
369370

371+
min_version_txt = '.'.join(map(str, min_version))
370372
return unittest.skipIf(
371373
skip,
372-
f"{sysname} version {min_version_txt} or higher required, not "
374+
f"{name} version {min_version_txt} or higher required, not "
373375
f"{version_txt}"
374376
)
375377

376378

379+
def _get_platform_release():
380+
# Replace '7.2.8-200.fc44.x86_64' with '7.2.8'
381+
return platform.release().partition('-')[0]
382+
383+
377384
def requires_freebsd_version(*min_version):
378385
"""Decorator raising SkipTest if the OS is FreeBSD and the FreeBSD version is
379386
less than `min_version`.
380387
381388
For example, @requires_freebsd_version(7, 2) raises SkipTest if the FreeBSD
382389
version is less than 7.2.
383390
"""
384-
return _requires_unix_version('FreeBSD', min_version)
391+
return _requires_platform_version(
392+
'FreeBSD', 'freebsd',
393+
get_version_text=_get_platform_release,
394+
min_version=min_version)
395+
385396

386397
def requires_linux_version(*min_version):
387398
"""Decorator raising SkipTest if the OS is Linux and the Linux version is
@@ -390,28 +401,10 @@ def requires_linux_version(*min_version):
390401
For example, @requires_linux_version(2, 6, 32) raises SkipTest if the Linux
391402
version is less than 2.6.32.
392403
"""
393-
return _requires_unix_version('Linux', min_version)
394-
395-
def _requires_apple_os_version(name, sys_platform, get_version_text, min_version):
396-
def decorator(func):
397-
@functools.wraps(func)
398-
def wrapper(*args, **kw):
399-
if sys.platform == sys_platform:
400-
version_txt = get_version_text()
401-
try:
402-
version = tuple(map(int, version_txt.split('.')))
403-
except ValueError:
404-
pass
405-
else:
406-
if version < min_version:
407-
min_version_txt = '.'.join(map(str, min_version))
408-
raise unittest.SkipTest(
409-
f"{name} {min_version_txt} or higher required, "
410-
+ f"not {version_txt}")
411-
return func(*args, **kw)
412-
wrapper.min_version = min_version
413-
return wrapper
414-
return decorator
404+
return _requires_platform_version(
405+
"Linux", "linux",
406+
get_version_text=_get_platform_release,
407+
min_version=min_version)
415408

416409

417410
def requires_mac_ver(*min_version):
@@ -421,7 +414,7 @@ def requires_mac_ver(*min_version):
421414
For example, @requires_mac_ver(10, 5) raises SkipTest if the OS X version
422415
is lesser than 10.5.
423416
"""
424-
return _requires_apple_os_version(
417+
return _requires_platform_version(
425418
"macOS", "darwin",
426419
get_version_text=lambda: platform.mac_ver()[0],
427420
min_version=min_version)
@@ -434,7 +427,7 @@ def requires_ios_ver(*min_version):
434427
For example, requires_ios_ver(16, 0) raises SkipTest if the iOS version
435428
is lesser than 16.0.
436429
"""
437-
return _requires_apple_os_version(
430+
return _requires_platform_version(
438431
"iOS", "ios",
439432
get_version_text=lambda: platform.ios_ver().release,
440433
min_version=min_version)

‎Lib/test/test_codeop.py‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,99 @@ def test_incomplete_string_diagnostics(self, symbol, prefix):
124124
self.assertEqual(cm.exception.args, (
125125
'incomplete input', ('<input>', 2, 9, text, 2, -1)))
126126

127+
@subTests('compiler', COMPILERS)
128+
@subTests('newline', ('\n', '\r', '\r\n'))
129+
def test_unterminated_trailing_line(self, compiler, newline):
130+
for head, symbol in (('1', 'eval'), ('if 1: pass', 'single'),
131+
('if 1:\n pass', 'single')):
132+
for tail in (' ', '\t', '\f ', '\n '):
133+
source = head + newline + tail
134+
with self.subTest(source=source, symbol=symbol):
135+
if compiler in RAW_COMPILERS:
136+
with self.assertRaises(_IncompleteInputError):
137+
compiler(source, '<input>', symbol)
138+
else:
139+
self.assertIsNone(compiler(source, '<input>', symbol))
140+
self.assertValid(source + '\n', symbol, compiler=compiler)
141+
for tail in ('\f', ' \f', '\n\f'):
142+
with self.subTest(head=head, tail=tail, symbol=symbol):
143+
self.assertValid(head + newline + tail, symbol,
144+
compiler=compiler)
145+
146+
for tail in ('# comment', ' # comment'):
147+
self.assertValid('if 1: pass' + newline + tail, compiler=compiler)
148+
self.assertValid('1' + newline + tail, 'eval', compiler=compiler)
149+
150+
for head in ('1', 'if 1: pass', 'if 1:\n pass'):
151+
for tail in (' ', '\t', '\f', '# comment'):
152+
self.assertValid(head + newline + tail, 'exec',
153+
compiler=compiler)
154+
155+
@subTests('symbol', ('single', 'exec', 'eval'))
156+
def test_unfinished_token_in_invalid_position(self, symbol):
157+
for source in ('a b', '(a b', '1 """', '(1 """',
158+
'f"{lambda:\\', 't"{lambda:\\'):
159+
for compiler in COMPILERS:
160+
with self.subTest(source=source, compiler=compiler):
161+
with self.assertRaises(SyntaxError) as cm:
162+
compiler(source, '<input>', symbol)
163+
self.assertNotIsInstance(cm.exception, _IncompleteInputError)
164+
165+
@subTests('symbol', ('single', 'exec', 'eval'))
166+
def test_invalid_partial_string_diagnostic(self, symbol):
167+
with self.assertRaisesRegex(SyntaxError, 'invalid syntax'):
168+
Compile()('a"""', '<input>', symbol)
169+
170+
@subTests('symbol', ('single', 'exec', 'eval'))
171+
@subTests('prefix', ('', 'r', 'b', 'f', 't', 'rf', 'rt'))
172+
def test_string_continuation(self, symbol, prefix):
173+
source = prefix + '"first\\'
174+
compile(source + '\nsecond"\n', '<input>', symbol)
175+
self.assertIsNone(compile_command(source, symbol=symbol))
176+
with self.assertRaises(_IncompleteInputError):
177+
Compile()(source, '<input>', symbol)
178+
179+
@subTests('symbol', ('single', 'exec', 'eval'))
180+
@subTests('newline', ('\n', '\r', '\r\n'))
181+
def test_continued_final_line(self, symbol, newline):
182+
source = '1\\' + newline
183+
compile(source + '+ 2\n', '<input>', symbol)
184+
self.assertIsNone(compile_command(source, symbol=symbol))
185+
with self.assertRaises(_IncompleteInputError):
186+
Compile()(source, '<input>', symbol)
187+
with self.assertRaises(SyntaxError) as cm:
188+
Compile()('1 + \\' + newline, '<input>', symbol)
189+
# Appending LF to CR completes the CRLF line terminator.
190+
if newline != '\r':
191+
self.assertNotIsInstance(cm.exception, _IncompleteInputError)
192+
193+
@subTests('symbol', ('single', 'exec'))
194+
def test_eof_before_nested_body(self, symbol):
195+
for source in ('if True:\n if True:',
196+
'if True:\n def f():',
197+
'match 1:\n case 1:'):
198+
for tail in ('', '\n', '\n\n'):
199+
with self.subTest(source=source, tail=tail):
200+
compile(source + tail + '\n pass\n', '<input>', symbol)
201+
self.assertIsNone(compile_command(source + tail, symbol=symbol))
202+
with self.assertRaises(_IncompleteInputError):
203+
Compile()(source + tail, '<input>', symbol)
204+
205+
def test_incomplete_suite_semantic_errors(self):
206+
cases = (
207+
('if True: break', "'break' outside loop"),
208+
('if True: return 1', "'return' outside function"),
209+
('def f(x, x):\n pass', 'duplicate parameter'),
210+
)
211+
for source, message in cases:
212+
for compiler in COMPILERS:
213+
with self.subTest(source=source, compiler=compiler):
214+
with self.assertRaises(SyntaxError) as cm:
215+
compiler(source, '<input>', 'single')
216+
self.assertNotIsInstance(cm.exception, _IncompleteInputError)
217+
if compiler in RAW_COMPILERS:
218+
self.assertIn(message, str(cm.exception))
219+
127220
@subTests('compiler', COMPILERS)
128221
def test_incomplete(self, compiler):
129222
ai = functools.partial(self.assertIncomplete, compiler=compiler)

‎Lib/test/test_curses.py‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
check_disallow_instantiation, MISSING_C_DOCSTRINGS,
1515
gc_collect, SHORT_TIMEOUT)
1616
from test.support.import_helper import import_module
17+
from test.support import os_helper
1718

1819
# Optionally test curses module. This currently requires that the
1920
# 'curses' resource be given on the regrtest command line using the -u
@@ -129,6 +130,9 @@ def _broken_variation_selector_width():
129130

130131
BROKEN_VARIATION_SELECTOR_WIDTH = _broken_variation_selector_width()
131132

133+
class CustomError(Exception):
134+
pass
135+
132136
# newterm() is used when available (it reports errors instead of exiting), but
133137
# initscr() is still the fallback, and an unusable $TERM has no terminal to
134138
# drive either way.
@@ -3353,6 +3357,50 @@ def test_set_term(self):
33533357
self.assertIs(curses.set_term(a), b) # returns the previous one
33543358
self.assertIs(curses.set_term(b), a)
33553359

3360+
def make_screens_of_different_terms(self):
3361+
# Two screens of different sizes and numbers of colors. Return them
3362+
# with the values of LINES, COLS, COLORS and COLOR_PAIRS for each.
3363+
screens = []
3364+
with os_helper.EnvironmentVarGuard() as env:
3365+
for term, lines, cols in [('xterm', 25, 80),
3366+
('xterm-256color', 30, 100)]:
3367+
env['LINES'] = str(lines)
3368+
env['COLUMNS'] = str(cols)
3369+
s = self.make_pty()
3370+
try:
3371+
screen = curses.newterm(term, s, s)
3372+
except curses.error:
3373+
self.skipTest(f'no {term} terminfo entry')
3374+
curses.start_color()
3375+
self.assertEqual(screen.stdscr.getmaxyx(), (lines, cols))
3376+
screens.append((screen, self.get_screen_values()))
3377+
self.assertNotEqual(screens[0][1], screens[1][1])
3378+
return screens
3379+
3380+
def get_screen_values(self):
3381+
return curses.LINES, curses.COLS, curses.COLORS, curses.COLOR_PAIRS
3382+
3383+
def test_set_term_refreshes_lines_cols_colors(self):
3384+
(a, avalues), (b, bvalues) = self.make_screens_of_different_terms()
3385+
self.assertIs(curses.set_term(a), b)
3386+
self.assertEqual(self.get_screen_values(), avalues)
3387+
self.assertIs(curses.set_term(b), a)
3388+
self.assertEqual(self.get_screen_values(), bvalues)
3389+
3390+
@unittest.skipUnless(hasattr(curses.screen, 'use'),
3391+
'requires curses.screen.use()')
3392+
def test_use_screen_refreshes_lines_cols_colors(self):
3393+
(a, avalues), (b, bvalues) = self.make_screens_of_different_terms()
3394+
self.assertEqual(a.use(lambda scr: self.get_screen_values()), avalues)
3395+
self.assertEqual(self.get_screen_values(), bvalues)
3396+
3397+
def raise_in_use(scr):
3398+
self.assertEqual(self.get_screen_values(), avalues)
3399+
raise CustomError
3400+
with self.assertRaises(CustomError):
3401+
a.use(raise_in_use)
3402+
self.assertEqual(self.get_screen_values(), bvalues)
3403+
33563404
def test_window_keeps_screen_alive(self):
33573405
# The standard window keeps its screen alive; dropping every other
33583406
# reference and collecting must not invalidate the window.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve incomplete input detection in :mod:`codeop` without an extra parser
2+
pass, and preserve syntax and compiler errors.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`curses.set_term` and :meth:`curses.screen.use` now update
2+
:data:`curses.LINES`, :data:`curses.COLS`, :data:`curses.COLORS`
3+
and :data:`curses.COLOR_PAIRS` for the current screen.

0 commit comments

Comments
 (0)