Skip to content

Commit c36cca8

Browse files
authored
Merge branch 'main' into dup3-pipe2
2 parents 0c40dcb + 83b3354 commit c36cca8

8 files changed

Lines changed: 109 additions & 6 deletions

File tree

Lib/mimetypes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ def read(self, filename, strict=True):
250250
list of standard types, else to the list of non-standard
251251
types.
252252
"""
253-
with open(filename, encoding='utf-8') as fp:
253+
with open(filename, encoding='utf-8', errors='surrogateescape') as fp:
254254
self.readfp(fp, strict)
255255

256256
def readfp(self, fp, strict=True):
@@ -444,7 +444,7 @@ def init(files=None):
444444

445445
def read_mime_types(file):
446446
try:
447-
f = open(file, encoding='utf-8')
447+
f = open(file, encoding='utf-8', errors='surrogateescape')
448448
except OSError:
449449
return None
450450
with f:

Lib/test/test_free_threading/test_io.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,23 @@ def reset_worker():
232232
decoder.reset()
233233

234234
run_concurrently([decode_worker] * 2 + [reset_worker] * 2)
235+
236+
237+
class TextIOWrapperTest(TestCase):
238+
def test_buffer_detach_race(self):
239+
make = lambda: io.TextIOWrapper(io.BytesIO())
240+
slot = [make()]
241+
242+
def reader():
243+
for _ in range(1000):
244+
try:
245+
slot[0].buffer
246+
except ValueError:
247+
pass
248+
249+
def detacher():
250+
for _ in range(1000):
251+
slot[0] = make()
252+
slot[0].detach()
253+
254+
run_concurrently([reader, detacher])

Lib/test/test_mimetypes.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,50 @@ def test_read_mime_types(self):
6767
with unittest.mock.patch.object(mimetypes, 'open',
6868
return_value=fp) as mock_open:
6969
mime_dict = mimetypes.read_mime_types(filename)
70-
mock_open.assert_called_with(filename, encoding='utf-8')
70+
mock_open.assert_called_with(filename, encoding='utf-8',
71+
errors='surrogateescape')
7172
eq(mime_dict[".Français"], "application/no-mans-land")
7273

74+
def test_read_mime_types_invalid_utf8_comment(self):
75+
with os_helper.temp_dir() as directory:
76+
data = (b"# non-UTF-8 comment: \x83\n"
77+
b"x-application/x-unittest pyunit\n")
78+
file = os.path.join(directory, "sample.mimetype")
79+
with open(file, "wb") as f:
80+
f.write(data)
81+
82+
mime_dict = mimetypes.read_mime_types(file)
83+
self.assertEqual(
84+
mime_dict[".pyunit"], "x-application/x-unittest")
85+
86+
db = mimetypes.MimeTypes()
87+
db.read(file)
88+
self.assertEqual(
89+
db.guess_file_type("sample.pyunit")[0],
90+
"x-application/x-unittest")
91+
92+
mimetypes.init(files=[file])
93+
self.assertEqual(
94+
mimetypes.guess_file_type("sample.pyunit")[0],
95+
"x-application/x-unittest")
96+
97+
def test_read_mime_types_invalid_utf8_type(self):
98+
# A non-UTF-8 byte in a type or extension (not only in a comment) is
99+
# preserved via surrogateescape, so the mapping is not corrupted.
100+
with os_helper.temp_dir() as directory:
101+
data = (b"x-application/x-unittest pyunit\n"
102+
b"application/bad\x83 badext\x83\n")
103+
file = os.path.join(directory, "sample.mimetype")
104+
with open(file, "wb") as f:
105+
f.write(data)
106+
107+
bad_type = b"application/bad\x83".decode("utf-8", "surrogateescape")
108+
bad_ext = b".badext\x83".decode("utf-8", "surrogateescape")
109+
110+
mime_dict = mimetypes.read_mime_types(file)
111+
self.assertEqual(mime_dict[".pyunit"], "x-application/x-unittest")
112+
self.assertEqual(mime_dict[bad_ext], bad_type)
113+
73114
def test_init_reinitializes(self):
74115
# Issue 4936: make sure an init starts clean
75116
# First, put some poison into the types table
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :mod:`mimetypes` initialization from MIME map files containing invalid
2+
UTF-8 bytes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed data-race when calling :meth:`io.TextIOBase.detach` in
2+
:term:`free-threaded build`.

Modules/_io/clinic/textio.c.h

Lines changed: 26 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/_io/textio.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3424,6 +3424,19 @@ _io_TextIOWrapper__CHUNK_SIZE_set_impl(textio *self, PyObject *value)
34243424
return 0;
34253425
}
34263426

3427+
/*[clinic input]
3428+
@critical_section
3429+
@getter
3430+
_io.TextIOWrapper.buffer
3431+
[clinic start generated code]*/
3432+
3433+
static PyObject *
3434+
_io_TextIOWrapper_buffer_get_impl(textio *self)
3435+
/*[clinic end generated code: output=d265a34555aa5d4b input=5951cfa148f7350a]*/
3436+
{
3437+
return Py_XNewRef(buffer_access_safe(self));
3438+
}
3439+
34273440
static PyMethodDef incrementalnewlinedecoder_methods[] = {
34283441
_IO_INCREMENTALNEWLINEDECODER_DECODE_METHODDEF
34293442
_IO_INCREMENTALNEWLINEDECODER_GETSTATE_METHODDEF
@@ -3482,7 +3495,6 @@ static PyMethodDef textiowrapper_methods[] = {
34823495

34833496
static PyMemberDef textiowrapper_members[] = {
34843497
{"encoding", _Py_T_OBJECT, offsetof(textio, encoding), Py_READONLY},
3485-
{"buffer", _Py_T_OBJECT, offsetof(textio, buffer), Py_READONLY},
34863498
{"line_buffering", Py_T_BOOL, offsetof(textio, line_buffering), Py_READONLY},
34873499
{"write_through", Py_T_BOOL, offsetof(textio, write_through), Py_READONLY},
34883500
{"_finalizing", Py_T_BOOL, offsetof(textio, finalizing), 0},
@@ -3497,6 +3509,7 @@ static PyGetSetDef textiowrapper_getset[] = {
34973509
_IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF
34983510
_IO_TEXTIOWRAPPER_ERRORS_GETSETDEF
34993511
_IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF
3512+
_IO_TEXTIOWRAPPER_BUFFER_GETSETDEF
35003513
{NULL}
35013514
};
35023515

Platforms/emscripten/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Any data that can vary between Python versions is to be kept in this file.
22
# This allows for blanket copying of the Emscripten build code between supported
33
# Python versions.
4-
emscripten-version = "6.0.4"
4+
emscripten-version = "6.0.5"
55
node-version = "24"
66
test-args = [
77
"-m", "test",

0 commit comments

Comments
 (0)