diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h index 1023dbc3395b2f..e38472ab13a9df 100644 --- a/Include/internal/pycore_pyerrors.h +++ b/Include/internal/pycore_pyerrors.h @@ -29,7 +29,8 @@ PyAPI_FUNC(PyObject*) _PyErr_FormatFromCause( ... ); -extern int _PyException_AddNote( +// Export for 'pyexpat' shared extension. +PyAPI_FUNC(int) _PyException_AddNote( PyObject *exc, PyObject *note); diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index cace780f79f515..aaa91aca36e3c4 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -510,6 +510,34 @@ def _test_exception(self, have_source): self.assertIn('call_with_frame("StartElement"', entries[1].line) + def test_invalid_NotStandalone(self): + parser = expat.ParserCreate() + parser.NotStandaloneHandler = mock.Mock(return_value="bad value") + parser.ElementDeclHandler = lambda _1, _2: None + + payload = b"""\ +]> +""" + with self.assertRaises(TypeError) as cm: + parser.Parse(payload, True) + parser.NotStandaloneHandler.assert_called_once() + + notes = ["invalid 'NotStandalone' event handler return value"] + self.assertEqual(cm.exception.__notes__, notes) + + def test_invalid_ExternalEntityRefHandler(self): + parser = expat.ParserCreate() + parser.UseForeignDTD() + parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_ALWAYS) + parser.ExternalEntityRefHandler = mock.Mock(return_value=None) + + with self.assertRaises(TypeError) as cm: + parser.Parse(b"", True) + parser.ExternalEntityRefHandler.assert_called_once() + + notes = ["invalid 'ExternalEntityRef' event handler return value"] + self.assertEqual(cm.exception.__notes__, notes) + # Test Current* members: class PositionTest(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2026-03-28-11-31-32.gh-issue-146563.cXtSym.rst b/Misc/NEWS.d/next/Library/2026-03-28-11-31-32.gh-issue-146563.cXtSym.rst new file mode 100644 index 00000000000000..2103024b616d4e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-28-11-31-32.gh-issue-146563.cXtSym.rst @@ -0,0 +1,2 @@ +:mod:`xml.parsers.expat`: add an exception note when a custom Expat handler +return value cannot be properly interpreted. Patch by Bénédikt Tran. diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index 31b883fe8bd548..0f0afe17513ef1 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -503,6 +503,28 @@ my_StartElementHandler(void *userData, } } +static inline void +invalid_expat_handler_rv(const char *name) +{ + PyObject *exc = PyErr_GetRaisedException(); + assert(exc != NULL); + PyObject *note = PyUnicode_FromFormat("invalid '%s' event handler return value", name); + if (note == NULL) { + goto error; + } + int rc = _PyException_AddNote(exc, note); + Py_DECREF(note); + if (rc < 0) { + goto error; + }; + goto done; + +error: + PyErr_Clear(); +done: + PyErr_SetRaisedException(exc); +} + #define RC_HANDLER(RETURN_TYPE, NAME, PARAMS, \ INIT, PARSE_FORMAT, CONVERSION, \ RETURN_VARIABLE, GETUSERDATA) \ @@ -536,6 +558,9 @@ my_ ## NAME ## Handler PARAMS { \ } \ CONVERSION \ Py_DECREF(rv); \ + if (PyErr_Occurred()) { \ + invalid_expat_handler_rv(#NAME); \ + } \ return RETURN_VARIABLE; \ }