Skip to content

Commit ec54340

Browse files
Merge branch '3.13' into backport-6bb7b33-3.13
2 parents 255595d + 9f15d25 commit ec54340

File tree

8 files changed

+134
-20
lines changed

8 files changed

+134
-20
lines changed

Doc/library/pkgutil.rst

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,24 +191,48 @@ support.
191191
:meth:`get_data <importlib.abc.ResourceLoader.get_data>` API. The
192192
*package* argument should be the name of a package, in standard module format
193193
(``foo.bar``). The *resource* argument should be in the form of a relative
194-
filename, using ``/`` as the path separator. The parent directory name
195-
``..`` is not allowed, and nor is a rooted name (starting with a ``/``).
194+
filename, using ``/`` as the path separator.
196195

197196
The function returns a binary string that is the contents of the specified
198197
resource.
199198

199+
This function uses the :term:`loader` method
200+
:func:`~importlib.abc.FileLoader.get_data`
201+
to support modules installed in the filesystem, but also in zip files,
202+
databases, or elsewhere.
203+
200204
For packages located in the filesystem, which have already been imported,
201205
this is the rough equivalent of::
202206

203207
d = os.path.dirname(sys.modules[package].__file__)
204208
data = open(os.path.join(d, resource), 'rb').read()
205209

210+
Like the :func:`open` function, :func:`!get_data` can follow parent
211+
directories (``../``) and absolute paths (starting with ``/`` or ``C:/``,
212+
for example).
213+
It can open compilation/installation artifacts like ``.py`` and ``.pyc``
214+
files or files with :func:`reserved filenames <os.path.isreserved>`.
215+
To be compatible with non-filesystem loaders, avoid using these features.
216+
217+
.. warning::
218+
219+
This function is intended for trusted input.
220+
It does not verify that *resource* "belongs" to *package*.
221+
222+
If you use a user-provided *resource* path, consider verifying it.
223+
For example, require an alphanumeric filename with a known extension, or
224+
install and check a list of known resources.
225+
206226
If the package cannot be located or loaded, or it uses a :term:`loader`
207227
which does not support :meth:`get_data <importlib.abc.ResourceLoader.get_data>`,
208228
then ``None`` is returned. In particular, the :term:`loader` for
209229
:term:`namespace packages <namespace package>` does not support
210230
:meth:`get_data <importlib.abc.ResourceLoader.get_data>`.
211231

232+
.. seealso::
233+
234+
The :mod:`importlib.resources` module provides structured access to
235+
module resources.
212236

213237
.. function:: resolve_name(name)
214238

Lib/test/test_itertools.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,38 @@ def keys():
10181018
next(g)
10191019
next(g) # must pass with address sanitizer
10201020

1021+
def test_grouper_reentrant_eq_does_not_crash(self):
1022+
# regression test for gh-146613
1023+
grouper_iter = None
1024+
1025+
class Key:
1026+
__hash__ = None
1027+
1028+
def __init__(self, do_advance):
1029+
self.do_advance = do_advance
1030+
1031+
def __eq__(self, other):
1032+
nonlocal grouper_iter
1033+
if self.do_advance:
1034+
self.do_advance = False
1035+
if grouper_iter is not None:
1036+
try:
1037+
next(grouper_iter)
1038+
except StopIteration:
1039+
pass
1040+
return NotImplemented
1041+
return True
1042+
1043+
def keyfunc(element):
1044+
if element == 0:
1045+
return Key(do_advance=True)
1046+
return Key(do_advance=False)
1047+
1048+
g = itertools.groupby(range(4), keyfunc)
1049+
key, grouper_iter = next(g)
1050+
items = list(grouper_iter)
1051+
self.assertEqual(len(items), 1)
1052+
10211053
def test_filter(self):
10221054
self.assertEqual(list(filter(isEven, range(6))), [0,2,4])
10231055
self.assertEqual(list(filter(None, [0,1,0,2,0])), [1,2])

