← board

collections.deque() compiles and segfaults

Measured

import collections
def uses_a_deque():
    q = collections.deque()
    q.append(5)
    return q.pop()
print(uses_a_deque())

CPython prints 5. pxx compiles clean (ok:) and the binary exits 139 (SIGSEGV, core dumped) having printed nothing.

Provenance — why this is not the promotion fix

Found while attempting a reduction for bug-n-a-dict-field-resolves-pop-against-a-list-or-deque-overload-set, whose fix touches the same method-candidate machinery, so attribution mattered. Built two binaries from the same tree differing only in that hunk:

fixture pre-fix post-fix
deque only rc=139 rc=139
deque + a dynamic .pop(k, d) rc=139 rc=139
dynamic .pop(k, d) only rc=0, matches CPython rc=0, matches CPython

Identical on both sides, so the crash is pre-existing. (That third row is also why no fixture for the promotion fix exists — see that ticket.)

What to do first

It is a CRASH, so it is the cheap case: it has a location. Run it under gdb with tools/pxx-gdb.py, or build with -dPXX_HEAP_DEBUG (freed bytes become $DD) and -dPXX_OBJTRACE. Do NOT start from the frontend — q.pop() is a zero-argument call on a statically known container and the interesting question is what collections.deque() actually constructs.

Note TPyDeque declares only popleft and pop (compiler/builtin/pylib.pas:948-949) and has no append-family entry visible in the same block; check whether q.append(5) bound to something else entirely before blaming pop.

A SECOND defect in the same object, found 2026-09-22 and NOT measurable while the segfault stands

TPyDeque.Compact rebuilds the buffer and installs it with FBuf := nb — and never releases the old FBuf. Nothing else releases it either: FBuf does not appear in any release, destructor or finalizer arm in pylib.pas. So every Compact strands an entire buffer, and Compact is on the ordinary paths — appendleft when the front is full, and popleft once the dead prefix reaches half the buffer. A deque used as a queue would leak proportionally to traffic.

Found by censusing the unreleased-temporary shape for bug-n-a-pylib-temporary-tpylist-is-never-freed, not by running anything, and it is recorded here rather than fixed because I cannot verify it: deque() segfaults before it can leak. Every other site in that census was confirmed by measuring bytes/call against CPython and re-measuring after the fix. This one has no such evidence and I am not patching an allocator path on a reading alone.

For whoever clears the segfault: measure Compact before assuming this, and note the release must come BEFORE the field is overwritten, or the handle is gone. The sibling fixes landed the same day in pylist_setslice and the pyiter_drain family show the shape.