[Patches] Safe destruction of recursive objects V2a

Christian Tismer tismer@tismer.com
Fri, 10 Mar 2000 14:05:17 +0100


Version 2a with one less typo. Sigh... :-)

This is the reworked patch version 2.

When recursive objects are destroyed, the recursion depth
of nested dealloc calls can cause a core dump.
The trashcan macro/functions are written to avoid this.
To keep the necessary changes as small as possible,
the patch add just two macro lines which enclose
the critical operation.

The affected objects are dict, tuple, list, frame and traceback.

The method:
While destructing, a global counter tracks the recursion depth.
It is incremented in the beginning macro and decremented in
the ending macro. If the count hits its limit, the enclosed
deallocation code is not executed. Instead, destruction is
deferred by appending the object to a list.
The ending macro checks wether we are on toplevel and if there
is a list to destroy. If so, the list is made local and cleared
out. This process will continue until there is nothing left
over. I'd call this "elevator destructor."

As an addition, I added some small changes to listmodule.c,
in order to make strict argument handling in append et al
optional. If this is undesired, simply remove that part
of the patch.

Checkin-message:
added wrapping macros to dictobject.c, listobject.c, tupleobject.c,
frameobject.c, traceback.c that safely prevends core dumps 
on stack overflow. Macros and functions in object.c, object.h.

Diff:
diff -c -b -P -r //d/python/python-1.5.2/Include/object.h trash/Include/object.h
*** //d/python/python-1.5.2/Include/object.h	Fri Mar 03 20:44:40 2000
--- trash/Include/object.h	Fri Mar 10 01:23:26 2000
***************
*** 514,519 ****
--- 514,566 ----
  times.
  */
  
+ /*
+   trashcan
+   CT 2k0130
+   non-recursively destroy nested objects
+ 
+   CT 2k0223
+   redefinition for better locality and less overhead.
+ 
+   Objects that want to be recursion safe need to use
+   the macroes 
+ 		Py_TRASHCAN_SAFE_BEGIN(name)
+   and
+ 		Py_TRASHCAN_SAFE_END(name)
+   surrounding their actual deallocation code.
+ 
+   It would be nice to do this using the thread state.
+   Also, we could do an exact stack measure then.
+   Unfortunately, deallocations also take place when
+   the thread state is undefined.
+ */
+ 
+ #define PyTrash_UNWIND_LEVEL 50
+ 
+ #define Py_TRASHCAN_SAFE_BEGIN(op) \
+ 	{ \
+ 		++_PyTrash_delete_nesting; \
+ 		if (_PyTrash_delete_nesting < PyTrash_UNWIND_LEVEL) { \
+ 
+ #define Py_TRASHCAN_SAFE_END(op) \
+ 		;} \
+ 		else \
+ 			_PyTrash_deposit_object((PyObject*)op);\
+ 		--_PyTrash_delete_nesting; \
+ 		if (_PyTrash_delete_later && _PyTrash_delete_nesting <= 0) \
+ 			_PyTrash_destroy_list(); \
+ 	} \
+ 
+ extern DL_IMPORT(void) _PyTrash_deposit_object Py_PROTO((PyObject*));
+ extern DL_IMPORT(void) _PyTrash_destroy_list Py_PROTO(());
+ 
+ extern DL_IMPORT(int) _PyTrash_delete_nesting;
+ extern DL_IMPORT(PyObject *) _PyTrash_delete_later;
+ 
+ /* swap the "xx" to check the speed loss */
+ 
+ #define xxPy_TRASHCAN_SAFE_BEGIN(op) 
+ #define xxPy_TRASHCAN_SAFE_END(op) ;
  #ifdef __cplusplus
  }
  #endif
diff -c -b -P -r //d/python/python-1.5.2/Objects/dictobject.c
trash/Objects/dictobject.c
*** //d/python/python-1.5.2/Objects/dictobject.c	Sat Feb 26 20:35:38 2000
--- trash/Objects/dictobject.c	Wed Mar 08 19:07:30 2000
***************
*** 479,484 ****
--- 479,485 ----
  {
  	register int i;
  	register dictentry *ep;
+ 	Py_TRASHCAN_SAFE_BEGIN(mp)
  	for (i = 0, ep = mp->ma_table; i < mp->ma_size; i++, ep++) {
  		if (ep->me_key != NULL) {
  			Py_DECREF(ep->me_key);
***************
*** 489,494 ****
--- 490,496 ----
  	}
  	PyMem_XDEL(mp->ma_table);
  	PyMem_DEL(mp);
+ 	Py_TRASHCAN_SAFE_END(mp)
  }
  
  static int
diff -c -b -P -r //d/python/python-1.5.2/Objects/frameobject.c
trash/Objects/frameobject.c
*** //d/python/python-1.5.2/Objects/frameobject.c	Sat Feb 26 20:35:40 2000
--- trash/Objects/frameobject.c	Wed Mar 08 19:07:30 2000
***************
*** 103,108 ****
--- 103,109 ----
  	int i;
  	PyObject **fastlocals;
  
+ 	Py_TRASHCAN_SAFE_BEGIN(f)
  	/* Kill all local variables */
  	fastlocals = f->f_localsplus;
  	for (i = f->f_nlocals; --i >= 0; ++fastlocals) {
***************
*** 120,125 ****
--- 121,127 ----
  	Py_XDECREF(f->f_exc_traceback);
  	f->f_back = free_list;
  	free_list = f;
+ 	Py_TRASHCAN_SAFE_END(f)
  }
  
  PyTypeObject PyFrame_Type = {
diff -c -b -P -r //d/python/python-1.5.2/Objects/listobject.c
trash/Objects/listobject.c
*** //d/python/python-1.5.2/Objects/listobject.c	Sat Feb 26 20:35:40 2000
--- trash/Objects/listobject.c	Wed Mar 08 19:07:30 2000
***************
*** 215,220 ****
--- 215,221 ----
  	PyListObject *op;
  {
  	int i;
+ 	Py_TRASHCAN_SAFE_BEGIN(op)
  	if (op->ob_item != NULL) {
  		/* Do it backwards, for Christian Tismer.
  		   There's a simple test case where somehow this reduces
***************
*** 227,232 ****
--- 228,234 ----
  		free((ANY *)op->ob_item);
  	}
  	free((ANY *)op);
+ 	Py_TRASHCAN_SAFE_END(op)
  }
  
  static int
diff -c -b -P -r //d/python/python-1.5.2/Objects/object.c trash/Objects/object.c
*** //d/python/python-1.5.2/Objects/object.c	Sat Feb 26 20:35:42 2000
--- trash/Objects/object.c	Fri Mar 10 00:40:24 2000
***************
*** 887,889 ****
--- 887,934 ----
  		}
  	}
  }
+ 
+ /*
+   trashcan
+   CT 2k0130
+   non-recursively destroy nested objects
+ 
+   CT 2k0223
+   everything is now done in a macro.
+ 
+   CT 2k0305
+   modified to use functions, after Tim Peter's suggestion.
+ 
+   CT 2k0309
+   modified to restore a possible error.
+ */
+ 
+ int _PyTrash_delete_nesting = 0;
+ PyObject * _PyTrash_delete_later = NULL;
+ 
+ void
+ _PyTrash_deposit_object(op)
+ 	PyObject *op;
+ {
+ 	PyObject *error_type, *error_value, *error_traceback;
+ 	PyErr_Fetch(&error_type, &error_value, &error_traceback);
+ 
+ 	if (!_PyTrash_delete_later)
+ 		_PyTrash_delete_later = PyList_New(0);
+ 	if (_PyTrash_delete_later)
+ 		PyList_Append(_PyTrash_delete_later, (PyObject *)op);
+ 
+ 	PyErr_Restore(error_type, error_value, error_traceback);
+ }
+ 
+ void
+ _PyTrash_destroy_list()
+ {
+ 	while (_PyTrash_delete_later) {
+ 		PyObject *shredder = _PyTrash_delete_later;
+ 		_PyTrash_delete_later = NULL;
+ 		++_PyTrash_delete_nesting;
+ 		Py_DECREF(shredder);
+ 		--_PyTrash_delete_nesting;
+ 	}
+ }
diff -c -b -P -r //d/python/python-1.5.2/Objects/tupleobject.c
trash/Objects/tupleobject.c
*** //d/python/python-1.5.2/Objects/tupleobject.c	Sat Feb 26 20:35:44 2000
--- trash/Objects/tupleobject.c	Wed Mar 08 19:07:30 2000
***************
*** 172,177 ****
--- 172,178 ----
  {
  	register int i;
  
+ 	Py_TRASHCAN_SAFE_BEGIN(op)
  	if (op->ob_size > 0) {
  		i = op->ob_size;
  		while (--i >= 0)
***************
*** 180,190 ****
  		if (op->ob_size < MAXSAVESIZE) {
  			op->ob_item[0] = (PyObject *) free_tuples[op->ob_size];
  			free_tuples[op->ob_size] = op;
! 			return;
  		}
  #endif
  	}
  	free((ANY *)op);
  }
  
  static int
--- 181,193 ----
  		if (op->ob_size < MAXSAVESIZE) {
  			op->ob_item[0] = (PyObject *) free_tuples[op->ob_size];
  			free_tuples[op->ob_size] = op;
! 			goto done; /* return */
  		}
  #endif
  	}
  	free((ANY *)op);
+ done:
+ 	Py_TRASHCAN_SAFE_END(op)
  }
  
  static int
diff -c -b -P -r //d/python/python-1.5.2/Python/traceback.c
trash/Python/traceback.c
*** //d/python/python-1.5.2/Python/traceback.c	Sat Sep 18 22:49:40 1999
--- trash/Python/traceback.c	Wed Mar 08 19:07:30 2000
***************
*** 68,76 ****
--- 68,78 ----
  tb_dealloc(tb)
  	tracebackobject *tb;
  {
+ 	Py_TRASHCAN_SAFE_BEGIN(tb)
  	Py_XDECREF(tb->tb_next);
  	Py_XDECREF(tb->tb_frame);
  	PyMem_DEL(tb);
+ 	Py_TRASHCAN_SAFE_END(tb)
  }
  
  #define Tracebacktype PyTraceBack_Type

%%%%%%%%%%%%%%%%%%%%%%% end patch %%%%%%%%%%%%%%%%%%%%%%%%%%
Disclaimer:
I confirm that, to the best of my knowledge and belief, this
contribution is free of any claims of third parties under
copyright, patent or other rights or interests ("claims").  To
the extent that I have any such claims, I hereby grant to CNRI a
nonexclusive, irrevocable, royalty-free, worldwide license to
reproduce, distribute, perform and/or display publicly, prepare
derivative versions, and otherwise use this contribution as part
of the Python software and its related documentation, or any
derivative versions thereof, at no cost to CNRI or its licensed
users, and to authorize others to do so.

I acknowledge that CNRI may, at its sole discretion, decide
whether or not to incorporate this contribution in the Python
software and its related documentation.  I further grant CNRI
permission to use my name and other identifying information
provided to CNRI by me for use in connection with the Python
software and its related documentation.

-- 
Christian Tismer             :^)   <mailto:tismer@appliedbiometrics.com>
Applied Biometrics GmbH      :     Have a break! Take a ride on Python's
Kaunstr. 26                  :    *Starship* http://starship.python.net
14163 Berlin                 :     PGP key -> http://wwwkeys.pgp.net
PGP Fingerprint       E182 71C7 1A9D 66E9 9D15  D3CC D4D7 93E2 1FAE F6DF
     we're tired of banana software - shipped green, ripens at home