Conversation
builder_fromiter had no branch for complex numpy scalars other than complex128, so np.clongdouble reached the tolist() branch; its tolist() returns itself and the builder recursed until the stack overflowed. - np.complexfloating scalars are built as complex128. - 0-d ndarrays are built from obj[()], keeping datetime64/timedelta64 units and structured field names that tolist() dropped. - structured np.void scalars are built as records; unstructured ones as bytes. - an object whose tolist() returns its own type raises TypeError instead of recursing.
builder_fromiter recursed on nested containers and on tolist()/to_list() results with nothing to stop it, so a tolist() 2-cycle, a to_list() that returns its own type, a 0-d object array containing itself, or a list, tuple or dict nested 200000 deep overflowed the stack. - A thread-local depth counter raises RecursionError once the nesting reaches sys.getrecursionlimit(). Py_EnterRecursiveCall is not enough: on Python 3.13 linux/amd64 its C-level limit lets tuples and dicts nested 200000 deep still overflow the stack. - The tolist() same-type TypeError is removed; the bound covers it. - The np.void branch moves after the dict branch and skips lists, so from_iter of records is not slowed by the numpy lookup. - Subprocess tests are skipped on emscripten, which has no subprocess.
The depth bound was sys.getrecursionlimit(), which users raise. Past about 7600 levels (tuples, linux/amd64) or 4800 (nested 0-d object arrays) the builder overflows the stack first, so setrecursionlimit(10_000) or higher brought the segfault back. The bound is now min(sys.getrecursionlimit(), 1000). 1000 is CPython's default limit, so default behavior is unchanged and no platform recurses deeper than it already did at the default; Windows gets roughly a third of the linux C-stack budget (Py_C_RECURSION_LIMIT 3000 vs 10000). A list nested deeper than 1000 now raises RecursionError even with a raised limit.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files
|
lgray
force-pushed
the
fix/from-iter-clongdouble
branch
from
September 26, 2026 20:21
d8c74c0 to
55f2271
Compare
Collaborator
Author
|
py3.14t failing for unrelated reasons (build itself fails, PR does not touch CI config) |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 AI text below 🤖
Bug
ak.from_itersegfaults onnp.clongdouble:builder_fromiter(awkward-cpp/src/python/content.cpp) has no branch for complex numpy scalars other than complex128 (a Pythoncomplexsubclass), sonp.clongdoublereaches thetolistbranch.np.clongdouble.tolist()returns the scalar itself, and the builder recurses until the stack overflows. The same happens for a clongdouble ndarray, and forak.from_iter(ak.to_list(<clongdouble array>)).The same function also recurses without a bound on anything that never bottoms out: a
tolist()/to_list()that returns another such object, a 0-d object array that contains itself, or very deep nesting with a raisedsys.setrecursionlimit. Each of these segfaults onmain.Fix
In
builder_fromiter:np.complexfloatingscalars are built as complex128, the same widening complex64 already gets.np.ndarrayrecurses onobj[()]. Before,tolist()dropped the datetime64/timedelta64 unit (datetime64[ns]gaveint64) and the field names of a structured value.np.voidscalar becomes a record overdtype.names; an unstructured one becomes bytes. Before, a structured scalar was iterated as a list (var * float64, ints turned into floats).min(sys.getrecursionlimit(), 1000)and exceeding it raisesRecursionError.In
ErrorContext.format_argument, arguments are formatted with areprlib.Reprbounded in depth. With a raised recursion limit on CPython 3.10 and 3.11, the plainrepr()of the deep argument overflowed the stack while theRecursionErrormessage was being built. The text is unchanged for any argument that fits in the message.Behavior changes
ak.from_iter([np.array([(1, 2.5)], dtype=[("a", "i8"), ("b", "f8")])])gave1 * var * var * float64and now gives1 * var * {a: int64, b: float64}; the same for.view(np.recarray). This is whatak.from_numpygives for the same array.RecursionErrorwhateversys.getrecursionlimit()is. At the default limit,mainalready raisesRecursionErrorfrom Python-level code for a list nested 987 to 20,000 deep inside[x], but segfaults at 200,000 deep (macOS arm64, and in the linux/amd64 test run below); this branch raisesRecursionErrorat every one of those depths. With a raised limit,mainbuilt deeper nesting until the stack overflowed; this branch raises from 999 levels. The cap is not tied tosys.getrecursionlimit()because a raised limit brings the segfault back, and neither CPython's C recursion limit (Py_EnterRecursiveCall) nor the limit stays below the stack's capacity for these frames. Nesting that deep could not be used anyway:ak.forms.from_jsonof the resulting form raisesRecursionErrorat depth 1000.Tests
tests/test_4392_from_iter_numpy_scalars.py: values checked againstak.from_numpy, clongdouble scalars/0-d/ndarrays, the non-terminating members and the depth cap. The cases that crash onmainrun in a subprocess (skipped on emscripten, as intest_2682), so onmainthey fail rather than take down the test session.On
main, 19 of the first 29 tests fail and 10 pass, on macOS arm64 and on linux/amd64 with awkward-cpp built from the tree. The later commits add a dict member to the non-terminating cases,test_argument_text_matches_repr(including a dict nested past the depth cap, which found thatreprlib.Repr.fillvaluedoes not exist on 3.10) andtest_argument_whose_repr_raises. On this branch all 38 tests pass on macOS arm64 with CPython 3.13 and 3.10; with theformat_argumentchange reverted, the two raised-limit members fail on 3.10.Found by the numpy-dtype survey behind #4390.