diff --git a/Include/pyexpat.h b/Include/pyexpat.h index f523f8bb273983..271e1b87a01547 100644 --- a/Include/pyexpat.h +++ b/Include/pyexpat.h @@ -3,19 +3,29 @@ /* note: you must import expat.h before importing this module! */ +#ifndef PyExpat_CAPI_H +#define PyExpat_CAPI_H +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Only bump the magic number when PyExpat_CAPI changes but not its size. + * If the structure changes because of an additional field, the magic number + * should not be bumped (see https://github.com/python/cpython/issues/115398). + */ #define PyExpat_CAPI_MAGIC "pyexpat.expat_CAPI 1.1" #define PyExpat_CAPSULE_NAME "pyexpat.expat_CAPI" -struct PyExpat_CAPI -{ - char* magic; /* set to PyExpat_CAPI_MAGIC */ - int size; /* set to sizeof(struct PyExpat_CAPI) */ +typedef struct PyExpat_CAPI { + char *magic; /* set to PyExpat_CAPI_MAGIC */ + int size; /* set to sizeof(PyExpat_CAPI) */ int MAJOR_VERSION; int MINOR_VERSION; int MICRO_VERSION; /* pointers to selected expat functions. add new functions at the end, if needed */ - const XML_LChar * (*ErrorString)(enum XML_Error code); + const XML_LChar *(*ErrorString)(enum XML_Error code); enum XML_Error (*GetErrorCode)(XML_Parser parser); XML_Size (*GetErrorColumnNumber)(XML_Parser parser); XML_Size (*GetErrorLineNumber)(XML_Parser parser); @@ -63,5 +73,22 @@ struct PyExpat_CAPI XML_Bool (*SetBillionLaughsAttackProtectionMaximumAmplification)( XML_Parser parser, float maxAmplificationFactor); /* always add new stuff to the end! */ -}; +} PyExpat_CAPI; + +static inline int +PyExpat_CheckCompatibility(const PyExpat_CAPI *capi) +{ + assert(capi != NULL); + return ( + strcmp(capi->magic, PyExpat_CAPI_MAGIC) == 0 + && (size_t)capi->size >= sizeof(PyExpat_CAPI) + && capi->MAJOR_VERSION == XML_MAJOR_VERSION + && capi->MINOR_VERSION == XML_MINOR_VERSION + && capi->MICRO_VERSION == XML_MICRO_VERSION + ); +} +#ifdef __cplusplus +} +#endif +#endif /* !PyExpat_CAPI_H */ diff --git a/Misc/NEWS.d/next/C_API/2025-11-09-17-26-19.gh-issue-141226.1GYBSN.rst b/Misc/NEWS.d/next/C_API/2025-11-09-17-26-19.gh-issue-141226.1GYBSN.rst new file mode 100644 index 00000000000000..4f5115ac24fb66 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-11-09-17-26-19.gh-issue-141226.1GYBSN.rst @@ -0,0 +1,2 @@ +Expose :c:func:`!PyExpat_CheckCompatibility` for checking Expat C API +compatibility. Patch by Bénédikt Tran. diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 3173b52afb31b6..8419d53ae6d0fe 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -98,7 +98,6 @@ typedef struct { PyTypeObject *TreeBuilder_Type; PyTypeObject *XMLParser_Type; - PyObject *expat_capsule; struct PyExpat_CAPI *expat_capi; } elementtreestate; @@ -156,7 +155,6 @@ elementtree_clear(PyObject *m) Py_CLEAR(st->ElementIter_Type); Py_CLEAR(st->TreeBuilder_Type); Py_CLEAR(st->XMLParser_Type); - Py_CLEAR(st->expat_capsule); st->expat_capi = NULL; return 0; @@ -177,7 +175,7 @@ elementtree_traverse(PyObject *m, visitproc visit, void *arg) Py_VISIT(st->ElementIter_Type); Py_VISIT(st->TreeBuilder_Type); Py_VISIT(st->XMLParser_Type); - Py_VISIT(st->expat_capsule); + return 0; } @@ -4430,28 +4428,18 @@ module_exec(PyObject *m) if (st->deepcopy_obj == NULL) { goto error; } - - assert(!PyErr_Occurred()); - if (!(st->elementpath_obj = PyImport_ImportModule("xml.etree.ElementPath"))) + st->elementpath_obj = PyImport_ImportModule("xml.etree.ElementPath"); + if (st->elementpath_obj == NULL) { goto error; + } /* link against pyexpat */ - if (!(st->expat_capsule = PyImport_ImportModuleAttrString("pyexpat", "expat_CAPI"))) - goto error; - if (!(st->expat_capi = PyCapsule_GetPointer(st->expat_capsule, PyExpat_CAPSULE_NAME))) + st->expat_capi = PyCapsule_Import(PyExpat_CAPSULE_NAME, 0); + if (st->expat_capi == NULL) { goto error; - if (st->expat_capi) { - /* check that it's usable */ - if (strcmp(st->expat_capi->magic, PyExpat_CAPI_MAGIC) != 0 || - (size_t)st->expat_capi->size < sizeof(struct PyExpat_CAPI) || - st->expat_capi->MAJOR_VERSION != XML_MAJOR_VERSION || - st->expat_capi->MINOR_VERSION != XML_MINOR_VERSION || - st->expat_capi->MICRO_VERSION != XML_MICRO_VERSION) { - PyErr_SetString(PyExc_ImportError, - "pyexpat version is incompatible"); - goto error; - } - } else { + } + if (!PyExpat_CheckCompatibility(st->expat_capi)) { + PyErr_SetString(PyExc_ImportError, "pyexpat version is incompatible"); goto error; }