Skip to content

Commit 6a30fd4

Browse files
gh-157217: Unwrap mappingproxy in dict_merge
1 parent ba3b9cd commit 6a30fd4

1 file changed

Lines changed: 14 additions & 3 deletions

File tree

Objects/dictobject.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ As a consequence of this, split keys have a maximum size of 16.
121121
#include "pycore_ceval.h" // _PyEval_GetBuiltin()
122122
#include "pycore_code.h" // stats
123123
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION
124+
#include "pycore_descrobject.h" // _PyDictProxy_GetMapping()
124125
#include "pycore_dict.h" // export _PyDict_SizeOf()
125126
#include "pycore_freelist.h" // _PyFreeListState_GET()
126127
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
@@ -4305,11 +4306,21 @@ dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey)
43054306

43064307
PyDictObject *mp = _PyAnyDict_CAST(a);
43074308

4309+
/* Mapping proxies (including type.__dict__) wrap a real dict. Unwrap
4310+
* so we take the locked dict-to-dict path instead of iterating the
4311+
* proxy without holding the underlying dict's critical section.
4312+
* See gh-157217.
4313+
*/
4314+
PyObject *source = b;
4315+
if (PyObject_TypeCheck(b, &PyDictProxy_Type)) {
4316+
source = _PyDictProxy_GetMapping(b);
4317+
}
4318+
43084319
int res = 0;
4309-
if (PyAnyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) {
4310-
PyDictObject *other = (PyDictObject*)b;
4320+
if (PyAnyDict_Check(source) && (Py_TYPE(source)->tp_iter == dict_iter)) {
4321+
PyDictObject *other = (PyDictObject*)source;
43114322
int res;
4312-
Py_BEGIN_CRITICAL_SECTION2(a, b);
4323+
Py_BEGIN_CRITICAL_SECTION2(a, source);
43134324
assert(can_modify_dict(mp));
43144325
res = dict_dict_merge((PyDictObject *)a, other, override, dupkey);
43154326
ASSERT_CONSISTENT(a);

0 commit comments

Comments
 (0)