simplejson 4.1.1__tar.gz → 4.2.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 (64) hide show
  1. {simplejson-4.1.1 → simplejson-4.2.0}/CHANGES.txt +35 -0
  2. {simplejson-4.1.1/simplejson.egg-info → simplejson-4.2.0}/PKG-INFO +5 -5
  3. {simplejson-4.1.1 → simplejson-4.2.0}/README.rst +2 -2
  4. {simplejson-4.1.1 → simplejson-4.2.0}/conf.py +3 -3
  5. {simplejson-4.1.1 → simplejson-4.2.0}/index.rst +5 -1
  6. {simplejson-4.1.1 → simplejson-4.2.0}/setup.py +3 -3
  7. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/__init__.py +2 -2
  8. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/_speedups.c +189 -51
  9. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/_speedups_scan.h +11 -3
  10. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/decoder.py +5 -3
  11. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/encoder.py +59 -15
  12. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_bitsize_int_as_string.py +97 -0
  13. simplejson-4.2.0/simplejson/tests/test_custom_method_cycles.py +71 -0
  14. simplejson-4.2.0/simplejson/tests/test_decimal.py +133 -0
  15. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_decode.py +27 -0
  16. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_fail.py +28 -0
  17. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_scanstring.py +47 -0
  18. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_speedups.py +1 -8
  19. {simplejson-4.1.1 → simplejson-4.2.0/simplejson.egg-info}/PKG-INFO +5 -5
  20. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson.egg-info/SOURCES.txt +1 -0
  21. simplejson-4.1.1/simplejson/tests/test_decimal.py +0 -71
  22. {simplejson-4.1.1 → simplejson-4.2.0}/LICENSE.txt +0 -0
  23. {simplejson-4.1.1 → simplejson-4.2.0}/MANIFEST.in +0 -0
  24. {simplejson-4.1.1 → simplejson-4.2.0}/pyproject.toml +0 -0
  25. {simplejson-4.1.1 → simplejson-4.2.0}/scripts/make_docs.py +0 -0
  26. {simplejson-4.1.1 → simplejson-4.2.0}/setup.cfg +0 -0
  27. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/compat.py +0 -0
  28. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/errors.py +0 -0
  29. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/ordered_dict.py +0 -0
  30. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/raw_json.py +0 -0
  31. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/scanner.py +0 -0
  32. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/__init__.py +0 -0
  33. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/_cibw_runner.py +0 -0
  34. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/_helpers.py +0 -0
  35. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_bigint_as_string.py +0 -0
  36. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_check_circular.py +0 -0
  37. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_default.py +0 -0
  38. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_dump.py +0 -0
  39. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_encode_basestring_ascii.py +0 -0
  40. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_encode_for_html.py +0 -0
  41. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_errors.py +0 -0
  42. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_float.py +0 -0
  43. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_for_json.py +0 -0
  44. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_free_threading.py +0 -0
  45. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_indent.py +0 -0
  46. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_item_sort_key.py +0 -0
  47. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_iterable.py +0 -0
  48. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_namedtuple.py +0 -0
  49. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_pass1.py +0 -0
  50. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_pass2.py +0 -0
  51. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_pass3.py +0 -0
  52. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_raw_json.py +0 -0
  53. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_recursion.py +0 -0
  54. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_separators.py +0 -0
  55. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_str_subclass.py +0 -0
  56. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_subclass.py +0 -0
  57. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_subinterpreters.py +0 -0
  58. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_tool.py +0 -0
  59. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_tuple.py +0 -0
  60. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tests/test_unicode.py +0 -0
  61. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson/tool.py +0 -0
  62. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson.egg-info/dependency_links.txt +0 -0
  63. {simplejson-4.1.1 → simplejson-4.2.0}/simplejson.egg-info/top_level.txt +0 -0
  64. {simplejson-4.1.1 → simplejson-4.2.0}/tsan_stress_simplejson.py +0 -0
