8000 gh-111178: fix UBSan failures in `Objects/exceptions.c` by picnixz · Pull Request #128154 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-111178: fix UBSan failures in Objects/exceptions.c #128154

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

Merged
merged 25 commits into from
Feb 17, 2025
Merged
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2683a25
fix UBSan failures for `PyBaseExceptionObject`
picnixz Dec 21, 2024
3f1196b
fix UBSan failures for `PyStopIterationObject`
picnixz Dec 21, 2024
1886b07
fix UBSan failures for `PySystemExitObject`
picnixz Dec 21, 2024
3df93a5
fix UBSan failures for `PyImportErrorObject`
picnixz Dec 21, 2024
96f97a5
fix UBSan failures for `PyOSErrorObject`
picnixz Dec 21, 2024
09514a9
fix UBSan failures for `PyNameErrorObject`
picnixz Dec 21, 2024
9d3c8b8
fix UBSan failures for `PyAttributeErrorObject`
picnixz Dec 21, 2024
2640a34
fix UBSan failures for `PySyntaxErrorObject`
picnixz Dec 21, 2024
150f34e
fix UBSan failures for `KeyError`
picnixz Dec 21, 2024
11d9e31
remove un-necessary casts for `UnicodeError*`
picnixz Dec 21, 2024
96430d9
remove un-necessary casts for `MemoryError`
picnixz Dec 21, 2024
0437291
fix UBSan failures for `PyBaseExceptionGroupObject`
picnixz Dec 21, 2024
d17a9b4
unify naming for cast functions
picnixz Dec 21, 2024
0d2434e
Merge remote-tracking branch 'upstream/main' into fix/ubsan/exception…
picnixz Jan 14, 2025
793196d
fixup
picnixz Jan 14, 2025
6abf1d8
fixup
picnixz Jan 14, 2025
6c2ef08
align naming convention
picnixz Jan 14, 2025
96f2d2d
remove redundant casts
picnixz Jan 16, 2025
0cd116b
Merge branch 'main' into fix/ubsan/exceptions-111178
picnixz Jan 18, 2025
248f195
Merge remote-tracking branch 'upstream/main' into fix/ubsan/exception…
picnixz Jan 25, 2025
e4daf11
remove un-necessary cast
picnixz Feb 6, 2025
565edab
Merge branch 'main' into fix/ubsan/exceptions-111178
picnixz Feb 7, 2025
122c914
Do not use `_` + capital letter in cast macros as it is also UB.
picnixz Feb 8, 2025
7906e3b
Do not use `_` + capital letter in cast macros as it is also UB.
picnixz Feb 8, 2025
8862b83
Merge branch 'main' into fix/ubsan/exceptions-111178
picnixz Feb 8, 2025
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
Next Next commit
fix UBSan failures for PyBaseExceptionObject
  • Loading branch information
picnixz committed Dec 21, 2024
commit 2683a2512c389bbc13f2854cd6982efd0d043da7
116 changes: 64 additions & 52 deletions Objects/exceptions.c
10000
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ get_exc_state(void)
* Lib/test/exception_hierarchy.txt
*/

static inline PyBaseExceptionObject *
_PyBaseExceptionObject_CAST(PyObject *exc)
{
assert(PyExceptionInstance_Check(exc));
return (PyBaseExceptionObject *)exc;
}

/*
* BaseException
*/
Expand Down Expand Up @@ -69,8 +76,9 @@ BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
}

static int
BaseException_init(PyBaseExceptionObject *self, PyObject *args, PyObject *kwds)
BaseException_init(PyObject *op, PyObject *args, PyObject *kwds)
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds))
return -1;

Expand Down Expand Up @@ -113,8 +121,9 @@ BaseException_vectorcall(PyObject *type_obj, PyObject * const*args,


static int
BaseException_clear(PyBaseExceptionObject *self)
BaseException_clear(PyObject *op)
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
Py_CLEAR(self->dict);
Py_CLEAR(self->args);
Py_CLEAR(self->notes);
Expand All @@ -125,21 +134,23 @@ BaseException_clear(PyBaseExceptionObject *self)
}

