simplejson 4.2.0__tar.gz → 4.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. {simplejson-4.2.0 → simplejson-4.3.0}/CHANGES.txt +33 -0
  2. {simplejson-4.2.0/simplejson.egg-info → simplejson-4.3.0}/PKG-INFO +1 -1
  3. {simplejson-4.2.0 → simplejson-4.3.0}/conf.py +2 -2
  4. {simplejson-4.2.0 → simplejson-4.3.0}/index.rst +9 -6
  5. {simplejson-4.2.0 → simplejson-4.3.0}/setup.py +1 -1
  6. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/__init__.py +6 -5
  7. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/_speedups.c +205 -42
  8. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/decoder.py +5 -3
  9. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/encoder.py +223 -177
  10. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/scanner.py +3 -3
  11. simplejson-4.3.0/simplejson/tests/test_check_circular.py +158 -0
  12. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_decimal.py +16 -0
  13. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_decode.py +66 -0
  14. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_dump.py +111 -0
  15. simplejson-4.3.0/simplejson/tests/test_errors.py +247 -0
  16. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_float.py +13 -0
  17. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_for_json.py +20 -0
  18. simplejson-4.3.0/simplejson/tests/test_item_sort_key.py +68 -0
  19. simplejson-4.3.0/simplejson/tests/test_iterable.py +263 -0
  20. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_speedups.py +11 -0
  21. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_tool.py +35 -2
  22. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tool.py +2 -2
  23. {simplejson-4.2.0 → simplejson-4.3.0/simplejson.egg-info}/PKG-INFO +1 -1
  24. simplejson-4.2.0/simplejson/tests/test_check_circular.py +0 -50
  25. simplejson-4.2.0/simplejson/tests/test_errors.py +0 -108
  26. simplejson-4.2.0/simplejson/tests/test_item_sort_key.py +0 -27
  27. simplejson-4.2.0/simplejson/tests/test_iterable.py +0 -61
  28. {simplejson-4.2.0 → simplejson-4.3.0}/LICENSE.txt +0 -0
  29. {simplejson-4.2.0 → simplejson-4.3.0}/MANIFEST.in +0 -0
  30. {simplejson-4.2.0 → simplejson-4.3.0}/README.rst +0 -0
  31. {simplejson-4.2.0 → simplejson-4.3.0}/pyproject.toml +0 -0
  32. {simplejson-4.2.0 → simplejson-4.3.0}/scripts/make_docs.py +0 -0
  33. {simplejson-4.2.0 → simplejson-4.3.0}/setup.cfg +0 -0
  34. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/_speedups_scan.h +0 -0
  35. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/compat.py +0 -0
  36. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/errors.py +0 -0
  37. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/ordered_dict.py +0 -0
  38. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/raw_json.py +0 -0
  39. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/__init__.py +0 -0
  40. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/_cibw_runner.py +0 -0
  41. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/_helpers.py +0 -0
  42. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_bigint_as_string.py +0 -0
  43. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_bitsize_int_as_string.py +0 -0
  44. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_custom_method_cycles.py +0 -0
  45. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_default.py +0 -0
  46. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_encode_basestring_ascii.py +0 -0
  47. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_encode_for_html.py +0 -0
  48. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_fail.py +0 -0
  49. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_free_threading.py +0 -0
  50. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_indent.py +0 -0
  51. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_namedtuple.py +0 -0
  52. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_pass1.py +0 -0
  53. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_pass2.py +0 -0
  54. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_pass3.py +0 -0
  55. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_raw_json.py +0 -0
  56. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_recursion.py +0 -0
  57. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_scanstring.py +0 -0
  58. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_separators.py +0 -0
  59. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_str_subclass.py +0 -0
  60. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_subclass.py +0 -0
  61. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_subinterpreters.py +0 -0
  62. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_tuple.py +0 -0
  63. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson/tests/test_unicode.py +0 -0
  64. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson.egg-info/SOURCES.txt +0 -0
  65. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson.egg-info/dependency_links.txt +0 -0
  66. {simplejson-4.2.0 → simplejson-4.3.0}/simplejson.egg-info/top_level.txt +0 -0
  67. {simplejson-4.2.0 → simplejson-4.3.0}/tsan_stress_simplejson.py +0 -0
