Skip to content

Commit e9f9b04

Browse files
[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 d896985)
1 parent c47c315 commit e9f9b04

File tree

3 files changed

+84
-5
lines changed

3 files changed

+84
-5
lines changed

Lib/test/test_class.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,49 @@ class C:
635635
self.assertRaises(TypeError, type(c).__getattribute__, c, [])
636636
self.assertRaises(TypeError, type(c).__setattr__, c, [], [])
637637

638+
def testSetattrWrapperNameIntern(self):
639+
# Issue #25794: __setattr__ should intern the attribute name
640+
class A(object):
641+
pass
642+
643+
def add(self, other):
644+
return 'summa'
645+
646+
name = ''.join(list('__add__')) # shouldn't be optimized
647+
self.assertIsNot(name, '__add__') # not interned
648+
type.__setattr__(A, name, add)
649+
self.assertEqual(A() + 1, 'summa')
650+
651+
name2 = ''.join(list('__add__'))
652+
self.assertIsNot(name2, '__add__')
653+
self.assertIsNot(name2, name)
654+
type.__delattr__(A, name2)
655+
with self.assertRaises(TypeError):
656+
A() + 1
657+
658+
@test_support.requires_unicode
659+
def testSetattrWrapperNameUnicode(self):
660+
# Issue #25794: __setattr__ should intern the attribute name
661+
class A(object):
662+
pass
663+
664+
def add(self, other):
665+
return 'summa'
666+
667+
type.__setattr__(A, u'__add__', add)
668+
self.assertEqual(A() + 1, 'summa')
669+
670+
type.__delattr__(A, u'__add__')
671+
with self.assertRaises(TypeError):
672+
A() + 1
673+
674+
def testSetattrNonStringName(self):
675+
class A(object):
676+
pass
677+
678+
with self.assertRaises(TypeError):
679+
type.__setattr__(A, bytearray(b'x'), None)
680+
638681
def test_main():
639682
with test_support.check_py3k_warnings(
640683
(".+__(get|set|del)slice__ has been removed", DeprecationWarning),

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 2.7.14?
1010
Core and Builtins
1111
-----------------
1212

13+
- bpo-25794: Fixed type.__setattr__() and type.__delattr__() for
14+
non-interned or unicode attribute names. Based on patch by Eryk Sun.
15+
1316
- bpo-29935: Fixed error messages in the index() method of tuple and list
1417
when pass indices of wrong type.
1518

Objects/typeobject.c

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,16 +2687,47 @@ type_getattro(PyTypeObject *type, PyObject *name)
26872687
static int
26882688
type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
26892689
{
2690+
int res;
26902691
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
26912692
PyErr_Format(
26922693
PyExc_TypeError,
26932694
"can't set attributes of built-in/extension type '%s'",
26942695
type->tp_name);
26952696
return -1;
26962697
}
2697-
if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0)
2698-
return -1;
2699-
return update_slot(type, name);
2698+
#ifdef Py_USING_UNICODE
2699+
if (PyUnicode_Check(name)) {
2700+
name = PyUnicode_AsEncodedString(name, NULL, NULL);
2701+
if (name == NULL)
2702+
return -1;
2703+
}
2704+
else
2705+
#endif
2706+
Py_INCREF(name);
2707+
2708+
if (PyString_Check(name)) {
2709+
if (!PyString_CheckExact(name)) {
2710+
Py_SETREF(name,
2711+
PyString_FromStringAndSize(PyString_AS_STRING(name),
2712+
PyString_GET_SIZE(name))
2713+
);
2714+
if (name == NULL)
2715+
return -1;
2716+
}
2717+
PyString_InternInPlace(&name);
2718+
if (!PyString_CHECK_INTERNED(name)) {
2719+
PyErr_SetString(PyExc_MemoryError,
2720+
"Out of memory interning an attribute name");
2721+
Py_DECREF(name);
2722+
return -1;
2723+
}
2724+
}
2725+
res = PyObject_GenericSetAttr((PyObject *)type, name, value);
2726+
if (res == 0) {
2727+
res = update_slot(type, name);
2728+
}
2729+
Py_DECREF(name);
2730+
return res;
27002731
}
27012732

27022733
static void
@@ -6355,7 +6386,7 @@ init_slotdefs(void)
63556386
/* Slots must be ordered by their offset in the PyHeapTypeObject. */
63566387
assert(!p[1].name || p->offset <= p[1].offset);
63576388
p->name_strobj = PyString_InternFromString(p->name);
6358-
if (!p->name_strobj)
6389+
if (!p->name_strobj || !PyString_CHECK_INTERNED(p->name_strobj))
63596390
Py_FatalError("Out of memory interning slotdef names");
63606391
}
63616392
initialized = 1;
@@ -6370,6 +6401,9 @@ update_slot(PyTypeObject *type, PyObject *name)
63706401
slotdef **pp;
63716402
int offset;
63726403

6404+
assert(PyString_CheckExact(name));
6405+
assert(PyString_CHECK_INTERNED(name));
6406+
63736407
/* Clear the VALID_VERSION flag of 'type' and all its
63746408
subclasses. This could possibly be unified with the
63756409
update_subclasses() recursion below, but carefully:
@@ -6380,7 +6414,6 @@ update_slot(PyTypeObject *type, PyObject *name)
63806414
init_slotdefs();
63816415
pp = ptrs;
63826416
for (p = slotdefs; p->name; p++) {
6383-
/* XXX assume name is interned! */
63846417
if (p->name_strobj == name)
63856418
*pp++ = p;
63866419
}

0 commit comments

Comments
 (0)