Skip to content

Commit 21b0f3f

Browse files
committed
gh-135075: Deprecate PyObject_SetAttr(obj, name, NULL) with exc
Deprecate calling PyObject_SetAttr() and PyObject_SetAttrString() with NULL value and an exception set.
1 parent 3612d8f commit 21b0f3f

File tree

6 files changed

+115
-16
lines changed

6 files changed

+115
-16
lines changed

Doc/c-api/object.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ Object Protocol
197197
in favour of using :c:func:`PyObject_DelAttr`, but there are currently no
198198
plans to remove it.
199199
200+
.. deprecated:: next
201+
Calling this function with ``NULL`` *v* and an exception set is now
202+
deprecated.
203+
200204
201205
.. c:function:: int PyObject_SetAttrString(PyObject *o, const char *attr_name, PyObject *v)
202206
@@ -215,6 +219,11 @@ Object Protocol
215219
For more details, see :c:func:`PyUnicode_InternFromString`, which may be
216220
used internally to create a key object.
217221
222+
.. deprecated:: next
223+
Calling this function with ``NULL`` *v* and an exception set is now
224+
deprecated.
225+
226+
218227
.. c:function:: int PyObject_GenericSetAttr(PyObject *o, PyObject *name, PyObject *value)
219228
220229
Generic attribute setter and deleter function that is meant

Doc/whatsnew/3.15.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,9 @@ Porting to Python 3.15
322322
Deprecated C APIs
323323
-----------------
324324

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

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

Lib/test/test_capi/test_object.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,5 +247,23 @@ def func(x):
247247

248248
func(object())
249249

250+
def test_object_setattr_null_exc(self):
251+
class Obj:
252+
pass
253+
obj = Obj()
254+
255+
obj.attr = 123
256+
with self.assertWarns(DeprecationWarning):
257+
with self.assertRaises(ValueError):
258+
_testcapi.object_setattr_null_exc(obj, 'attr')
259+
self.assertFalse(hasattr(obj, 'attr'))
260+
261+
obj.attr = 456
262+
with self.assertWarns(DeprecationWarning):
263+
with self.assertRaises(ValueError):
264+
_testcapi.object_setattrstring_null_exc(obj, 'attr')
265+
self.assertFalse(hasattr(obj, 'attr'))
266+
267+
250268
if __name__ == "__main__":
251269
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Deprecate calling :c:func:`PyObject_SetAttr` and
2+
:c:func:`PyObject_SetAttrString` with ``NULL`` value and an exception set is
3+
now deprecated. Patch by Victor Stinner.

Modules/_testcapi/object.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,41 @@ is_uniquely_referenced(PyObject *self, PyObject *op)
485485
}
486486

487487

