Skip to content

Fix vendored ujson crashes with sort_keys and buffer leak on errors; test 3.13/3.14 - #128

Merged
honnibal merged 3 commits into
v2.xfrom
fix/ujson-sort-keys-py314
Sep 27, 2026
Merged

honnibal merged 3 commits into
v2.xfrom
fix/ujson-sort-keys-py314

Conversation

@honnibal

Copy link
Copy Markdown
Member

Fixes three problems in the vendored ujson (1.35), all found while testing on Python 3.14. The fixes are backported from upstream ultrajson; re-vendoring current upstream isn't an option for 2.x because it changes output (bytes handling, float formatting, NaN, indent spacing).

Crashes with sort_keys=True

  • SortedDict_iterEnd released the sorted items list, and Object_endTypeContext then released it again. Every srsly.ujson.dumps(d, sort_keys=True) on a dict double-freed it, which segfaults on Python 3.14. Ported from ultrajson#243.
  • The encoder ignored iterNext returning -1, so dicts with keys that can't be sorted (e.g. {1: 1, "a": 2}) segfaulted on every Python version. The sort's TypeError is now raised, matching stdlib json, and encoding stops. Ported from ultrajson#247.
  • Fixed the SortedDict_iterNext error path, which released a borrowed value, passed a NULL key to PyTuple_Pack if key conversion failed, and released the item after PyList_SetItem had taken ownership of it.

srsly.json_dumps(sort_keys=True) was never affected, because it falls back to stdlib json; only direct srsly.ujson callers were.

Buffer leak on encoding errors
When encoding a large object failed partway through (NaN, unserializable values, unorderable keys), the heap-allocated output buffer was never freed: about 1MB per failed call. Backport of upstream commit 666d159.

CI
Adds Python 3.13 and 3.14 to the test matrix. python_requires allows up to 3.14, but CI only covered 3.9–3.12, which is how the 3.14 crash went unnoticed.

Tests: new test functions only. Without the fixes they crash (unorderable keys on all versions, everything with sort_keys on 3.14) or fail the leak check (~105MB over 100 calls).

🤖 Generated with Claude Code

honnibal and others added 3 commits September 27, 2026 12:51
- SortedDict_iterEnd released the sorted items list, which
  Object_endTypeContext then released again. Every sort_keys=True encode
  of a dict double-freed it, which segfaults on Python 3.14 (ported from
  ultrajson#243).
- The encoder ignored iterNext returning -1, so dicts with unorderable
  keys segfaulted on all Python versions. Now the TypeError from the
  sort is raised and encoding stops (ultrajson#247).
- Fix the SortedDict_iterNext error path: it released the borrowed
  value, passed a NULL key to PyTuple_Pack if key conversion failed, and
  released the item after PyList_SetItem had stolen it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
python_requires allows up to 3.14, but CI only covered 3.9-3.12, which is
how the sort_keys crash on 3.14 went unnoticed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JSON_EncodeObject returned NULL on error without freeing a heap-allocated
output buffer, and objToJSON didn't free it when a Python exception was
raised. Each failed dumps() of a large object leaked about 1MB. Backport
of upstream ultrajson commit 666d159.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@honnibal
honnibal merged commit 09aa416 into v2.x Sep 27, 2026
12 checks passed
@honnibal honnibal mentioned this pull request Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant