Files
RustPython/crates/derive-impl
강동안 7376e96665 Raise MemoryError without allocating via a MemoryError freelist (#8645)
* Add no_memory_error for allocation failures

Assisted-by: Claude Code:claude-fable-5-1

* Use no_memory_error at allocation failure sites

Assisted-by: Claude Code:claude-fable-5-1

* Raise MemoryError instead of aborting on sequence repeat allocation failure

Vec::with_capacity and Vec::reserve in SequenceExt::mul and
SequenceMutExt::imul call handle_alloc_error on failure, which aborts
the process under panic = "abort". `[1, 2, 3] * 10**17` passes the
MAX_MEMORY_SIZE guard (2.4e18 bytes < isize::MAX) and aborted at the
allocation instead of raising MemoryError.

Use try_reserve_exact and map failures to vm.no_memory_error(). This
also removes the n != 2 special case in imul: with the reserve done
unconditionally, the copy loop runs zero times for n == 2 and the
final append uses the reserved capacity.

Assisted-by: Claude Code:claude-fable-5-1

* Recycle MemoryError instances through a dedicated payload freelist

no_memory_error allocated the exception object on every raise, so
under real memory exhaustion the raise itself could abort. Keep dead
MemoryError husks in a process-wide reserve and revive them on the
next raise without touching the allocator.

The freelist is scoped to MemoryError at the type level. PyMemoryError
already exists as a transparent wrapper payload, but instances never
used it: the default exception slot_new delegates to the base class,
so everything was constructed as PyBaseException. Route
no_memory_error through a PyMemoryError-typed construction instead;
the freelist hooks live on PyMemoryError's own PyPayload impl, the
dealloc glue is monomorphized per payload type, and the existing
exact-type push gate admits exactly MemoryError husks.

The #[pyexception] macro always generates the PyPayload impl, which
left no way to attach freelist configuration; a payload = "manual"
attribute now skips that generation so a type can write its own impl,
mirroring the generated PAYLOAD_TYPE_ID chain and class-based
downcast validation that keep the payload flavors interchangeable.

Exceptions are the first HAS_DICT freelist type, so their husks carry
an ObjExt prefix that PyInner::dealloc would normally drop; clear its
instance dict and member slots before pushing so no stale attributes
survive into a revived instance.

The reserve is a process-wide Mutex rather than a thread_local so a
single prewarm at interpreter init covers every thread; MemoryError
is a cold path, so the lock is uncontended. Python-level MemoryError()
still constructs a PyBaseException payload through the delegated
slot_new and simply does not participate, matching CPython, where
only the PyErr_NoMemory path uses the preallocated reserve.

Assisted-by: Claude Code:claude-fable-5-1

* Clear MemoryError husks before they enter the freelist

Assisted-by: Claude Code:claude-fable-5-1

* Use no_memory_error for the bz2 allocation failure

The shared bz2 engine maps BZ_MEM_ERROR to Bz2Error::Mem; report it
through no_memory_error like the other allocation-failure sites, matching
PyErr_NoMemory in CPython's _bz2module.

Assisted-by: Claude Code:claude-fable-5-1

* Change MemoryError husks Vec to a fixed-size array

Assisted-by: Claude Code:claude-fable-5-1

* Increase MemoryError freelist size from 4 to 16

* Address review: keep repeat and husk clearing allocation-safe

Assisted-by: Claude Code:claude-fable-5-1
2026-09-05 11:43:10 +09:00
..