488+
static PyObject *
489+
object_setattr_null_exc(PyObject *self, PyObject *args)
490+
{
491+
PyObject *obj, *name;
492+
if (!PyArg_ParseTuple(args, "OO", &obj, &name)) {
493+
return NULL;
494+
}
495+
496+
PyErr_SetString(PyExc_ValueError, "error");
497+
if (PyObject_SetAttr(obj, name, NULL) < 0) {
498+
return NULL;
499+
}
500+
assert(PyErr_Occurred());
501+
return NULL;
502+
}
503+
504+
505+
static PyObject *
506+
object_setattrstring_null_exc(PyObject *self, PyObject *args)
507+
{
508+
PyObject *obj;
509+
const char *name;
510+
if (!PyArg_ParseTuple(args, "Os", &obj, &name)) {
511+
return NULL;
512+
}
513+
514+
PyErr_SetString(PyExc_ValueError, "error");
515+
if (PyObject_SetAttrString(obj, name, NULL) < 0) {
516+
return NULL;
517+
}
518+
assert(PyErr_Occurred());
519+
return NULL;
520+
}
521+
522+
488523
static PyMethodDef test_methods[] = {
489524
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
490525
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
@@ -511,6 +546,8 @@ static PyMethodDef test_methods[] = {
511546
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
512547
{"clear_managed_dict", clear_managed_dict, METH_O, NULL},
513548
{"is_uniquely_referenced", is_uniquely_referenced, METH_O},
549+
{"object_setattr_null_exc", object_setattr_null_exc, METH_VARARGS},
550+
{"object_setattrstring_null_exc", object_setattrstring_null_exc, METH_VARARGS},
514551
{NULL},
515552
};
516553

Objects/object.c

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,8 +1212,18 @@ PyObject_SetAttrString(PyObject *v, const char *name, PyObject *w)
12121212
PyObject *s;
12131213
int res;
12141214

1215-
if (Py_TYPE(v)->tp_setattr != NULL)
1215+
if (Py_TYPE(v)->tp_setattr != NULL) {
1216+
if (w == NULL && PyErr_Occurred()) {
1217+
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 0,
1218+
"calling PyObject_SetAttrString() with NULL value "
1219+
"and an exception set is deprecated; "
1220+
"use PyObject_DelAttrString() instead")) {
1221+
return -1;
1222+
}
1223+
}
1224+
12161225
return (*Py_TYPE(v)->tp_setattr)(v, (char*)name, w);
1226+
}
12171227
s = PyUnicode_InternFromString(name);
12181228
if (s == NULL)
12191229
return -1;
@@ -1437,7 +1447,7 @@ int
14371447
PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
14381448
{
14391449
PyTypeObject *tp = Py_TYPE(v);
1440-
int err;
1450+
int res;
14411451

14421452
if (!PyUnicode_Check(name)) {
14431453
PyErr_Format(PyExc_TypeError,
@@ -1447,25 +1457,37 @@ PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
14471457
}
14481458
Py_INCREF(name);
14491459

1450-
PyInterpreterState *interp = _PyInterpreterState_GET();
1451-
_PyUnicode_InternMortal(interp, &name);
1460+
PyThreadState *tstate = _PyThreadState_GET();
1461+
PyObject *exc = NULL;
1462+
if (value == NULL && _PyErr_Occurred(tstate)) {
1463+
exc = _PyErr_GetRaisedException(tstate);
1464+
res = PyErr_WarnFormat(PyExc_DeprecationWarning, 0,
1465+
"calling PyObject_SetAttr() with NULL value "
1466+
"and an exception set is deprecated; "
1467+
"use PyObject_DelAttr() instead");
1468+
if (res) {
1469+
res = -1;
1470+
goto done;
1471+
}
1472+
}
1473+
1474+
_PyUnicode_InternMortal(tstate->interp, &name);
1475+
14521476
if (tp->tp_setattro != NULL) {
1453-
err = (*tp->tp_setattro)(v, name, value);
1454-
Py_DECREF(name);
1455-
return err;
1477+
res = (*tp->tp_setattro)(v, name, value);
1478+
goto done;
14561479
}
1480+
14571481
if (tp->tp_setattr != NULL) {
14581482
const char *name_str = PyUnicode_AsUTF8(name);
14591483
if (name_str == NULL) {
1460-
Py_DECREF(name);
1461-
return -1;
1484+
res = -1;
1485+
goto done;
14621486
}
1463-
err = (*tp->tp_setattr)(v, (char *)name_str, value);
1464-
Py_DECREF(name);
1465-
return err;
1487+
res = (*tp->tp_setattr)(v, (char *)name_str, value);
1488+
goto done;
14661489
}
1467-
Py_DECREF(name);
1468-
_PyObject_ASSERT(name, Py_REFCNT(name) >= 1);
1490+
14691491
if (tp->tp_getattr == NULL && tp->tp_getattro == NULL)
14701492
PyErr_Format(PyExc_TypeError,
14711493
"'%.100s' object has no attributes "
@@ -1480,7 +1502,15 @@ PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
14801502
tp->tp_name,
14811503
value==NULL ? "del" : "assign to",
14821504
name);
1483-
return -1;
1505+
res = -1;
1506+
goto done;
1507+
1508+
done:
1509+
if (exc) {
1510+
_PyErr_ChainExceptions1Tstate(tstate, exc);
1511+
}
1512+
Py_DECREF(name);
1513+
return res;
14841514
}
14851515

14861516
int

0 commit comments

Comments
 (0)