@@ -1,3 +1,38 @@
1
+ Version 4.2.0 released 2026-10-02
2
+
3
+ * Start building wheels for Python 3.15 and drop Python 3.8 support
4
+ via upgrade to cibuildwheel v4.2.0
5
+ https://github.com/simplejson/simplejson/pull/385
6
+ * Build and test riscv64 Linux wheels on native RISE runners
7
+ https://github.com/simplejson/simplejson/pull/389
8
+ * The C scanner now reports the same error message as the pure Python
9
+ decoder for an invalid first key in an object
10
+ https://github.com/simplejson/simplejson/pull/388
11
+ * ``int_as_string_bitcount`` now supports thresholds of 64 bits and above
12
+ (including 128-bit integers) in both the C and pure Python encoders;
13
+ the C encoder previously rejected them
14
+ https://github.com/simplejson/simplejson/pull/387
15
+ * Circular references introduced through ``for_json()`` or ``_asdict()``
16
+ now raise ``ValueError("Circular reference detected")`` instead of
17
+ recursing without bound
18
+ https://github.com/simplejson/simplejson/pull/390
19
+ * CI now tests Python 3.15 (including free-threaded 3.15t), PyPy 3.12,
20
+ CPython 3.14.8 debug builds, and uses current GitHub Actions and
21
+ cibuildwheel v4.2.1
22
+ https://github.com/simplejson/simplejson/pull/386
23
+
24
+ Version 4.1.2 released 2026-08-26
25
+
26
+ * Fix control character error position when content precedes the control char
27
+ https://github.com/simplejson/simplejson/pull/379
28
+ * Report the offending char, not the backslash, for invalid \X escapes
29
+ https://github.com/simplejson/simplejson/pull/380
30
+ * Handle non-finite Decimals like floats in the encoder
31
+ https://github.com/simplejson/simplejson/pull/381
32
+ * Parse failures due to trailing commas are now reported consistently
33
+ across implementations
34
+ https://github.com/simplejson/simplejson/pull/382
35
+
1
36
  Version 4.1.1 released 2026-04-24
2
37
 
3
38
  * The ``build_wheels_py27`` CI job now also builds Python 2.7 wheels
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplejson
3
- Version: 4.1.1
3
+ Version: 4.2.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
@@ -14,18 +14,18 @@ Classifier: Programming Language :: Python
14
14
  Classifier: Programming Language :: Python :: 2
15
15
  Classifier: Programming Language :: Python :: 2.7
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.8
18
17
  Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
23
22
  Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
24
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
25
25
  Classifier: Programming Language :: Python :: Implementation :: GraalPy
26
26
  Classifier: Programming Language :: Python :: Implementation :: PyPy
27
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
- Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*
28
+ Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, !=3.8.*
29
29
  License-File: LICENSE.txt
30
30
  Dynamic: author
31
31
  Dynamic: author-email
@@ -42,7 +42,7 @@ simplejson
42
42
  ----------
43
43
 
44
44
  simplejson is a simple, fast, complete, correct and extensible
45
- JSON <http://json.org> encoder and decoder for Python 3.8+
45
+ JSON <http://json.org> encoder and decoder for Python 3.9+
46
46
  with legacy support for Python 2.7. It is pure Python code
47
47
  with no dependencies, but includes an optional C extension
48
48
  for a serious speed boost.
@@ -53,7 +53,7 @@ https://simplejson.readthedocs.io/
53
53
  simplejson is the externally maintained development version of the
54
54
  json library included with Python (since 2.6). This version is tested
55
55
  with Python 3.14 (including free-threaded builds) and maintains
56
- backwards compatibility with Python 3.8+. A legacy Python 2.7 wheel
56
+ backwards compatibility with Python 3.9+. A legacy Python 2.7 wheel
57
57
  is also published.
58
58
 
59
59
  The encoder can be specialized to provide serialization in any kind of
@@ -2,7 +2,7 @@ simplejson
2
2
  ----------
3
3
 
4
4
  simplejson is a simple, fast, complete, correct and extensible
5
- JSON <http://json.org> encoder and decoder for Python 3.8+
5
+ JSON <http://json.org> encoder and decoder for Python 3.9+
6
6
  with legacy support for Python 2.7. It is pure Python code
7
7
  with no dependencies, but includes an optional C extension
8
8
  for a serious speed boost.
@@ -13,7 +13,7 @@ https://simplejson.readthedocs.io/
13
13
  simplejson is the externally maintained development version of the
14
14
  json library included with Python (since 2.6). This version is tested
15
15
  with Python 3.14 (including free-threaded builds) and maintains
16
- backwards compatibility with Python 3.8+. A legacy Python 2.7 wheel
16
+ backwards compatibility with Python 3.9+. A legacy Python 2.7 wheel
17
17
  is also published.
18
18
 
19
19
  The encoder can be specialized to provide serialization in any kind of
@@ -36,15 +36,15 @@ master_doc = 'index'
36
36
 
37
37
  # General substitutions.
38
38
  project = 'simplejson'
39
- copyright = '2025, Bob Ippolito'
39
+ copyright = '2026, Bob Ippolito'
40
40
 
41
41
  # The default replacements for |version| and |release|, also used in various
42
42
  # other places throughout the built documents.
