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.
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 @@
 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 @@
 
 #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 PyObject *
-func_getattro(PyObject *op, PyObject *name)
+static int
+restricted(void)
 {
-	char *sname = PyString_AsString(name);
-	
-	if (sname[0] != '_' && PyEval_GetRestricted()) {
-		PyErr_SetString(PyExc_RuntimeError,
-		  "function attributes not accessible in restricted mode");
+	if (!PyEval_GetRestricted())
+		return 0;
+	PyErr_SetString(PyExc_RuntimeError,
+		"function attributes not accessible in restricted mode");
+	return 1;
+}
+
+static PyObject *
+func_get_dict(PyFunctionObject *op)
+{
+	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;
 	}
-	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,
-				"func_code must be set to a code object");
-			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;
-		}
-	}
-	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,
+	/* 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;
-		}
+		return -1;
 	}
-	return PyObject_GenericSetAttr(op, name, value);
+	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;
+	}
+	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;
+	}
+	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;
+	}
+	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 @@
 	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 @@
 	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 */