Skip to content
6 changes: 6 additions & 0 deletions Doc/c-api/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ Object Protocol
This case can arise from forgetting ``NULL`` checks and would delete the
attribute.

.. versionchanged:: next
Raise an exception if called with ``NULL`` *v* and an an exception set.


.. c:function:: int PyObject_SetAttrString(PyObject *o, const char *attr_name, PyObject *v)

Expand All @@ -223,6 +226,9 @@ Object Protocol
For more details, see :c:func:`PyUnicode_InternFromString`, which may be
used internally to create a key object.

.. versionchanged:: next
Raise an exception if called with ``NULL`` *v* and an an exception set.

.. c:function:: int PyObject_GenericSetAttr(PyObject *o, PyObject *name, PyObject *value)

Generic attribute setter and deleter function that is meant
Expand Down
26 changes: 19 additions & 7 deletions Lib/test/test_capi/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,15 +251,27 @@ def test_object_setattr_null_exc(self):
class Obj:
pass
obj = Obj()

obj.attr = 123
with self.assertRaises(SystemError):
_testcapi.object_setattr_null_exc(obj, 'attr')
self.assertTrue(hasattr(obj, 'attr'))

with self.assertRaises(SystemError):
_testcapi.object_setattrstring_null_exc(obj, 'attr')
self.assertTrue(hasattr(obj, 'attr'))
exc = ValueError("error")
with self.assertRaises(SystemError) as cm:
_testcapi.object_setattr_null_exc(obj, 'attr', exc)
self.assertIs(cm.exception.__context__, exc)
self.assertIsNone(cm.exception.__cause__)
self.assertHasAttr(obj, 'attr')

with self.assertRaises(SystemError) as cm:
_testcapi.object_setattrstring_null_exc(obj, 'attr', exc)
self.assertIs(cm.exception.__context__, exc)
self.assertIsNone(cm.exception.__cause__)
self.assertHasAttr(obj, 'attr')

with self.assertRaises(SystemError) as cm:
# undecodable name
_testcapi.object_setattrstring_null_exc(obj, b'\xff', exc)
self.assertIs(cm.exception.__context__, exc)
self.assertIsNone(cm.exception.__cause__)
self.assertHasAttr(obj, 'attr')


if __name__ == "__main__":
Expand Down
38 changes: 38 additions & 0 deletions Modules/_testcapi/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,42 @@ sequence_fast_get_item(PyObject *self, PyObject *args)
}


static PyObject *
object_setattr_null_exc(PyObject *self, PyObject *args)
{
PyObject *obj, *name, *exc;
if (!PyArg_ParseTuple(args, "OOO", &obj, &name, &exc)) {
return NULL;
}

PyErr_SetObject((PyObject*)Py_TYPE(exc), exc);
if (PyObject_SetAttr(obj, name, NULL) < 0) {
return NULL;
}
assert(PyErr_Occurred());
return NULL;
}


static PyObject *
object_setattrstring_null_exc(PyObject *self, PyObject *args)
{
PyObject *obj, *exc;
const char *name;
Py_ssize_t size;
if (!PyArg_ParseTuple(args, "Oz#O", &obj, &name, &size, &exc)) {
return NULL;
}

PyErr_SetObject((PyObject*)Py_TYPE(exc), exc);
if (PyObject_SetAttrString(obj, name, NULL) < 0) {
return NULL;
}
assert(PyErr_Occurred());
return NULL;
}


static PyMethodDef test_methods[] = {
{"object_getoptionalattr", object_getoptionalattr, METH_VARARGS},
{"object_getoptionalattrstring", object_getoptionalattrstring, METH_VARARGS},
Expand All @@ -191,6 +227,8 @@ static PyMethodDef test_methods[] = {

{"sequence_fast_get_size", sequence_fast_get_size, METH_O},
{"sequence_fast_get_item", sequence_fast_get_item, METH_VARARGS},
{"object_setattr_null_exc", object_setattr_null_exc, METH_VARARGS},
{"object_setattrstring_null_exc", object_setattrstring_null_exc, METH_VARARGS},
{NULL},
};

Expand Down
37 changes: 0 additions & 37 deletions Modules/_testcapi/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -485,41 +485,6 @@ is_uniquely_referenced(PyObject *self, PyObject *op)
}


static PyObject *
object_setattr_null_exc(PyObject *self, PyObject *args)
{
PyObject *obj, *name;
if (!PyArg_ParseTuple(args, "OO", &obj, &name)) {
return NULL;
}

PyErr_SetString(PyExc_ValueError, "error");
if (PyObject_SetAttr(obj, name, NULL) < 0) {
return NULL;
}
assert(PyErr_Occurred());
return NULL;
}


static PyObject *
object_setattrstring_null_exc(PyObject *self, PyObject *args)
{
PyObject *obj;
const char *name;
if (!PyArg_ParseTuple(args, "Os", &obj, &name)) {
return NULL;
}

PyErr_SetString(PyExc_ValueError, "error");
if (PyObject_SetAttrString(obj, name, NULL) < 0) {
return NULL;
}
assert(PyErr_Occurred());
return NULL;
}


static PyMethodDef test_methods[] = {
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
Expand All @@ -546,8 +511,6 @@ static PyMethodDef test_methods[] = {
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
{"clear_managed_dict", clear_managed_dict, METH_O, NULL},
{"is_uniquely_referenced", is_uniquely_referenced, METH_O},
{"object_setattr_null_exc", object_setattr_null_exc, METH_VARARGS},
{"object_setattrstring_null_exc", object_setattrstring_null_exc, METH_VARARGS},
{NULL},
};

Expand Down
Loading