diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index aba284719df0..121fa63ac2f6 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -41,30 +41,27 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi) unsigned stride(width * 4); - pixBuffer = new agg::int8u[NUMBYTES]; - renderingBuffer.attach(pixBuffer, width, height, stride); + pixBuffer.reset(new agg::int8u[NUMBYTES]); + renderingBuffer.attach(pixBuffer.get(), width, height, stride); pixFmt.attach(renderingBuffer); rendererBase.attach(pixFmt); rendererBase.clear(_fill_color); rendererAA.attach(rendererBase); rendererBin.attach(rendererBase); hatch_size = int(dpi); - hatchBuffer = new agg::int8u[hatch_size * hatch_size * 4]; - hatchRenderingBuffer.attach(hatchBuffer, hatch_size, hatch_size, hatch_size * 4); + hatchBuffer.reset(new agg::int8u[hatch_size * hatch_size * 4]); + hatchRenderingBuffer.attach(hatchBuffer.get(), hatch_size, hatch_size, hatch_size * 4); } RendererAgg::~RendererAgg() { - delete[] hatchBuffer; - delete[] alphaBuffer; - delete[] pixBuffer; } void RendererAgg::create_alpha_buffers() { if (!alphaBuffer) { - alphaBuffer = new agg::int8u[width * height]; - alphaMaskRenderingBuffer.attach(alphaBuffer, width, height, width); + alphaBuffer.reset(new agg::int8u[width * height]); + alphaMaskRenderingBuffer.attach(alphaBuffer.get(), width, height, width); rendererBaseAlphaMask.attach(pixfmtAlphaMask); rendererAlphaMask.attach(rendererBaseAlphaMask); } diff --git a/src/_backend_agg.h b/src/_backend_agg.h index a4e2aa10040d..d59157c3dec3 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -206,10 +207,10 @@ class RendererAgg double dpi; size_t NUMBYTES; // the number of bytes in buffer - agg::int8u *pixBuffer; + std::unique_ptr pixBuffer; agg::rendering_buffer renderingBuffer; - agg::int8u *alphaBuffer; + std::unique_ptr alphaBuffer; agg::rendering_buffer alphaMaskRenderingBuffer; alpha_mask_type alphaMask; agg::pixfmt_gray8 pixfmtAlphaMask; @@ -229,7 +230,7 @@ class RendererAgg agg::trans_affine lastclippath_transform; size_t hatch_size; - agg::int8u *hatchBuffer; + std::unique_ptr hatchBuffer; agg::rendering_buffer hatchRenderingBuffer; agg::rgba _fill_color; diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 3db347854da6..6840d99de6a2 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -234,7 +234,7 @@ PYBIND11_MODULE(_backend_agg, m, py::mod_gil_not_used()) 4, 1 }; - return py::buffer_info(renderer->pixBuffer, shape, strides); + return py::buffer_info(renderer->pixBuffer.get(), shape, strides); }); py::class_(m, "BufferRegion", py::buffer_protocol()) diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp index 31eb92444862..ed7e58c5a4c5 100644 --- a/src/_c_internal_utils.cpp +++ b/src/_c_internal_utils.cpp @@ -105,7 +105,13 @@ mpl_GetCurrentProcessExplicitAppUserModelID(void) PyErr_SetFromWindowsErr(hr); throw py::error_already_set(); } - auto py_appid = py::cast(appid); + py::object py_appid; + try { + py_appid = py::cast(appid); + } catch (...) { + CoTaskMemFree(appid); // don't leak the COM-allocated string on error + throw; + } CoTaskMemFree(appid); return py_appid; #else diff --git a/src/_qhull_wrapper.cpp b/src/_qhull_wrapper.cpp index 509b48ecb3e1..9f313826fac6 100644 --- a/src/_qhull_wrapper.cpp +++ b/src/_qhull_wrapper.cpp @@ -130,6 +130,10 @@ class QhullInfo { } } + // Uniquely owns qh and error_file; forbid copying (Rule of Three). + QhullInfo(const QhullInfo&) = delete; + QhullInfo& operator=(const QhullInfo&) = delete; + private: FILE* error_file; qhT* qh; diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index bef60445e26e..158cdd24d0a0 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -6,6 +6,8 @@ #include "ft2font.h" #include "_enums.h" +#include +#include #include #include #include @@ -360,6 +362,18 @@ class PyFT2Font final : public FT2Font FT_StreamRec stream; py::list fallbacks; + // Serializes this object's mutating Python-facing methods so that concurrent + // calls on the SAME FT2Font (under free-threading / py::mod_gil_not_used) + // cannot corrupt its FreeType glyph slot, glyph list or image buffer. It is + // taken ONLY at the wrapper boundary -- never inside FT2Font's C++ methods, + // which also run during construction and fallback recursion -- so a single + // lock is held per top-level call and it never nests (deadlock-free by + // construction). Recursive to tolerate re-entry via Python callbacks such as + // ft_glyph_warn(). Contract: one FT2Font (together with its fallback graph) + // is used by a single thread at a time; matplotlib enforces this via the + // thread-keyed cache in font_manager._get_font. + std::recursive_mutex mutex; + ~PyFT2Font() { // Because destructors are called from subclass up to base class, we need to @@ -503,7 +517,9 @@ PyFT2Font_init(py::object filename, std::optional hinting_factor = std::nu std::back_inserter(fallback_fonts)); } - auto self = new PyFT2Font(fallback_fonts, warn_if_used); + // Own the object locally so it is released if any step below throws (e.g. a + // corrupt font makes self->open() fail); pybind11 adopts it only on success. + auto self = std::make_unique(fallback_fonts, warn_if_used); self->set_kerning_factor(*kerning_factor); if (fallback_list) { @@ -517,7 +533,7 @@ PyFT2Font_init(py::object filename, std::optional hinting_factor = std::nu self->stream.base = nullptr; self->stream.size = 0x7fffffff; // Unknown size. self->stream.pos = 0; - self->stream.descriptor.pointer = self; + self->stream.descriptor.pointer = self.get(); self->stream.read = &read_from_file_callback; FT_Open_Args open_args; memset((void *)&open_args, 0, sizeof(FT_Open_Args)); @@ -547,7 +563,7 @@ PyFT2Font_init(py::object filename, std::optional hinting_factor = std::nu self->open(open_args, face_index); - return self; + return self.release(); } static py::object @@ -687,6 +703,7 @@ PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0 std::optional> features = std::nullopt, std::variant languages_or_str = nullptr) { + std::scoped_lock lock{self->mutex}; std::vector xys; FT2Font::LanguageType languages; @@ -745,6 +762,7 @@ static PyGlyph * PyFT2Font_load_char(PyFT2Font *self, long charcode, LoadFlags flags = LoadFlags::FORCE_AUTOHINT) { + std::scoped_lock lock{self->mutex}; bool fallback = true; FT2Font *ft_object = nullptr; @@ -783,6 +801,7 @@ static PyGlyph * PyFT2Font_load_glyph(PyFT2Font *self, FT_UInt glyph_index, LoadFlags flags = LoadFlags::FORCE_AUTOHINT) { + std::scoped_lock lock{self->mutex}; self->load_glyph(glyph_index, static_cast(flags)); return PyGlyph_from_FT2Font(self); @@ -885,6 +904,7 @@ PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image, int xd, int yd, PyGlyph *glyph, bool antialiased = true) { + std::scoped_lock lock{self->mutex}; self->draw_glyph_to_bitmap( py::array_t{image}, xd, yd, glyph->glyphInd, antialiased); @@ -1409,6 +1429,7 @@ PyFT2Font_layout(PyFT2Font *self, std::u32string text, LoadFlags flags, std::optional> features = std::nullopt, std::variant languages_or_str = nullptr) { + std::scoped_lock lock{self->mutex}; const auto load_flags = static_cast(flags); FT2Font::LanguageType languages; @@ -1577,7 +1598,12 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) "_fallback_list"_a=py::none(), "_kerning_factor"_a=py::none(), "_warn_if_used"_a=false, PyFT2Font_init__doc__) - .def("clear", &PyFT2Font::clear, PyFT2Font_clear__doc__) + .def("clear", + [](PyFT2Font& self) { + std::scoped_lock lock{self.mutex}; + self.clear(); + }, + PyFT2Font_clear__doc__) .def("set_size", &PyFT2Font::set_size, "ptsize"_a, "dpi"_a, PyFT2Font_set_size__doc__) .def("_set_transform", &PyFT2Font::_set_transform, "matrix"_a, "delta"_a, @@ -1608,7 +1634,11 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) .def("get_bitmap_offset", &PyFT2Font::get_bitmap_offset, PyFT2Font_get_bitmap_offset__doc__) .def("get_descent", &PyFT2Font::get_descent, PyFT2Font_get_descent__doc__) - .def("draw_glyphs_to_bitmap", &PyFT2Font::draw_glyphs_to_bitmap, + .def("draw_glyphs_to_bitmap", + [](PyFT2Font& self, bool antialiased) { + std::scoped_lock lock{self.mutex}; + self.draw_glyphs_to_bitmap(antialiased); + }, py::kw_only(), "antialiased"_a=true, PyFT2Font_draw_glyphs_to_bitmap__doc__); // The generated docstring uses an unqualified "Buffer" as type hint,