@@ -1,3 +1,36 @@
1
+ Version 4.3.0 released 2026-11-10
2
+
3
+ * The C encoder no longer crashes when ``default()`` or ``for_json()``
4
+ shrinks or grows the list being encoded, and no longer reads a freed
5
+ key when encoding a dict value removes its entry. Both encoders now
6
+ raise ``RuntimeError("dictionary changed size during iteration")``
7
+ when a dict is resized while it is being encoded. ``make_encoder()``
8
+ raises ``TypeError`` for an invalid ``markers`` argument instead of
9
+ crashing, and C and pure Python error messages now match
10
+ https://github.com/simplejson/simplejson/pull/395
11
+ * Circular references are now detected through re-iterable objects
12
+ encoded with ``iterable_as_array=True``, in both encoders
13
+ https://github.com/simplejson/simplejson/pull/391
14
+ * A failure while adding a PEP 678 context note no longer replaces the
15
+ original serialization error
16
+ https://github.com/simplejson/simplejson/pull/392
17
+ * Explicitly supplied ``parse_float``, ``parse_int`` and
18
+ ``parse_constant`` callbacks are now used even when they are falsy
19
+ https://github.com/simplejson/simplejson/pull/394
20
+ * An explicit ``item_sort_key`` is now honored even when it is falsy
21
+ https://github.com/simplejson/simplejson/pull/396
22
+ * ``python -m simplejson.tool`` now reads input files as UTF-8
23
+ regardless of the locale
24
+ https://github.com/simplejson/simplejson/pull/397
25
+ * With ``allow_nan=False`` the C encoder's ``ValueError`` now names the
26
+ out-of-range value, matching the pure Python encoder
27
+ https://github.com/simplejson/simplejson/pull/399
28
+ * Build and test native Windows arm64 wheels
29
+ https://github.com/simplejson/simplejson/pull/401
30
+ * Wheels for CPython 3.15 are now built against the final 3.15 release
31
+ via cibuildwheel v4.3.5, and CI uses astral-sh/setup-uv v10.3.3
32
+ https://github.com/simplejson/simplejson/pull/405
33
+
1
34
  Version 4.2.0 released 2026-10-02
2
35
 
3
36
  * Start building wheels for Python 3.15 and drop Python 3.8 support
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplejson
3
- Version: 4.2.0
3
+ Version: 4.3.0
4
4
  Summary: Simple, fast, extensible JSON encoder/decoder for Python
5
5
  Home-page: https://github.com/simplejson/simplejson
6
6
  Author: Bob Ippolito
@@ -42,9 +42,9 @@ copyright = '2026, Bob Ippolito'
42
42
  # other places throughout the built documents.
43
43
  #
44
44
  # The short X.Y version.
45
- version = '4.2'
45
+ version = '4.3'
46
46
  # The full version, including alpha/beta/rc tags.
47
- release = '4.2.0'
47
+ release = '4.3.0'
48
48
 
49
49
  # There are two options for replacing |today|: either, you set today to some
50
50
  # non-false value, then it is used:
@@ -215,8 +215,10 @@ Basic Usage
215
215
  optimization, the result would be a :class:`str` object.
216
216
 
217
217
  If *check_circular* is false (default: ``True``), then the circular
218
- reference check for container types will be skipped and a circular
219
- reference will result in an :exc:`OverflowError` (or worse).
218
+ reference check for container types (including iterables encoded with
219
+ *iterable_as_array*) will be skipped and a circular reference will
220
+ result in a :exc:`RecursionError` (:exc:`RuntimeError` on Python 2) or
221
+ worse.
220
222
 
221
223
  If *allow_nan* is false (default: ``False``), then it will be a
222
224
  :exc:`ValueError` to serialize out of range :class:`float` values
@@ -632,10 +634,11 @@ Encoders and decoders
632
634
  :class:`str` objects with all incoming unicode characters escaped. If
633
635
  *ensure_ascii* is false, the output will be a unicode object.
634
636
 
635
- If *check_circular* is true (the default), then lists, dicts, and custom
636
- encoded objects will be checked for circular references during encoding to
637
- prevent an infinite recursion (which would cause an :exc:`OverflowError`).
638
- Otherwise, no such check takes place.
637
+ If *check_circular* is true (the default), then lists, dicts, iterables
638
+ encoded with *iterable_as_array*, and custom encoded objects will be checked
639
+ for circular references during encoding to prevent an infinite recursion
640
+ (which would cause a :exc:`RecursionError`, or :exc:`RuntimeError` on
641
+ Python 2). Otherwise, no such check takes place.
639
642
 
640
643
  If *allow_nan* is true (not the default), then ``NaN``, ``Infinity``, and
641
644
  ``-Infinity`` will be encoded as such. This behavior is not JSON
@@ -12,7 +12,7 @@ from distutils.errors import CCompilerError, DistutilsExecError, \
12
12
 
13
13
  IS_PYPY = hasattr(sys, 'pypy_translation_info')
14
14
  IS_GRAALPY = getattr(getattr(sys, "implementation", None), "name", None) == "graalpy"
15
- VERSION = '4.2.0'
15
+ VERSION = '4.3.0'
16
16
  DESCRIPTION = "Simple, fast, extensible JSON encoder/decoder for Python"
17
17
 
18
18
  with open('README.rst', 'r') as f:
@@ -118,7 +118,7 @@ Serializing multiple objects to JSON lines (newline-delimited JSON)::
118
118
 