43
43
  #
44
44
  # The short X.Y version.
45
- version = '4.1'
45
+ version = '4.2'
46
46
  # The full version, including alpha/beta/rc tags.
47
- release = '4.1.1'
47
+ release = '4.2.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:
@@ -21,7 +21,7 @@ for a serious speed boost.
21
21
  :mod:`simplejson` exposes an API familiar to users of the standard library
22
22
  :mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
23
23
  version of the :mod:`json` library, but maintains
24
- compatibility with Python 3.8 through 3.14 (including free-threaded
24
+ compatibility with Python 3.9 through 3.15 (including free-threaded
25
25
  builds) as well as legacy Python 2.7.
26
26
 
27
27
  Development of simplejson happens on Github:
@@ -310,6 +310,10 @@ Basic Usage
310
310
  :class:`int` ``2**n`` and higher or lower than ``-2**n`` will be encoded as strings. This is to
311
311
  avoid the rounding that happens in Javascript otherwise. Note that this
312
312
  option loses type information, so use with extreme caution.
313
+ Thresholds of 64 bits and above are supported by both encoders, including
314
+ 128-bit integers. Very large thresholds do not allocate a power-of-two
315
+ boundary.
316
+
313
317
  See also *bigint_as_string* (which is equivalent to `int_as_string_bitcount=53`).
314
318
 
315
319
  .. note::
@@ -12,13 +12,13 @@ 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.1.1'
15
+ VERSION = '4.2.0'
16
16
  DESCRIPTION = "Simple, fast, extensible JSON encoder/decoder for Python"
17
17
 
18
18
  with open('README.rst', 'r') as f:
19
19
  LONG_DESCRIPTION = f.read()
20
20
 
21
- PYTHON_REQUIRES = '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*'
21
+ PYTHON_REQUIRES = '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, !=3.8.*'
22
22
 
23
23
  CLASSIFIERS = [
24
24
  'Development Status :: 5 - Production/Stable',
@@ -28,13 +28,13 @@ CLASSIFIERS = [
28
28
  'Programming Language :: Python :: 2',
29
29
  'Programming Language :: Python :: 2.7',
30
30
  'Programming Language :: Python :: 3',
31
- 'Programming Language :: Python :: 3.8',
32
31
  'Programming Language :: Python :: 3.9',
33
32
  'Programming Language :: Python :: 3.10',
34
33
  'Programming Language :: Python :: 3.11',
35
34
  'Programming Language :: Python :: 3.12',
36
35
  'Programming Language :: Python :: 3.13',
37
36
  'Programming Language :: Python :: 3.14',
37
+ 'Programming Language :: Python :: 3.15',
38
38
  'Programming Language :: Python :: Implementation :: CPython',
39
39
  'Programming Language :: Python :: Implementation :: GraalPy',
40
40
  'Programming Language :: Python :: Implementation :: PyPy',
@@ -5,7 +5,7 @@ interchange format.
5
5
  :mod:`simplejson` exposes an API familiar to users of the standard library
6
6
  :mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
7
7
  version of the :mod:`json` library contained in Python 2.6+, supporting
8
- Python 2.7 and Python 3.8+, and has significant performance advantages,
8
+ Python 2.7 and Python 3.9+, and has significant performance advantages,
9
9
  even without using the optional C extension for speedups.
10
10
 
11
11
  Encoding basic Python object hierarchies::
@@ -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.1.1'
121
+ __version__ = '4.2.0'
122
122
  __all__ = [
123
123
  'dump', 'dumps', 'load', 'loads',
124
124
  'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
@@ -301,6 +301,7 @@ typedef struct _PyEncoderObject {
301
301
  int iterable_as_array;
302
302
  PyObject *max_long_size;
303
303
  PyObject *min_long_size;
304
+ PyObject *large_int_bitcount;
304
305
  PyObject *item_sort_key;
305
306
  PyObject *item_sort_kw;
306
307
  int for_json;
@@ -327,6 +328,7 @@ typedef struct _PyEncoderObject {
327
328
  X(item_sort_key) \
328
329
  X(max_long_size) \
329
330
  X(min_long_size) \
331
+ X(large_int_bitcount) \
330
332
  X(Decimal)
331
333
 
332
334
  static PyMemberDef encoder_members[] = {
@@ -429,6 +431,8 @@ static PyObject *
429
431
  encoder_long_to_str(PyObject *obj);
430
432
  static PyObject *
431
433
  encoder_encode_float(PyEncoderObject *s, PyObject *obj);
434
+ static PyObject *
435
+ encoder_encode_decimal(PyEncoderObject *s, PyObject *obj);
432
436
  static int
433
437
  encoder_accumulate_newline_indent(PyEncoderObject *s, _speedups_state *state,
434
438
  JSON_Accu *rval, Py_ssize_t indent_level);
@@ -706,22 +710,46 @@ maybe_quote_bigint(PyEncoderObject* s, PyObject *encoded, PyObject *obj)
706
710
  int ge, le;
707
711
  PyObject *quoted;
708
712
 
709
- /* int_as_string_bitcount is not set: fast path, return as-is. */
710
- if (s->max_long_size == Py_None || s->min_long_size == Py_None)
711
- return encoded;
712
-
713
- ge = PyObject_RichCompareBool(obj, s->max_long_size, Py_GE);
714
- if (ge < 0) {
715
- Py_DECREF(encoded);
716
- return NULL;
713
+ if (s->large_int_bitcount != NULL) {
714
+ size_t nbits;
715
+ PyObject *bits;
716
+ nbits = _PyLong_NumBits(obj);
717
+ if (nbits == (size_t)-1 && PyErr_Occurred()) {
718
+ Py_DECREF(encoded);
719
+ return NULL;
720
+ }
721
+ bits = PyLong_FromSize_t(nbits);
722
+ if (bits == NULL) {
723
+ Py_DECREF(encoded);
724
+ return NULL;
725
+ }
726
+ ge = PyObject_RichCompareBool(bits, s->large_int_bitcount, Py_GT);
727
+ Py_DECREF(bits);
728
+ if (ge < 0) {
729
+ Py_DECREF(encoded);
730
+ return NULL;
731
+ }
732
+ if (!ge)
733
+ return encoded;
717
734
  }
718
- le = PyObject_RichCompareBool(obj, s->min_long_size, Py_LE);
719
- if (le < 0) {
720
- Py_DECREF(encoded);
721
- return NULL;
735
+ else {
736
+ /* int_as_string_bitcount is not set: fast path, return as-is. */
737
+ if (s->max_long_size == Py_None || s->min_long_size == Py_None)
738
+ return encoded;
739
+
740
+ ge = PyObject_RichCompareBool(obj, s->max_long_size, Py_GE);
741
+ if (ge < 0) {
742
+ Py_DECREF(encoded);
743
+ return NULL;
744
+ }
745
+ le = PyObject_RichCompareBool(obj, s->min_long_size, Py_LE);
746
+ if (le < 0) {
747
+ Py_DECREF(encoded);
748
+ return NULL;
749
+ }
750
+ if (!(ge || le))
751
+ return encoded;
722
752
  }
723
- if (!(ge || le))
724
- return encoded;
725
753
 
726
754
  #if PY_MAJOR_VERSION >= 3
727
755
  quoted = PyUnicode_FromFormat("\"%U\"", encoded);
@@ -1062,7 +1090,7 @@ encoder_stringify_key(PyEncoderObject *s, PyObject *key)
1062
1090
  return encoder_long_to_str(key);
1063
1091
  }
1064
1092
  else if (s->use_decimal && PyObject_TypeCheck(key, (PyTypeObject *)s->Decimal)) {
1065
- return PyObject_Str(key);
1093
+ return encoder_encode_decimal(s, key);
1066
1094
  }
1067
1095
  if (s->skipkeys) {
1068
1096
  Py_INCREF(Py_None);
@@ -1365,7 +1393,7 @@ scanstring_str(_speedups_state *state, PyObject *pystr, Py_ssize_t end,
1365
1393
  default: c = 0;
1366
1394
  }
1367
1395
  if (c == 0) {
1368
- raise_errmsg(state, ERR_STRING_ESC1, pystr, end - 2);
1396
+ raise_errmsg(state, ERR_STRING_ESC1, pystr, end - 1);
1369
1397
  goto bail;
1370
1398
  }
1371
1399
  }
@@ -1564,7 +1592,7 @@ scanstring_unicode(_speedups_state *state, PyObject *pystr, Py_ssize_t end,
1564
1592
  default: c = 0;
1565
1593
  }
1566
1594
  if (c == 0) {
1567
- raise_errmsg(state, ERR_STRING_ESC1, pystr, end - 2);
1595
+ raise_errmsg(state, ERR_STRING_ESC1, pystr, end - 1);
1568
1596
  goto bail;
1569
1597
  }
1570
1598
  }
@@ -1755,7 +1783,7 @@ scanstring_unicode(_speedups_state *state, PyObject *pystr, Py_ssize_t end,
1755
1783
  default: c = 0;
1756
1784
  }
1757
1785
  if (c == 0) {
1758
- raise_errmsg(state, ERR_STRING_ESC1, pystr, end - 2);
1786
+ raise_errmsg(state, ERR_STRING_ESC1, pystr, end - 1);
1759
1787
  goto bail;
1760
1788
  }
1761
1789
  }
@@ -2651,10 +2679,11 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
2651
2679
  goto bail;
2652
2680
  if (PyInt_Check(int_as_string_bitcount) || PyLong_Check(int_as_string_bitcount)) {
2653
2681
  static const unsigned long long_long_bitsize = sizeof(long long) * CHAR_BIT;
2654
- long int_as_string_bitcount_val = PyLong_AsLong(int_as_string_bitcount);
2682
+ int overflow = 0;
2683
+ long int_as_string_bitcount_val = PyLong_AsLongAndOverflow(int_as_string_bitcount, &overflow);
2655
2684
  if (int_as_string_bitcount_val == -1 && PyErr_Occurred())
2656
2685
  goto bail;
2657
- if (int_as_string_bitcount_val > 0 && int_as_string_bitcount_val < (long)long_long_bitsize) {
2686
+ if (!overflow && int_as_string_bitcount_val > 0 && int_as_string_bitcount_val < (long)long_long_bitsize) {
2658
2687
  int n = (int)int_as_string_bitcount_val;
2659
2688
  /* Compute 2^n as unsigned (well-defined for n < 64) and
2660
2689
  * -(2^n) as signed without UB. Naive "-1LL << n" is a
@@ -2670,10 +2699,19 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
2670
2699
  goto bail;
2671
2700
  }
2672
2701
  }
2702
+ else if (overflow > 0 || int_as_string_bitcount_val > 0) {
2703
+ /* Compare magnitudes without allocating a 2**n boundary. */
2704
+ s->large_int_bitcount = PyNumber_Long(int_as_string_bitcount);
2705
+ if (s->large_int_bitcount == NULL)
2706
+ goto bail;
2707
+ Py_INCREF(Py_None);
2708
+ s->max_long_size = Py_None;
2709
+ Py_INCREF(Py_None);
2710
+ s->min_long_size = Py_None;
2711
+ }
2673
2712
  else {
2674
- PyErr_Format(PyExc_TypeError,
2675
- "int_as_string_bitcount (%ld) must be greater than 0 and less than the number of bits of a `long long` type (%lu bits)",
2676
- int_as_string_bitcount_val, long_long_bitsize);
2713
+ PyErr_SetString(PyExc_TypeError,
2714
+ "int_as_string_bitcount must be a positive integer");
2677
2715
  goto bail;
2678
2716
  }
2679
2717
  }
@@ -2785,33 +2823,38 @@ _encoded_const(_speedups_state *state, PyObject *obj)
2785
2823
  }
2786
2824
  }
2787
2825
 
2826
+ static PyObject *
2827
+ encoder_encode_nonfinite(PyEncoderObject *s, int sign)
2828
+ {
2829
+ _speedups_state *state = get_speedups_state(s->module_ref);
2830
+
2831
+ if (!s->allow_or_ignore_nan) {
2832
+ PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant");
2833
+ return NULL;
2834
+ }
2835
+ if (s->allow_or_ignore_nan & JSON_IGNORE_NAN) {
2836
+ return _encoded_const(state, Py_None);
2837
+ }
2838
+ /* JSON_ALLOW_NAN is set */
2839
+ if (sign > 0) {
2840
+ Py_INCREF(state->JSON_Infinity);
2841
+ return state->JSON_Infinity;
2842
+ }
2843
+ if (sign < 0) {
2844
+ Py_INCREF(state->JSON_NegInfinity);
2845
+ return state->JSON_NegInfinity;
2846
+ }
2847
+ Py_INCREF(state->JSON_NaN);
2848
+ return state->JSON_NaN;
2849
+ }
2850
+
2788
2851
  static PyObject *
2789
2852
  encoder_encode_float(PyEncoderObject *s, PyObject *obj)
2790
2853
  {
2791
2854
  /* Return the JSON representation of a PyFloat */
2792
- _speedups_state *state = get_speedups_state(s->module_ref);
2793
2855
  double i = PyFloat_AS_DOUBLE(obj);
2794
2856
  if (!Py_IS_FINITE(i)) {
2795
- if (!s->allow_or_ignore_nan) {
2796
- PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant");
2797
- return NULL;
2798
- }
2799
- if (s->allow_or_ignore_nan & JSON_IGNORE_NAN) {
2800
- return _encoded_const(state, Py_None);
2801
- }
2802
- /* JSON_ALLOW_NAN is set */
2803
- else if (i > 0) {
2804
- Py_INCREF(state->JSON_Infinity);
2805
- return state->JSON_Infinity;
2806
- }
2807
- else if (i < 0) {
2808
- Py_INCREF(state->JSON_NegInfinity);
2809
- return state->JSON_NegInfinity;
2810
- }
2811
- else {
2812
- Py_INCREF(state->JSON_NaN);
2813
- return state->JSON_NaN;
2814
- }
2857
+ return encoder_encode_nonfinite(s, i > 0 ? 1 : (i < 0 ? -1 : 0));
2815
2858
  }
2816
2859
  /* Use a better float format here? */
2817
2860
  if (PyFloat_CheckExact(obj)) {
@@ -2830,6 +2873,61 @@ encoder_encode_float(PyEncoderObject *s, PyObject *obj)
2830
2873
  }
2831
2874
  }
2832
2875
 
2876
+ static PyObject *
2877
+ encoder_encode_decimal(PyEncoderObject *s, PyObject *obj)
2878
+ {
2879
+ /* Return the JSON representation of a Decimal. Finite Decimals are
2880
+ stringified as-is (preserving precision and trailing zeros); non-finite
2881
+ Decimals (NaN, sNaN, Infinity) are routed through the same
2882
+ allow_nan/ignore_nan handling as floats so they never emit invalid
2883
+ JSON. See https://github.com/simplejson/simplejson/issues/149
2884
+
2885
+ str(Decimal) carries a leading '-' sign at most (never '+'); a finite
2886
+ value's first significant character is always a digit while a non-finite
2887
+ value's is a letter ('N'/'s' for [s]NaN, 'I' for Infinity). So a single
2888
+ str() plus a one/two character check distinguishes the two. */
2889
+ PyObject *str = PyObject_Str(obj);
2890
+ Py_ssize_t len;
2891
+ int negative;
2892
+ JSON_UNICHR c;
2893
+ #if PY_MAJOR_VERSION >= 3
2894
+ int kind;
2895
+ void *data;
2896
+ #else
2897
+ const char *data;
2898
+ #endif
2899
+ if (str == NULL)
2900
+ return NULL;
2901
+ #if PY_MAJOR_VERSION >= 3
2902
+ if (PyUnicode_READY(str)) {
2903
+ Py_DECREF(str);
2904
+ return NULL;
2905
+ }
2906
+ len = PyUnicode_GET_LENGTH(str);
2907
+ kind = PyUnicode_KIND(str);
2908
+ data = PyUnicode_DATA(str);
2909
+ negative = (len > 0 && PyUnicode_READ(kind, data, 0) == '-');
2910
+ c = (JSON_UNICHR)0;
2911
+ if (len > (negative ? 1 : 0))
2912
+ c = PyUnicode_READ(kind, data, negative ? 1 : 0);
2913
+ #else
2914
+ len = PyString_GET_SIZE(str);
2915
+ data = PyString_AS_STRING(str);
2916
+ negative = (len > 0 && data[0] == '-');
2917
+ c = (JSON_UNICHR)0;
2918
+ if (len > (negative ? 1 : 0))
2919
+ c = (unsigned char)data[negative ? 1 : 0];
2920
+ #endif
2921
+ if (IS_DIGIT(c)) {
2922
+ /* First significant character is a digit: finite Decimal, emit as-is. */
2923
+ return str;
2924
+ }
2925
+ /* Non-finite Decimal; use the same allow_nan/ignore_nan path as floats
2926
+ without creating a temporary float. */
2927
+ Py_DECREF(str);
2928
+ return encoder_encode_nonfinite(s, c == 'I' ? (negative ? -1 : 1) : 0);
2929
+ }
2930
+
2833
2931
  static PyObject *
2834
2932
  encoder_encode_string(PyEncoderObject *s, PyObject *obj)
2835
2933
  {
@@ -2917,7 +3015,8 @@ encoder_markers_pop(PyEncoderObject *s, PyObject *ident)
2917
3015
 
2918
3016
  /* Helper for the for_json / _asdict paths in encoder_listencode_obj.
2919
3017
  * Steals the reference to `newobj` (returned by _call_json_method),
2920
- * handles recursion-depth tracking, and dispatches to the right
3018
+ * tracks the original object's circular-reference marker and recursion
3019
+ * depth, and dispatches to the right
2921
3020
  * sub-encoder:
2922
3021
  * - if as_dict is 0, encodes newobj as a generic JSON value via
2923
3022
  * encoder_listencode_obj (for_json contract: return any JSON-
@@ -2928,14 +3027,20 @@ encoder_markers_pop(PyEncoderObject *s, PyObject *ident)
2928
3027
  * Cleans up on every exit path. */
2929
3028
  static int
2930
3029
  encoder_steal_encode(PyEncoderObject *s, JSON_Accu *rval,
2931
- PyObject *newobj, Py_ssize_t indent_level,
3030
+ PyObject *obj, PyObject *newobj, Py_ssize_t indent_level,
2932
3031
  int as_dict)
2933
3032
  {
2934
3033
  int rv;
3034
+ PyObject *ident = NULL;
2935
3035
  if (newobj == NULL)
2936
3036
  return -1;
3037
+ if (encoder_markers_push(s, obj, &ident)) {
3038
+ Py_DECREF(newobj);
3039
+ return -1;
3040
+ }
2937
3041
  if (Py_EnterRecursiveCall(" while encoding a JSON object")) {
2938
3042
  Py_DECREF(newobj);
3043
+ Py_XDECREF(ident);
2939
3044
  return -1;
2940
3045
  }
2941
3046
  if (as_dict) {
@@ -2952,6 +3057,12 @@ encoder_steal_encode(PyEncoderObject *s, JSON_Accu *rval,
2952
3057
  }
2953
3058
  Py_DECREF(newobj);
2954
3059
  Py_LeaveRecursiveCall();
3060
+ if (rv == 0) {
3061
+ if (encoder_markers_pop(s, ident) < 0)
3062
+ rv = -1;
3063
+ } else {
3064
+ Py_XDECREF(ident);
3065
+ }
2955
3066
  return rv;
2956
3067
  }
2957
3068
 
@@ -3059,12 +3170,39 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss
3059
3170
  rv = _steal_accumulate(state, rval, cstr);
3060
3171
  }
3061
3172
  else if (PyInt_Check(obj) || PyLong_Check(obj)) {
3062
- PyObject *encoded = encoder_long_to_str(obj);
3173
+ PyObject *integer, *encoded;
3174
+ /* Large thresholds inspect the same normalized value we serialize. */
3175
+ if (s->large_int_bitcount != NULL) {
3176
+ #if PY_MAJOR_VERSION < 3
3177
+ /* Match int(value) in the Python encoder before widening for
3178
+ * _PyLong_NumBits: long(value) would invoke __long__ instead. */
3179
+ PyObject *normalized = PyNumber_Int(obj);
3180
+ if (normalized == NULL)
3181
+ return -1;
3182
+ /* PyNumber_Int may retain a long subclass. Copy its payload
3183
+ * instead of invoking a second conversion hook. */
3184
+ if (PyInt_Check(normalized))
3185
+ integer = PyLong_FromLong(PyInt_AS_LONG(normalized));
3186
+ else
3187
+ integer = PyLong_Type.tp_as_number->nb_long(normalized);
3188
+ Py_DECREF(normalized);
3189
+ #else
3190
+ integer = PyNumber_Long(obj);
3191
+ #endif
3192
+ }
3193
+ else {
3194
+ integer = obj;
3195
+ Py_INCREF(integer);
3196
+ }
3197
+ if (integer == NULL)
3198
+ return -1;
3199
+ encoded = encoder_long_to_str(integer);
3063
3200
  if (encoded != NULL) {
3064
- encoded = maybe_quote_bigint(s, encoded, obj);
3201
+ encoded = maybe_quote_bigint(s, encoded, integer);
3065
3202
  if (encoded != NULL)
3066
3203
  rv = _steal_accumulate(state, rval, encoded);
3067
3204
  }
3205
+ Py_DECREF(integer);
3068
3206
  }
3069
3207
  else if (PyFloat_Check(obj)) {
3070
3208
  PyObject *encoded = encoder_encode_float(s, obj);
@@ -3072,10 +3210,10 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss
3072
3210
  rv = _steal_accumulate(state, rval, encoded);
3073
3211
  }
3074
3212
  else if (s->for_json && _call_json_method(obj, state->JSON_attr_for_json, &newobj)) {
3075
- rv = encoder_steal_encode(s, rval, newobj, indent_level, /*as_dict=*/0);
3213
+ rv = encoder_steal_encode(s, rval, obj, newobj, indent_level, /*as_dict=*/0);
3076
3214
  }
3077
3215
  else if (s->namedtuple_as_object && _call_json_method(obj, state->JSON_attr_asdict, &newobj)) {
3078
- rv = encoder_steal_encode(s, rval, newobj, indent_level, /*as_dict=*/1);
3216
+ rv = encoder_steal_encode(s, rval, obj, newobj, indent_level, /*as_dict=*/1);
3079
3217
  }
3080
3218
  else if (PyList_Check(obj) || (s->tuple_as_array && PyTuple_Check(obj))) {
3081
3219
  if (Py_EnterRecursiveCall(" while encoding a JSON object"))
@@ -3090,7 +3228,7 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss
3090
3228
  Py_LeaveRecursiveCall();
3091
3229
  }
3092
3230
  else if (s->use_decimal && PyObject_TypeCheck(obj, (PyTypeObject *)s->Decimal)) {
3093
- PyObject *encoded = PyObject_Str(obj);
3231
+ PyObject *encoded = encoder_encode_decimal(s, obj);
3094
3232
  if (encoded != NULL)
3095
3233
  rv = _steal_accumulate(state, rval, encoded);
3096
3234
  }
@@ -204,12 +204,17 @@ JSON_SCAN_FN(_parse_object)(PyScannerObject *s, PyObject *pystr,
204
204
  /* only loop if the object is non-empty */
205
205
  if (idx <= end_idx && JSON_SCAN_READ(idx) != '}') {
206
206
  int trailing_delimiter = 0;
207
+ Py_ssize_t comma_idx = 0;
207
208
  while (idx <= end_idx) {
208
209
  trailing_delimiter = 0;
209
210
 
210
211
  /* read key */
211
212
  if (JSON_SCAN_READ(idx) != '"') {
212
- raise_errmsg(state, ERR_OBJECT_PROPERTY, pystr, idx);
213
+ /* '}' is still legal where the first key would go. */
214
+ raise_errmsg(state,
215
+ did_parse ? ERR_OBJECT_PROPERTY
216
+ : ERR_OBJECT_PROPERTY_FIRST,
217
+ pystr, idx);
213
218
  goto bail;
214
219
  }
215
220
  key = JSON_SCAN_SCANSTRING_CALL(idx + 1, &next_idx);
@@ -271,6 +276,7 @@ JSON_SCAN_FN(_parse_object)(PyScannerObject *s, PyObject *pystr,
271
276
  raise_errmsg(state, ERR_OBJECT_DELIMITER, pystr, idx);
272
277
  goto bail;
273
278
  }
279
+ comma_idx = idx;
274
280
  idx++;
275
281
 
276
282
  /* skip whitespace after , delimiter */
@@ -279,7 +285,7 @@ JSON_SCAN_FN(_parse_object)(PyScannerObject *s, PyObject *pystr,
279
285
 
280
286
  /* check for trailing comma before } */
281
287
  if (idx <= end_idx && JSON_SCAN_READ(idx) == '}') {
282
- raise_errmsg(state, ERR_TRAILING_COMMA_OBJECT, pystr, idx);
288
+ raise_errmsg(state, ERR_TRAILING_COMMA_OBJECT, pystr, comma_idx);
283
289
  goto bail;
284
290
  }
285
291
  }
@@ -351,6 +357,7 @@ JSON_SCAN_FN(_parse_array)(PyScannerObject *s, PyObject *pystr,
351
357
  /* only loop if the array is non-empty */
352
358
  if (idx <= end_idx && JSON_SCAN_READ(idx) != ']') {
353
359
  int trailing_delimiter = 0;
360
+ Py_ssize_t comma_idx = 0;
354
361
  while (idx <= end_idx) {
355
362
  trailing_delimiter = 0;
356
363
  /* read any JSON term and de-tuplefy the (rval, idx) */
@@ -377,6 +384,7 @@ JSON_SCAN_FN(_parse_array)(PyScannerObject *s, PyObject *pystr,
377
384
  raise_errmsg(state, ERR_ARRAY_DELIMITER, pystr, idx);
378
385
  goto bail;
379
386
  }
387
+ comma_idx = idx;
380
388
  idx++;
381
389
 
382
390
  /* skip whitespace after , */
@@ -385,7 +393,7 @@ JSON_SCAN_FN(_parse_array)(PyScannerObject *s, PyObject *pystr,
385
393
 
386
394
  /* check for trailing comma before ] */
387
395
  if (idx <= end_idx && JSON_SCAN_READ(idx) == ']') {
388
- raise_errmsg(state, ERR_TRAILING_COMMA_ARRAY, pystr, idx);
396
+ raise_errmsg(state, ERR_TRAILING_COMMA_ARRAY, pystr, comma_idx);
389
397
  goto bail;
390
398
  }
391
399
  }