Skip to content

Commit 6940c1d

Browse files
authored
gh-141510: Check argument in PyDict_MergeFromSeq2() (#145082)
PyDict_MergeFromSeq2() now fails with SystemError if the first argument is not a dict or a dict subclass. PyDict_Update(), PyDict_Merge() and _PyDict_MergeEx() no longer accept frozendict.
1 parent 770d354 commit 6940c1d

File tree

2 files changed

+49
-24
lines changed

2 files changed

+49
-24
lines changed

Lib/test/test_capi/test_dict.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ def test_dict_next(self):
419419
# CRASHES dict_next(NULL, 0)
420420

421421
def test_dict_update(self):
422+
# Test PyDict_Update()
422423
update = _testlimitedcapi.dict_update
423424
for cls1 in dict, DictSubclass:
424425
for cls2 in dict, DictSubclass, UserDict:
@@ -429,11 +430,13 @@ def test_dict_update(self):
429430
self.assertRaises(AttributeError, update, {}, [])
430431
self.assertRaises(AttributeError, update, {}, 42)
431432
self.assertRaises(SystemError, update, UserDict(), {})
433+
self.assertRaises(SystemError, update, frozendict(), {})
432434
self.assertRaises(SystemError, update, 42, {})
433435
self.assertRaises(SystemError, update, {}, NULL)
434436
self.assertRaises(SystemError, update, NULL, {})
435437

436438
def test_dict_merge(self):
439+
# Test PyDict_Merge()
437440
merge = _testlimitedcapi.dict_merge
438441
for cls1 in dict, DictSubclass:
439442
for cls2 in dict, DictSubclass, UserDict:
@@ -447,11 +450,13 @@ def test_dict_merge(self):
447450
self.assertRaises(AttributeError, merge, {}, [], 0)
448451
self.assertRaises(AttributeError, merge, {}, 42, 0)
449452
self.assertRaises(SystemError, merge, UserDict(), {}, 0)
453+
self.assertRaises(SystemError, merge, frozendict(), {}, 0)
450454
self.assertRaises(SystemError, merge, 42, {}, 0)
451455
self.assertRaises(SystemError, merge, {}, NULL, 0)
452456
self.assertRaises(SystemError, merge, NULL, {}, 0)
453457

454458
def test_dict_mergefromseq2(self):
459+
# Test PyDict_MergeFromSeq2()
455460
mergefromseq2 = _testlimitedcapi.dict_mergefromseq2
456461
for cls1 in dict, DictSubclass:
457462
for cls2 in list, iter:
@@ -466,8 +471,8 @@ def test_dict_mergefromseq2(self):
466471
self.assertRaises(ValueError, mergefromseq2, {}, [(1, 2, 3)], 0)
467472
self.assertRaises(TypeError, mergefromseq2, {}, [1], 0)
468473
self.assertRaises(TypeError, mergefromseq2, {}, 42, 0)
469-
# CRASHES mergefromseq2(UserDict(), [], 0)
470-
# CRASHES mergefromseq2(42, [], 0)
474+
self.assertRaises(SystemError, mergefromseq2, UserDict(), [], 0)
475+
self.assertRaises(SystemError, mergefromseq2, 42, [], 0)
471476
# CRASHES mergefromseq2({}, NULL, 0)
472477
# CRASHES mergefromseq2(NULL, {}, 0)
473478

Objects/dictobject.c

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ static PyObject* frozendict_new(PyTypeObject *type, PyObject *args,
140140
PyObject *kwds);
141141
static PyObject* dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
142142
static int dict_merge(PyObject *a, PyObject *b, int override);
143+
static int dict_merge_from_seq2(PyObject *d, PyObject *seq2, int override);
143144

144145

145146
/*[clinic input]
@@ -3818,16 +3819,16 @@ static int
38183819
dict_update_arg(PyObject *self, PyObject *arg)
38193820
{
38203821
if (PyAnyDict_CheckExact(arg)) {
3821-
return PyDict_Merge(self, arg, 1);
3822+
return dict_merge(self, arg, 1);
38223823
}
38233824
int has_keys = PyObject_HasAttrWithError(arg, &_Py_ID(keys));
38243825
if (has_keys < 0) {
38253826
return -1;
38263827
}
38273828
if (has_keys) {
3828-
return PyDict_Merge(self, arg, 1);
3829+
return dict_merge(self, arg, 1);
38293830
}
3830-
return PyDict_MergeFromSeq2(self, arg, 1);
3831+
return dict_merge_from_seq2(self, arg, 1);
38313832
}
38323833

38333834
static int
@@ -3846,7 +3847,7 @@ dict_update_common(PyObject *self, PyObject *args, PyObject *kwds,
38463847

38473848
if (result == 0 && kwds != NULL) {
38483849
if (PyArg_ValidateKeywordArguments(kwds))
3849-
result = PyDict_Merge(self, kwds, 1);
3850+
result = dict_merge(self, kwds, 1);
38503851
else
38513852
result = -1;
38523853
}
@@ -3960,8 +3961,8 @@ merge_from_seq2_lock_held(PyObject *d, PyObject *seq2, int override)
39603961
return Py_SAFE_DOWNCAST(i, Py_ssize_t, int);
39613962
}
39623963

3963-
int
3964-
PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
3964+
static int
3965+
dict_merge_from_seq2(PyObject *d, PyObject *seq2, int override)
39653966
{
39663967
int res;
39673968
Py_BEGIN_CRITICAL_SECTION(d);
@@ -3971,6 +3972,19 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
39713972
return res;
39723973
}
39733974

3975+
int
3976+
PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
3977+
{
3978+
assert(d != NULL);
3979+
assert(seq2 != NULL);
3980+
if (!PyDict_Check(d)) {
3981+
PyErr_BadInternalCall();
3982+
return -1;
3983+
}
3984+
3985+
return dict_merge_from_seq2(d, seq2, override);
3986+
}
3987+
39743988
static int
39753989
dict_dict_merge(PyDictObject *mp, PyDictObject *other, int override)
39763990
{
@@ -4070,23 +4084,14 @@ dict_dict_merge(PyDictObject *mp, PyDictObject *other, int override)
40704084
static int
40714085
dict_merge(PyObject *a, PyObject *b, int override)
40724086
{
4073-
PyDictObject *mp, *other;
4074-
4087+
assert(a != NULL);
4088+
assert(b != NULL);
40754089
assert(0 <= override && override <= 2);
40764090

4077-
/* We accept for the argument either a concrete dictionary object,
4078-
* or an abstract "mapping" object. For the former, we can do
4079-
* things quite efficiently. For the latter, we only require that
4080-
* PyMapping_Keys() and PyObject_GetItem() be supported.
4081-
*/
4082-
if (a == NULL || !PyAnyDict_Check(a) || b == NULL) {
4083-
PyErr_BadInternalCall();
4084-
return -1;
4085-
}
4086-
mp = (PyDictObject*)a;
4091+
PyDictObject *mp = _PyAnyDict_CAST(a);
40874092
int res = 0;
40884093
if (PyAnyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) {
4089-
other = (PyDictObject*)b;
4094+
PyDictObject *other = (PyDictObject*)b;
40904095
int res;
40914096
Py_BEGIN_CRITICAL_SECTION2(a, b);
40924097
res = dict_dict_merge((PyDictObject *)a, other, override);
@@ -4167,23 +4172,38 @@ dict_merge(PyObject *a, PyObject *b, int override)
41674172
}
41684173
}
41694174

