Skip to content

Commit 433bb6c

Browse files
Merge branch 'main' into gh-113148-handle-exceptions-at-thread-exit
2 parents 854dfab + 9e474a9 commit 433bb6c

33 files changed

+837
-421
lines changed

Doc/library/email.errors.rst

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,12 @@ The following exception classes are defined in the :mod:`email.errors` module:
4545

4646
.. exception:: MultipartConversionError()
4747

48-
Raised when a payload is added to a :class:`~email.message.Message` object
49-
using :meth:`add_payload`, but the payload is already a scalar and the
50-
message's :mailheader:`Content-Type` main type is not either
51-
:mimetype:`multipart` or missing. :exc:`MultipartConversionError` multiply
52-
inherits from :exc:`MessageError` and the built-in :exc:`TypeError`.
53-
54-
Since :meth:`Message.add_payload` is deprecated, this exception is rarely
55-
raised in practice. However the exception may also be raised if the
56-
:meth:`~email.message.Message.attach`
57-
method is called on an instance of a class derived from
48+
Raised if the :meth:`~email.message.Message.attach` method is called
49+
on an instance of a class derived from
5850
:class:`~email.mime.nonmultipart.MIMENonMultipart` (e.g.
5951
:class:`~email.mime.image.MIMEImage`).
52+
:exc:`MultipartConversionError` multiply
53+
inherits from :exc:`MessageError` and the built-in :exc:`TypeError`.
6054

6155

6256
.. exception:: HeaderWriteError()

