Skip to content

Commit 861ac51

Browse files
generatedunixname1734921407115435meta-codesync[bot]
authored andcommitted
Sync pre-release CPython 3.15 branch from GitHub (2026-07-13)
Summary: Imported python/cpython `3.15.0b3+dev` from upstream rev [`a442c89`](https://www.github.com/python/cpython/commit/a442c8945906ad0e600088a2bedb2fa00ba0b8a3) (committed 2026-07-13 23:13:23+00:00). # Commit Info - Base: (`3.15.0b3+dev`) - [`9efded4`](https://www.github.com/python/cpython/commit/9efded46646ae25de8e8c6fdef85f2f7d188dfd3) (commit date: 2026-07-13 10:09:39+00:00) - Imported: (`3.15.0b3+dev`) - [`a442c89`](https://www.github.com/python/cpython/commit/a442c8945906ad0e600088a2bedb2fa00ba0b8a3) (commit date: 2026-07-13 23:13:23+00:00) # Noteworthy file changes - Low-signal files (1 added) (NEWS.d, docs, .github) Complete list of added/removed files: https://www.internalfb.com/intern/everpaste/?color=0&handle=GIRRoiTH7BDfxKgDALb2zKY6_54wbr0LAAAz Differential Revision: D111858437 fbshipit-source-id: bfcc41f597dc3a9781a1ca0aebd2635459fb8ff2
1 parent 5f31e36 commit 861ac51

5 files changed

Lines changed: 147 additions & 23 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -408,23 +408,14 @@ jobs:
408408
needs: build-context
409409
if: needs.build-context.outputs.run-ios == 'true'
410410
timeout-minutes: 60
411-
runs-on: macos-14
411+
runs-on: macos-26
412412
steps:
413413
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
414414
with:
415415
persist-credentials: false
416416

417-
# GitHub recommends explicitly selecting the desired Xcode version:
418-
# https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140
419-
# This became a necessity as a result of
420-
# https://github.com/actions/runner-images/issues/12541 and
421-
# https://github.com/actions/runner-images/issues/12751.
422-
- name: Select Xcode version
423-
run: |
424-
sudo xcode-select --switch /Applications/Xcode_15.4.app
425-
426417
- name: Build and test
427-
run: python3 Platforms/Apple ci iOS --fast-ci --simulator 'iPhone SE (3rd generation),OS=17.5'
418+
run: python3 Platforms/Apple ci iOS --fast-ci
428419

429420
build-emscripten:
430421
name: 'Emscripten'

Lib/test/test_types.py

Lines changed: 111 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from test.support.import_helper import import_fresh_module
99

1010
import collections.abc
11-
from collections import namedtuple, UserDict
11+
from collections import namedtuple, UserDict, OrderedDict
1212
import copy
1313
import _datetime
1414
import gc
@@ -1391,26 +1391,127 @@ def __hash__(self):
13911391
view = self.mappingproxy(mapping)
13921392
self.assertEqual(hash(view), hash(mapping))
13931393

1394-
def test_richcompare(self):
1395-
mp1 = self.mappingproxy({'a': 1})
1396-
mp1_2 = self.mappingproxy({'a': 1})
1397-
mp2 = self.mappingproxy({'a': 2})
1394+
def check_richcompare(self, mapping_type):
1395+
data1 = mapping_type({'a': 1})
1396+
data2 = mapping_type({'a': 2})
1397+
mp1 = self.mappingproxy(data1)
1398+
copy1 = self.mappingproxy(data1)
1399+
mp2 = self.mappingproxy(data2)
1400+
1401+
self.assertTrue(mp1 == data1)
1402+
self.assertTrue(data1 == mp1)
1403+
self.assertTrue(copy1 == data1)
1404+
1405+
self.assertTrue(mp1 == copy1)
1406+
self.assertFalse(mp1 != copy1)
13981407

1399-
self.assertTrue(mp1 == mp1_2)
1400-
self.assertFalse(mp1 != mp1_2)
14011408
self.assertFalse(mp1 == mp2)
14021409
self.assertTrue(mp1 != mp2)
1410+
self.assertFalse(mp2 == data1)
1411+
self.assertTrue(mp2 != data1)
1412+
self.assertTrue(data1 != mp2)
14031413

14041414
msg = "not supported between instances of 'mappingproxy' and 'mappingproxy'"
1405-
14061415
with self.assertRaisesRegex(TypeError, msg):
14071416
mp1 > mp2
14081417
with self.assertRaisesRegex(TypeError, msg):
1409-
mp1 < mp1_2
1418+
mp1 < copy1
14101419
with self.assertRaisesRegex(TypeError, msg):
14111420
mp2 >= mp2
14121421
with self.assertRaisesRegex(TypeError, msg):
1413-
mp1_2 <= mp1
1422+
copy1 <= mp1
1423+
1424+
if mapping_type.__module__ == 'collections':
1425+
mapping_name = f'{mapping_type.__module__}.{mapping_type.__name__}'
1426+
else:
1427+
mapping_name = mapping_type.__name__
1428+
with self.assertRaisesRegex(
1429+
TypeError,
1430+
f"not supported between instances of 'mappingproxy' and '{mapping_name}'",
1431+
):
1432+
copy1 <= data1
1433+
1434+
class Evil:
1435+
def __eq__(self, other):
1436+
return other
1437+
1438+
result = (mp1 == Evil())
1439+
self.assertIs(type(result), mapping_type)
1440+
if mapping_type == dict:
1441+
# Evil.__eq__() gets a copy of the mapping
1442+
self.assertEqual(result, data1)
1443+
else:
1444+
self.assertIs(result, data1)
1445+
1446+
def test_richcompare_dict(self):
1447+
self.check_richcompare(dict)
1448+
1449+
def test_richcompare_custom_mapping(self):
1450+
class CustomMapping(collections.abc.Mapping):
1451+
def __init__(self, data):
1452+
self._data = data
1453+
1454+
def __getitem__(self, key):
1455+
return self._data[key]
1456+
1457+
def __iter__(self):
1458+
return iter(self._data)
1459+
1460+
def __len__(self):
1461+
return len(self._data)
1462+
1463+
def __contains__(self, item):
1464+
return item in self._data
1465+
1466+
self.check_richcompare(CustomMapping)
1467+
1468+
class CustomMapping2(CustomMapping):
1469+
def __eq__(self, other):
1470+
return (
1471+
isinstance(other, CustomMapping)
1472+
and self._data == other._data
1473+
)
1474+
1475+
mp1 = self.mappingproxy(CustomMapping({'a': 1}))
1476+
self.assertEqual(mp1, CustomMapping2({'a': 1}))
1477+
self.assertNotEqual(CustomMapping2({'a': 1}), mp1)
1478+
1479+
def test_richcompare_odict(self):
1480+
self.check_richcompare(OrderedDict)
1481+
1482+
def test_richcompare_frozendict(self):
1483+
self.check_richcompare(frozendict)
1484+
1485+
def test_richcompare_evil(self):
1486+
# This test used to mutate the list dictionary,
1487+
# but MappingProxyType now creates a copy to call `__eq__`:
1488+
# https://github.com/python/cpython/issues/152405
1489+
key = "__mappingproxy_crash_key__"
1490+
1491+
class Evil:
1492+
def __eq__(self, other):
1493+
other[key] = 1
1494+
return other
1495+
1496+
# Checks that it does not mutate the internals of `MappingProxyType`:
1497+
dc = {}
1498+
leaked = self.mappingproxy(dc) == Evil()
1499+
self.assertIs(type(leaked), dict)
1500+
self.assertIn(key, leaked)
1501+
self.assertNotIn(key, dc)
1502+
1503+
# Exposes the internals of `MappingProxyType` via richcompare:
1504+
leaked = vars(list) == Evil()
1505+
self.assertIs(type(leaked), dict)
1506+
self.assertIn(key, leaked)
1507+
name = "__mappingproxy_crash_probe__"
1508+
leaked[name] = lambda self: "probe"
1509+
self.assertIn(name, leaked)
1510+
self.assertNotHasAttr(list, key)
1511+
self.assertNotHasAttr(list, name) # it used to return `True`
1512+
del leaked[name]
1513+
self.assertNotIn(name, leaked)
1514+
self.assertNotHasAttr(list, name) # it used to crash
14141515

14151516

14161517
class ClassCreationTests(unittest.TestCase):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Do not expose the internal mapping of :class:`types.MappingProxyType`
2+
when performing rich-compare operations with non-stdlib types.
3+
Previously, it was possible to mutate the internal mapping of a proxy there.
4+
Now we pass not the original :class:`dict` instance, but its copy,
5+
when dealing with custom types.

Objects/descrobject.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1232,8 +1232,29 @@ mappingproxy_traverse(PyObject *self, visitproc visit, void *arg)
12321232
static PyObject *
12331233
mappingproxy_richcompare(PyObject *self, PyObject *w, int op)
12341234
{
1235-
mappingproxyobject *v = (mappingproxyobject *)self;
12361235
if (op == Py_EQ || op == Py_NE) {
1236+
mappingproxyobject *v = (mappingproxyobject *)self;
1237+
// We have to guard the mutable `dict` instances, because it can
1238+
// otherwise mutate the type's `__dict__` entries and cause crashes.
1239+
// But, do not create copies on known types like `OrderedDict`
1240+
// or immutable types like `frozendict`
1241+
// for memory optimization. See gh-152405 for the details.
1242+
if (
1243+
PyDict_CheckExact(v->mapping) &&
1244+
!(PyAnyDict_CheckExact(w) ||
1245+
Py_TYPE(w) == &PyDictProxy_Type ||
1246+
PyODict_CheckExact(w))
1247+
) {
1248+
// So, instead we send a copy:
1249+
PyObject *copy = PyDict_Copy(v->mapping);
1250+
if (copy == NULL) {
1251+
return NULL;
1252+
}
1253+
PyObject *res = PyObject_RichCompare(copy, w, op);
1254+
Py_DECREF(copy);
1255+
return res;
1256+
}
1257+
// Otherwise we are free to share the mapping directly:
12371258
return PyObject_RichCompare(v->mapping, w, op);
12381259
}
12391260
Py_RETURN_NOTIMPLEMENTED;

Platforms/Apple/testbed/__main__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,14 @@ def run_testbed(
271271
update_test_plan(location, platform, args)
272272
print(" done.")
273273

274+
# xcodebuild doesn't guarantee that the CoreSimulatorService daemon is
275+
# running prior to using a simulator, but calling `xcrun simctl list` does.
276+
# Determining the default simulator that *would* be used is a cheap action;
277+
# so use that as a way to ensure that the CoreSimulatorService daemon is
278+
# running.
279+
default_simulator = select_simulator_device(platform)
274280
if simulator is None:
275-
simulator = select_simulator_device(platform)
281+
simulator = default_simulator
276282
print(f"Running test on {simulator}")
277283

278284
xcode_test(

0 commit comments

Comments
 (0)