static void
BaseException_dealloc(PyBaseExceptionObject *self)
BaseException_dealloc(PyObject *op)
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
PyObject_GC_UnTrack(self);
// bpo-44348: The trashcan mechanism prevents stack overflow when deleting
// long chains of exceptions. For example, exceptions can be chained
// through the __context__ attributes or the __traceback__ attribute.
Py_TRASHCAN_BEGIN(self, BaseException_dealloc)
BaseException_clear(self);
Py_TYPE(self)->tp_free((PyObject *)self);
(void)BaseException_clear(op);
Py_TYPE(self)->tp_free(self);
Py_TRASHCAN_END
}

static int
BaseException_traverse(PyBaseExceptionObject *self, visitproc visit, void *arg)
BaseException_traverse(PyObject *op, visitproc visit, void *arg)
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
Py_VISIT(self->dict);
Py_VISIT(self->args);
Py_VISIT(self->notes);
Expand All @@ -150,8 +161,9 @@ BaseException_traverse(PyBaseExceptionObject *self, visitproc visit, void *arg)
}

static PyObject *
BaseException_str(PyBaseExceptionObject *self)
BaseException_str(PyObject *op)
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
switch (PyTuple_GET_SIZE(self->args)) {
case 0:
return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
Expand All @@ -163,8 +175,9 @@ BaseException_str(PyBaseExceptionObject *self)
}

