Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5974757

Browse files
author
NGRsoftlab
authored
Merge branch 'python:main' into fix-issue-116180
2 parents 7f4c496 + 1aa8bbe commit 5974757

File tree

10 files changed

+121
-152
lines changed

10 files changed

+121
-152
lines changed

Doc/c-api/exceptions.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,14 @@ For convenience, some of these functions will always return a
221221
222222
.. c:function:: PyObject* PyErr_SetFromWindowsErr(int ierr)
223223
224-
This is a convenience function to raise :exc:`WindowsError`. If called with
224+
This is a convenience function to raise :exc:`OSError`. If called with
225225
*ierr* of ``0``, the error code returned by a call to :c:func:`!GetLastError`
226226
is used instead. It calls the Win32 function :c:func:`!FormatMessage` to retrieve
227227
the Windows description of error code given by *ierr* or :c:func:`!GetLastError`,
228-
then it constructs a tuple object whose first item is the *ierr* value and whose
229-
second item is the corresponding error message (gotten from
230-
:c:func:`!FormatMessage`), and then calls ``PyErr_SetObject(PyExc_WindowsError,
228+
then it constructs a :exc:`OSError` object with the :attr:`~OSError.winerror`
229+
attribute set to the error code, the :attr:`~OSError.strerror` attribute
230+
set to the corresponding error message (gotten from
231+
:c:func:`!FormatMessage`), and then calls ``PyErr_SetObject(PyExc_OSError,
231232
object)``. This function always returns ``NULL``.
232233
233234
.. availability:: Windows.

Include/cpython/object.h

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,8 @@ without deallocating anything (and so unbounded call-stack depth is avoided).
448448
When the call stack finishes unwinding again, code generated by the END macro
449449
notices this, and calls another routine to deallocate all the objects that
450450
may have been added to the list of deferred deallocations. In effect, a
451-
chain of N deallocations is broken into (N-1)/(_PyTrash_UNWIND_LEVEL-1) pieces,
452-
with the call stack never exceeding a depth of _PyTrash_UNWIND_LEVEL.
451+
chain of N deallocations is broken into (N-1)/(Py_TRASHCAN_HEADROOM-1) pieces,
452+
with the call stack never exceeding a depth of Py_TRASHCAN_HEADROOM.
453453
454454
Since the tp_dealloc of a subclass typically calls the tp_dealloc of the base
455455
class, we need to ensure that the trashcan is only triggered on the tp_dealloc
@@ -461,30 +461,33 @@ passed as second argument to Py_TRASHCAN_BEGIN().
461461
/* Python 3.9 private API, invoked by the macros below. */
462462
PyAPI_FUNC(int) _PyTrash_begin(PyThreadState *tstate, PyObject *op);
463463
PyAPI_FUNC(void) _PyTrash_end(PyThreadState *tstate);
464+
465+
PyAPI_FUNC(void) _PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op);
466+
PyAPI_FUNC(void) _PyTrash_thread_destroy_chain(PyThreadState *tstate);
467+
468+
464469
/* Python 3.10 private API, invoked by the Py_TRASHCAN_BEGIN(). */
465-
PyAPI_FUNC(int) _PyTrash_cond(PyObject *op, destructor dealloc);
466470

467-
#define Py_TRASHCAN_BEGIN_CONDITION(op, cond) \
468-
do { \
469-
PyThreadState *_tstate = NULL; \
470-
/* If "cond" is false, then _tstate remains NULL and the deallocator \
471-
* is run normally without involving the trashcan */ \
472-
if (cond) { \
473-
_tstate = PyThreadState_GetUnchecked(); \
474-
if (_PyTrash_begin(_tstate, _PyObject_CAST(op))) { \
475-
break; \
476-
} \
477-
}
478-
/* The body of the deallocator is here. */
479-
#define Py_TRASHCAN_END \
480-
if (_tstate) { \
481-
_PyTrash_end(_tstate); \
482-
} \
483-
} while (0);
471+
/* To avoid raising recursion errors during dealloc trigger trashcan before we reach
472+
* recursion limit. To avoid trashing, we don't attempt to empty the trashcan until
473+
* we have headroom above the trigger limit */
474+
#define Py_TRASHCAN_HEADROOM 50
484475

