Skip to content

Commit ada81dd

Browse files
markshannonmiss-islington
authored andcommitted
pythonGH-135106: Restrict trashcan to GC'ed objects (pythonGH-135682)
(cherry picked from commit 2060089) Co-authored-by: Mark Shannon <[email protected]>
1 parent 148f31f commit ada81dd

File tree

2 files changed

+26
-52
lines changed

2 files changed

+26
-52
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Restrict the trashcan mechanism to GC'ed objects and untrack them while in
2+
the trashcan to prevent the GC and trashcan mechanisms conflicting.

Objects/object.c

Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2923,57 +2923,28 @@ Py_ReprLeave(PyObject *obj)
29232923

29242924
/* Trashcan support. */
29252925

2926-
#ifndef Py_GIL_DISABLED
2927-
/* We need to store a pointer in the refcount field of
2928-
* an object. It is important that we never store 0 (NULL).
2929-
* It is also important to not make the object appear immortal,
2930-
* or it might be untracked by the cycle GC. */
2931-
static uintptr_t
2932-
pointer_to_safe_refcount(void *ptr)
2933-
{
2934-
uintptr_t full = (uintptr_t)ptr;
2935-
assert((full & 3) == 0);
2936-
#if SIZEOF_VOID_P > 4
2937-
uint32_t refcnt = (uint32_t)full;
2938-
if (refcnt >= (uint32_t)_Py_IMMORTAL_MINIMUM_REFCNT) {
2939-
full = full - ((uintptr_t)_Py_IMMORTAL_MINIMUM_REFCNT) + 1;
2940-
}
2941-
return full + 2;
2942-
#else
2943-
// Make the top two bits 0, so it appears mortal.
2944-
return (full >> 2) + 1;
2945-
#endif
2946-
}
2947-
2948-
static void *
2949-
safe_refcount_to_pointer(uintptr_t refcnt)
2950-
{
2951-
#if SIZEOF_VOID_P > 4
2952-
if (refcnt & 1) {
2953-
refcnt += _Py_IMMORTAL_MINIMUM_REFCNT - 1;
2954-
}
2955-
return (void *)(refcnt - 2);
2956-
#else
2957-
return (void *)((refcnt -1) << 2);
2958-
#endif
2959-
}
2960-
#endif
2961-
29622926
/* Add op to the gcstate->trash_delete_later list. Called when the current
2963-
* call-stack depth gets large. op must be a currently untracked gc'ed
2964-
* object, with refcount 0. Py_DECREF must already have been called on it.
2927+
* call-stack depth gets large. op must be a gc'ed object, with refcount 0.
2928+
* Py_DECREF must already have been called on it.
29652929
*/
29662930
void
29672931
_PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op)
29682932
{
29692933
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
2934+
PyTypeObject *tp = Py_TYPE(op);
2935+
assert(tp->tp_flags & Py_TPFLAGS_HAVE_GC);
2936+
int tracked = 0;
2937+
if (tp->tp_is_gc == NULL || tp->tp_is_gc(op)) {
2938+
tracked = _PyObject_GC_IS_TRACKED(op);
2939+
if (tracked) {
2940+
_PyObject_GC_UNTRACK(op);
2941+
}
2942+
}
2943+
uintptr_t tagged_ptr = ((uintptr_t)tstate->delete_later) | tracked;
29702944
#ifdef Py_GIL_DISABLED
2971-
op->ob_tid = (uintptr_t)tstate->delete_later;
2945+
op->ob_tid = tagged_ptr;
29722946
#else
2973-
/* Store the delete_later pointer in the refcnt field. */
2974-
uintptr_t refcnt = pointer_to_safe_refcount(tstate->delete_later);
2975-
*((uintptr_t*)op) = refcnt;
2976-
assert(!_Py_IsImmortal(op));
2947+
_Py_AS_GC(op)->_gc_next = tagged_ptr;
29772948
#endif
29782949
tstate->delete_later = op;
29792950
}
@@ -2988,17 +2959,17 @@ _PyTrash_thread_destroy_chain(PyThreadState *tstate)
29882959
destructor dealloc = Py_TYPE(op)->tp_dealloc;
29892960

29902961
#ifdef Py_GIL_DISABLED
2991-
tstate->delete_later = (PyObject*) op->ob_tid;
2962+
uintptr_t tagged_ptr = op->ob_tid;
29922963
op->ob_tid = 0;
29932964
_Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_MERGED);
29942965
#else
2995-
/* Get the delete_later pointer from the refcnt field.
2996-
* See _PyTrash_thread_deposit_object(). */
2997-
uintptr_t refcnt = *((uintptr_t*)op);
2998-
tstate->delete_later = safe_refcount_to_pointer(refcnt);
2999-
op->ob_refcnt = 0;
2966+
uintptr_t tagged_ptr = _Py_AS_GC(op)->_gc_next;
2967+
_Py_AS_GC(op)->_gc_next = 0;
30002968
#endif
3001-
2969+
tstate->delete_later = (PyObject *)(tagged_ptr & ~1);
2970+
if (tagged_ptr & 1) {
2971+
_PyObject_GC_TRACK(op);
2972+
}
30022973
/* Call the deallocator directly. This used to try to
30032974
* fool Py_DECREF into calling it indirectly, but
30042975
* Py_DECREF was already called on this object, and in
@@ -3072,10 +3043,11 @@ void
30723043
_Py_Dealloc(PyObject *op)
30733044
{
30743045
PyTypeObject *type = Py_TYPE(op);
3046+
unsigned long gc_flag = type->tp_flags & Py_TPFLAGS_HAVE_GC;
30753047
destructor dealloc = type->tp_dealloc;
30763048
PyThreadState *tstate = _PyThreadState_GET();
30773049
intptr_t margin = _Py_RecursionLimit_GetMargin(tstate);
3078-
if (margin < 2) {
3050+
if (margin < 2 && gc_flag) {
30793051
_PyTrash_thread_deposit_object(tstate, (PyObject *)op);
30803052
return;
30813053
}
@@ -3121,7 +3093,7 @@ _Py_Dealloc(PyObject *op)
31213093
Py_XDECREF(old_exc);
31223094
Py_DECREF(type);
31233095
#endif
3124-
if (tstate->delete_later && margin >= 4) {
3096+
if (tstate->delete_later && margin >= 4 && gc_flag) {
31253097
_PyTrash_thread_destroy_chain(tstate);
31263098
}
31273099
}

0 commit comments

Comments
 (0)