@@ -1443,6 +1443,96 @@ write_newline_indent(PyUnicodeWriter *writer, |
1443 | 1443 | } |
1444 | 1444 |
|
1445 | 1445 |
|
| 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 | + |
1446 | 1536 | static PyObject * |
1447 | 1537 | encoder_call(PyObject *op, PyObject *args, PyObject *kwds) |
1448 | 1538 | { |
@@ -1616,23 +1706,9 @@ encoder_listencode_obj(PyEncoderObject *s, PyUnicodeWriter *writer, |
1616 | 1706 | return rv; |
1617 | 1707 | } |
1618 | 1708 | 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; |
1636 | 1712 | } |
1637 | 1713 | newobj = PyObject_CallOneArg(s->defaultfn, obj); |
1638 | 1714 | if (newobj == NULL) { |
@@ -1654,14 +1730,7 @@ encoder_listencode_obj(PyEncoderObject *s, PyUnicodeWriter *writer, |
1654 | 1730 | Py_XDECREF(ident); |
1655 | 1731 | return -1; |
1656 | 1732 | } |
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); |
1665 | 1734 | } |
1666 | 1735 | } |
1667 | 1736 |
|
@@ -1671,35 +1740,11 @@ encoder_encode_key_value(PyEncoderObject *s, PyUnicodeWriter *writer, bool *firs |
1671 | 1740 | Py_ssize_t indent_level, PyObject *indent_cache, |
1672 | 1741 | PyObject *item_separator) |
1673 | 1742 | { |
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; |
1700 | 1744 |
|
| 1745 | + PyObject *keystr = encoder_key_to_str(s, key, &skip); |
1701 | 1746 | if (keystr == NULL) { |
1702 | | - return -1; |
| 1747 | + return skip ? 0 : -1; |
1703 | 1748 | } |
1704 | 1749 |
|
1705 | 1750 | if (*first) { |
@@ -1807,20 +1852,8 @@ encoder_listencode_dict(PyEncoderObject *s, PyUnicodeWriter *writer, |
1807 | 1852 | return PyUnicodeWriter_WriteASCII(writer, "{}", 2); |
1808 | 1853 | } |
1809 | 1854 |
|
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; |
1824 | 1857 | } |
1825 | 1858 |
|
1826 | 1859 | if (PyUnicodeWriter_WriteChar(writer, '{')) { |
@@ -1863,10 +1896,12 @@ encoder_listencode_dict(PyEncoderObject *s, PyUnicodeWriter *writer, |
1863 | 1896 | } |
1864 | 1897 | } |
1865 | 1898 |
|
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) { |
1868 | 1903 | goto bail; |
1869 | | - Py_CLEAR(ident); |
| 1904 | + } |
1870 | 1905 | } |
1871 | 1906 | if (s->indent != Py_None && !first) { |
1872 | 1907 | indent_level--; |
@@ -1927,20 +1962,8 @@ encoder_listencode_list(PyEncoderObject *s, PyUnicodeWriter *writer, |
1927 | 1962 | return PyUnicodeWriter_WriteASCII(writer, "[]", 2); |
1928 | 1963 | } |
1929 | 1964 |
|
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; |
1944 | 1967 | } |
1945 | 1968 |
|
1946 | 1969 | if (PyUnicodeWriter_WriteChar(writer, '[')) { |
@@ -1965,10 +1988,12 @@ encoder_listencode_list(PyEncoderObject *s, PyUnicodeWriter *writer, |
1965 | 1988 | if (result < 0) { |
1966 | 1989 | goto bail; |
1967 | 1990 | } |
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) { |
1970 | 1995 | goto bail; |
1971 | | - Py_CLEAR(ident); |
| 1996 | + } |
1972 | 1997 | } |
1973 | 1998 |
|
1974 | 1999 | if (s->indent != Py_None) { |
|