119
119
  """
120
120
  from __future__ import absolute_import
121
- __version__ = '4.2.0'
121
+ __version__ = '4.3.0'
122
122
  __all__ = [
123
123
  'dump', 'dumps', 'load', 'loads',
124
124
  'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
@@ -246,7 +246,7 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
246
246
  encoding == 'utf-8' and default is None and use_decimal
247
247
  and namedtuple_as_object and tuple_as_array and not iterable_as_array
248
248
  and not bigint_as_string and not sort_keys
249
- and not item_sort_key and not for_json
249
+ and item_sort_key is None and not for_json
250
250
  and not ignore_nan and int_as_string_bitcount is None
251
251
  and not kw
252
252
  ):
@@ -292,8 +292,9 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
292
292
  by JSON. When it is true, all non-ASCII characters are escaped.
293
293
 
294
294
  If ``check_circular`` is false, then the circular reference check
295
- for container types will be skipped and a circular reference will
296
- result in an ``OverflowError`` (or worse).
295
+ for container types (including iterables encoded with
296
+ ``iterable_as_array``) will be skipped and a circular reference will
297
+ result in a ``RecursionError`` (``RuntimeError`` on Python 2) or worse.
297
298
 
298
299
  If *allow_nan* is true (default: ``False``), then out of range ``float``
299
300
  values (``nan``, ``inf``, ``-inf``) will be serialized to
@@ -372,7 +373,7 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
372
373
  encoding == 'utf-8' and default is None and use_decimal
373
374
  and namedtuple_as_object and tuple_as_array and not iterable_as_array
374
375
  and not bigint_as_string and not sort_keys
375
- and not item_sort_key and not for_json
376
+ and item_sort_key is None and not for_json
376
377
  and not ignore_nan and int_as_string_bitcount is None
377
378
  and not kw
378
379
  ):
@@ -414,7 +414,8 @@ is_raw_json(_speedups_state *state, PyObject *obj);
414
414
  static PyObject *
415
415
  encoder_stringify_key(PyEncoderObject *s, PyObject *key);
416
416
  static int
417
- encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_ssize_t indent_level);
417
+ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq,
418
+ PyObject *origin, Py_ssize_t indent_level);
418
419
  static int
419
420
  encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ssize_t indent_level);
420
421
  static int
@@ -491,6 +492,31 @@ json_PyDict_GetItemRef(PyObject *dict, PyObject *key, PyObject **result)
491
492
  #endif
492
493
  }
493
494
 
495
+ /* The type name used in error messages and exception notes, matching
496
+ * the pure-Python encoder's type(obj).__name__, which is what type's
497
+ * __name__ getter returns: ht_name for heap types, and tp_name after its
498
+ * module prefix ("collections.deque") for static types. Nothing here can
499
+ * fail, since a note is formatted while an exception is set. */
500
+ static const char *
501
+ json_type_name(PyObject *obj)
502
+ {
503
+ PyTypeObject *tp = Py_TYPE(obj);
504
+ const char *dot;
505
+ if (PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
506
+ #if PY_MAJOR_VERSION >= 3
507
+ /* A spec-created type keeps its module prefix in tp_name only.
508
+ * For a class, tp_name is the UTF-8 of ht_name, which may itself
509
+ * contain dots, so it is the fallback for non-ASCII names. */
510
+ PyObject *ht_name = ((PyHeapTypeObject *)tp)->ht_name;
511
+ if (PyUnicode_IS_ASCII(ht_name))
512
+ return (const char *)PyUnicode_DATA(ht_name);
513
+ #endif
514
+ return tp->tp_name;
515
+ }
516
+ dot = strrchr(tp->tp_name, '.');
517
+ return dot == NULL ? tp->tp_name : dot + 1;
518
+ }
519
+
494
520
  static inline int
495
521
  json_memo_intern_key(PyObject *memo, PyObject **key_ptr)
496
522
  {
@@ -1098,7 +1124,7 @@ encoder_stringify_key(PyEncoderObject *s, PyObject *key)
1098
1124
  }
1099
1125
  PyErr_Format(PyExc_TypeError,
1100
1126
  "keys must be str, int, float, bool or None, "
1101
- "not %.100s", key->ob_type->tp_name);
1127
+ "not %.100s", json_type_name(key));
1102
1128
  return NULL;
1103
1129
  }
1104
1130
 
@@ -1151,6 +1177,18 @@ encoder_dict_iteritems(PyEncoderObject *s, PyObject *dct)
1151
1177
  items = PyMapping_Items(dct);
1152
1178
  if (items == NULL)
1153
1179
  return NULL;
1180
+ #if PY_MAJOR_VERSION < 3
1181
+ /* Python 3's PyMapping_Items always makes a list, but Python 2's
1182
+ * returns whatever items() returned, and the sort below reads it as
1183
+ * a list. */
1184
+ if (!PyList_CheckExact(items)) {
1185
+ PyObject *items_list = PySequence_List(items);
1186
+ Py_DECREF(items);
1187
+ if (items_list == NULL)
1188
+ return NULL;
1189
+ items = items_list;
1190
+ }
1191
+ #endif
1154
1192
 
1155
1193
  /* Unsorted path: return iter(items) directly. */
1156
1194
  if (s->item_sort_kw == Py_None) {
@@ -2606,6 +2644,15 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
2606
2644
  &ignore_nan, &Decimal, &iterable_as_array))
2607
2645
  return NULL;
2608
2646
 
2647
+ /* markers goes straight to PyDict_Contains, PyDict_SetItem and
2648
+ * PyDict_DelItem, which do not check the type of their argument. */
2649
+ if (markers != Py_None && !PyDict_Check(markers)) {
2650
+ PyErr_Format(PyExc_TypeError,
2651
+ "make_encoder() argument 1 must be dict or None, not %.80s",
2652
+ json_type_name(markers));
2653
+ return NULL;
2654
+ }
2655
+
2609
2656
  s = (PyEncoderObject *)type->tp_alloc(type, 0);
2610
2657
  if (s == NULL)
2611
2658
  return NULL;
@@ -2829,7 +2876,14 @@ encoder_encode_nonfinite(PyEncoderObject *s, int sign)
2829
2876
  _speedups_state *state = get_speedups_state(s->module_ref);
2830
2877
 
2831
2878
  if (!s->allow_or_ignore_nan) {
2832
- PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant");
2879
+ const char *msg;
2880
+ if (sign > 0)
2881
+ msg = "Out of range float values are not JSON compliant: inf";
2882
+ else if (sign < 0)
2883
+ msg = "Out of range float values are not JSON compliant: -inf";
2884
+ else
2885
+ msg = "Out of range float values are not JSON compliant: nan";
2886
+ PyErr_SetString(PyExc_ValueError, msg);
2833
2887
  return NULL;
2834
2888
  }
2835
2889
  if (s->allow_or_ignore_nan & JSON_IGNORE_NAN) {
@@ -2965,9 +3019,10 @@ _steal_accumulate(_speedups_state *state, JSON_Accu *accu, PyObject *stolen)
2965
3019
  /* Push a reference to `obj` into the encoder's circular-reference marker
2966
3020
  * dict, keyed by the object's address. Allocates a fresh PyLong ident and
2967
3021
  * stores it in *ident_ptr (caller owns the reference, and must pass it to
2968
- * encoder_markers_pop to remove the entry). If markers tracking is off
3022
+ * encoder_markers_pop on success, or encoder_markers_discard on an error
3023
+ * path, to remove the entry). If markers tracking is off
2969
3024
  * (s->markers == Py_None), stores NULL in *ident_ptr and returns 0 with
2970
- * no side effects — that NULL is the sentinel markers_pop understands.
3025
+ * no side effects — that NULL is the sentinel both of those understand.
2971
3026
  * On circular reference sets the ValueError; on any other failure sets
2972
3027
  * the corresponding exception. Returns 0 on success, -1 on error. */
2973
3028
  static int
@@ -3013,6 +3068,52 @@ encoder_markers_pop(PyEncoderObject *s, PyObject *ident)
3013
3068
  return rv;
3014
3069
  }
3015
3070
 
3071
+ /* Error-path counterpart to encoder_markers_pop. Removes the ident
3072
+ * entry while an exception is propagating, so an encoder whose markers
3073
+ * dict is reused after a failed call does not report a false circular
3074
+ * reference, and drops the caller's reference. The in-flight exception
3075
+ * is preserved; a failure to delete the entry is swallowed. Passing
3076
+ * NULL is a no-op. */
3077
+ static void
3078
+ encoder_markers_discard(PyEncoderObject *s, PyObject *ident)
3079
+ {
3080
+ PyObject *exc_type;
3081
+ PyObject *exc_value;
3082
+ PyObject *exc_tb;
3083
+ if (ident == NULL)
3084
+ return;
3085
+ PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
3086
+ if (PyDict_DelItem(s->markers, ident) < 0)
3087
+ PyErr_Clear();
3088
+ PyErr_Restore(exc_type, exc_value, exc_tb);
3089
+ Py_DECREF(ident);
3090
+ }
3091
+
3092
+ /* Raise ValueError if obj is already in the markers dict, i.e. it is
3093
+ * currently being encoded further up the stack. Used where a side-
3094
+ * effecting call (such as __iter__) has to run before the marker can
3095
+ * be pushed. Returns 0 when obj is not marked or tracking is off, -1
3096
+ * with an exception set otherwise. */
3097
+ static int
3098
+ encoder_markers_check(PyEncoderObject *s, PyObject *obj)
3099
+ {
3100
+ PyObject *ident;
3101
+ int has_key;
3102
+ if (s->markers == Py_None)
3103
+ return 0;
3104
+ ident = PyLong_FromVoidPtr(obj);
3105
+ if (ident == NULL)
3106
+ return -1;
3107
+ has_key = PyDict_Contains(s->markers, ident);
3108
+ Py_DECREF(ident);
3109
+ if (has_key) {
3110
+ if (has_key != -1)
3111
+ PyErr_SetString(PyExc_ValueError, "Circular reference detected");
3112
+ return -1;
3113
+ }
3114
+ return 0;
3115
+ }
3116
+
3016
3117
  /* Helper for the for_json / _asdict paths in encoder_listencode_obj.
3017
3118
  * Steals the reference to `newobj` (returned by _call_json_method),
3018
3119
  * tracks the original object's circular-reference marker and recursion
@@ -3040,7 +3141,7 @@ encoder_steal_encode(PyEncoderObject *s, JSON_Accu *rval,
3040
3141
  }
3041
3142
  if (Py_EnterRecursiveCall(" while encoding a JSON object")) {
3042
3143
  Py_DECREF(newobj);
3043
- Py_XDECREF(ident);
3144
+ encoder_markers_discard(s, ident);
3044
3145
  return -1;
3045
3146
  }
3046
3147
  if (as_dict) {
@@ -3049,7 +3150,7 @@ encoder_steal_encode(PyEncoderObject *s, JSON_Accu *rval,
3049
3150
  } else {
3050
3151
  PyErr_Format(PyExc_TypeError,
3051
3152
  "_asdict() must return a dict, not %.80s",
3052
- Py_TYPE(newobj)->tp_name);
3153
+ json_type_name(newobj));
3053
3154
  rv = -1;
3054
3155
  }
3055
3156
  } else {
@@ -3061,7 +3162,7 @@ encoder_steal_encode(PyEncoderObject *s, JSON_Accu *rval,
3061
3162
  if (encoder_markers_pop(s, ident) < 0)
3062
3163
  rv = -1;
3063
3164
  } else {
3064
- Py_XDECREF(ident);
3165
+ encoder_markers_discard(s, ident);
3065
3166
  }
3066
3167
  return rv;
3067
3168
  }
@@ -3072,6 +3173,9 @@ encoder_steal_encode(PyEncoderObject *s, JSON_Accu *rval,
3072
3173
  * three sub-cases in order:
3073
3174
  * 1. RawJSON — emit the already-encoded string verbatim.
3074
3175
  * 2. iterable_as_array — treat any iterable object as a JSON array.
3176
+ * obj itself (not its iterator, which may be fresh on every
3177
+ * visit) is the circular-reference marker, and the recursion
3178
+ * depth is tracked.
3075
3179
  * 3. default(obj) — call the user-supplied default hook and recurse
3076
3180
  * on its result, with circular-reference tracking via markers.
3077
3181
  * Returns 0 on success, -1 on error. */
@@ -3096,13 +3200,22 @@ encoder_listencode_default(PyEncoderObject *s, JSON_Accu *rval,
3096
3200
  }
3097
3201
 
3098
3202
  if (s->iterable_as_array) {
3203
+ /* Reject a revisited iterable before its __iter__ runs again;
3204
+ * encoder_listencode_list pushes the marker for obj. */
3205
+ if (encoder_markers_check(s, obj))
3206
+ return -1;
3099
3207
  newobj = PyObject_GetIter(obj);
3100
3208
  if (newobj == NULL) {
3101
3209
  if (!PyErr_ExceptionMatches(PyExc_TypeError))
3102
3210
  return -1;
3103
3211
  PyErr_Clear();
3104
3212
  } else {
3105
- rv = encoder_listencode_list(s, rval, newobj, indent_level);
3213
+ if (Py_EnterRecursiveCall(" while encoding a JSON object")) {
3214
+ Py_DECREF(newobj);
3215
+ return -1;
3216
+ }
3217
+ rv = encoder_listencode_list(s, rval, newobj, obj, indent_level);
3218
+ Py_LeaveRecursiveCall();
3106
3219
  Py_DECREF(newobj);
3107
3220
  return rv;
3108
3221
  }
@@ -3111,7 +3224,7 @@ encoder_listencode_default(PyEncoderObject *s, JSON_Accu *rval,
3111
3224
  if (encoder_markers_push(s, obj, &ident))
3112
3225
  return -1;
3113
3226
  if (Py_EnterRecursiveCall(" while encoding a JSON object")) {
3114
- Py_XDECREF(ident);
3227
+ encoder_markers_discard(s, ident);
3115
3228
  return -1;
3116
3229
  }
3117
3230
  newobj = PyObject_CallOneArg(s->defaultfn, obj);
@@ -3122,10 +3235,10 @@ encoder_listencode_default(PyEncoderObject *s, JSON_Accu *rval,
3122
3235
  * encoder which binds type(o).__name__ before `o = default(o)`
3123
3236
  * runs. */
3124
3237
  encoder_annotate_exception(state,
3125
- "when serializing %s object", Py_TYPE(obj)->tp_name);
3238
+ "when serializing %s object", json_type_name(obj));
3126
3239
  #endif
3127
3240
  Py_LeaveRecursiveCall();
3128
- Py_XDECREF(ident);
3241
+ encoder_markers_discard(s, ident);
3129
3242
  return -1;
3130
3243
  }
3131
3244
  rv = encoder_listencode_obj(s, rval, newobj, indent_level);
@@ -3136,7 +3249,7 @@ encoder_listencode_default(PyEncoderObject *s, JSON_Accu *rval,
3136
3249
  * the Python encoder `o` has been rebound to newobj at this
3137
3250
  * point, so the note reflects that type. */
3138
3251
  encoder_annotate_exception(state,
3139
- "when serializing %s object", Py_TYPE(newobj)->tp_name);
3252
+ "when serializing %s object", json_type_name(newobj));
3140
3253
  #endif
3141
3254
  }
3142
3255
  Py_DECREF(newobj);
@@ -3144,7 +3257,7 @@ encoder_listencode_default(PyEncoderObject *s, JSON_Accu *rval,
3144
3257
  if (encoder_markers_pop(s, ident) < 0)
3145
3258
  rv = -1;
3146
3259
  } else {
3147
- Py_XDECREF(ident);
3260
+ encoder_markers_discard(s, ident);
3148
3261
  }
3149
3262
  return rv;
3150
3263
  }
@@ -3218,7 +3331,7 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss
3218
3331
  else if (PyList_Check(obj) || (s->tuple_as_array && PyTuple_Check(obj))) {
3219
3332
  if (Py_EnterRecursiveCall(" while encoding a JSON object"))
3220
3333
  return rv;
3221
- rv = encoder_listencode_list(s, rval, obj, indent_level);
3334
+ rv = encoder_listencode_list(s, rval, obj, obj, indent_level);
3222
3335
  Py_LeaveRecursiveCall();
3223
3336
  }
3224
3337
  else if (JSON_AnyDict_Check(obj)) {
@@ -3396,6 +3509,7 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
3396
3509
  int indented = (s->indent != Py_None);
3397
3510
  Py_ssize_t inner_indent_level = indented ? indent_level + 1 : indent_level;
3398
3511
  Py_ssize_t idx;
3512
+ int popped;
3399
3513
 
3400
3514
  {
3401
3515
  Py_ssize_t dct_size = PyDict_Check(dct) ? PyDict_Size(dct)
@@ -3424,51 +3538,86 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
3424
3538
  if (s->item_sort_kw == Py_None && PyDict_CheckExact(dct)) {
3425
3539
  Py_ssize_t pos = 0;
3426
3540
  PyObject *key, *value;
3541
+ Py_ssize_t size;
3542
+ #if PY_MAJOR_VERSION >= 3
3543
+ Py_ssize_t remaining;
3544
+ #endif
3427
3545
  int err = 0;
3428
3546
 
3429
3547
  idx = 0;
3430
3548
  Py_BEGIN_CRITICAL_SECTION(dct);
3549
+ size = PyDict_Size(dct);
3550
+ #if PY_MAJOR_VERSION >= 3
3551
+ remaining = size;
3552
+ #endif
3431
3553
  while (PyDict_Next(dct, &pos, &key, &value)) {
3554
+ #if PY_MAJOR_VERSION >= 3
3555
+ /* User code that replaced keys without changing the size
3556
+ * (pop and re-add a key) leaves entries past the original
3557
+ * ones. Fail the way Python 3's dict iterator does rather
3558
+ * than encode keys twice. */
3559
+ if (remaining == 0) {
3560
+ PyErr_SetString(PyExc_RuntimeError,
3561
+ "dictionary keys changed during iteration");
3562
+ err = 1;
3563
+ break;
3564
+ }
3565
+ remaining--;
3566
+ #endif
3567
+ /* Hold key until the value is encoded: encoding the value
3568
+ * can run user code (default, for_json, ...) that removes
3569
+ * the entry, and the error note below still needs key. */
3432
3570
  Py_INCREF(key);
3433
3571
  Py_INCREF(value);
3434
3572
 
3435
3573
  encoded = encoder_encode_dict_key(s, key);
3436
- Py_DECREF(key);
3437
3574
  if (encoded == NULL) {
3438
- Py_DECREF(value); err = 1; break;
3575
+ Py_DECREF(key); Py_DECREF(value); err = 1; break;
3439
3576
  }
3440
3577
  if (encoded == Py_None) {
3441
3578
  /* skipkeys */
3442
3579
  encoded = NULL;
3580
+ Py_DECREF(key);
3443
3581
  Py_DECREF(value);
3444
3582
  continue;
3445
3583
  }
3446
3584
  if (idx) {
3447
3585
  if (JSON_Accu_Accumulate(state, rval, s->item_separator)) {
3448
- Py_DECREF(value); err = 1; break;
3586
+ Py_DECREF(key); Py_DECREF(value); err = 1; break;
3449
3587
  }
3450
3588
  if (indented && encoder_accumulate_newline_indent(
3451
3589
  s, state, rval, inner_indent_level)) {
3452
- Py_DECREF(value); err = 1; break;
3590
+ Py_DECREF(key); Py_DECREF(value); err = 1; break;
3453
3591
  }
3454
3592
  }
3455
3593
  if (JSON_Accu_Accumulate(state, rval, encoded)) {
3456
- Py_DECREF(value); err = 1; break;
3594
+ Py_DECREF(key); Py_DECREF(value); err = 1; break;
3457
3595
  }
3458
3596
  Py_CLEAR(encoded);
3459
3597
  if (JSON_Accu_Accumulate(state, rval, s->key_separator)) {
3460
- Py_DECREF(value); err = 1; break;
3598
+ Py_DECREF(key); Py_DECREF(value); err = 1; break;
3461
3599
  }
3462
3600
  if (encoder_listencode_obj(s, rval, value, inner_indent_level)) {
3463
3601
  #if PY_VERSION_HEX >= 0x030B0000
3464
3602
  encoder_annotate_exception(state,
3465
3603
  "when serializing %s item %R",
3466
- Py_TYPE(dct)->tp_name, key);
3604
+ json_type_name(dct), key);
3467
3605
  #endif
3468
- Py_DECREF(value); err = 1; break;
3606
+ Py_DECREF(key); Py_DECREF(value); err = 1; break;
3469
3607
  }
3608
+ Py_DECREF(key);
3470
3609
  Py_DECREF(value);
3471
3610
  idx++;
3611
+ /* PyDict_Next walks entry slots, so a dict resized by that
3612
+ * user code would have entries skipped or repeated. Fail
3613
+ * the way the pure-Python encoder's dict iteration does,
3614
+ * which on Python 2 is only this check. */
3615
+ if (PyDict_Size(dct) != size) {
3616
+ PyErr_SetString(PyExc_RuntimeError,
3617
+ "dictionary changed size during iteration");
3618
+ err = 1;
3619
+ break;
3620
+ }
3472
3621
  }
3473
3622
  Py_END_CRITICAL_SECTION();
3474
3623
 
@@ -3517,7 +3666,7 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
3517
3666
  #if PY_VERSION_HEX >= 0x030B0000
3518
3667
  encoder_annotate_exception(state,
3519
3668
  "when serializing %s item %R",
3520
- Py_TYPE(dct)->tp_name, key);
3669
+ json_type_name(dct), key);
3521
3670
  #endif
3522
3671
  goto bail;
3523
3672
  }
@@ -3529,9 +3678,12 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
3529
3678
  goto bail;
3530
3679
  }
3531
3680
 
3532
- if (encoder_markers_pop(s, ident))
3533
- goto bail;
3681
+ /* encoder_markers_pop releases ident even when it fails, so clear
3682
+ * it before bail can see it. */
3683
+ popped = encoder_markers_pop(s, ident);
3534
3684
  ident = NULL;
3685
+ if (popped)
3686
+ goto bail;
3535
3687
  if (indented) {
3536
3688
  if (encoder_accumulate_newline_indent(s, state, rval, indent_level))
3537
3689
  goto bail;
@@ -3544,15 +3696,19 @@ bail:
3544
3696
  Py_XDECREF(encoded);
3545
3697
  Py_XDECREF(item);
3546
3698
  Py_XDECREF(iter);
3547
- Py_XDECREF(ident);
3699
+ encoder_markers_discard(s, ident);
3548
3700
  return -1;
3549
3701
  }
3550
3702
 
3551
3703
 
3552
3704
  static int
3553
- encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_ssize_t indent_level)
3705
+ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq,
3706
+ PyObject *origin, Py_ssize_t indent_level)
3554
3707
  {
3555
- /* Encode Python list seq to a JSON term */
3708
+ /* Encode the items of seq as a JSON array. origin is the object
3709
+ * being encoded: seq itself for a list or tuple, or the iterable
3710
+ * that produced the iterator seq under iterable_as_array. origin is
3711
+ * the circular-reference marker and is named in error notes. */
3556
3712
  _speedups_state *state = get_speedups_state(s->module_ref);
3557
3713
  PyObject *ident = NULL;
3558
3714
  PyObject *iter = NULL;
@@ -3564,6 +3720,7 @@ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_s
3564
3720
  Py_ssize_t inner_indent_level = indented ? indent_level + 1 : indent_level;
3565
3721
  Py_ssize_t i = 0;
3566
3722
  int is_exact_fast;
3723
+ int popped;
3567
3724
 
3568
3725
  /* Emptiness check: use direct size for exact types to skip the
3569
3726
  * __bool__/__len__ method dispatch of PyObject_IsTrue. */
@@ -3580,14 +3737,14 @@ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_s
3580
3737
  else {
3581
3738
  /* For non-exact types (list/tuple subclasses or other iterables)
3582
3739
  * we cannot short-circuit on emptiness without consuming the
3583
- * iterator. iterable_as_array in particular reaches us with a
3584
- * bare iterator whose PyObject_IsTrue always returns 1, so the
3585
- * emptiness is only discovered on the first PyIter_Next call
3586
- * inside the slow path below. */
3740
+ * iterator. iterable_as_array in particular reaches us with an
3741
+ * iterator, whose truth value says nothing about whether it
3742
+ * will yield items, so the emptiness is only discovered on the
3743
+ * first PyIter_Next call inside the slow path below. */
3587
3744
  is_exact_fast = 0;
3588
3745
  }
3589
3746
 
3590
- if (encoder_markers_push(s, seq, &ident))
3747
+ if (encoder_markers_push(s, origin, &ident))
3591
3748
  goto bail;
3592
3749
 
3593
3750
  if (is_exact_fast) {
@@ -3603,7 +3760,6 @@ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_s
3603
3760
  * the bail handler's Py_XDECREF(obj) stays a no-op for this
3604
3761
  * path. */
3605
3762
  PyObject *item;
3606
- Py_ssize_t size;
3607
3763
  int is_list = PyList_CheckExact(seq);
3608
3764
  int err = 0;
3609
3765
 
@@ -3616,8 +3772,12 @@ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_s
3616
3772
  }
3617
3773
 
3618
3774
  Py_BEGIN_CRITICAL_SECTION(seq);
3619
- size = is_list ? PyList_GET_SIZE(seq) : PyTuple_GET_SIZE(seq);
3620
- for (i = 0; i < size; i++) {
3775
+ /* Re-read the size on every iteration, as a list iterator does:
3776
+ * encoding an item can run user code (default, for_json, ...)
3777
+ * that resizes the list, and on free-threaded builds the
3778
+ * critical section can be released while that code runs. */
3779
+ for (i = 0; i < (is_list ? PyList_GET_SIZE(seq)
3780
+ : PyTuple_GET_SIZE(seq)); i++) {
3621
3781
  item = is_list ? PyList_GET_ITEM(seq, i)
3622
3782
  : PyTuple_GET_ITEM(seq, i);
3623
3783
  Py_INCREF(item);
@@ -3634,7 +3794,7 @@ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_s
3634
3794
  #if PY_VERSION_HEX >= 0x030B0000
3635
3795
  encoder_annotate_exception(state,
3636
3796
  "when serializing %s item %zd",
3637
- Py_TYPE(seq)->tp_name, i);
3797
+ json_type_name(origin), i);
3638
3798
  #endif
3639
3799
  Py_DECREF(item); err = 1; break;
3640
3800
  }
@@ -3682,7 +3842,7 @@ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_s
3682
3842
  #if PY_VERSION_HEX >= 0x030B0000
3683
3843
  encoder_annotate_exception(state,
3684
3844
  "when serializing %s item %zd",
3685
- Py_TYPE(seq)->tp_name, i);
3845
+ json_type_name(origin), i);
3686
3846
  #endif
3687
3847
  goto bail;
3688
3848
  }
@@ -3707,15 +3867,18 @@ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_s
3707
3867
  }
3708
3868
  }
3709
3869
 
3710
- if (encoder_markers_pop(s, ident))
3711
- goto bail;
3870
+ /* encoder_markers_pop releases ident even when it fails, so clear
3871
+ * it before bail can see it. */
3872
+ popped = encoder_markers_pop(s, ident);
3712
3873
  ident = NULL;
3874
+ if (popped)
3875
+ goto bail;
3713
3876
  return 0;
3714
3877
 
3715
3878
  bail:
3716
3879
  Py_XDECREF(obj);
3717
3880
  Py_XDECREF(iter);
3718
- Py_XDECREF(ident);
3881
+ encoder_markers_discard(s, ident);
3719
3882
  return -1;
3720
3883
  }
3721
3884
 
@@ -377,9 +377,11 @@ class JSONDecoder(object):
377
377
  self.encoding = encoding
378
378
  self.object_hook = object_hook
379
379
  self.object_pairs_hook = object_pairs_hook
380
- self.parse_float = parse_float or float
381
- self.parse_int = parse_int or bounded_int
382
- self.parse_constant = parse_constant or (allow_nan and _CONSTANTS.__getitem__ or None)
380
+ self.parse_float = float if parse_float is None else parse_float
381
+ self.parse_int = bounded_int if parse_int is None else parse_int
382
+ if parse_constant is None:
383
+ parse_constant = allow_nan and _CONSTANTS.__getitem__ or None
384
+ self.parse_constant = parse_constant
383
385
  self.strict = strict
384
386
  self.array_hook = array_hook
385
387
  self.parse_object = JSONObject