485476
#define Py_TRASHCAN_BEGIN(op, dealloc) \
486-
Py_TRASHCAN_BEGIN_CONDITION((op), \
487-
_PyTrash_cond(_PyObject_CAST(op), (destructor)(dealloc)))
477+
do { \
478+
PyThreadState *tstate = PyThreadState_Get(); \
479+
if (tstate->c_recursion_remaining <= Py_TRASHCAN_HEADROOM && Py_TYPE(op)->tp_dealloc == (destructor)dealloc) { \
480+
_PyTrash_thread_deposit_object(tstate, (PyObject *)op); \
481+
break; \
482+
} \
483+
tstate->c_recursion_remaining--;
484+
/* The body of the deallocator is here. */
485+
#define Py_TRASHCAN_END \
486+
tstate->c_recursion_remaining++; \
487+
if (tstate->delete_later && tstate->c_recursion_remaining > (Py_TRASHCAN_HEADROOM*2)) { \
488+
_PyTrash_thread_destroy_chain(tstate); \
489+
} \
490+
} while (0);
488491

489492

490493
PyAPI_FUNC(void *) PyObject_GetItemData(PyObject *obj);

Include/cpython/pystate.h

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,6 @@ typedef struct _stack_chunk {
5656
PyObject * data[1]; /* Variable sized */
5757
} _PyStackChunk;
5858

