mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00
gh-135075: Make PyObject_SetAttr() fail with NULL value and exception (#136180)
Some checks are pending
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Docs (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Undefined behavior sanitizer (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
Some checks are pending
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Docs (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Undefined behavior sanitizer (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
Make PyObject_SetAttr() and PyObject_SetAttrString() fail if called with NULL value and an exception set.
This commit is contained in:
parent
b2e498ac26
commit
da79ac9d26
5 changed files with 111 additions and 11 deletions
|
@ -197,6 +197,13 @@ Object Protocol
|
||||||
in favour of using :c:func:`PyObject_DelAttr`, but there are currently no
|
in favour of using :c:func:`PyObject_DelAttr`, but there are currently no
|
||||||
plans to remove it.
|
plans to remove it.
|
||||||
|
|
||||||
|
The function must not be called with ``NULL`` *v* and an an exception set.
|
||||||
|
This case can arise from forgetting ``NULL`` checks and would delete the
|
||||||
|
attribute.
|
||||||
|
|
||||||
|
.. versionchanged:: next
|
||||||
|
Must not be called with NULL value if an exception is set.
|
||||||
|
|
||||||
|
|
||||||
.. c:function:: int PyObject_SetAttrString(PyObject *o, const char *attr_name, PyObject *v)
|
.. c:function:: int PyObject_SetAttrString(PyObject *o, const char *attr_name, PyObject *v)
|
||||||
|
|
||||||
|
@ -207,6 +214,10 @@ Object Protocol
|
||||||
If *v* is ``NULL``, the attribute is deleted, but this feature is
|
If *v* is ``NULL``, the attribute is deleted, but this feature is
|
||||||
deprecated in favour of using :c:func:`PyObject_DelAttrString`.
|
deprecated in favour of using :c:func:`PyObject_DelAttrString`.
|
||||||
|
|
||||||
|
The function must not be called with ``NULL`` *v* and an an exception set.
|
||||||
|
This case can arise from forgetting ``NULL`` checks and would delete the
|
||||||
|
attribute.
|
||||||
|
|
||||||
The number of different attribute names passed to this function
|
The number of different attribute names passed to this function
|
||||||
should be kept small, usually by using a statically allocated string
|
should be kept small, usually by using a statically allocated string
|
||||||
as *attr_name*.
|
as *attr_name*.
|
||||||
|
@ -215,6 +226,10 @@ Object Protocol
|
||||||
For more details, see :c:func:`PyUnicode_InternFromString`, which may be
|
For more details, see :c:func:`PyUnicode_InternFromString`, which may be
|
||||||
used internally to create a key object.
|
used internally to create a key object.
|
||||||
|
|
||||||
|
.. versionchanged:: next
|
||||||
|
Must not be called with NULL value if an exception is set.
|
||||||
|
|
||||||
|
|
||||||
.. c:function:: int PyObject_GenericSetAttr(PyObject *o, PyObject *name, PyObject *value)
|
.. c:function:: int PyObject_GenericSetAttr(PyObject *o, PyObject *name, PyObject *value)
|
||||||
|
|
||||||
Generic attribute setter and deleter function that is meant
|
Generic attribute setter and deleter function that is meant
|
||||||
|
|
|
@ -1077,6 +1077,31 @@ class CAPITest(unittest.TestCase):
|
||||||
with self.assertRaisesRegex(TypeError, regex):
|
with self.assertRaisesRegex(TypeError, regex):
|
||||||
PyIter_NextItem(10)
|
PyIter_NextItem(10)
|
||||||
|
|
||||||
|
def test_object_setattr_null_exc(self):
|
||||||
|
class Obj:
|
||||||
|
pass
|
||||||
|
obj = Obj()
|
||||||
|
obj.attr = 123
|
||||||
|
|
||||||
|
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__)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Make :c:func:`PyObject_SetAttr` and :c:func:`PyObject_SetAttrString` fail if
|
||||||
|
called with ``NULL`` value and an exception set. Patch by Victor Stinner.
|
|
@ -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[] = {
|
static PyMethodDef test_methods[] = {
|
||||||
{"object_getoptionalattr", object_getoptionalattr, METH_VARARGS},
|
{"object_getoptionalattr", object_getoptionalattr, METH_VARARGS},
|
||||||
{"object_getoptionalattrstring", object_getoptionalattrstring, METH_VARARGS},
|
{"object_getoptionalattrstring", object_getoptionalattrstring, METH_VARARGS},
|
||||||
|
@ -191,6 +227,8 @@ static PyMethodDef test_methods[] = {
|
||||||
|
|
||||||
{"sequence_fast_get_size", sequence_fast_get_size, METH_O},
|
{"sequence_fast_get_size", sequence_fast_get_size, METH_O},
|
||||||
{"sequence_fast_get_item", sequence_fast_get_item, METH_VARARGS},
|
{"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},
|
{NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1213,16 +1213,27 @@ PyObject_HasAttrString(PyObject *obj, const char *name)
|
||||||
int
|
int
|
||||||
PyObject_SetAttrString(PyObject *v, const char *name, PyObject *w)
|
PyObject_SetAttrString(PyObject *v, const char *name, PyObject *w)
|
||||||
{
|
{
|
||||||
PyObject *s;
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
int res;
|
if (w == NULL && _PyErr_Occurred(tstate)) {
|
||||||
|
PyObject *exc = _PyErr_GetRaisedException(tstate);
|
||||||
if (Py_TYPE(v)->tp_setattr != NULL)
|
_PyErr_SetString(tstate, PyExc_SystemError,
|
||||||
return (*Py_TYPE(v)->tp_setattr)(v, (char*)name, w);
|
"PyObject_SetAttrString() must not be called with NULL value "
|
||||||
s = PyUnicode_InternFromString(name);
|
"and an exception set");
|
||||||
if (s == NULL)
|
_PyErr_ChainExceptions1Tstate(tstate, exc);
|
||||||
return -1;
|
return -1;
|
||||||
res = PyObject_SetAttr(v, s, w);
|
}
|
||||||
Py_XDECREF(s);
|
|
||||||
|
if (Py_TYPE(v)->tp_setattr != NULL) {
|
||||||
|
return (*Py_TYPE(v)->tp_setattr)(v, (char*)name, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *s = PyUnicode_InternFromString(name);
|
||||||
|
if (s == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = PyObject_SetAttr(v, s, w);
|
||||||
|
Py_DECREF(s);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1440,6 +1451,16 @@ PyObject_HasAttr(PyObject *obj, PyObject *name)
|
||||||
int
|
int
|
||||||
PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
|
PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
|
||||||
{
|
{
|
||||||
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
|
if (value == NULL && _PyErr_Occurred(tstate)) {
|
||||||
|
PyObject *exc = _PyErr_GetRaisedException(tstate);
|
||||||
|
_PyErr_SetString(tstate, PyExc_SystemError,
|
||||||
|
"PyObject_SetAttr() must not be called with NULL value "
|
||||||
|
"and an exception set");
|
||||||
|
_PyErr_ChainExceptions1Tstate(tstate, exc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
PyTypeObject *tp = Py_TYPE(v);
|
PyTypeObject *tp = Py_TYPE(v);
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
@ -1451,8 +1472,7 @@ PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
|
||||||
}
|
}
|
||||||
Py_INCREF(name);
|
Py_INCREF(name);
|
||||||
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
_PyUnicode_InternMortal(tstate->interp, &name);
|
||||||
_PyUnicode_InternMortal(interp, &name);
|
|
||||||
if (tp->tp_setattro != NULL) {
|
if (tp->tp_setattro != NULL) {
|
||||||
err = (*tp->tp_setattro)(v, name, value);
|
err = (*tp->tp_setattro)(v, name, value);
|
||||||
Py_DECREF(name);
|
Py_DECREF(name);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue