mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	Wrote down the invariants of some common objects whose structure is
exposed in header files. Fixed a few comments in these headers. As we might have expected, writing down invariants systematically exposed a (minor) bug. In this case, function objects have a writeable func_code attribute, which could be set to code objects with the wrong number of free variables. Calling the resulting function segfaulted the interpreter. Added a corresponding test.
This commit is contained in:
		
							parent
							
								
									063e1e846d
								
							
						
					
					
						commit
						89a39461bf
					
				
					 12 changed files with 98 additions and 25 deletions
				
			
		|  | @ -8,7 +8,7 @@ extern "C" { | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| 	PyObject_HEAD | 	PyObject_HEAD | ||||||
| 	PyObject *ob_ref; | 	PyObject *ob_ref;	/* Content of the cell or NULL when empty */ | ||||||
| } PyCellObject; | } PyCellObject; | ||||||
| 
 | 
 | ||||||
| PyAPI_DATA(PyTypeObject) PyCell_Type; | PyAPI_DATA(PyTypeObject) PyCell_Type; | ||||||
|  |  | ||||||
|  | @ -7,17 +7,34 @@ | ||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | /* Function objects and code objects should not be confused with each other:
 | ||||||
|  |  * | ||||||
|  |  * Function objects are created by the execution of the 'def' statement. | ||||||
|  |  * They reference a code object in their func_code attribute, which is a | ||||||
|  |  * purely syntactic object, i.e. nothing more than a compiled version of some | ||||||
|  |  * source code lines.  There is one code object per source code "fragment", | ||||||
|  |  * but each code object can be referenced by zero or many function objects | ||||||
|  |  * depending only on how many times the 'def' statement in the source was | ||||||
|  |  * executed so far. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     PyObject_HEAD |     PyObject_HEAD | ||||||
|     PyObject *func_code; |     PyObject *func_code;	/* A code object */ | ||||||
|     PyObject *func_globals; |     PyObject *func_globals;	/* A dictionary (other mappings won't do) */ | ||||||
|     PyObject *func_defaults; |     PyObject *func_defaults;	/* NULL or a tuple */ | ||||||
|     PyObject *func_closure; |     PyObject *func_closure;	/* NULL or a tuple of cell objects */ | ||||||
|     PyObject *func_doc; |     PyObject *func_doc;		/* The __doc__ attribute, can be anything */ | ||||||
|     PyObject *func_name; |     PyObject *func_name;	/* The __name__ attribute, a string object */ | ||||||
|     PyObject *func_dict; |     PyObject *func_dict;	/* The __dict__ attribute, a dict or NULL */ | ||||||
|     PyObject *func_weakreflist; |     PyObject *func_weakreflist;	/* List of weak references */ | ||||||
|     PyObject *func_module; |     PyObject *func_module;	/* The __module__ attribute, can be anything */ | ||||||
|  | 
 | ||||||
|  |     /* Invariant:
 | ||||||
|  |      *     func_closure contains the bindings for func_code->co_freevars, so | ||||||
|  |      *     PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code) | ||||||
|  |      *     (func_closure may be NULL if PyCode_GetNumFree(func_code) == 0). | ||||||
|  |      */ | ||||||
| } PyFunctionObject; | } PyFunctionObject; | ||||||
| 
 | 
 | ||||||
| PyAPI_DATA(PyTypeObject) PyFunction_Type; | PyAPI_DATA(PyTypeObject) PyFunction_Type; | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ returns -1 and sets errno to EBADF if the object is not an PyIntObject. | ||||||
| None of the functions should be applied to nil objects. | None of the functions should be applied to nil objects. | ||||||
| 
 | 
 | ||||||
