@@ -651,26 +651,26 @@ times.
651651
652652When deallocating a container object, it's possible to trigger an unbounded
653653chain of deallocations, as each Py_DECREF in turn drops the refcount on "the
654- next" object in the chain to 0. This can easily lead to stack faults, and
654+ next" object in the chain to 0. This can easily lead to stack overflows,
655655especially in threads (which typically have less stack space to work with).
656656
657- A container object that participates in cyclic gc can avoid this by
658- bracketing the body of its tp_dealloc function with a pair of macros:
657+ A container object can avoid this by bracketing the body of its tp_dealloc
658+ function with a pair of macros:
659659
660660static void
661661mytype_dealloc(mytype *p)
662662{
663663 ... declarations go here ...
664664
665665 PyObject_GC_UnTrack(p); // must untrack first
666- Py_TRASHCAN_SAFE_BEGIN(p )
666+ Py_TRASHCAN_BEGIN(p, mytype_dealloc )
667667 ... The body of the deallocator goes here, including all calls ...
668668 ... to Py_DECREF on contained objects. ...
669- Py_TRASHCAN_SAFE_END(p)
669+ Py_TRASHCAN_END
670670}
671671
672672CAUTION: Never return from the middle of the body! If the body needs to
673- "get out early", put a label immediately before the Py_TRASHCAN_SAFE_END
673+ "get out early", put a label immediately before the Py_TRASHCAN_END
674674call, and goto it. Else the call-depth counter (see below) will stay
675675above 0 forever, and the trashcan will never get emptied.
676676
@@ -686,6 +686,12 @@ notices this, and calls another routine to deallocate all the objects that
686686may have been added to the list of deferred deallocations. In effect, a
687687chain of N deallocations is broken into (N-1)/(PyTrash_UNWIND_LEVEL-1) pieces,
688688with the call stack never exceeding a depth of PyTrash_UNWIND_LEVEL.
689+
690+ Since the tp_dealloc of a subclass typically calls the tp_dealloc of the base
691+ class, we need to ensure that the trashcan is only triggered on the tp_dealloc
692+ of the actual class being deallocated. Otherwise we might end up with a
693+ partially-deallocated object. To check this, the tp_dealloc function must be
694+ passed as second argument to Py_TRASHCAN_BEGIN().
689695*/
690696
691697/* The new thread-safe private API, invoked by the macros below. */
@@ -694,21 +700,38 @@ PyAPI_FUNC(void) _PyTrash_thread_destroy_chain(void);
694700
695701#define PyTrash_UNWIND_LEVEL 50
696702
697- #define Py_TRASHCAN_SAFE_BEGIN (op ) \
703+ #define Py_TRASHCAN_BEGIN_CONDITION (op , cond ) \
698704 do { \
699- PyThreadState *_tstate = PyThreadState_GET(); \
700- if (_tstate->trash_delete_nesting < PyTrash_UNWIND_LEVEL) { \
701- ++_tstate->trash_delete_nesting;
702- /* The body of the deallocator is here. */
703- #define Py_TRASHCAN_SAFE_END (op ) \
705+ PyThreadState *_tstate = NULL; \
706+ /* If "cond" is false, then _tstate remains NULL and the deallocator \
707+ * is run normally without involving the trashcan */ \
708+ if (cond ) { \
709+ _tstate = PyThreadState_GET (); \
710+ if (_tstate -> trash_delete_nesting >= PyTrash_UNWIND_LEVEL ) { \
711+ /* Store the object (to be deallocated later) and jump past \
712+ * Py_TRASHCAN_END, skipping the body of the deallocator */ \
713+ _PyTrash_thread_deposit_object (_PyObject_CAST (op )); \
714+ break ; \
715+ } \
716+ ++ _tstate -> trash_delete_nesting ; \
717+ }
718+ /* The body of the deallocator is here. */
719+ #define Py_TRASHCAN_END \
720+ if (_tstate) { \
704721 --_tstate->trash_delete_nesting; \
705722 if (_tstate->trash_delete_later && _tstate->trash_delete_nesting <= 0) \
706723 _PyTrash_thread_destroy_chain(); \
707724 } \
708- else \
709- _PyTrash_thread_deposit_object(_PyObject_CAST(op)); \
710725 } while (0);
711726
727+ #define Py_TRASHCAN_BEGIN (op , dealloc ) Py_TRASHCAN_BEGIN_CONDITION(op, \
728+ Py_TYPE(op)->tp_dealloc == (destructor)(dealloc))
729+
730+ /* For backwards compatibility, these macros enable the trashcan
731+ * unconditionally */
732+ #define Py_TRASHCAN_SAFE_BEGIN (op ) Py_TRASHCAN_BEGIN_CONDITION(op, 1)
733+ #define Py_TRASHCAN_SAFE_END (op ) Py_TRASHCAN_END
734+
712735
713736#ifndef Py_LIMITED_API
714737# define Py_CPYTHON_OBJECT_H
0 commit comments