Skip to content

Commit 433bb6c

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

33 files changed

+837
-421
lines changed

Doc/library/email.errors.rst

+4-10
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

+2-2
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

+4-2
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

+5-2
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

+23
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

+17-5
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

+11
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

+13
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

+24
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

+25-2
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'))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix possible crashes related to concurrent
2+
change and use of the :mod:`sys` module attributes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The zipapp module now calculates the list of files to be added to the archive before creating the archive. This avoids accidentally including the target when it is being created in the source directory.

Modules/_cursesmodule.c

+6-2
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ static const char PyCursesVersion[] = "2.2";
108108
#include "pycore_capsule.h" // _PyCapsule_SetTraverse()
109109
#include "pycore_long.h" // _PyLong_GetZero()
110110
#include "pycore_structseq.h" // _PyStructSequence_NewType()
111+
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
111112

112113
#ifdef __hpux
113114
#define STRICT_SYSV_CURSES
@@ -3542,16 +3543,19 @@ _curses_setupterm_impl(PyObject *module, const char *term, int fd)
35423543
if (fd == -1) {
35433544
PyObject* sys_stdout;
35443545

3545-
sys_stdout = PySys_GetObject("stdout");
3546+
if (_PySys_GetOptionalAttrString("stdout", &sys_stdout) < 0) {
3547+
return NULL;
3548+
}
35463549

35473550
if (sys_stdout == NULL || sys_stdout == Py_None) {
35483551
cursesmodule_state *state = get_cursesmodule_state(module);
35493552
PyErr_SetString(state->error, "lost sys.stdout");
3553+
Py_XDECREF(sys_stdout);
35503554
return NULL;
35513555
}
35523556

35533557
fd = PyObject_AsFileDescriptor(sys_stdout);
3554-
3558+
Py_DECREF(sys_stdout);
35553559
if (fd == -1) {
35563560
return NULL;
35573561
}

0 commit comments

Comments
 (0)