mirror of
https://github.com/python/cpython.git
synced 2025-08-24 10:45:53 +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 @@ d[foo]
|
||||||
|
|
||||||
# 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 test_func_name():
|
||||||
|
|
||||||
|
|
||||||
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