Include/cpython/object.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,13 +494,13 @@ PyAPI_FUNC(int) _Py_ReachedRecursionLimitWithMargin(PyThreadState *tstate, int m
494494
#define Py_TRASHCAN_BEGIN(op, dealloc) \
495495
do { \
496496
PyThreadState *tstate = PyThreadState_Get(); \
497-
if (_Py_ReachedRecursionLimitWithMargin(tstate, 1) && Py_TYPE(op)->tp_dealloc == (destructor)dealloc) { \
497+
if (_Py_ReachedRecursionLimitWithMargin(tstate, 2) && Py_TYPE(op)->tp_dealloc == (destructor)dealloc) { \
498498
_PyTrash_thread_deposit_object(tstate, (PyObject *)op); \
499499
break; \
500500
}
501501
/* The body of the deallocator is here. */
502502
#define Py_TRASHCAN_END \
503-
if (tstate->delete_later && !_Py_ReachedRecursionLimitWithMargin(tstate, 2)) { \
503+
if (tstate->delete_later && !_Py_ReachedRecursionLimitWithMargin(tstate, 4)) { \
504504
_PyTrash_thread_destroy_chain(tstate); \
505505
} \
506506
} while (0);

Include/internal/pycore_sysmodule.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11-
// Export for '_pickle' shared extension
12-
PyAPI_FUNC(PyObject*) _PySys_GetAttr(PyThreadState *tstate, PyObject *name);
11+
PyAPI_FUNC(int) _PySys_GetOptionalAttr(PyObject *, PyObject **);
12+
PyAPI_FUNC(int) _PySys_GetOptionalAttrString(const char *, PyObject **);
13+
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttr(PyObject *);
14+
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttrString(const char *);
1315

1416
// Export for '_pickle' shared extension
1517
PyAPI_FUNC(size_t) _PySys_GetSizeOf(PyObject *);

Lib/test/support/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -835,15 +835,18 @@ def gc_threshold(*args):
835835
finally:
836836
gc.set_threshold(*old_threshold)
837837

838-
839838
def python_is_optimized():
840839
"""Find if Python was built with optimizations."""
841840
cflags = sysconfig.get_config_var('PY_CFLAGS') or ''
842841
final_opt = ""
843842
for opt in cflags.split():
844843
if opt.startswith('-O'):
845844
final_opt = opt
846-
return final_opt not in ('', '-O0', '-Og')
845+
if sysconfig.get_config_var("CC") == "gcc":
846+
non_opts = ('', '-O0', '-Og')
847+
else:
848+
non_opts = ('', '-O0')
849+
return final_opt not in non_opts
847850

848851

849852
def check_cflags_pgo():

Lib/test/test_builtin.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1710,6 +1710,29 @@ def test_input(self):
17101710
sys.stdout = savestdout
17111711
fp.close()
17121712

1713+
def test_input_gh130163(self):
1714+
class X(io.StringIO):
1715+
def __getattribute__(self, name):
1716+
nonlocal patch
1717+
if patch:
1718+
patch = False
1719+
sys.stdout = X()
1720+
sys.stderr = X()
1721+
sys.stdin = X('input\n')
1722+
support.gc_collect()
1723+
return io.StringIO.__getattribute__(self, name)
1724+
1725+
with (support.swap_attr(sys, 'stdout', None),
1726+
support.swap_attr(sys, 'stderr', None),
1727+
support.swap_attr(sys, 'stdin', None)):
1728+
patch = False
1729+
# the only references:
1730+
sys.stdout = X()
1731+
sys.stderr = X()
1732+
sys.stdin = X('input\n')
1733+
patch = True
1734+
input() # should not crash
1735+
17131736
# test_int(): see test_int.py for tests of built-in function int().
17141737

17151738
def test_repr(self):

Lib/test/test_http_cookiejar.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,19 @@ def test_parse_ns_headers_special_names(self):
227227
self.assertEqual(parse_ns_headers([hdr]), expected)
228228

229229
def test_join_header_words(self):
230-
joined = join_header_words([[("foo", None), ("bar", "baz")]])
231-
self.assertEqual(joined, "foo; bar=baz")
232-
233-
self.assertEqual(join_header_words([[]]), "")
230+
for src, expected in [
231+
([[("foo", None), ("bar", "baz")]], "foo; bar=baz"),
232+
(([]), ""),
233+
(([[]]), ""),
234+
(([[("a", "_")]]), "a=_"),
235+
(([[("a", ";")]]), 'a=";"'),
236+
([[("n", None), ("foo", "foo;_")], [("bar", "foo_bar")]],
237+
'n; foo="foo;_", bar=foo_bar'),
238+
([[("n", "m"), ("foo", None)], [("bar", "foo_bar")]],
239+
'n=m; foo, bar=foo_bar'),
240+
]:
241+
with self.subTest(src=src):
242+
self.assertEqual(join_header_words(src), expected)
234243

235244
def test_split_header_words(self):
236245
tests = [
@@ -286,7 +295,10 @@ def test_roundtrip(self):
286295
'foo=bar; port="80,81"; discard, bar=baz'),
287296

288297
(r'Basic realm="\"foo\\\\bar\""',
289-
r'Basic; realm="\"foo\\\\bar\""')
298+
r'Basic; realm="\"foo\\\\bar\""'),
299+
300+
('n; foo="foo;_", bar=foo!_',
301+
'n; foo="foo;_", bar="foo!_"'),
290302
]
291303

292304
for arg, expect in tests:

Lib/test/test_print.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,17 @@ def flush(self):
129129
raise RuntimeError
130130
self.assertRaises(RuntimeError, print, 1, file=noflush(), flush=True)
131131

132+
def test_gh130163(self):
133+
class X:
134+
def __str__(self):
135+
sys.stdout = StringIO()
136+
support.gc_collect()
137+
return 'foo'
138+
139+
with support.swap_attr(sys, 'stdout', None):
140+
sys.stdout = StringIO() # the only reference
141+
print(X()) # should not crash
142+
132143

133144
class TestPy2MigrationHint(unittest.TestCase):
134145
"""Test that correct hint is produced analogous to Python3 syntax,

Lib/test/test_sys.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import codecs
33
import _datetime
44
import gc
5+
import io
56
import locale
67
import operator
78
import os
@@ -80,6 +81,18 @@ def baddisplayhook(obj):
8081
code = compile("42", "<string>", "single")
8182
self.assertRaises(ValueError, eval, code)
8283

84+
def test_gh130163(self):
85+
class X:
86+
def __repr__(self):
87+
sys.stdout = io.StringIO()
88+
support.gc_collect()
89+
return 'foo'
90+
91+
with support.swap_attr(sys, 'stdout', None):
92+
sys.stdout = io.StringIO() # the only reference
93+
sys.displayhook(X()) # should not crash
94+
95+
8396
class ActiveExceptionTests(unittest.TestCase):
8497
def test_exc_info_no_exception(self):
8598
self.assertEqual(sys.exc_info(), (None, None, None))

Lib/test/test_zipapp.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,30 @@ def skip_pyc_files(path):
8989
self.assertIn('test.py', z.namelist())
9090
self.assertNotIn('test.pyc', z.namelist())
9191

92+
def test_create_archive_self_insertion(self):
93+
# When creating an archive, we shouldn't
94+
# include the archive in the list of files to add.
95+
source = self.tmpdir
96+
(source / '__main__.py').touch()
97+
(source / 'test.py').touch()
98+
target = self.tmpdir / 'target.pyz'
99+
100+
zipapp.create_archive(source, target)
101+
with zipfile.ZipFile(target, 'r') as z:
102+
self.assertEqual(len(z.namelist()), 2)
103+
self.assertIn('__main__.py', z.namelist())
104+
self.assertIn('test.py', z.namelist())
105+
106+
def test_target_overwrites_source_file(self):
107+
# The target cannot be one of the files to add.
108+
source = self.tmpdir
109+
(source / '__main__.py').touch()
110+
target = source / 'target.pyz'
111+
target.touch()
112+
113+
with self.assertRaises(zipapp.ZipAppError):
114+
zipapp.create_archive(source, target)
115+
92116
def test_create_archive_filter_exclude_dir(self):
93117
# Test packing a directory and using a filter to exclude a
94118
# subdirectory (ensures that the path supplied to include

Lib/zipapp.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,37 @@ def create_archive(source, target=None, interpreter=None, main=None,
131131
elif not hasattr(target, 'write'):
132132
target = pathlib.Path(target)
133133

134+
# Create the list of files to add to the archive now, in case
135+
# the target is being created in the source directory - we
136+
# don't want the target being added to itself
137+
files_to_add = sorted(source.rglob('*'))
138+
139+
# The target cannot be in the list of files to add. If it were, we'd
140+
# end up overwriting the source file and writing the archive into
141+
# itself, which is an error. We therefore check for that case and
142+
# provide a helpful message for the user.
143+
144+
# Note that we only do a simple path equality check. This won't
145+
# catch every case, but it will catch the common case where the
146+
# source is the CWD and the target is a file in the CWD. More
147+
# thorough checks don't provide enough value to justify the extra
148+
# cost.
149+
150+
# If target is a file-like object, it will simply fail to compare
151+
# equal to any of the entries in files_to_add, so there's no need
152+
# to add a special check for that.
153+
if target in files_to_add:
154+
raise ZipAppError(
155+
f"The target archive {target} overwrites one of the source files.")
156+
134157
with _maybe_open(target, 'wb') as fd:
135158
_write_file_prefix(fd, interpreter)
136159
compression = (zipfile.ZIP_DEFLATED if compressed else
137160
zipfile.ZIP_STORED)
138161
with zipfile.ZipFile(fd, 'w', compression=compression) as z:
139-
for child in sorted(source.rglob('*')):
162+
for child in files_to_add:
140163
arcname = child.relative_to(source)
141-
if filter is None or filter(arcname) and child.resolve() != arcname.resolve():
164+
if filter is None or filter(arcname):
142165
z.write(child, arcname.as_posix())
143166
if main_py:
144167
z.writestr('__main__.py', main_py.encode('utf-8'))

0 commit comments

Comments
 (0)