Skip to content

Commit 72e86c4

Browse files
committed
pythongh-129711: Factor out circular-marker and dict-key helpers in _json
Extract json_marker_enter()/json_marker_leave() and encoder_key_to_str() (with json_key_type_ok()) from the one-shot C encoder, in preparation for a streaming encoder that shares them. No behavior change.
1 parent 3f99ebe commit 72e86c4

1 file changed

Lines changed: 111 additions & 86 deletions

File tree

‎Modules/_json.c‎

Lines changed: 111 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,96 @@ write_newline_indent(PyUnicodeWriter *writer,
14431443
}
14441444

14451445

1446+
/* Register obj for circular-reference detection. Stores a new marker key in
1447+
* *ident_out (NULL if markers are disabled); returns 0, or -1 on a circular
1448+
* reference or error. */
1449+
static int
1450+
json_marker_enter(PyObject *markers, PyObject *obj, PyObject **ident_out)
1451+
{
1452+
*ident_out = NULL;
1453+
if (markers == Py_None) {
1454+
return 0;
1455+
}
1456+
PyObject *ident = PyLong_FromVoidPtr(obj);
1457+
if (ident == NULL) {
1458+
return -1;
1459+
}
1460+
int has_key = PyDict_Contains(markers, ident);
1461+
if (has_key) {
1462+
if (has_key != -1) {
1463+
PyErr_SetString(PyExc_ValueError, "Circular reference detected");
1464+
}
1465+
Py_DECREF(ident);
1466+
return -1;
1467+
}
1468+
if (PyDict_SetItem(markers, ident, obj) < 0) {
1469+
Py_DECREF(ident);
1470+
return -1;
1471+
}
1472+
*ident_out = ident;
1473+
return 0;
1474+
}
1475+
1476+
/* Undo json_marker_enter: drop the marker and release ident (NULL-safe).
1477+
* Returns 0, or -1 if removal fails; ident is released either way. */
1478+
static int
1479+
json_marker_leave(PyObject *markers, PyObject *ident)
1480+
{
1481+
if (ident == NULL) {
1482+
return 0;
1483+
}
1484+
int rv = 0;
1485+
/* markers can already be NULL here: the encoder and a streaming iterator
1486+
* can sit in the same reference cycle, and the GC may run the encoder's
1487+
* tp_clear before the iterator pops its remaining frames. */
1488+
if (markers != NULL) {
1489+
rv = PyDict_DelItem(markers, ident);
1490+
}
1491+
Py_DECREF(ident);
1492+
return rv;
1493+
}
1494+
1495+
1496+
/* True if key is an allowed JSON object key type (str, int, float, bool,
1497+
* None). */
1498+
static inline int
1499+
json_key_type_ok(PyObject *key)
1500+
{
1501+
return PyUnicode_Check(key) || PyLong_Check(key) || PyFloat_Check(key)
1502+
|| key == Py_True || key == Py_False || key == Py_None;
1503+
}
1504+
1505+
/* Convert a dict key to the unescaped string to be quoted (str kept as-is;
1506+
* int/float/bool/None stringified). Returns a new ref, or NULL: for an
1507+
* unsupported type *skip is set under skipkeys (no exception), else a
1508+
* TypeError is raised. */
1509+
static PyObject *
1510+
encoder_key_to_str(PyEncoderObject *s, PyObject *key, int *skip)
1511+
{
1512+
*skip = 0;
1513+
if (PyUnicode_Check(key)) {
1514+
return Py_NewRef(key);
1515+
}
1516+
if (PyFloat_Check(key)) {
1517+
return encoder_encode_float(s, key);
1518+
}
1519+
/* Must precede PyLong_Check: True and False are also 1 and 0. */
1520+
if (key == Py_True || key == Py_False || key == Py_None) {
1521+
return _encoded_const(key);
1522+
}
1523+
if (PyLong_Check(key)) {
1524+
return PyLong_Type.tp_repr(key);
1525+
}
1526+
if (s->skipkeys) {
1527+
*skip = 1;
1528+
return NULL;
1529+
}
1530+
PyErr_Format(PyExc_TypeError,
1531+
"keys must be str, int, float, bool or None, "
1532+
"not %.100s", Py_TYPE(key)->tp_name);
1533+
return NULL;
1534+
}
1535+
14461536
static PyObject *
14471537
encoder_call(PyObject *op, PyObject *args, PyObject *kwds)
14481538
{
@@ -1616,23 +1706,9 @@ encoder_listencode_obj(PyEncoderObject *s, PyUnicodeWriter *writer,
16161706
return rv;
16171707
}
16181708
else {
1619-
PyObject *ident = NULL;
1620-
if (s->markers != Py_None) {
1621-
int has_key;
1622-
ident = PyLong_FromVoidPtr(obj);
1623-
if (ident == NULL)
1624-
return -1;
1625-
has_key = PyDict_Contains(s->markers, ident);
1626-
if (has_key) {
1627-
if (has_key != -1)
1628-
PyErr_SetString(PyExc_ValueError, "Circular reference detected");
1629-
Py_DECREF(ident);
1630-
return -1;
1631-
}
1632-
if (PyDict_SetItem(s->markers, ident, obj)) {
1633-
Py_DECREF(ident);
1634-
return -1;
1635-
}
1709+
PyObject *ident;
1710+
if (json_marker_enter(s->markers, obj, &ident) < 0) {
1711+
return -1;
16361712
}
16371713
newobj = PyObject_CallOneArg(s->defaultfn, obj);
16381714
if (newobj == NULL) {
@@ -1654,14 +1730,7 @@ encoder_listencode_obj(PyEncoderObject *s, PyUnicodeWriter *writer,
16541730
Py_XDECREF(ident);
16551731
return -1;
16561732
}
1657-
if (ident != NULL) {
1658-
if (PyDict_DelItem(s->markers, ident)) {
1659-
Py_XDECREF(ident);
1660-
return -1;
1661-
}
1662-
Py_XDECREF(ident);
1663-
}
1664-
return rv;
1733+
return json_marker_leave(s->markers, ident);
16651734
}
16661735
}
16671736

@@ -1671,35 +1740,11 @@ encoder_encode_key_value(PyEncoderObject *s, PyUnicodeWriter *writer, bool *firs
16711740
Py_ssize_t indent_level, PyObject *indent_cache,
16721741
PyObject *item_separator)
16731742
{
1674-
PyObject *keystr = NULL;
1675-
int rv;
1676-
1677-
if (PyUnicode_Check(key)) {
1678-
keystr = Py_NewRef(key);
1679-
}
1680-
else if (PyFloat_Check(key)) {
1681-
keystr = encoder_encode_float(s, key);
1682-
}
1683-
else if (key == Py_True || key == Py_False || key == Py_None) {
1684-
/* This must come before the PyLong_Check because
1685-
True and False are also 1 and 0.*/
1686-
keystr = _encoded_const(key);
1687-
}
1688-
else if (PyLong_Check(key)) {
1689-
keystr = PyLong_Type.tp_repr(key);
1690-
}
1691-
else if (s->skipkeys) {
1692-
return 0;
1693-
}
1694-
else {
1695-
PyErr_Format(PyExc_TypeError,
1696-
"keys must be str, int, float, bool or None, "
1697-
"not %.100s", Py_TYPE(key)->tp_name);
1698-
return -1;
1699-
}
1743+
int rv, skip;
17001744

1745+
PyObject *keystr = encoder_key_to_str(s, key, &skip);
17011746
if (keystr == NULL) {
1702-
return -1;
1747+
return skip ? 0 : -1;
17031748
}
17041749

17051750
if (*first) {
@@ -1807,20 +1852,8 @@ encoder_listencode_dict(PyEncoderObject *s, PyUnicodeWriter *writer,
18071852
return PyUnicodeWriter_WriteASCII(writer, "{}", 2);
18081853
}
18091854

1810-
if (s->markers != Py_None) {
1811-
int has_key;
1812-
ident = PyLong_FromVoidPtr(dct);
1813-
if (ident == NULL)
1814-
goto bail;
1815-
has_key = PyDict_Contains(s->markers, ident);
1816-
if (has_key) {
1817-
if (has_key != -1)
1818-
PyErr_SetString(PyExc_ValueError, "Circular reference detected");
1819-
goto bail;
1820-
}
1821-
if (PyDict_SetItem(s->markers, ident, dct)) {
1822-
goto bail;
1823-
}
1855+
if (json_marker_enter(s->markers, dct, &ident) < 0) {
1856+
goto bail;
18241857
}
18251858

18261859
if (PyUnicodeWriter_WriteChar(writer, '{')) {
@@ -1863,10 +1896,12 @@ encoder_listencode_dict(PyEncoderObject *s, PyUnicodeWriter *writer,
18631896
}
18641897
}
18651898

1866-
if (ident != NULL) {
1867-
if (PyDict_DelItem(s->markers, ident))
1899+
{
1900+
int leave_rv = json_marker_leave(s->markers, ident);
1901+
ident = NULL;
1902+
if (leave_rv < 0) {
18681903
goto bail;
1869-
Py_CLEAR(ident);
1904+
}
18701905
}
18711906
if (s->indent != Py_None && !first) {
18721907
indent_level--;
@@ -1927,20 +1962,8 @@ encoder_listencode_list(PyEncoderObject *s, PyUnicodeWriter *writer,
19271962
return PyUnicodeWriter_WriteASCII(writer, "[]", 2);
19281963
}
19291964

1930-
if (s->markers != Py_None) {
1931-
int has_key;
1932-
ident = PyLong_FromVoidPtr(seq);
1933-
if (ident == NULL)
1934-
goto bail;
1935-
has_key = PyDict_Contains(s->markers, ident);
1936-
if (has_key) {
1937-
if (has_key != -1)
1938-
PyErr_SetString(PyExc_ValueError, "Circular reference detected");
1939-
goto bail;
1940-
}
1941-
if (PyDict_SetItem(s->markers, ident, seq)) {
1942-
goto bail;
1943-
}
1965+
if (json_marker_enter(s->markers, seq, &ident) < 0) {
1966+
goto bail;
19441967
}
19451968

19461969
if (PyUnicodeWriter_WriteChar(writer, '[')) {
@@ -1965,10 +1988,12 @@ encoder_listencode_list(PyEncoderObject *s, PyUnicodeWriter *writer,
19651988
if (result < 0) {
19661989
goto bail;
19671990
}
1968-
if (ident != NULL) {
1969-
if (PyDict_DelItem(s->markers, ident))
1991+
{
1992+
int leave_rv = json_marker_leave(s->markers, ident);
1993+
ident = NULL;
1994+
if (leave_rv < 0) {
19701995
goto bail;
1971-
Py_CLEAR(ident);
1996+
}
19721997
}
19731998

19741999
if (s->indent != Py_None) {

0 commit comments

Comments
 (0)