from test.test_json import CTest
+from test.support import gc_collect
class BadBool:
def test_unsortable_keys(self):
with self.assertRaises(TypeError):
self.json.encoder.JSONEncoder(sort_keys=True).encode({'a': 1, 1: 'a'})
+
+ def test_mutate_dict_items_during_encode(self):
+ # gh-142831: Clearing the items list via a re-entrant key encoder
+ # must not cause a use-after-free. BadDict.items() returns a
+ # mutable list; encode_str clears it while iterating.
+ items = None
+
+ class BadDict(dict):
+ def items(self):
+ nonlocal items
+ items = [("boom", object())]
+ return items
+
+ cleared = False
+ def encode_str(obj):
+ nonlocal items, cleared
+ if items is not None:
+ items.clear()
+ items = None
+ cleared = True
+ gc_collect()
+ return '"x"'
+
+ encoder = self.json.encoder.c_make_encoder(
+ None, lambda o: "null",
+ encode_str, None,
+ ": ", ", ", False,
+ False, True
+ )
+
+ # Must not crash (use-after-free under ASan before fix)
+ encoder(BadDict(real=1), 0)
+ self.assertTrue(cleared)
+
+ def test_mutate_list_during_encode(self):
+ # gh-142831: Clearing a list mid-iteration via the default
+ # callback must not cause a use-after-free.
+ call_count = 0
+ lst = [object() for _ in range(10)]
+
+ def default(obj):
+ nonlocal call_count
+ call_count += 1
+ if call_count == 3:
+ lst.clear()
+ gc_collect()
+ return None
+
+ encoder = self.json.encoder.c_make_encoder(
+ None, default,
+ self.json.encoder.c_encode_basestring, None,
+ ": ", ", ", False,
+ False, True
+ )
+
+ # Must not crash (use-after-free under ASan before fix)
+ encoder(lst, 0)
+ # Verify the mutation path was actually hit and the loop
+ # stopped iterating after the list was cleared.
+ self.assertEqual(call_count, 3)
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(items); i++) {
PyObject *item = PyList_GET_ITEM(items, i);
+ // gh-142831: encoder_encode_key_value() can invoke user code
+ // that mutates the items list, invalidating this borrowed ref.
+ Py_INCREF(item);
if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 2) {
PyErr_SetString(PyExc_ValueError, "items must return 2-tuples");
+ Py_DECREF(item);
goto bail;
}
value = PyTuple_GET_ITEM(item, 1);
if (encoder_encode_key_value(s, writer, &first, key, value,
new_newline_indent,
- current_item_separator) < 0)
+ current_item_separator) < 0) {
+ Py_DECREF(item);
goto bail;
+ }
+ Py_DECREF(item);
}
Py_CLEAR(items);
} else {
Py_ssize_t pos = 0;
while (PyDict_Next(dct, &pos, &key, &value)) {
+ // gh-142831: encoder_encode_key_value() can invoke user code
+ // that mutates the dict, invalidating these borrowed refs.
+ Py_INCREF(key);
+ Py_INCREF(value);
if (encoder_encode_key_value(s, writer, &first, key, value,
new_newline_indent,
- current_item_separator) < 0)
+ current_item_separator) < 0) {
+ Py_DECREF(key);
+ Py_DECREF(value);
goto bail;
+ }
+ Py_DECREF(key);
+ Py_DECREF(value);
}
}
}
for (i = 0; i < PySequence_Fast_GET_SIZE(s_fast); i++) {
PyObject *obj = PySequence_Fast_GET_ITEM(s_fast, i);
+ // gh-142831: encoder_listencode_obj() can invoke user code
+ // that mutates the sequence, invalidating this borrowed ref.
+ Py_INCREF(obj);
if (i) {
- if (_PyUnicodeWriter_WriteStr(writer, separator) < 0)
+ if (_PyUnicodeWriter_WriteStr(writer, separator) < 0) {
+ Py_DECREF(obj);
goto bail;
+ }
}
- if (encoder_listencode_obj(s, writer, obj, new_newline_indent))
+ if (encoder_listencode_obj(s, writer, obj, new_newline_indent)) {
+ Py_DECREF(obj);
goto bail;
+ }
+ Py_DECREF(obj);
}
if (ident != NULL) {
if (PyDict_DelItem(s->markers, ident))