Skip to content

Commit 7833d7d

Browse files
committed
gh-141510: Fix frozendict.fromkeys() for subclasses
Don't call the constructor of the subclass, but frozendict constructor instead.
1 parent 7a7521b commit 7833d7d

File tree

2 files changed

+30
-4
lines changed

2 files changed

+30
-4
lines changed

Lib/test/test_dict.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,6 +1787,23 @@ def test_hash(self):
17871787
with self.assertRaisesRegex(TypeError, "unhashable type: 'list'"):
17881788
hash(fd)
17891789

1790+
def test_fromkeys(self):
1791+
self.assertEqual(frozendict.fromkeys('abc'),
1792+
frozendict(a=None, b=None, c=None))
1793+
1794+
# frozendict.fromkeys() must not call subclass constructor
1795+
class FrozenDictSubclass(frozendict):
1796+
def __new__(self):
1797+
raise ValueError("must not be called")
1798+
1799+
fd = FrozenDictSubclass.fromkeys("abc")
1800+
self.assertEqual(fd, frozendict(a=None, b=None, c=None))
1801+
self.assertEqual(type(fd), FrozenDictSubclass)
1802+
1803+
fd = FrozenDictSubclass.fromkeys(frozendict(x=1))
1804+
self.assertEqual(fd, frozendict(x=None))
1805+
self.assertEqual(type(fd), FrozenDictSubclass)
1806+
17901807

17911808
if __name__ == "__main__":
17921809
unittest.main()

Objects/dictobject.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3285,10 +3285,17 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
32853285
PyObject *d;
32863286
int status;
32873287

3288-
d = _PyObject_CallNoArgs(cls);
3289-
if (d == NULL)
3288+
if (PyObject_IsSubclass(cls, (PyObject*)&PyFrozenDict_Type)) {
3289+
// Never call frozendict subclass constructor which would call
3290+
// arbitrary Python code.
3291+
d = frozendict_new(_PyType_CAST(cls), NULL, NULL);
3292+
}
3293+
else {
3294+
d = _PyObject_CallNoArgs(cls);
3295+
}
3296+
if (d == NULL) {
32903297
return NULL;
3291-
3298+
}
32923299

32933300
if (PyDict_CheckExact(d)) {
32943301
if (PyDict_CheckExact(iterable)) {
@@ -3359,7 +3366,7 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
33593366
dict_iter_exit:;
33603367
Py_END_CRITICAL_SECTION();
33613368
}
3362-
else if (PyFrozenDict_CheckExact(d)) {
3369+
else if (PyFrozenDict_Check(d)) {
33633370
while ((key = PyIter_Next(it)) != NULL) {
33643371
// anydict_setitem_take2 consumes a reference to key
33653372
status = anydict_setitem_take2((PyDictObject *)d,
@@ -7994,6 +8001,8 @@ frozendict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
79948001
if (d == NULL) {
79958002
return NULL;
79968003
}
8004+
assert(Py_REFCNT(d) == 1);
8005+
79978006
PyFrozenDictObject *self = _PyFrozenDictObject_CAST(d);
79988007
self->ma_hash = -1;
79998008

0 commit comments

Comments
 (0)