@@ -649,26 +649,26 @@ times.
649649
650650When deallocating a container object, it's possible to trigger an unbounded
651651chain of deallocations, as each Py_DECREF in turn drops the refcount on "the
652- next" object in the chain to 0. This can easily lead to stack faults, and
652+ next" object in the chain to 0. This can easily lead to stack overflows,
653653especially in threads (which typically have less stack space to work with).
654654
655- A container object that participates in cyclic gc can avoid this by
656- bracketing the body of its tp_dealloc function with a pair of macros:
655+ A container object can avoid this by bracketing the body of its tp_dealloc
656+ function with a pair of macros:
657657
658658static void
659659mytype_dealloc(mytype *p)
660660{
661661 ... declarations go here ...
662662
663663 PyObject_GC_UnTrack(p); // must untrack first
664- Py_TRASHCAN_SAFE_BEGIN(p )
664+ Py_TRASHCAN_BEGIN(p, mytype_dealloc )
665665 ... The body of the deallocator goes here, including all calls ...
666666 ... to Py_DECREF on contained objects. ...
667- Py_TRASHCAN_SAFE_END(p)
667+ Py_TRASHCAN_END // there should be no code after this
668668}
669669
670670CAUTION: Never return from the middle of the body! If the body needs to
671- "get out early", put a label immediately before the Py_TRASHCAN_SAFE_END
671+ "get out early", put a label immediately before the Py_TRASHCAN_END
672672call, and goto it. Else the call-depth counter (see below) will stay
673673above 0 forever, and the trashcan will never get emptied.
674674
@@ -684,6 +684,12 @@ notices this, and calls another routine to deallocate all the objects that
684684may have been added to the list of deferred deallocations. In effect, a
685685chain of N deallocations is broken into (N-1)/(PyTrash_UNWIND_LEVEL-1) pieces,
686686with the call stack never exceeding a depth of PyTrash_UNWIND_LEVEL.
687+
688+ Since the tp_dealloc of a subclass typically calls the tp_dealloc of the base
689+ class, we need to ensure that the trashcan is only triggered on the tp_dealloc
690+ of the actual class being deallocated. Otherwise we might end up with a
691+ partially-deallocated object. To check this, the tp_dealloc function must be
692+ passed as second argument to Py_TRASHCAN_BEGIN().
687693*/
688694
689695/* The new thread-safe private API, invoked by the macros below. */
@@ -692,21 +698,38 @@ PyAPI_FUNC(void) _PyTrash_thread_destroy_chain(void);
692698
693699#define PyTrash_UNWIND_LEVEL 50
694700
695- #define Py_TRASHCAN_SAFE_BEGIN (op ) \
701+ #define Py_TRASHCAN_BEGIN_CONDITION (op , cond ) \
696702 do { \
697- PyThreadState *_tstate = PyThreadState_GET(); \
698- if (_tstate->trash_delete_nesting < PyTrash_UNWIND_LEVEL) { \
699- ++_tstate->trash_delete_nesting;
700- /* The body of the deallocator is here. */
701- #define Py_TRASHCAN_SAFE_END (op ) \
703+ PyThreadState *_tstate = NULL; \
704+ /* If "cond" is false, then _tstate remains NULL and the deallocator \
705+ * is run normally without involving the trashcan */ \
706+ if (cond ) { \
707+ _tstate = PyThreadState_GET (); \
708+ if (_tstate -> trash_delete_nesting >= PyTrash_UNWIND_LEVEL ) { \
709+ /* Store the object (to be deallocated later) and jump past \
710+ * Py_TRASHCAN_END, skipping the body of the deallocator */ \
711+ _PyTrash_thread_deposit_object (_PyObject_CAST (op )); \
712+ break ; \
713+ } \
714+ ++ _tstate -> trash_delete_nesting ; \
715+ }
716+ /* The body of the deallocator is here. */
717+ #define Py_TRASHCAN_END \
718+ if (_tstate) { \
702719 --_tstate->trash_delete_nesting; \
703720 if (_tstate->trash_delete_later && _tstate->trash_delete_nesting <= 0) \
704721 _PyTrash_thread_destroy_chain(); \
705722 } \
706- else \
707- _PyTrash_thread_deposit_object(_PyObject_CAST(op)); \
708723 } while (0);
709724
725+ #define Py_TRASHCAN_BEGIN (op , dealloc ) Py_TRASHCAN_BEGIN_CONDITION(op, \
726+ Py_TYPE(op)->tp_dealloc == (destructor)(dealloc))
727+
728+ /* For backwards compatibility, these macros enable the trashcan
729+ * unconditionally */
730+ #define Py_TRASHCAN_SAFE_BEGIN (op ) Py_TRASHCAN_BEGIN_CONDITION(op, 1)
731+ #define Py_TRASHCAN_SAFE_END (op ) Py_TRASHCAN_END
732+
710733
711734#ifndef Py_LIMITED_API
712735# define Py_CPYTHON_OBJECT_H
0 commit comments