static PyObject *
BaseException_repr(PyBaseExceptionObject *self)
BaseException_repr(PyObject *op)
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
const char *name = _PyType_Name(Py_TYPE(self));
if (PyTuple_GET_SIZE(self->args) == 1)
return PyUnicode_FromFormat("%s(%R)", name,
Expand All @@ -175,8 +188,9 @@ BaseException_repr(PyBaseExceptionObject *self)

/* Pickling support */
static PyObject *
BaseException_reduce(PyBaseExceptionObject *self, PyObject *Py_UNUSED(ignored))
BaseException_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
if (self->args && self->dict)
return PyTuple_Pack(3, Py_TYPE(self), self->args, self->dict);
else
Expand Down Expand Up @@ -225,13 +239,6 @@ PyDoc_STRVAR(with_traceback_doc,
"Exception.with_traceback(tb) --\n\
set self.__traceback__ to tb and return self.");

static inline PyBaseExceptionObject*
_PyBaseExceptionObject_cast(PyObject *exc)
{
assert(PyExceptionInstance_Check(exc));
return (PyBaseExceptionObject *)exc;
}

static PyObject *
BaseException_add_note(PyObject *self, PyObject *note)
{
Expand Down Expand Up @@ -274,26 +281,26 @@ PyDoc_STRVAR(add_note_doc,
add a note to the exception");

static PyMethodDef BaseException_methods[] = {
{"__reduce__", (PyCFunction)BaseException_reduce, METH_NOARGS },
{"__setstate__", (PyCFunction)BaseException_setstate, METH_O },
{"with_traceback", (PyCFunction)BaseException_with_traceback, METH_O,
{"__reduce__", BaseException_reduce, METH_NOARGS},
{"__setstate__", BaseException_setstate, METH_O},
{"with_traceback", BaseException_with_traceback, METH_O,
with_traceback_doc},
{"add_note", (PyCFunction)BaseException_add_note, METH_O,
add_note_doc},
{"add_note", BaseException_add_note, METH_O, add_note_doc},
{NULL, NULL, 0, NULL},
};

static PyObject *
BaseException_get_args(PyBaseExceptionObject *self, void *Py_UNUSED(ignored))
BaseException_get_args(PyObject *op, void *Py_UNUSED(ignored))
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
if (self->args == NULL) {
Py_RETURN_NONE;
}
return Py_NewRef(self->args);
}

static int
BaseException_set_args(PyBaseExceptionObject *self, PyObject *val, void *Py_UNUSED(ignored))
BaseException_set_args(PyObject *op, PyObject *val, void *Py_UNUSED(ignored))
{
PyObject *seq;
if (val == NULL) {
Expand All @@ -303,22 +310,25 @@ BaseException_set_args(PyBaseExceptionObject *self, PyObject *val, void *Py_UNUS
seq = PySequence_Tuple(val);
if (!seq)
return -1;
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
Py_XSETREF(self->args, seq);
return 0;
}

static PyObject *
BaseException_get_tb(PyBaseExceptionObject *self, void *Py_UNUSED(ignored))
BaseException_get_tb(PyObject *op, void *Py_UNUSED(ignored))
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
if (self->traceback == NULL) {
Py_RETURN_NONE;
}
return Py_NewRef(self->traceback);
}

static int
BaseException_set_tb(PyBaseExceptionObject *self, PyObject *tb, void *Py_UNUSED(ignored))
BaseException_set_tb(PyObject *op, PyObject *tb, void *Py_UNUSED(ignored))
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_CAST(op);
if (tb == NULL) {
PyErr_SetString(PyExc_TypeError, "__traceback__ may not be deleted");
return -1;
Expand Down Expand Up @@ -398,8 +408,8 @@ BaseException_set_cause(PyObject *self, PyObject *arg, void *Py_UNUSED(ignored))

static PyGetSetDef BaseException_getset[] = {
{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},
{"args", (getter)BaseException_get_args, (setter)BaseException_set_args},
{"__traceback__", (getter)BaseException_get_tb, (setter)BaseException_set_tb},
{"args", BaseException_get_args, BaseException_set_args},
{"__traceback__", BaseException_get_tb, BaseException_set_tb},
{"__context__", BaseException_get_context,
BaseException_set_context, PyDoc_STR("exception context")},
{"__cause__", BaseException_get_cause,
Expand All @@ -411,59 +421,61 @@ static PyGetSetDef BaseException_getset[] = {
PyObject *
PyException_GetTraceback(PyObject *self)
{
PyBaseExceptionObject *base_self = _PyBaseExceptionObject_cast(self);
return Py_XNewRef(base_self->traceback);
PyBaseExceptionObject *exc = _PyBaseExceptionObject_CAST(self);
return Py_XNewRef(exc->traceback);
}


int
PyException_SetTraceback(PyObject *self, PyObject *tb)
{
return BaseException_set_tb(_PyBaseExceptionObject_cast(self), tb, NULL);
return BaseException_set_tb(self, tb, NULL);
}

PyObject *
PyException_GetCause(PyObject *self)
{
PyObject *cause = _PyBaseExceptionObject_cast(self)->cause;
return Py_XNewRef(cause);
PyBaseExceptionObject *exc = _PyBaseExceptionObject_CAST(self);
return Py_XNewRef(exc->cause);
}

/* Steals a reference to cause */
void
PyException_SetCause(PyObject *self, PyObject *cause)
{
PyBaseExceptionObject *base_self = _PyBaseExceptionObject_cast(self);
base_self->suppress_context = 1;
Py_XSETREF(base_self->cause, cause);
PyBaseExceptionObject *exc = _PyBaseExceptionObject_CAST(self);
exc->suppress_context = 1;
Py_XSETREF(exc->cause, cause);
}

PyObject *
PyException_GetContext(PyObject *self)
{
PyObject *context = _PyBaseExceptionObject_cast(self)->context;
return Py_XNewRef(context);
PyBaseExceptionObject *exc = _PyBaseExceptionObject_CAST(self);
return Py_XNewRef(exc->context);
}

/* Steals a reference to context */
void
PyException_SetContext(PyObject *self, PyObject *context)
{
Py_XSETREF(_PyBaseExceptionObject_cast(self)->context, context);
PyBaseExceptionObject *exc = _PyBaseExceptionObject_CAST(self);
Py_XSETREF(exc->context, context);
}

PyObject *
PyException_GetArgs(PyObject *self)
{
PyObject *args = _PyBaseExceptionObject_cast(self)->args;
return Py_NewRef(args);
PyBaseExceptionObject *exc = _PyBaseExceptionObject_CAST(self);
return Py_NewRef(exc->args);
}

void
PyException_SetArgs(PyObject *self, PyObject *args)
{
Py_INCREF(args);
Py_XSETREF(_PyBaseExceptionObject_cast(self)->args, args);
PyBaseExceptionObject *exc = _PyBaseExceptionObject_CAST(self);
Py_XSETREF(exc->args, args);
}

const char *
Expand All @@ -485,26 +497,26 @@ static PyTypeObject _PyExc_BaseException = {
"BaseException", /*tp_name*/
sizeof(PyBaseExceptionObject), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)BaseException_dealloc, /*tp_dealloc*/
BaseException_dealloc, /*tp_dealloc*/
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
(reprfunc)BaseException_repr, /*tp_repr*/
BaseException_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
(reprfunc)BaseException_str, /*tp_str*/
BaseException_str, /*tp_str*/
PyObject_GenericGetAttr, /*tp_getattro*/
PyObject_GenericSetAttr, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASE_EXC_SUBCLASS, /*tp_flags*/
PyDoc_STR("Common base class for all exceptions"), /* tp_doc */
(traverseproc)BaseException_traverse, /* tp_traverse */
(inquiry)BaseException_clear, /* tp_clear */
BaseException_traverse, /* tp_traverse */
BaseException_clear, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
Expand All @@ -517,7 +529,7 @@ static PyTypeObject _PyExc_BaseException = {
0, /* tp_descr_get */
0, /* tp_descr_set */
offsetof(PyBaseExceptionObject, dict), /* tp_dictoffset */
(initproc)BaseException_init, /* tp_init */
BaseException_init, /* tp_init */
0, /* tp_alloc */
BaseException_new, /* tp_new */
.tp_vectorcall = BaseException_vectorcall,
Expand All @@ -535,13 +547,13 @@ static PyTypeObject _PyExc_ ## EXCNAME = { \
PyVarObject_HEAD_INIT(NULL, 0) \
# EXCNAME, \
sizeof(PyBaseExceptionObject), \
0, (destructor)BaseException_dealloc, 0, 0, 0, 0, 0, 0, 0, \
0, BaseException_dealloc, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, \
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, \
PyDoc_STR(EXCDOC), (traverseproc)BaseException_traverse, \
(inquiry)BaseException_clear, 0, 0, 0, 0, 0, 0, 0, &_ ## EXCBASE, \
PyDoc_STR(EXCDOC), BaseException_traverse, \
BaseException_clear, 0, 0, 0, 0, 0, 0, 0, &_ ## EXCBASE, \
0, 0, 0, offsetof(PyBaseExceptionObject, dict), \
(initproc)BaseException_init, 0, BaseException_new,\
BaseException_init, 0, BaseException_new,\
Comment on lines +668 to +674
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you're touching these, (introducing conflicts with any in-flight PR, and adding yourself to git blame), could you switch to named initializers?
Feel free to do it in a follow-up PR if you want to merge this one soon.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've said it in another PR, but Serhiy asked for a discussion on DPO (which never happened I think) in #127679 and you asked for it to be included in PEP-7 first so I left those out (especially for static types like these; I think I touched some heap types and internal ones never exposed, but I don't think I touched something that was publicly exposed).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to do it in a follow-up PR if you want to merge this one soon.

For UBSan failures, I expect them to be merged whenever it's fine to merge them. I just have a bunch of branches that I ignore when I do git branch (otherwise I just have too many of them locally). And I don't mind solving conflicts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For changing them all at once, it IMO should definitely go in PEP-7. In the usual case there's nothing wrong with the "old" syntax, and we generally avoid style-only changes.

In this case though, the style is a lot less readable than usual -- it's missing the comment column, and it has more items per line. Since you are touching this anyway (so backports will conflict, etc.), this is the time to clean it up.

I'll merge this PR; feel free to follow up.

}; \
PyObject *PyExc_ ## EXCNAME = (PyObject *)&_PyExc_ ## EXCNAME

Expand Down Expand Up @@ -1378,7 +1390,7 @@ _PyExc_PrepReraiseStar(PyObject *orig, PyObject *excs)
{
/* orig must be a raised & caught exception, so it has a traceback */
assert(PyExceptionInstance_Check(orig));
assert(_PyBaseExceptionObject_cast(orig)->traceback != NULL);
assert(_PyBaseExceptionObject_CAST(orig)->traceback != NULL);

assert(PyList_Check(excs));

Expand Down
0