mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 21:51:50 +00:00 
			
		
		
		
	bpo-45061: Detect refcount bug on empty tuple singleton (GH-28503)
Detect refcount bugs in C extensions when the empty tuple singleton is destroyed by mistake. Add the _Py_FatalRefcountErrorFunc() function.
This commit is contained in:
		
							parent
							
								
									f604cf1c37
								
							
						
					
					
						commit
						79a3148099
					
				
					 6 changed files with 41 additions and 5 deletions
				
			
		|  | @ -90,6 +90,12 @@ extern PyObject* _Py_Offer_Suggestions(PyObject* exception); | ||||||
| PyAPI_FUNC(Py_ssize_t) _Py_UTF8_Edit_Cost(PyObject *str_a, PyObject *str_b, | PyAPI_FUNC(Py_ssize_t) _Py_UTF8_Edit_Cost(PyObject *str_a, PyObject *str_b, | ||||||
|                                           Py_ssize_t max_cost); |                                           Py_ssize_t max_cost); | ||||||
| 
 | 
 | ||||||
|  | PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc( | ||||||
|  |     const char *func, | ||||||
|  |     const char *message); | ||||||
|  | 
 | ||||||
|  | #define _Py_FatalRefcountError(message) _Py_FatalRefcountErrorFunc(__func__, message) | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -0,0 +1,4 @@ | ||||||
|  | Add a deallocator to the bool type to detect refcount bugs in C extensions | ||||||
|  | which call Py_DECREF(Py_True) or Py_DECREF(Py_False) by mistake. Detect also | ||||||
|  | refcount bugs when the empty tuple singleton is destroyed by mistake. Patch | ||||||
|  | by Victor Stinner. | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| /* Boolean type, a subtype of int */ | /* Boolean type, a subtype of int */ | ||||||
| 
 | 
 | ||||||
| #include "Python.h" | #include "Python.h" | ||||||
|  | #include "pycore_pyerrors.h"      // _Py_FatalRefcountError() | ||||||
| #include "longintrepr.h" | #include "longintrepr.h" | ||||||
| 
 | 
 | ||||||
| /* We define bool_repr to return "False" or "True" */ | /* We define bool_repr to return "False" or "True" */ | ||||||
|  | @ -156,8 +157,7 @@ static PyNumberMethods bool_as_number = { | ||||||
| static void _Py_NO_RETURN | static void _Py_NO_RETURN | ||||||
| bool_dealloc(PyObject* Py_UNUSED(ignore)) | bool_dealloc(PyObject* Py_UNUSED(ignore)) | ||||||
| { | { | ||||||
|     Py_FatalError("deallocating True or False likely caused by " |     _Py_FatalRefcountError("deallocating True or False"); | ||||||
|                   "a refcount bug in a C extension"); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* The type object for bool.  Note that this cannot be subclassed! */ | /* The type object for bool.  Note that this cannot be subclassed! */ | ||||||
|  |  | ||||||
|  | @ -1563,8 +1563,7 @@ none_repr(PyObject *op) | ||||||
| static void _Py_NO_RETURN | static void _Py_NO_RETURN | ||||||
| none_dealloc(PyObject* Py_UNUSED(ignore)) | none_dealloc(PyObject* Py_UNUSED(ignore)) | ||||||
| { | { | ||||||
|     Py_FatalError("deallocating None likely caused by a refcount bug " |     _Py_FatalRefcountError("deallocating None"); | ||||||
|                   "in a C extension"); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static PyObject * | static PyObject * | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| #include "pycore_gc.h"            // _PyObject_GC_IS_TRACKED() | #include "pycore_gc.h"            // _PyObject_GC_IS_TRACKED() | ||||||
| #include "pycore_initconfig.h"    // _PyStatus_OK() | #include "pycore_initconfig.h"    // _PyStatus_OK() | ||||||
| #include "pycore_object.h"        // _PyObject_GC_TRACK() | #include "pycore_object.h"        // _PyObject_GC_TRACK() | ||||||
|  | #include "pycore_pyerrors.h"      // _Py_FatalRefcountError() | ||||||
| 
 | 
 | ||||||
| /*[clinic input]
 | /*[clinic input]
 | ||||||
| class tuple "PyTupleObject *" "&PyTuple_Type" | class tuple "PyTupleObject *" "&PyTuple_Type" | ||||||
|  | @ -287,6 +288,17 @@ tupledealloc(PyTupleObject *op) | ||||||
|         } |         } | ||||||
| #endif | #endif | ||||||
|     } |     } | ||||||
|  | #if defined(Py_DEBUG) && PyTuple_MAXSAVESIZE > 0 | ||||||
|  |     else { | ||||||
|  |         assert(len == 0); | ||||||
|  |         struct _Py_tuple_state *state = get_tuple_state(); | ||||||
|  |         // The empty tuple singleton must only be deallocated by
 | ||||||
|  |         // _PyTuple_Fini(): not before, not after
 | ||||||
|  |         if (op == state->free_list[0] && state->numfree[0] != 0) { | ||||||
|  |             _Py_FatalRefcountError("deallocating the empty tuple singleton"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|     Py_TYPE(op)->tp_free((PyObject *)op); |     Py_TYPE(op)->tp_free((PyObject *)op); | ||||||
| 
 | 
 | ||||||
| #if PyTuple_MAXSAVESIZE > 0 | #if PyTuple_MAXSAVESIZE > 0 | ||||||
|  | @ -1048,11 +1060,16 @@ _PyTuple_Fini(PyInterpreterState *interp) | ||||||
|     struct _Py_tuple_state *state = &interp->tuple; |     struct _Py_tuple_state *state = &interp->tuple; | ||||||
|     // The empty tuple singleton must not be tracked by the GC
 |     // The empty tuple singleton must not be tracked by the GC
 | ||||||
|     assert(!_PyObject_GC_IS_TRACKED(state->free_list[0])); |     assert(!_PyObject_GC_IS_TRACKED(state->free_list[0])); | ||||||
|  | 
 | ||||||
|  | #ifdef Py_DEBUG | ||||||
|  |     state->numfree[0] = 0; | ||||||
|  | #endif | ||||||
|     Py_CLEAR(state->free_list[0]); |     Py_CLEAR(state->free_list[0]); | ||||||
|     _PyTuple_ClearFreeList(interp); |  | ||||||
| #ifdef Py_DEBUG | #ifdef Py_DEBUG | ||||||
|     state->numfree[0] = -1; |     state->numfree[0] = -1; | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  |     _PyTuple_ClearFreeList(interp); | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2805,6 +2805,16 @@ _Py_FatalErrorFormat(const char *func, const char *format, ...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | void _Py_NO_RETURN | ||||||
|  | _Py_FatalRefcountErrorFunc(const char *func, const char *msg) | ||||||
|  | { | ||||||
|  |     _Py_FatalErrorFormat(func, | ||||||
|  |                          "%s: bug likely caused by a refcount error " | ||||||
|  |                          "in a C extension", | ||||||
|  |                          msg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| void _Py_NO_RETURN | void _Py_NO_RETURN | ||||||
| Py_ExitStatusException(PyStatus status) | Py_ExitStatusException(PyStatus status) | ||||||
| { | { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Victor Stinner
						Victor Stinner