From c8e301c47456114f245261fccd316b4fd8964f3e Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Thu, 24 Oct 2024 15:52:59 +0200 Subject: [PATCH 1/3] Fix SEGFAULT from calling FT_New_Face/FT_Done_Face in multiple threads --- src/_imagingft.c | 12 +- src/thirdparty/pythoncapi_compat.h | 341 ++++++++++++++++++++++++++++- 2 files changed, 351 insertions(+), 2 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 7f246412455..6fed821bc0c 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -81,7 +81,10 @@ struct { /* -------------------------------------------------------------------- */ /* font objects */ - static FT_Library library; +static FT_Library library; +#ifdef Py_GIL_DISABLED +PyMutex ft_library_mutex; +#endif typedef struct { PyObject_HEAD FT_Face face; @@ -187,7 +190,9 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { if (filename && font_bytes_size <= 0) { self->font_bytes = NULL; + MUTEX_LOCK(&ft_library_mutex); error = FT_New_Face(library, filename, index, &self->face); + MUTEX_UNLOCK(&ft_library_mutex); } else { /* need to have allocated storage for font_bytes for the life of the object.*/ /* Don't free this before FT_Done_Face */ @@ -197,6 +202,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { } if (!error) { memcpy(self->font_bytes, font_bytes, (size_t)font_bytes_size); + MUTEX_LOCK(&ft_library_mutex); error = FT_New_Memory_Face( library, (FT_Byte *)self->font_bytes, @@ -204,6 +210,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { index, &self->face ); + MUTEX_UNLOCK(&ft_library_mutex); } } @@ -1433,7 +1440,9 @@ font_setvaraxes(FontObject *self, PyObject *args) { static void font_dealloc(FontObject *self) { if (self->face) { + MUTEX_LOCK(&ft_library_mutex); FT_Done_Face(self->face); + MUTEX_UNLOCK(&ft_library_mutex); } if (self->font_bytes) { PyMem_Free(self->font_bytes); @@ -1639,6 +1648,7 @@ PyInit__imagingft(void) { } #ifdef Py_GIL_DISABLED + ft_library_mutex = (PyMutex) {0}; PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); #endif diff --git a/src/thirdparty/pythoncapi_compat.h b/src/thirdparty/pythoncapi_compat.h index 51e8c0de752..ca23d5ffa9a 100644 --- a/src/thirdparty/pythoncapi_compat.h +++ b/src/thirdparty/pythoncapi_compat.h @@ -7,7 +7,10 @@ // https://github.com/python/pythoncapi_compat // // Latest version: -// https://raw.githubusercontent.com/python/pythoncapi_compat/master/pythoncapi_compat.h +// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h +// +// This file was vendored from the following commit: +// https://github.com/python/pythoncapi-compat/commit/0041177c4f348c8952b4c8980b2c90856e61c7c7 // // SPDX-License-Identifier: 0BSD @@ -45,6 +48,13 @@ extern "C" { # define _PyObject_CAST(op) _Py_CAST(PyObject*, op) #endif +#ifndef Py_BUILD_ASSERT +# define Py_BUILD_ASSERT(cond) \ + do { \ + (void)sizeof(char [1 - 2 * !(cond)]); \ + } while(0) +#endif + // bpo-42262 added Py_NewRef() to Python 3.10.0a3 #if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef) @@ -1338,6 +1348,166 @@ PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, } #endif +#if PY_VERSION_HEX < 0x030D00B3 +# define Py_BEGIN_CRITICAL_SECTION(op) { +# define Py_END_CRITICAL_SECTION() } +# define Py_BEGIN_CRITICAL_SECTION2(a, b) { +# define Py_END_CRITICAL_SECTION2() } +#endif + +#if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) +typedef struct PyUnicodeWriter PyUnicodeWriter; + +static inline void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) +{ + _PyUnicodeWriter_Dealloc((_PyUnicodeWriter*)writer); + PyMem_Free(writer); +} + +static inline PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length) +{ + if (length < 0) { + PyErr_SetString(PyExc_ValueError, + "length must be positive"); + return NULL; + } + + const size_t size = sizeof(_PyUnicodeWriter); + PyUnicodeWriter *pub_writer = (PyUnicodeWriter *)PyMem_Malloc(size); + if (pub_writer == _Py_NULL) { + PyErr_NoMemory(); + return _Py_NULL; + } + _PyUnicodeWriter *writer = (_PyUnicodeWriter *)pub_writer; + + _PyUnicodeWriter_Init(writer); + if (_PyUnicodeWriter_Prepare(writer, length, 127) < 0) { + PyUnicodeWriter_Discard(pub_writer); + return NULL; + } + writer->overallocate = 1; + return pub_writer; +} + +static inline PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer) +{ + PyObject *str = _PyUnicodeWriter_Finish((_PyUnicodeWriter*)writer); + assert(((_PyUnicodeWriter*)writer)->buffer == NULL); + PyMem_Free(writer); + return str; +} + +static inline int +PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) +{ + if (ch > 0x10ffff) { + PyErr_SetString(PyExc_ValueError, + "character must be in range(0x110000)"); + return -1; + } + + return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter*)writer, ch); +} + +static inline int +PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) +{ + PyObject *str = PyObject_Str(obj); + if (str == NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} + +static inline int +PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) +{ + PyObject *str = PyObject_Repr(obj); + if (str == NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} + +static inline int +PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, + const char *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)strlen(str); + } + + PyObject *str_obj = PyUnicode_FromStringAndSize(str, size); + if (str_obj == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj); + Py_DECREF(str_obj); + return res; +} + +static inline int +PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, + const wchar_t *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)wcslen(str); + } + + PyObject *str_obj = PyUnicode_FromWideChar(str, size); + if (str_obj == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj); + Py_DECREF(str_obj); + return res; +} + +static inline int +PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, + Py_ssize_t start, Py_ssize_t end) +{ + if (!PyUnicode_Check(str)) { + PyErr_Format(PyExc_TypeError, "expect str, not %T", str); + return -1; + } + if (start < 0 || start > end) { + PyErr_Format(PyExc_ValueError, "invalid start argument"); + return -1; + } + if (end > PyUnicode_GET_LENGTH(str)) { + PyErr_Format(PyExc_ValueError, "invalid end argument"); + return -1; + } + + return _PyUnicodeWriter_WriteSubstring((_PyUnicodeWriter*)writer, str, + start, end); +} + +static inline int +PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...) +{ + va_list vargs; + va_start(vargs, format); + PyObject *str = PyUnicode_FromFormatV(format, vargs); + va_end(vargs); + if (str == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} +#endif // PY_VERSION_HEX < 0x030E0000 // gh-116560 added PyLong_GetSign() to Python 3.14.0a0 #if PY_VERSION_HEX < 0x030E00A0 @@ -1354,6 +1524,175 @@ static inline int PyLong_GetSign(PyObject *obj, int *sign) #endif +// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) +{ + if (!PyUnicode_Check(str1)) { + PyErr_Format(PyExc_TypeError, "first argument must be str, not %s", + Py_TYPE(str1)->tp_name); + return -1; + } + if (!PyUnicode_Check(str2)) { + PyErr_Format(PyExc_TypeError, "second argument must be str, not %s", + Py_TYPE(str2)->tp_name); + return -1; + } + +#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) + PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2); + + return _PyUnicode_Equal(str1, str2); +#elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#else + return (PyUnicode_Compare(str1, str2) == 0); +#endif +} +#endif + + +// gh-121645 added PyBytes_Join() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) +{ + return _PyBytes_Join(sep, iterable); +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) +{ +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len); + + return _Py_HashBytes(ptr, len); +#else + Py_hash_t hash; + PyObject *bytes = PyBytes_FromStringAndSize((const char*)ptr, len); + if (bytes == NULL) { + return -1; + } + hash = PyObject_Hash(bytes); + Py_DECREF(bytes); + return hash; +#endif +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyIter_NextItem(PyObject *iter, PyObject **item) +{ + iternextfunc tp_iternext; + + assert(iter != NULL); + assert(item != NULL); + + tp_iternext = Py_TYPE(iter)->tp_iternext; + if (tp_iternext == NULL) { + *item = NULL; + PyErr_Format(PyExc_TypeError, "expected an iterator, got '%s'", + Py_TYPE(iter)->tp_name); + return -1; + } + + if ((*item = tp_iternext(iter))) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + return 0; + } + return -1; +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyLong_FromInt32(int32_t value) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + return PyLong_FromLong(value); +} + +static inline PyObject* PyLong_FromInt64(int64_t value) +{ + Py_BUILD_ASSERT(sizeof(long long) >= 8); + return PyLong_FromLongLong(value); +} + +static inline PyObject* PyLong_FromUInt32(uint32_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long) >= 4); + return PyLong_FromUnsignedLong(value); +} + +static inline PyObject* PyLong_FromUInt64(uint64_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long long) >= 8); + return PyLong_FromUnsignedLongLong(value); +} + +static inline int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(int) == 4); + int value = PyLong_AsInt(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int32_t)value; + return 0; +} + +static inline int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + long long value = PyLong_AsLongLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int64_t)value; + return 0; +} + +static inline int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + unsigned long value = PyLong_AsUnsignedLong(obj); + if (value == (unsigned long)-1 && PyErr_Occurred()) { + return -1; + } +#if SIZEOF_LONG > 4 + if ((unsigned long)UINT32_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C uint32_t"); + return -1; + } +#endif + *pvalue = (uint32_t)value; + return 0; +} + +static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + unsigned long long value = PyLong_AsUnsignedLongLong(obj); + if (value == (unsigned long long)-1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (uint64_t)value; + return 0; +} +#endif + + #ifdef __cplusplus } #endif From 7999da38a7ff5aa359aa6ae3a7f88b835c7f7f06 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:07:43 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_imagingft.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 6fed821bc0c..884b8f96c2e 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -81,7 +81,7 @@ struct { /* -------------------------------------------------------------------- */ /* font objects */ -static FT_Library library; + static FT_Library library; #ifdef Py_GIL_DISABLED PyMutex ft_library_mutex; #endif @@ -1648,7 +1648,7 @@ PyInit__imagingft(void) { } #ifdef Py_GIL_DISABLED - ft_library_mutex = (PyMutex) {0}; + ft_library_mutex = (PyMutex){0}; PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); #endif From bb3515d649cda179037161ee3bea264a4d289b32 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 25 Oct 2024 17:32:29 +0200 Subject: [PATCH 3/3] Make PyMutex static and get rid of initialization --- src/_imagingft.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 884b8f96c2e..d38279f3e4b 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -83,7 +83,7 @@ struct { static FT_Library library; #ifdef Py_GIL_DISABLED -PyMutex ft_library_mutex; +static PyMutex ft_library_mutex; #endif typedef struct { @@ -1648,7 +1648,6 @@ PyInit__imagingft(void) { } #ifdef Py_GIL_DISABLED - ft_library_mutex = (PyMutex){0}; PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); #endif