@@ -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+
14461536static PyObject *
14471537encoder_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