| The type PyIntObject is (unfortunately) exposed here so we can declare | The type PyIntObject is (unfortunately) exposed here so we can declare | ||||||
| _Py_TrueStruct and _Py_ZeroStruct below; don't use this. | _Py_TrueStruct and _Py_ZeroStruct in boolobject.h; don't use this. | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| #ifndef Py_INTOBJECT_H | #ifndef Py_INTOBJECT_H | ||||||
|  |  | ||||||
|  | @ -31,6 +31,9 @@ typedef struct { | ||||||
|      *     len(list) == ob_size |      *     len(list) == ob_size | ||||||
|      *     ob_item == NULL implies ob_size == allocated == 0 |      *     ob_item == NULL implies ob_size == allocated == 0 | ||||||
|      * list.sort() temporarily sets allocated to -1 to detect mutations. |      * list.sort() temporarily sets allocated to -1 to detect mutations. | ||||||
|  |      * | ||||||
|  |      * Items must normally not be NULL, except during construction when | ||||||
|  |      * the list is not yet visible outside the function that builds it. | ||||||
|      */ |      */ | ||||||
|     int allocated; |     int allocated; | ||||||
| } PyListObject; | } PyListObject; | ||||||
|  |  | ||||||
|  | @ -7,6 +7,10 @@ | ||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | /* This is about the type 'builtin_function_or_method',
 | ||||||
|  |    not Python methods in user-defined classes.  See classobject.h | ||||||
|  |    for the latter. */ | ||||||
|  | 
 | ||||||
| PyAPI_DATA(PyTypeObject) PyCFunction_Type; | PyAPI_DATA(PyTypeObject) PyCFunction_Type; | ||||||
| 
 | 
 | ||||||
| #define PyCFunction_Check(op) ((op)->ob_type == &PyCFunction_Type) | #define PyCFunction_Check(op) ((op)->ob_type == &PyCFunction_Type) | ||||||
|  | @ -31,10 +35,11 @@ PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *); | ||||||
| PyAPI_FUNC(PyObject *) PyCFunction_Call(PyObject *, PyObject *, PyObject *); | PyAPI_FUNC(PyObject *) PyCFunction_Call(PyObject *, PyObject *, PyObject *); | ||||||
| 
 | 
 | ||||||
| struct PyMethodDef { | struct PyMethodDef { | ||||||
|     char	*ml_name; |     char	*ml_name;	/* The name of the built-in function/method */ | ||||||
|     PyCFunction  ml_meth; |     PyCFunction  ml_meth;	/* The C function that implements it */ | ||||||
|     int		 ml_flags; |     int		 ml_flags;	/* Combination of METH_xxx flags, which mostly
 | ||||||
|     char	*ml_doc; | 				   describe the args expected by the C func */ | ||||||
|  |     char	*ml_doc;	/* The __doc__ attribute, or NULL */ | ||||||
| }; | }; | ||||||
| typedef struct PyMethodDef PyMethodDef; | typedef struct PyMethodDef PyMethodDef; | ||||||
| 
 | 
 | ||||||
|  | @ -75,9 +80,9 @@ PyAPI_FUNC(PyObject *) Py_FindMethodInChain(PyMethodChain *, PyObject *, | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     PyObject_HEAD |     PyObject_HEAD | ||||||
|     PyMethodDef *m_ml; |     PyMethodDef *m_ml; /* Description of the C function to call */ | ||||||
|     PyObject    *m_self; |     PyObject    *m_self; /* Passed as 'self' arg to the C func, can be NULL */ | ||||||
|     PyObject    *m_module; |     PyObject    *m_module; /* The __module__ attribute, can be anything */ | ||||||
| } PyCFunctionObject; | } PyCFunctionObject; | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
|  |  | ||||||
|  | @ -7,6 +7,9 @@ | ||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | /* This is about the type 'xrange', not the built-in function range(), which
 | ||||||
|  |    returns regular lists. */ | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
| A range object represents an integer range.  This is an immutable object; | A range object represents an integer range.  This is an immutable object; | ||||||
| a range cannot change its value after creation. | a range cannot change its value after creation. | ||||||
|  |  | ||||||
|  | @ -16,6 +16,14 @@ typedef struct { | ||||||
| 	PyObject *data; | 	PyObject *data; | ||||||
| 	long hash;	/* only used by frozenset objects */ | 	long hash;	/* only used by frozenset objects */ | ||||||
| 	PyObject *weakreflist; /* List of weak references */ | 	PyObject *weakreflist; /* List of weak references */ | ||||||
|  | 
 | ||||||
|  | 	/* Invariants:
 | ||||||
|  | 	 *     data is a dictionary whose values are all True. | ||||||
|  | 	 *     data points to the same dict for the whole life of the set. | ||||||
|  | 	 * For frozensets only: | ||||||
|  | 	 *     data is immutable. | ||||||
|  | 	 *     hash is the hash of the frozenset or -1 if not computed yet. | ||||||
|  | 	 */ | ||||||
| } PySetObject; | } PySetObject; | ||||||
| 
 | 
 | ||||||
