Commit d9d1d4ac authored by Guido van Rossum's avatar Guido van Rossum

Rewrite function attributes to use the generic routines properly.

This uses the new "restricted" feature of structmember, and getset
descriptors for some of the type checks.
parent 5d1e34aa
from test_support import verbose, TestFailed
from test_support import verbose, TestFailed, verify
import types
class F:
def a(self):
......@@ -210,3 +211,159 @@ d[foo] = 1
foo.func_code = temp.func_code
d[foo]
# Test all predefined function attributes systematically
def test_func_closure():
a = 12
def f(): print a
c = f.func_closure
verify(isinstance(c, tuple))
verify(len(c) == 1)
verify(c[0].__class__.__name__ == "cell") # don't have a type object handy
try:
f.func_closure = c
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to set func_closure"
try:
del a.func_closure
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to del func_closure"
def test_func_doc():
def f(): pass
verify(f.__doc__ is None)
verify(f.func_doc is None)
f.__doc__ = "hello"
verify(f.__doc__ == "hello")
verify(f.func_doc == "hello")
del f.__doc__
verify(f.__doc__ is None)
verify(f.func_doc is None)
f.func_doc = "world"
verify(f.__doc__ == "world")
verify(f.func_doc == "world")
del f.func_doc
verify(f.func_doc is None)
verify(f.__doc__ is None)
def test_func_globals():
def f(): pass
verify(f.func_globals is globals())
try:
f.func_globals = globals()
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to set func_globals"
try:
del f.func_globals
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to del func_globals"
def test_func_name():
def f(): pass
verify(f.__name__ == "f")
verify(f.func_name == "f")
try:
f.func_name = "f"
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to set func_name"
try:
f.__name__ = "f"
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to set __name__"
try:
del f.func_name
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to del func_name"
try:
del f.__name__
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to del __name__"
def test_func_code():
def f(): pass
def g(): print 12
verify(type(f.func_code) is types.CodeType)
f.func_code = g.func_code
try:
del f.func_code
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to del func_code"
def test_func_defaults():
def f(a, b): return (a, b)
verify(f.func_defaults is None)
f.func_defaults = (1, 2)
verify(f.func_defaults == (1, 2))
verify(f(10) == (10, 2))
def g(a=1, b=2): return (a, b)
verify(g.func_defaults == (1, 2))
del g.func_defaults
verify(g.func_defaults is None)
try:
g()
except TypeError:
pass
else:
raise TestFailed, "shouldn't be allowed to call g() w/o defaults"
def test_func_dict():
def f(): pass
a = f.__dict__
b = f.func_dict
verify(a == {})
verify(a is b)
f.hello = 'world'
verify(a == {'hello': 'world'})
verify(f.func_dict is a is f.__dict__)
f.func_dict = {}
try:
f.hello
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "hello attribute should have disappeared"
f.__dict__ = {'world': 'hello'}
verify(f.world == "hello")
verify(f.__dict__ is f.func_dict == {'world': 'hello'})
try:
del f.func_dict
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to delete func_dict"
try:
del f.__dict__
except (AttributeError, TypeError):
pass
else:
raise TestFailed, "shouldn't be allowed to delete __dict__"
def testmore():
test_func_closure()
test_func_doc()
test_func_globals()
test_func_name()
test_func_code()
test_func_defaults()
test_func_dict()
testmore()
......@@ -127,99 +127,145 @@ PyFunction_SetClosure(PyObject *op, PyObject *closure)
#define OFF(x) offsetof(PyFunctionObject, x)
#define RR ()
static struct memberlist func_memberlist[] = {
{"func_code", T_OBJECT, OFF(func_code)},
{"func_globals", T_OBJECT, OFF(func_globals), READONLY},
{"func_closure", T_OBJECT, OFF(func_closure),
RESTRICTED|READONLY},
{"func_doc", T_OBJECT, OFF(func_doc), WRITE_RESTRICTED},
{"__doc__", T_OBJECT, OFF(func_doc), WRITE_RESTRICTED},
{"func_globals", T_OBJECT, OFF(func_globals),
RESTRICTED|READONLY},
{"func_name", T_OBJECT, OFF(func_name), READONLY},
{"__name__", T_OBJECT, OFF(func_name), READONLY},
{"func_closure", T_OBJECT, OFF(func_closure), READONLY},
{"func_defaults", T_OBJECT, OFF(func_defaults)},
{"func_doc", T_OBJECT, OFF(func_doc)},
{"__doc__", T_OBJECT, OFF(func_doc)},
{"func_dict", T_OBJECT, OFF(func_dict)},
{"__dict__", T_OBJECT, OFF(func_dict)},
{NULL} /* Sentinel */
};
static int
restricted(void)
{
if (!PyEval_GetRestricted())
return 0;
PyErr_SetString(PyExc_RuntimeError,
"function attributes not accessible in restricted mode");
return 1;
}
static PyObject *
func_getattro(PyObject *op, PyObject *name)
func_get_dict(PyFunctionObject *op)
{
char *sname = PyString_AsString(name);
if (sname[0] != '_' && PyEval_GetRestricted()) {
PyErr_SetString(PyExc_RuntimeError,
"function attributes not accessible in restricted mode");
if (restricted())
return NULL;
}
/* If func_dict is being accessed but no attribute has been set
* yet, then initialize it to the empty dictionary.
*/
if ((!strcmp(sname, "func_dict") || !strcmp(sname, "__dict__"))
&& ((PyFunctionObject*)op)->func_dict == NULL)
{
PyFunctionObject* funcop = (PyFunctionObject*)op;
funcop->func_dict = PyDict_New();
if (funcop->func_dict == NULL)
if (op->func_dict == NULL) {
op->func_dict = PyDict_New();
if (op->func_dict == NULL)
return NULL;
}
return PyObject_GenericGetAttr(op, name);
Py_INCREF(op->func_dict);
return op->func_dict;
}
static int
func_setattro(PyObject *op, PyObject *name, PyObject *value)
func_set_dict(PyFunctionObject *op, PyObject *value)
{
char *sname = PyString_AsString(name);
PyObject *tmp;
if (PyEval_GetRestricted()) {
PyErr_SetString(PyExc_RuntimeError,
"function attributes not settable in restricted mode");
if (restricted())
return -1;
/* It is illegal to del f.func_dict */
if (value == NULL) {
PyErr_SetString(PyExc_TypeError,
"function's dictionary may not be deleted");
return -1;
}
/* Can only set func_dict to a dictionary */
if (!PyDict_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"setting function's dictionary to a non-dict");
return -1;
}
if (strcmp(sname, "func_code") == 0) {
/* not legal to del f.func_code or to set it to anything
* other than a code object.
*/
if (value == NULL || !PyCode_Check(value)) {
PyErr_SetString(
PyExc_TypeError,
tmp = op->func_dict;
Py_INCREF(value);
op->func_dict = value;
Py_XDECREF(tmp);
return 0;
}
static PyObject *
func_get_code(PyFunctionObject *op)
{
if (restricted())
return NULL;
Py_INCREF(op->func_code);
return op->func_code;
}
static int
func_set_code(PyFunctionObject *op, PyObject *value)
{
PyObject *tmp;
if (restricted())
return -1;
/* Not legal to del f.func_code or to set it to anything
* other than a code object. */
if (value == NULL || !PyCode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"func_code must be set to a code object");
return -1;
}
return -1;
}
else if (strcmp(sname, "func_defaults") == 0) {
/* legal to del f.func_defaults. Can only set
* func_defaults to NULL or a tuple.
*/
if (value == Py_None)
value = NULL;
if (value != NULL && !PyTuple_Check(value)) {
PyErr_SetString(
PyExc_TypeError,
"func_defaults must be set to a tuple object");
return -1;
}
tmp = op->func_code;
Py_INCREF(value);
op->func_code = value;
Py_DECREF(tmp);
return 0;
}
static PyObject *
func_get_defaults(PyFunctionObject *op)
{
if (restricted())
return NULL;
if (op->func_defaults == NULL) {
Py_INCREF(Py_None);
return Py_None;
}
else if (!strcmp(sname, "func_dict") || !strcmp(sname, "__dict__")) {
/* It is illegal to del f.func_dict. Can only set
* func_dict to a dictionary.
*/
if (value == NULL) {
PyErr_SetString(
PyExc_TypeError,
"function's dictionary may not be deleted");
return -1;
}
if (!PyDict_Check(value)) {
PyErr_SetString(
PyExc_TypeError,
"setting function's dictionary to a non-dict");
return -1;
}
Py_INCREF(op->func_defaults);
return op->func_defaults;
}
static int
func_set_defaults(PyFunctionObject *op, PyObject *value)
{
PyObject *tmp;
if (restricted())
return -1;
/* Legal to del f.func_defaults.
* Can only set func_defaults to NULL or a tuple. */
if (value == Py_None)
value = NULL;
if (value != NULL && !PyTuple_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"func_defaults must be set to a tuple object");
return -1;
}
return PyObject_GenericSetAttr(op, name, value);
tmp = op->func_defaults;
Py_XINCREF(value);
op->func_defaults = value;
Py_XDECREF(tmp);
return 0;
}
static struct getsetlist func_getsetlist[] = {
{"func_code", (getter)func_get_code, (setter)func_set_code},
{"func_defaults", (getter)func_get_defaults,
(setter)func_set_defaults},
{"func_dict", (getter)func_get_dict, (setter)func_set_dict},
{"__dict__", (getter)func_get_dict, (setter)func_set_dict},
{NULL} /* Sentinel */
};
static void
func_dealloc(PyFunctionObject *op)
{
......@@ -365,8 +411,8 @@ PyTypeObject PyFunction_Type = {
0, /* tp_hash */
function_call, /* tp_call */
0, /* tp_str */
func_getattro, /* tp_getattro */
func_setattro, /* tp_setattro */
PyObject_GenericGetAttr, /* tp_getattro */
PyObject_GenericSetAttr, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
0, /* tp_doc */
......@@ -378,7 +424,7 @@ PyTypeObject PyFunction_Type = {
0, /* tp_iternext */
0, /* tp_methods */
func_memberlist, /* tp_members */
0, /* tp_getset */
func_getsetlist, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
func_descr_get, /* tp_descr_get */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment