]> granicus.if.org Git - python/commitdiff
bpo-25794: Fix `type.__setattr__()` for non-interned attribute names. (#1652)
authorSerhiy Storchaka <storchaka@gmail.com>
Sat, 20 May 2017 05:48:06 +0000 (08:48 +0300)
committerGitHub <noreply@github.com>
Sat, 20 May 2017 05:48:06 +0000 (08:48 +0300)
Based on patch by Eryk Sun.

Lib/test/test_class.py
Misc/NEWS
Objects/typeobject.c

index 4d554a397b4a51b307d60aee4baf5aa4a24ff979..ecc01f277954d53a871d8ff7126191080f92d1f8 100644 (file)
@@ -568,5 +568,32 @@ class ClassTests(unittest.TestCase):
         a = A(hash(A.f)^(-1))
         hash(a.f)
 
+    def testSetattrWrapperNameIntern(self):
+        # Issue #25794: __setattr__ should intern the attribute name
+        class A:
+            pass
+
+        def add(self, other):
+            return 'summa'
+
+        name = str(b'__add__', 'ascii')  # shouldn't be optimized
+        self.assertIsNot(name, '__add__')  # not interned
+        type.__setattr__(A, name, add)
+        self.assertEqual(A() + 1, 'summa')
+
+        name2 = str(b'__add__', 'ascii')
+        self.assertIsNot(name2, '__add__')
+        self.assertIsNot(name2, name)
+        type.__delattr__(A, name2)
+        with self.assertRaises(TypeError):
+            A() + 1
+
+    def testSetattrNonStringName(self):
+        class A:
+            pass
+
+        with self.assertRaises(TypeError):
+            type.__setattr__(A, b'x', None)
+
 if __name__ == '__main__':
     unittest.main()
index c6aed7f48c8dcb380fef453c073e78f5c9a2fb7c..9aa982ef62c41be755ec709991dc5df1b311202b 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@ What's New in Python 3.7.0 alpha 1?
 Core and Builtins
 -----------------
 
+- bpo-25794: Fixed type.__setattr__() and type.__delattr__() for
+  non-interned attribute names.  Based on patch by Eryk Sun.
+
 - bpo-30039: If a KeyboardInterrupt happens when the interpreter is in
   the middle of resuming a chain of nested 'yield from' or 'await'
   calls, it's now correctly delivered to the innermost frame.
index 121d66d8bcabccaa552a15d2a8f9cdea6f681cfc..224fe6199fca0ddf58085ca65901edeec60751de 100644 (file)
@@ -3138,10 +3138,35 @@ type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
             type->tp_name);
         return -1;
     }
-    if (_PyObject_GenericSetAttrWithDict((PyObject *)type, name, value, NULL) < 0)
-        return -1;
-    res = update_slot(type, name);
-    assert(_PyType_CheckConsistency(type));
+    if (PyUnicode_Check(name)) {
+        if (PyUnicode_CheckExact(name)) {
+            if (PyUnicode_READY(name) == -1)
+                return -1;
+            Py_INCREF(name);
+        }
+        else {
+            name = _PyUnicode_Copy(name);
+            if (name == NULL)
+                return -1;
+        }
+        PyUnicode_InternInPlace(&name);
+        if (!PyUnicode_CHECK_INTERNED(name)) {
+            PyErr_SetString(PyExc_MemoryError,
+                            "Out of memory interning an attribute name");
+            Py_DECREF(name);
+            return -1;
+        }
+    }
+    else {
+        /* Will fail in _PyObject_GenericSetAttrWithDict. */
+        Py_INCREF(name);
+    }
+    res = _PyObject_GenericSetAttrWithDict((PyObject *)type, name, value, NULL);
+    if (res == 0) {
+        res = update_slot(type, name);
+        assert(_PyType_CheckConsistency(type));
+    }
+    Py_DECREF(name);
     return res;
 }
 
@@ -7065,7 +7090,7 @@ init_slotdefs(void)
         /* Slots must be ordered by their offset in the PyHeapTypeObject. */
         assert(!p[1].name || p->offset <= p[1].offset);
         p->name_strobj = PyUnicode_InternFromString(p->name);
-        if (!p->name_strobj)
+        if (!p->name_strobj || !PyUnicode_CHECK_INTERNED(p->name_strobj))
             Py_FatalError("Out of memory interning slotdef names");
     }
     slotdefs_initialized = 1;
@@ -7090,6 +7115,9 @@ update_slot(PyTypeObject *type, PyObject *name)
     slotdef **pp;
     int offset;
 
+    assert(PyUnicode_CheckExact(name));
+    assert(PyUnicode_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:
@@ -7100,7 +7128,6 @@ update_slot(PyTypeObject *type, PyObject *name)
     init_slotdefs();
     pp = ptrs;
     for (p = slotdefs; p->name; p++) {
-        /* XXX assume name is interned! */
         if (p->name_strobj == name)
             *pp++ = p;
     }