Lib/test/test_perf_profiler.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,13 @@ def baz():
6363
"""
6464
with temp_dir() as script_dir:
6565
script = make_script(script_dir, "perftest", code)
66+
env = {**os.environ, "PYTHON_JIT": "0"}
6667
with subprocess.Popen(
6768
[sys.executable, "-Xperf", script],
6869
text=True,
6970
stderr=subprocess.PIPE,
7071
stdout=subprocess.PIPE,
72+
env=env,
7173
) as process:
7274
stdout, stderr = process.communicate()
7375

@@ -131,11 +133,13 @@ def baz():
131133
"""
132134
with temp_dir() as script_dir:
133135
script = make_script(script_dir, "perftest", code)
136+
env = {**os.environ, "PYTHON_JIT": "0"}
134137
with subprocess.Popen(
135138
[sys.executable, "-Xperf", script],
136139
text=True,
137140
stderr=subprocess.PIPE,
138141
stdout=subprocess.PIPE,
142+
env=env,
139143
) as process:
140144
stdout, stderr = process.communicate()
141145

@@ -232,11 +236,13 @@ def baz():
232236
"""
233237
with temp_dir() as script_dir:
234238
script = make_script(script_dir, "perftest", code)
239+
env = {**os.environ, "PYTHON_JIT": "0"}
235240
with subprocess.Popen(
236241
[sys.executable, script],
237242
text=True,
238243
stderr=subprocess.PIPE,
239244
stdout=subprocess.PIPE,
245+
env=env,
240246
) as process:
241247
stdout, stderr = process.communicate()
242248

@@ -333,8 +339,9 @@ def perf_command_works():
333339
"-c",
334340
'print("hello")',
335341
)
342+
env = {**os.environ, "PYTHON_JIT": "0"}
336343
stdout = subprocess.check_output(
337-
cmd, cwd=script_dir, text=True, stderr=subprocess.STDOUT
344+
cmd, cwd=script_dir, text=True, stderr=subprocess.STDOUT, env=env
338345
)
339346
except (subprocess.SubprocessError, OSError):
340347
return False
@@ -346,11 +353,10 @@ def perf_command_works():
346353

347354

348355
def run_perf(cwd, *args, use_jit=False, **env_vars):
356+
env = os.environ.copy()
349357
if env_vars:
350-
env = os.environ.copy()
351358
env.update(env_vars)
352-
else:
353-
env = None
359+
env["PYTHON_JIT"] = "0"
354360
output_file = cwd + "/perf_output.perf"
355361
if not use_jit:
356362
base_cmd = (
@@ -529,11 +535,13 @@ def compile_trampolines_for_all_functions():
529535

530536
with temp_dir() as script_dir:
531537
script = make_script(script_dir, "perftest", code)
538+
env = {**os.environ, "PYTHON_JIT": "0"}
532539
with subprocess.Popen(
533540
[sys.executable, "-Xperf", script],
534541
universal_newlines=True,
535542
stderr=subprocess.PIPE,
536543
stdout=subprocess.PIPE,
544+
env=env,
537545
) as process:
538546
stdout, stderr = process.communicate()
539547

Lib/test/test_pyexpat.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,45 @@ def test_parent_parser_outlives_its_subparsers__chain(self):
827827
del subparser
828828

829829

830+
class ExternalEntityParserCreateErrorTest(unittest.TestCase):
831+
"""ExternalEntityParserCreate error paths should not crash or leak
832+
refcounts on the parent parser.
833+
834+
See https://github.com/python/cpython/issues/144984.
835+
"""
836+
837+
@classmethod
838+
def setUpClass(cls):
839+
cls.testcapi = import_helper.import_module('_testcapi')
840+
841+
@unittest.skipIf(support.Py_TRACE_REFS,
842+
'Py_TRACE_REFS conflicts with testcapi.set_nomemory')
843+
def test_error_path_no_crash(self):
844+
# When an allocation inside ExternalEntityParserCreate fails,
845+
# the partially-initialized subparser is deallocated. This
846+
# must not dereference NULL handlers or double-decrement the
847+
# parent parser's refcount.
848+
parser = expat.ParserCreate()
849+
parser.buffer_text = True
850+
rc_before = sys.getrefcount(parser)
851+
852+
# We avoid self.assertRaises(MemoryError) here because the
853+
# context manager itself needs memory allocations that fail
854+
# while the nomemory hook is active.
855+
self.testcapi.set_nomemory(1, 10)
856+
raised = False
857+
try:
858+
parser.ExternalEntityParserCreate(None)
859+
except MemoryError:
860+
raised = True
861+
finally:
862+
self.testcapi.remove_mem_hooks()
863+
self.assertTrue(raised, "MemoryError not raised")
864+
865+
rc_after = sys.getrefcount(parser)
866+
self.assertEqual(rc_after, rc_before)
867+
868+
830869
class ReparseDeferralTest(unittest.TestCase):
831870
def test_getter_setter_round_trip(self):
832871
parser = expat.ParserCreate()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix crash in :meth:`xml.parsers.expat.xmlparser.ExternalEntityParserCreate`
2+
when an allocation fails. The error paths could dereference NULL ``handlers``
3+
and double-decrement the parent parser's reference count.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`itertools`: Fix a crash in :func:`itertools.groupby` when
2+
the grouper iterator is concurrently mutated.

Modules/itertoolsmodule.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,16 @@ _grouper_next(_grouperobject *igo)
712712
}
713713

714714
assert(gbo->currkey != NULL);
715-
rcmp = PyObject_RichCompareBool(igo->tgtkey, gbo->currkey, Py_EQ);
715+
/* A user-defined __eq__ can re-enter the grouper and advance the iterator,
716+
mutating gbo->currkey while we are comparing them.
717+
Take local snapshots and hold strong references so INCREF/DECREF
718+
apply to the same objects even under re-entrancy. */
719+
PyObject *tgtkey = Py_NewRef(igo->tgtkey);
720+
PyObject *currkey = Py_NewRef(gbo->currkey);
721+
rcmp = PyObject_RichCompareBool(tgtkey, currkey, Py_EQ);
722+
Py_DECREF(tgtkey);
723+
Py_DECREF(currkey);
724+
716725
if (rcmp <= 0)
717726
/* got any error or current group is end */
718727
return NULL;

Modules/pyexpat.c

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,11 +1045,6 @@ pyexpat_xmlparser_ExternalEntityParserCreate_impl(xmlparseobject *self,
10451045
return NULL;
10461046
}
10471047

1048-
// The new subparser will make use of the parent XML_Parser inside of Expat.
1049-
// So we need to take subparsers into account with the reference counting
1050-
// of their parent parser.
1051-
Py_INCREF(self);
1052-
10531048
new_parser->buffer_size = self->buffer_size;
10541049
new_parser->buffer_used = 0;
10551050
new_parser->buffer = NULL;
@@ -1059,21 +1054,22 @@ pyexpat_xmlparser_ExternalEntityParserCreate_impl(xmlparseobject *self,
10591054
new_parser->ns_prefixes = self->ns_prefixes;
10601055
new_parser->itself = XML_ExternalEntityParserCreate(self->itself, context,
10611056
encoding);
1062-
new_parser->parent = (PyObject *)self;
1057+
// The new subparser will make use of the parent XML_Parser inside of Expat.
1058+
// So we need to take subparsers into account with the reference counting
1059+
// of their parent parser.
1060+
new_parser->parent = Py_NewRef(self);
10631061
new_parser->handlers = 0;
10641062
new_parser->intern = Py_XNewRef(self->intern);
10651063

10661064
if (self->buffer != NULL) {
10671065
new_parser->buffer = PyMem_Malloc(new_parser->buffer_size);
10681066
if (new_parser->buffer == NULL) {
10691067
Py_DECREF(new_parser);
1070-
Py_DECREF(self);
10711068
return PyErr_NoMemory();
10721069
}
10731070
}
10741071
if (!new_parser->itself) {
10751072
Py_DECREF(new_parser);
1076-
Py_DECREF(self);
10771073
return PyErr_NoMemory();
10781074
}
10791075

@@ -1086,7 +1082,6 @@ pyexpat_xmlparser_ExternalEntityParserCreate_impl(xmlparseobject *self,
10861082
new_parser->handlers = PyMem_New(PyObject *, i);
10871083
if (!new_parser->handlers) {
10881084
Py_DECREF(new_parser);
1089-
Py_DECREF(self);
10901085
return PyErr_NoMemory();
10911086
}
10921087
clear_handlers(new_parser, 1);
@@ -2333,11 +2328,13 @@ PyInit_pyexpat(void)
23332328
static void
23342329
clear_handlers(xmlparseobject *self, int initial)
23352330
{
2336-
int i = 0;
2337-
2338-
for (; handler_info[i].name != NULL; i++) {
2339-
if (initial)
2331+
if (self->handlers == NULL) {
2332+
return;
2333+
}
2334+
for (size_t i = 0; handler_info[i].name != NULL; i++) {
2335+
if (initial) {
23402336
self->handlers[i] = NULL;
2337+
}
23412338
else {
23422339
Py_CLEAR(self->handlers[i]);
23432340
handler_info[i].setter(self->itself, NULL);

0 commit comments

Comments
 (0)