4175+
static int
4176+
dict_merge_api(PyObject *a, PyObject *b, int override)
4177+
{
4178+
/* We accept for the argument either a concrete dictionary object,
4179+
* or an abstract "mapping" object. For the former, we can do
4180+
* things quite efficiently. For the latter, we only require that
4181+
* PyMapping_Keys() and PyObject_GetItem() be supported.
4182+
*/
4183+
if (a == NULL || !PyDict_Check(a) || b == NULL) {
4184+
PyErr_BadInternalCall();
4185+
return -1;
4186+
}
4187+
return dict_merge(a, b, override);
4188+
}
4189+
41704190
int
41714191
PyDict_Update(PyObject *a, PyObject *b)
41724192
{
4173-
return dict_merge(a, b, 1);
4193+
return dict_merge_api(a, b, 1);
41744194
}
41754195

41764196
int
41774197
PyDict_Merge(PyObject *a, PyObject *b, int override)
41784198
{
41794199
/* XXX Deprecate override not in (0, 1). */
4180-
return dict_merge(a, b, override != 0);
4200+
return dict_merge_api(a, b, override != 0);
41814201
}
41824202

41834203
int
41844204
_PyDict_MergeEx(PyObject *a, PyObject *b, int override)
41854205
{
4186-
return dict_merge(a, b, override);
4206+
return dict_merge_api(a, b, override);
41874207
}
41884208

41894209
/*[clinic input]

0 commit comments

Comments
 (0)