| PyAPI_DATA(PyTypeObject) PySet_Type; | PyAPI_DATA(PyTypeObject) PySet_Type; | ||||||
|  |  | ||||||
|  | @ -16,12 +16,12 @@ PyAPI_DATA(PyObject) _Py_EllipsisObject; /* Don't use this directly */ | ||||||
| 
 | 
 | ||||||
| A slice object containing start, stop, and step data members (the | A slice object containing start, stop, and step data members (the | ||||||
| names are from range).  After much talk with Guido, it was decided to | names are from range).  After much talk with Guido, it was decided to | ||||||
| let these be any arbitrary python type.  | let these be any arbitrary python type.  Py_None stands for omitted values. | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     PyObject_HEAD |     PyObject_HEAD | ||||||
|     PyObject *start, *stop, *step; |     PyObject *start, *stop, *step;	/* not NULL */ | ||||||
| } PySliceObject; | } PySliceObject; | ||||||
| 
 | 
 | ||||||
| PyAPI_DATA(PyTypeObject) PySlice_Type; | PyAPI_DATA(PyTypeObject) PySlice_Type; | ||||||
|  |  | ||||||
|  | @ -37,6 +37,15 @@ typedef struct { | ||||||
|     long ob_shash; |     long ob_shash; | ||||||
|     int ob_sstate; |     int ob_sstate; | ||||||
|     char ob_sval[1]; |     char ob_sval[1]; | ||||||
|  | 
 | ||||||
|  |     /* Invariants:
 | ||||||
|  |      *     ob_sval contains space for 'ob_size+1' elements. | ||||||
|  |      *     ob_sval[ob_size] == 0. | ||||||
|  |      *     ob_shash is the hash of the string or -1 if not computed yet. | ||||||
|  |      *     ob_sstate != 0 iff the string object is in stringobject.c's | ||||||
|  |      *       'interned' dictionary; in this case the two references | ||||||
|  |      *       from 'interned' to this object are *not counted* in ob_refcnt. | ||||||
|  |      */ | ||||||
| } PyStringObject; | } PyStringObject; | ||||||
| 
 | 
 | ||||||
| #define SSTATE_NOT_INTERNED 0 | #define SSTATE_NOT_INTERNED 0 | ||||||
|  |  | ||||||
|  | @ -8,9 +8,11 @@ extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
| Another generally useful object type is an tuple of object pointers. | Another generally useful object type is a tuple of object pointers. | ||||||
| This is a mutable type: the tuple items can be changed (but not their | For Python, this is an immutable type.  C code can change the tuple items | ||||||
| number).  Out-of-range indices or non-tuple objects are ignored. | (but not their number), and even use tuples are general-purpose arrays of | ||||||
|  | object references, but in general only brand new tuples should be mutated, | ||||||
|  | not ones that might already have been exposed to Python code. | ||||||
| 
 | 
 | ||||||
| *** WARNING *** PyTuple_SetItem does not increment the new item's reference | *** WARNING *** PyTuple_SetItem does not increment the new item's reference | ||||||
| count, but does decrement the reference count of the item it replaces, | count, but does decrement the reference count of the item it replaces, | ||||||
|  | @ -22,6 +24,11 @@ returned item's reference count. | ||||||
| typedef struct { | typedef struct { | ||||||
|     PyObject_VAR_HEAD |     PyObject_VAR_HEAD | ||||||
|     PyObject *ob_item[1]; |     PyObject *ob_item[1]; | ||||||
|  | 
 | ||||||
|  |     /* ob_item contains space for 'ob_size' elements.
 | ||||||
|  |      * Items must normally not be NULL, except during construction when | ||||||
|  |      * the tuple is not yet visible outside the function that builds it. | ||||||
|  |      */ | ||||||
| } PyTupleObject; | } PyTupleObject; | ||||||
| 
 | 
 | ||||||
