[2.7] bpo-25794: Fix `type.__setattr__()` for non-interned or unicode attribute names. (GH-1652) (#1675)

Based on patch by Eryk Sun.
(cherry picked from commit d896985bb2de49046f9b6879e906d1e4db255e23)
diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py
index e5cdf08..5cd138d 100644
--- a/Lib/test/test_class.py
+++ b/Lib/test/test_class.py
@@ -635,6 +635,49 @@
             self.assertRaises(TypeError, type(c).__getattribute__, c, [])
             self.assertRaises(TypeError, type(c).__setattr__, c, [], [])
 
+    def testSetattrWrapperNameIntern(self):
+        # Issue #25794: __setattr__ should intern the attribute name
+        class A(object):
+            pass
+
+        def add(self, other):
+            return 'summa'
+
+        name = ''.join(list('__add__'))  # shouldn't be optimized
+        self.assertIsNot(name, '__add__')  # not interned
+        type.__setattr__(A, name, add)
+        self.assertEqual(A() + 1, 'summa')
+
+        name2 = ''.join(list('__add__'))
+        self.assertIsNot(name2, '__add__')
+        self.assertIsNot(name2, name)
+        type.__delattr__(A, name2)
+        with self.assertRaises(TypeError):
+            A() + 1
+
+    @test_support.requires_unicode
+    def testSetattrWrapperNameUnicode(self):
+        # Issue #25794: __setattr__ should intern the attribute name
+        class A(object):
+            pass
+
+        def add(self, other):
+            return 'summa'
+
+        type.__setattr__(A, u'__add__', add)
+        self.assertEqual(A() + 1, 'summa')
+
+        type.__delattr__(A, u'__add__')
+        with self.assertRaises(TypeError):
+            A() + 1
+
+    def testSetattrNonStringName(self):
+        class A(object):
+            pass
+
+        with self.assertRaises(TypeError):
+            type.__setattr__(A, bytearray(b'x'), None)
+
 def test_main():
     with test_support.check_py3k_warnings(
             (".+__(get|set|del)slice__ has been removed", DeprecationWarning),
diff --git a/Misc/NEWS b/Misc/NEWS
index 967e861..ebe9d2c 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@
 Core and Builtins
 -----------------
 
+- bpo-25794: Fixed type.__setattr__() and type.__delattr__() for
+  non-interned or unicode attribute names.  Based on patch by Eryk Sun.
+
 - bpo-29935: Fixed error messages in the index() method of tuple and list
   when pass indices of wrong type.
 
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 3494fa9..eb5f5a4 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -2687,6 +2687,7 @@
 static int
 type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
 {
+    int res;
     if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
         PyErr_Format(
             PyExc_TypeError,
@@ -2694,9 +2695,39 @@
             type->tp_name);
         return -1;
     }
-    if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0)
-        return -1;
-    return update_slot(type, name);
+#ifdef Py_USING_UNICODE
+    if (PyUnicode_Check(name)) {
+        name = PyUnicode_AsEncodedString(name, NULL, NULL);
+        if (name == NULL)
+            return -1;
+    }
+    else
+#endif
+        Py_INCREF(name);
+
+    if (PyString_Check(name)) {
+        if (!PyString_CheckExact(name)) {
+            Py_SETREF(name,
+                      PyString_FromStringAndSize(PyString_AS_STRING(name),
+                                                 PyString_GET_SIZE(name))
+            );
+            if (name == NULL)
+                return -1;
+        }
+        PyString_InternInPlace(&name);
+        if (!PyString_CHECK_INTERNED(name)) {
+            PyErr_SetString(PyExc_MemoryError,
+                            "Out of memory interning an attribute name");
+            Py_DECREF(name);
+            return -1;
+        }
+    }
+    res = PyObject_GenericSetAttr((PyObject *)type, name, value);
+    if (res == 0) {
+        res = update_slot(type, name);
+    }
+    Py_DECREF(name);
+    return res;
 }
 
 static void
@@ -6355,7 +6386,7 @@
         /* Slots must be ordered by their offset in the PyHeapTypeObject. */
         assert(!p[1].name || p->offset <= p[1].offset);
         p->name_strobj = PyString_InternFromString(p->name);
-        if (!p->name_strobj)
+        if (!p->name_strobj || !PyString_CHECK_INTERNED(p->name_strobj))
             Py_FatalError("Out of memory interning slotdef names");
     }
     initialized = 1;
@@ -6370,6 +6401,9 @@
     slotdef **pp;
     int offset;
 
+    assert(PyString_CheckExact(name));
+    assert(PyString_CHECK_INTERNED(name));
+
     /* Clear the VALID_VERSION flag of 'type' and all its
        subclasses.  This could possibly be unified with the
        update_subclasses() recursion below, but carefully:
@@ -6380,7 +6414,6 @@
     init_slotdefs();
     pp = ptrs;
     for (p = slotdefs; p->name; p++) {
-        /* XXX assume name is interned! */
         if (p->name_strobj == name)
             *pp++ = p;
     }