8000 gh-135075: Deprecate PyObject_SetAttr(obj, name, NULL) with exc by vstinner · Pull Request #135082 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-135075: Deprecate PyObject_SetAttr(obj, name, NULL) with exc #135082

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Doc/c-api/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ Object Protocol
in favour of using :c:func:`PyObject_DelAttr`, but there are currently no
plans to remove it.

.. deprecated:: next
Calling this function with ``NULL`` *v* and an exception set is now
deprecated.


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

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

.. deprecated:: next
Calling this function with ``NULL`` *v* and an exception set is now
deprecated.


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

Generic attribute setter and deleter function that is meant
Expand Down
4 changes: 3 additions & 1 deletion Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,9 @@ Porting to Python 3.15
Deprecated C APIs
-----------------

* TODO
* Calling :c:func:`PyObject_SetAttr` and :c:func:`PyObject_SetAttrString` with
``NULL`` value and an exception set is now deprecated.
(Contributed by Victor Stinner in :gh:`135075`.)

.. Add C API deprecations above alphabetically, not here at the end.

Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_capi/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,5 +247,23 @@ def func(x):

func(object())

def test_object_setattr_null_exc(self):
class Obj:
pass
obj = Obj()

obj.attr = 123
with self.assertWarns(DeprecationWarning):
with self.assertRaises(ValueError):
_testcapi.object_setattr_null_exc(obj, 'attr')
self.assertFalse(hasattr(obj, 'attr'))

obj.attr = 456
with self.assertWarns(DeprecationWarning):
with self.assertRaises(ValueError):
_testcapi.object_setattrstring_null_exc(obj, 'attr')
self.assertFalse(hasattr(obj, 'attr'))


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Deprecate calling :c:func:`PyObject_SetAttr` and
:c:func:`PyObject_SetAttrString` with ``NULL`` value and an exception set is
now deprecated. Patch by Victor Stinner.
37 changes: 37 additions & 0 deletions Modules/_testcapi/object.c
8000
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,41 @@ 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 @@ -511,6 +546,8 @@ 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
60 changes: 45 additions & 15 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1212,8 +1212,18 @@ PyObject_SetAttrString(PyObject *v, const char *name, PyObject *w)
PyObject *s;
int res;

if (Py_TYPE(v)->tp_setattr != NULL)
if (Py_TYPE(v)->tp_setattr != NULL) {
if (w == NULL && PyErr_Occurred()) {
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 0,
"calling PyObject_SetAttrString() with NULL value "
"and an exception set is deprecated; "
"use PyObject_DelAttrString() instead")) {
return -1;
}
}

return (*Py_TYPE(v)->tp_setattr)(v, (char*)name, w);
}
s = PyUnicode_InternFromString(name);
if (s == NULL)
return -1;
Expand Down Expand Up @@ -1437,7 +1447,7 @@ int
PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
{
PyTypeObject *tp = Py_TYPE(v);
int err;
int res;

if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
Expand All @@ -1447,25 +1457,37 @@ PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
}
Py_INCREF(name);

PyInterpreterState *interp = _PyInterpreterState_GET();
_PyUnicode_InternMortal(interp, &name);
PyThreadState *tstate = _PyThreadState_GET();
PyObject *exc = NULL;
if (value == NULL && _PyErr_Occurred(tstate)) {
exc = _PyErr_GetRaisedException(tstate);
res = PyErr_WarnFormat(PyExc_DeprecationWarning, 0,
"calling PyObject_SetAttr() with NULL value "
"and an exception set is deprecated; "
"use PyObject_DelAttr() instead");
if (res) {
res = -1;
goto done;
}
}

_PyUnicode_InternMortal(tstate->interp, &name);

if (tp->tp_setattro != NULL) {
err = (*tp->tp_setattro)(v, name, value);
Py_DECREF(name);
return err;
res = (*tp->tp_setattro)(v, name, value);
goto done;
}

if (tp->tp_setattr != NULL) {
const char *name_str = PyUnicode_AsUTF8(name);
if (name_str == NULL) {
Py_DECREF(name);
return -1;
res = -1;
goto done;
}
err = (*tp->tp_setattr)(v, (char *)name_str, value);
Py_DECREF(name);
return err;
res = (*tp->tp_setattr)(v, (char *)name_str, value);
goto done;
}
Py_DECREF(name);
_PyObject_ASSERT(name, Py_REFCNT(name) >= 1);

if (tp->tp_getattr == NULL && tp->tp_getattro == NULL)
PyErr_Format(PyExc_TypeError,
"'%.100s' object has no attributes "
Expand All @@ -1480,7 +1502,15 @@ PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
tp->tp_name,
value==NULL ? "del" : "assign to",
name);
return -1;
res = -1;
goto done;

done:
if (exc) {
_PyErr_ChainExceptions1Tstate(tstate, exc);
}
Py_DECREF(name);
return res;
}

int
Expand Down
Loading
0