Title: [Bug]: Umbrella issue: matplotlib C/C++ Extension Analysis Report · Issue #31424 · matplotlib/matplotlib · GitHub
Open Graph Title: [Bug]: Umbrella issue: matplotlib C/C++ Extension Analysis Report · Issue #31424 · matplotlib/matplotlib
X Title: [Bug]: Umbrella issue: matplotlib C/C++ Extension Analysis Report · Issue #31424 · matplotlib/matplotlib
Description: Bug summary This is an umbrella issue meant to present the findings of an analysis report so matplotlib developers and contributors can discuss them, figure out which are worth fixing, open individual issues and PRs, etc. Findings by Pri...
Open Graph Description: Bug summary This is an umbrella issue meant to present the findings of an analysis report so matplotlib developers and contributors can discuss them, figure out which are worth fixing, open individ...
X Description: Bug summary This is an umbrella issue meant to present the findings of an analysis report so matplotlib developers and contributors can discuss them, figure out which are worth fixing, open individ...
Opengraph URL: https://github.com/matplotlib/matplotlib/issues/31424
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"[Bug]: Umbrella issue: matplotlib C/C++ Extension Analysis Report","articleBody":"### Bug summary\n\nThis is an umbrella issue meant to present the findings of an [analysis report](https://gist.github.com/devdanzin/2a79188c7a41a03b1476544c7dd775c1) so matplotlib developers and contributors can discuss them, figure out which are worth fixing, open individual issues and PRs, etc.\n\n## Findings by Priority\n\n### FIX\n\n1. **`std::string` constructed from NULL `ft_error_string`** — `ft_error_string()` returns NULL for unknown FreeType error codes. `THROW_FT_ERROR` constructs `std::string{nullptr}` — undefined behavior (typically crashes in `strlen`). Triggered when FreeType returns an error code not in the compiled-in list.\n - `ft2font.h:39,52`\n\n2. **Inverted `PyErr_Occurred` logic in enum type caster** — `return !(ival == -1 \u0026\u0026 !PyErr_Occurred())` returns `true` (success) when `PyLong_AsLong` fails with an active exception. One-character fix: the `!` before `PyErr_Occurred()` should be removed.\n - `_enums.h:83`\n\n3. **Unchecked `malloc` + leaked `Py_buffer` in `_copy_agg_buffer`** — `malloc(sizeof(Py_buffer))` not NULL-checked. If `PyObject_GetBuffer` fails, the buffer is leaked.\n - `_macosx.m:1221-1225`\n\n4. **Unchecked `PyOS_double_to_string` — `strlen(NULL)` crash** — `PyOS_double_to_string()` can return NULL on OOM. `strlen(str)` follows without a NULL check. Crash confirmed under OOM injection.\n - `_path.h:1070-1073`\n\n5. **`FigureCanvas_set_cursor` returns NULL without exception** — The `default:` case returns NULL without calling `PyErr_Set*`, causing `SystemError`.\n - `_macosx.m:451`\n\n6. **`FigureManager__set_window_mode` returns NULL without exception** — When `self-\u003ewindow` is NULL (after `destroy()`), returns NULL without setting an exception.\n - `_macosx.m:653-654`\n\n7. **NULL `char*` streamed to `std::stringstream` in `ft_glyph_warn`** — `face-\u003efamily_name` can be NULL. When NULL is inserted into `std::set\u003cFT_String*\u003e` and later streamed via `ss \u003c\u003c *it`, it's undefined behavior.\n - `ft2font.cpp:457`, `ft2font_wrapper.cpp:410`\n\n8. **`NSFileHandle` leak in `wake_on_fd_write`** — `[[NSFileHandle alloc] initWithFileDescriptor: fd]` is never released.\n - `_macosx.m:246-257`\n\n### CONSIDER\n\n9. **GIL not released during Agg rendering** — `draw_path`, `draw_markers`, `draw_path_collection`, etc. hold the GIL during software rasterization, blocking all threads. `_image_wrapper.cpp` already demonstrates the correct GIL-release pattern.\n - `_backend_agg_wrapper.cpp:41-215`\n\n10. **GIL not released during Qhull triangulation** — 500K-point triangulation holds the GIL for 3+ seconds, reducing background thread throughput to 0.6% of baseline.\n - `_qhull_wrapper.cpp:138-253`\n\n11. **Global `FT_Library` not thread-safe** — `_ft2Library` is shared across all threads. FreeType is not thread-safe for shared library instances.\n - `ft2font.cpp:44`\n\n12. **FreeType stream read callback lacks GIL guard for free-threading** — Calls Python I/O without GIL guard on free-threaded builds.\n - `ft2font_wrapper.cpp:367-388`\n\n13. **`ft_glyph_warn` calls Python APIs without GIL guard for free-threading** — Module import/attr access without GIL on free-threaded builds.\n - `ft2font_wrapper.cpp:405-418`\n\n14. **Global `p11x::enums` map lacks synchronization** — `std::unordered_map` with `py::object` values, no synchronization for free-threading.\n - `_enums.h:37`\n\n15. **`NSTrackingArea` leak in `FigureCanvas_init`** — `alloc` without matching `release`.\n - `_macosx.m:378-382`\n\n16. **`Window.close` double-decref risk** — `Py_DECREF(manager)` if `dealloc` runs without prior `destroy`.\n - `_macosx.m:1177-1188`\n\n17. **`PyFT2Font_init` leaks on exception** — Raw `new` without `unique_ptr`. ~59 bytes leaked per failed font construction (corrupt font file), ~131 bytes per failed construction (failing file-like object).\n - `ft2font_wrapper.cpp:457-505`\n\n18. **Timer NSTimer captures raw `self` pointer** — Block captures Python object without `Py_INCREF`.\n - `_macosx.m:1807-1816`\n\n19. **View stores raw `canvas` pointer** — No `Py_INCREF`, potential dangling pointer.\n - `_macosx.m:139`\n\n20. **Exception clobbering in ft2font catch-all** — `catch(std::exception\u0026)` replaces meaningful errors (including `MemoryError`, `KeyboardInterrupt`) with generic `TypeError`.\n - `ft2font_wrapper.cpp:492-494`\n\n21. **Unchecked `PyUnicode_EncodeFSDefault`** — NULL wrapped in `py::bytes`.\n - `_tkagg.cpp:333-335`\n\n22. **`PyErr_Fetch`/`PyErr_Restore` deprecated since 3.12** — Should migrate to `PyErr_GetRaisedException`/`PyErr_SetRaisedException`.\n - `ft2font_wrapper.cpp:394,402`\n\n23. **`PY_SSIZE_T_CLEAN` defines (×3)** — No-op since 3.10, can be removed.\n - `mplutils.h`, `py_adaptors.h`, `_macosx.m`\n\n24. **Solaris `_XPG4`/`_XPG3` undefs** — Likely dead code.\n - `mplutils.h:22-28`\n\n25. **macOS SDK \u003c 10.14 compat defines** — Likely dead code.\n - `_macosx.m:11-16`\n\n\n\n### Code for reproduction\n\nSee https://gist.github.com/devdanzin/2a79188c7a41a03b1476544c7dd775c1#matplotlib-c-extension--reproducer-appendix for reproducers.\n\n### Actual outcome\n\n-\n\n### Expected outcome\n\n-\n\n### Additional information\n\nThe issue above is part of an analysis report created by [cext-review-toolkit](https://github.com/devdanzin/cext-review-toolkit), a Claude Code plugin for reviewing CPython C extensions. It covers the C/C++ source in `src/` (13 source files, 9 extension modules — 8 pybind11 + 1 raw Python/C API in `_macosx.m`).\n\nSome findings may be false positives or low-priority issues not worth fixing. Feedback on any misclassified findings is welcome.\n\nThe full report (including a reproducers appendix) is available at https://gist.github.com/devdanzin/2a79188c7a41a03b1476544c7dd775c1.\n\n### Operating system\n\nLinux\n\n### Matplotlib Version\n\nmain\n\n### Matplotlib Backend\n\n_No response_\n\n### Python version\n\n_No response_\n\n### Jupyter version\n\n_No response_\n\n### Installation\n\ngit checkout","author":{"url":"https://github.com/devdanzin","@type":"Person","name":"devdanzin"},"datePublished":"2026-03-29T22:43:33.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":3},"url":"https://github.com/31424/matplotlib/issues/31424"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:2617ddf9-f7d0-1c58-0462-99e20f5e7c1c |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 96E2:13D32C:12A1857:19688D4:6A521653 |
| html-safe-nonce | 6717848ea223719b12930e1d17eef7a02024d2538060ce27cd0500721b660186 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5NkUyOjEzRDMyQzoxMkExODU3OjE5Njg4RDQ6NkE1MjE2NTMiLCJ2aXNpdG9yX2lkIjoiNDE1Njk2ODY0NTY0ODU4NjMyMyIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 4532c74e806900a3ddcc871d63afef62a7e474c3ec2f56c3c112ea0f353a8738 |
| hovercard-subject-tag | issue:4165982181 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/matplotlib/matplotlib/31424/issue_layout |
| twitter:image | https://opengraph.githubassets.com/c0afcf5f2b0d7287ddc9a737b4ce702b5a7ec65307ce74b5c5e11a1558920005/matplotlib/matplotlib/issues/31424 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/c0afcf5f2b0d7287ddc9a737b4ce702b5a7ec65307ce74b5c5e11a1558920005/matplotlib/matplotlib/issues/31424 |
| og:image:alt | Bug summary This is an umbrella issue meant to present the findings of an analysis report so matplotlib developers and contributors can discuss them, figure out which are worth fixing, open individ... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | devdanzin |
| hostname | github.com |
| expected-hostname | github.com |
| None | b9a586c06a05a7a86fc7e3f4dbd03e42f6869085879aa184aa6369456dbd50fb |
| turbo-cache-control | no-preview |
| go-import | github.com/matplotlib/matplotlib git https://github.com/matplotlib/matplotlib.git |
| octolytics-dimension-user_id | 215947 |
| octolytics-dimension-user_login | matplotlib |
| octolytics-dimension-repository_id | 1385122 |
| octolytics-dimension-repository_nwo | matplotlib/matplotlib |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1385122 |
| octolytics-dimension-repository_network_root_nwo | matplotlib/matplotlib |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 7aed05249554b889eb33d002851a973eebcc7e91 |
| ui-target | canary-1 |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width