From d9d1d4ac6fda7f4c898b55194be37b03b89450e9 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 17 Sep 2001 23:46:56 +0000 Subject: 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. --- Lib/test/test_funcattrs.py | 159 ++++++++++++++++++++++++++++++++++++- Objects/funcobject.c | 190 ++++++++++++++++++++++++++++----------------- 2 files changed, 276 insertions(+), 73 deletions(-) diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index 746c91e..f4ee329 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -1,4 +1,5 @@ -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() diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 57d02fe..91a3127 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -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 */ -- cgit v0.12