| PyAPI_DATA(PyTypeObject) PyTuple_Type; | PyAPI_DATA(PyTypeObject) PyTuple_Type; | ||||||
|  |  | ||||||
|  | @ -218,11 +218,11 @@ def temp(): | ||||||
| 
 | 
 | ||||||
| # Test all predefined function attributes systematically | # Test all predefined function attributes systematically | ||||||
| 
 | 
 | ||||||
| def cantset(obj, name, value): | def cantset(obj, name, value, exception=(AttributeError, TypeError)): | ||||||
|     verify(hasattr(obj, name)) # Otherwise it's probably a typo |     verify(hasattr(obj, name)) # Otherwise it's probably a typo | ||||||
|     try: |     try: | ||||||
|         setattr(obj, name, value) |         setattr(obj, name, value) | ||||||
|     except (AttributeError, TypeError): |     except exception: | ||||||
|         pass |         pass | ||||||
|     else: |     else: | ||||||
|         raise TestFailed, "shouldn't be able to set %s to %r" % (name, value) |         raise TestFailed, "shouldn't be able to set %s to %r" % (name, value) | ||||||
|  | @ -279,11 +279,20 @@ def f(): pass | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_func_code(): | def test_func_code(): | ||||||
|  |     a = b = 24 | ||||||
|     def f(): pass |     def f(): pass | ||||||
|     def g(): print 12 |     def g(): print 12 | ||||||
|  |     def f1(): print a | ||||||
|  |     def g1(): print b | ||||||
|  |     def f2(): print a, b | ||||||
|     verify(type(f.func_code) is types.CodeType) |     verify(type(f.func_code) is types.CodeType) | ||||||
|     f.func_code = g.func_code |     f.func_code = g.func_code | ||||||
|     cantset(f, "func_code", None) |     cantset(f, "func_code", None) | ||||||
|  |     # can't change the number of free vars | ||||||
|  |     cantset(f,  "func_code", f1.func_code, exception=ValueError) | ||||||
|  |     cantset(f1, "func_code",  f.func_code, exception=ValueError) | ||||||
|  |     cantset(f1, "func_code", f2.func_code, exception=ValueError) | ||||||
|  |     f1.func_code = g1.func_code | ||||||
| 
 | 
 | ||||||
| def test_func_defaults(): | def test_func_defaults(): | ||||||
|     def f(a, b): return (a, b) |     def f(a, b): return (a, b) | ||||||
|  |  | ||||||
|  | @ -230,6 +230,7 @@ static int | ||||||
| func_set_code(PyFunctionObject *op, PyObject *value) | func_set_code(PyFunctionObject *op, PyObject *value) | ||||||
| { | { | ||||||
| 	PyObject *tmp; | 	PyObject *tmp; | ||||||
|  | 	int nfree, nclosure; | ||||||
| 
 | 
 | ||||||
| 	if (restricted()) | 	if (restricted()) | ||||||
| 		return -1; | 		return -1; | ||||||
|  | @ -240,6 +241,17 @@ func_set_code(PyFunctionObject *op, PyObject *value) | ||||||
| 				"func_code must be set to a code object"); | 				"func_code must be set to a code object"); | ||||||
| 		return -1; | 		return -1; | ||||||
| 	} | 	} | ||||||
|  | 	nfree = PyCode_GetNumFree((PyCodeObject *)value); | ||||||
|  | 	nclosure = (op->func_closure == NULL ? 0 : | ||||||
|  | 		    PyTuple_GET_SIZE(op->func_closure)); | ||||||
|  | 	if (nclosure != nfree) { | ||||||
|  | 		PyErr_Format(PyExc_ValueError, | ||||||
|  | 			     "%s() requires a code object with %d free vars," | ||||||
|  | 			     " not %d", | ||||||
|  | 			     PyString_AsString(op->func_name), | ||||||
|  | 			     nclosure, nfree); | ||||||
|  | 		return -1; | ||||||
|  | 	} | ||||||
| 	tmp = op->func_code; | 	tmp = op->func_code; | ||||||
| 	Py_INCREF(value); | 	Py_INCREF(value); | ||||||
| 	op->func_code = value; | 	op->func_code = value; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Armin Rigo
						Armin Rigo