59-
struct _py_trashcan {
60-
int delete_nesting;
61-
PyObject *delete_later;
62-
};
63-
6459
struct _ts {
6560
/* See Python/ceval.c for comments explaining most fields */
6661

@@ -152,7 +147,7 @@ struct _ts {
152147
*/
153148
unsigned long native_thread_id;
154149

155-
struct _py_trashcan trash;
150+
PyObject *delete_later;
156151

157152
/* Tagged pointer to top-most critical section, or zero if there is no
158153
* active critical section. Critical sections are only used in

Include/internal/pycore_object.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,6 @@ _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(PyObject *op)
615615
return (PyWeakReference **)((char *)op + offset);
616616
}
617617

618-
619618
// Fast inlined version of PyObject_IS_GC()
620619
static inline int
621620
_PyObject_IS_GC(PyObject *obj)

Lib/email/_header_value_parser.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,7 +1213,7 @@ def get_bare_quoted_string(value):
12131213
value is the text between the quote marks, with whitespace
12141214
preserved and quoted pairs decoded.
12151215
"""
1216-
if value[0] != '"':
1216+
if not value or value[0] != '"':
12171217
raise errors.HeaderParseError(
12181218
"expected '\"' but found '{}'".format(value))
12191219
bare_quoted_string = BareQuotedString()
@@ -1454,7 +1454,7 @@ def get_local_part(value):
14541454
"""
14551455
local_part = LocalPart()
14561456
leader = None
1457-
if value[0] in CFWS_LEADER:
1457+
if value and value[0] in CFWS_LEADER:
14581458
leader, value = get_cfws(value)
14591459
if not value:
14601460
raise errors.HeaderParseError(
@@ -1613,7 +1613,7 @@ def get_domain(value):
16131613
"""
16141614
domain = Domain()
16151615
leader = None
1616-
if value[0] in CFWS_LEADER:
1616+
if value and value[0] in CFWS_LEADER:
16171617
leader, value = get_cfws(value)
16181618
if not value:
16191619
raise errors.HeaderParseError(
@@ -1689,6 +1689,8 @@ def get_obs_route(value):
16891689
if value[0] in CFWS_LEADER:
16901690
token, value = get_cfws(value)
16911691
obs_route.append(token)
1692+
if not value:
1693+
break
16921694
if value[0] == '@':
16931695
obs_route.append(RouteComponentMarker)
16941696
token, value = get_domain(value[1:])
@@ -1707,7 +1709,7 @@ def get_angle_addr(value):
17071709
17081710
"""
17091711
angle_addr = AngleAddr()
1710-
if value[0] in CFWS_LEADER:
1712+
if value and value[0] in CFWS_LEADER:
17111713
token, value = get_cfws(value)
17121714
angle_addr.append(token)
17131715
if not value or value[0] != '<':
@@ -1717,7 +1719,7 @@ def get_angle_addr(value):
17171719
value = value[1:]
17181720
# Although it is not legal per RFC5322, SMTP uses '<>' in certain
17191721
# circumstances.
1720-
if value[0] == '>':
1722+
if value and value[0] == '>':
17211723
angle_addr.append(ValueTerminal('>', 'angle-addr-end'))
17221724
angle_addr.defects.append(errors.InvalidHeaderDefect(
17231725
"null addr-spec in angle-addr"))
@@ -1769,6 +1771,9 @@ def get_name_addr(value):
17691771
name_addr = NameAddr()
17701772
# Both the optional display name and the angle-addr can start with cfws.
17711773
leader = None
1774+
if not value:
1775+
raise errors.HeaderParseError(
1776+
"expected name-addr but found '{}'".format(value))
17721777
if value[0] in CFWS_LEADER:
17731778
leader, value = get_cfws(value)
17741779
if not value:

Lib/pydoc.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,11 +1637,11 @@ def bold(self, text):
16371637

16381638
# --------------------------------------------------------- user interfaces
16391639

1640-
def pager(text):
1640+
def pager(text, title=''):
16411641
"""The first time this is called, determine what kind of pager to use."""
16421642
global pager
16431643
pager = getpager()
1644-
pager(text)
1644+
pager(text, title)
16451645

16461646
def getpager():
16471647
"""Decide what method to use for paging through text."""
@@ -1656,24 +1656,24 @@ def getpager():
16561656
use_pager = os.environ.get('MANPAGER') or os.environ.get('PAGER')
16571657
if use_pager:
16581658
if sys.platform == 'win32': # pipes completely broken in Windows
1659-
return lambda text: tempfilepager(plain(text), use_pager)
1659+
return lambda text, title='': tempfilepager(plain(text), use_pager)
16601660
elif os.environ.get('TERM') in ('dumb', 'emacs'):
1661-
return lambda text: pipepager(plain(text), use_pager)
1661+
return lambda text, title='': pipepager(plain(text), use_pager, title)
16621662
else:
1663-
return lambda text: pipepager(text, use_pager)
1663+
return lambda text, title='': pipepager(text, use_pager, title)
16641664
if os.environ.get('TERM') in ('dumb', 'emacs'):
16651665
return plainpager
16661666
if sys.platform == 'win32':
1667-
return lambda text: tempfilepager(plain(text), 'more <')
1667+
return lambda text, title='': tempfilepager(plain(text), 'more <')
16681668
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
1669-
return lambda text: pipepager(text, 'less')
1669+
return lambda text, title='': pipepager(text, 'less', title)
16701670

16711671
import tempfile
16721672
(fd, filename) = tempfile.mkstemp()
16731673
os.close(fd)
16741674
try:
16751675
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
1676-
return lambda text: pipepager(text, 'more')
1676+
return lambda text, title='': pipepager(text, 'more', title)
16771677
else:
16781678
return ttypager
16791679
finally:
@@ -1683,12 +1683,18 @@ def plain(text):
16831683
"""Remove boldface formatting from text."""
16841684
return re.sub('.\b', '', text)
16851685

1686-
def pipepager(text, cmd):
1686+
def escape_less(s):
1687+
return re.sub(r'([?:.%\\])', r'\\\1', s)
1688+
1689+
def pipepager(text, cmd, title=''):
16871690
"""Page through text by feeding it to another program."""
16881691
import subprocess
16891692
env = os.environ.copy()
1693+
if title:
1694+
title += ' '
1695+
esc_title = escape_less(title)
16901696
prompt_string = (
1691-
' '
1697+
f' {esc_title}' +
16921698
'?ltline %lt?L/%L.'
16931699
':byte %bB?s/%s.'
16941700
'.'
@@ -1716,7 +1722,7 @@ def pipepager(text, cmd):
17161722
# left running and the terminal is in raw mode and unusable.
17171723
pass
17181724

1719-
def tempfilepager(text, cmd):
1725+
def tempfilepager(text, cmd, title=''):
17201726
"""Page through text by invoking a program on a temporary file."""
17211727
import tempfile
17221728
with tempfile.TemporaryDirectory() as tempdir:
@@ -1733,7 +1739,7 @@ def _escape_stdout(text):
17331739
encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8'
17341740
return text.encode(encoding, 'backslashreplace').decode(encoding)
17351741

1736-
def ttypager(text):
1742+
def ttypager(text, title=''):
17371743
"""Page through text on a text terminal."""
17381744
lines = plain(_escape_stdout(text)).split('\n')
17391745
try:
@@ -1777,7 +1783,7 @@ def ttypager(text):
17771783
if tty:
17781784
tty.tcsetattr(fd, tty.TCSAFLUSH, old)
17791785

1780-
def plainpager(text):
1786+
def plainpager(text, title=''):
17811787
"""Simply print unformatted text. This is the ultimate fallback."""
17821788
sys.stdout.write(plain(_escape_stdout(text)))
17831789

@@ -1879,7 +1885,8 @@ def doc(thing, title='Python Library Documentation: %s', forceload=0,
18791885
"""Display text documentation, given an object or a path to an object."""
18801886
if output is None:
18811887
try:
1882-
pager(render_doc(thing, title, forceload))
1888+
what = thing if isinstance(thing, str) else type(thing).__name__
1889+
pager(render_doc(thing, title, forceload), f'Help on {what!s}')
18831890
except ImportError as exc:
18841891
if is_cli:
18851892
raise
@@ -2253,7 +2260,7 @@ def showtopic(self, topic, more_xrefs=''):
22532260
text = 'Related help topics: ' + ', '.join(xrefs.split()) + '\n'
22542261
wrapped_text = textwrap.wrap(text, 72)
22552262
doc += '\n%s\n' % '\n'.join(wrapped_text)
2256-
pager(doc)
2263+
pager(doc, f'Help on {topic!s}')
22572264

22582265
def _gettopic(self, topic, more_xrefs=''):
22592266
"""Return unbuffered tuple of (topic, xrefs).

Lib/test/test_email/test__header_value_parser.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,10 @@ def test_get_quoted_string_header_ends_in_qcontent(self):
801801
self.assertEqual(qs.content, 'bob')
802802
self.assertEqual(qs.quoted_value, ' "bob"')
803803

804+
def test_get_quoted_string_cfws_only_raises(self):
805+
with self.assertRaises(errors.HeaderParseError):
806+
parser.get_quoted_string(' (foo) ')
807+
804808
def test_get_quoted_string_no_quoted_string(self):
805809
with self.assertRaises(errors.HeaderParseError):
806810
parser.get_quoted_string(' (ab) xyz')
@@ -1135,6 +1139,10 @@ def test_get_local_part_complex_obsolete_invalid(self):
11351139
'@python.org')
11361140
self.assertEqual(local_part.local_part, 'Fred.A.Johnson and dogs')
11371141

1142+
def test_get_local_part_empty_raises(self):
1143+
with self.assertRaises(errors.HeaderParseError):
1144+
parser.get_local_part('')
1145+
11381146
def test_get_local_part_no_part_raises(self):
11391147
with self.assertRaises(errors.HeaderParseError):
11401148
parser.get_local_part(' (foo) ')
@@ -1387,6 +1395,10 @@ def test_get_domain_obsolete(self):
13871395
'')
13881396
self.assertEqual(domain.domain, 'example.com')
13891397

1398+
def test_get_domain_empty_raises(self):
1399+
with self.assertRaises(errors.HeaderParseError):
1400+
parser.get_domain("")
1401+
13901402
def test_get_domain_no_non_cfws_raises(self):
13911403
with self.assertRaises(errors.HeaderParseError):
13921404
parser.get_domain(" (foo)\t")
@@ -1512,6 +1524,10 @@ def test_get_obs_route_no_route_before_end_raises(self):
15121524
with self.assertRaises(errors.HeaderParseError):
15131525
parser.get_obs_route('(foo) @example.com,')
15141526

1527+
def test_get_obs_route_no_route_before_end_raises2(self):
1528+
with self.assertRaises(errors.HeaderParseError):
1529+
parser.get_obs_route('(foo) @example.com, (foo) ')
1530+
15151531
def test_get_obs_route_no_route_before_special_raises(self):
15161532
with self.assertRaises(errors.HeaderParseError):
15171533
parser.get_obs_route('(foo) [abc],')
@@ -1520,6 +1536,14 @@ def test_get_obs_route_no_route_before_special_raises2(self):
15201536
with self.assertRaises(errors.HeaderParseError):
15211537
parser.get_obs_route('(foo) @example.com [abc],')
15221538

1539+
def test_get_obs_route_no_domain_after_at_raises(self):
1540+
with self.assertRaises(errors.HeaderParseError):
1541+
parser.get_obs_route('@')
1542+
1543+
def test_get_obs_route_no_domain_after_at_raises2(self):
1544+
with self.assertRaises(errors.HeaderParseError):
1545+
parser.get_obs_route('@example.com, @')
1546+
15231547
# get_angle_addr
15241548

15251549
def test_get_angle_addr_simple(self):
@@ -1646,6 +1670,14 @@ def test_get_angle_addr_ends_at_special(self):
16461670
self.assertIsNone(angle_addr.route)
16471671
self.assertEqual(angle_addr.addr_spec, '[email protected]')
16481672

1673+
def test_get_angle_addr_empty_raise(self):
1674+
with self.assertRaises(errors.HeaderParseError):
1675+
parser.get_angle_addr('')
1676+
1677+
def test_get_angle_addr_left_angle_only_raise(self):
1678+
with self.assertRaises(errors.HeaderParseError):
1679+
parser.get_angle_addr('<')
1680+
16491681
def test_get_angle_addr_no_angle_raise(self):
16501682
with self.assertRaises(errors.HeaderParseError):
16511683
parser.get_angle_addr('(foo) ')
@@ -1857,6 +1889,10 @@ def test_get_name_addr_ends_at_special(self):
18571889
self.assertIsNone(name_addr.route)
18581890
self.assertEqual(name_addr.addr_spec, '[email protected]')
18591891

1892+
def test_get_name_addr_empty_raises(self):
1893+
with self.assertRaises(errors.HeaderParseError):
1894+
parser.get_name_addr('')
1895+
18601896
def test_get_name_addr_no_content_raises(self):
18611897
with self.assertRaises(errors.HeaderParseError):
18621898
parser.get_name_addr(' (foo) ')
@@ -2732,6 +2768,10 @@ def test_get_msg_id_empty_id_right(self):
27322768
with self.assertRaises(errors.HeaderParseError):
27332769
parser.get_msg_id("<simplelocal@>")
27342770

2771+
def test_get_msg_id_no_id_right(self):
2772+
with self.assertRaises(errors.HeaderParseError):
2773+
parser.get_msg_id("<simplelocal@")
2774+
27352775
def test_get_msg_id_with_brackets(self):
27362776
# Microsof Outlook generates non-standard one-off addresses:
27372777
# https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/one-off-addresses
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix several IndexError when parse emails with truncated Message-ID, address, routes, etc, e.g. ``example@``.

0 commit comments

Comments
 (0)