Commit 05090ce
committed
[mypyc] Fix memory leak in Exception and dict subclasses with instance attributes
Fixes #21716.
When mypyc compiles a subclass of a builtin type (`Exception` or `dict`)
with instance attributes on Python <3.12, it appends a __dict__ slot at
tp_dictoffset = sizeof(builtin_base) to hold them. But it emits no
subtype-specific tp_dealloc, so the type inherits the base's dealloc which
knows nothing about the extra slot. That dict (and everything it
references) leaks on every instance destruction.
Hit downstream in sqlglot[c] (tobymao/sqlglot#7853) where every
raised-and-caught ParseError leaked ~1 KB. On 3.12+ mypyc already avoids
adding the extra offset, so only <3.12 is affected.
Changes in `mypyc/codegen/emitclass.py`:
* Extend the gate that emits tp_dealloc/tp_clear/tp_traverse (and
Py_TPFLAGS_HAVE_GC) to fire for any builtin_base subclass with
has_dict under capi < 3.12. The existing builtin-base dealloc dance
handles the flow.
* Set Py_TPFLAGS_HAVE_GC explicitly. PyType_Ready only inherits HAVE_GC
from the base when it also inherits both tp_traverse and tp_dealloc;
once we supply our own, that inheritance is lost.
* Fix a latent offset bug in generate_traverse_for_class /
generate_clear_for_class: they used sizeof(struct_name) for the extra
dict slot, but for builtin_base classes tp_dictoffset is
sizeof(builtin_base). Previously unreachable because the emission
gate never fired for builtin_base classes.
Changes in `mypyc/codegen/emit.py` (`emit_base_tp_function_call`):
* Walk the tp_base chain past any Py_TPFLAGS_HEAPTYPE ancestors before
delegating tp_dealloc/tp_traverse/tp_clear. Required for Exception
subclasses that inherit through an interpreted Python heap type (e.g.
`argparse.ArgumentTypeError` in mypy's own VersionTypeError): calling
the heap type's tp_dealloc dispatches through CPython's
subtype_dealloc, which reads Py_TYPE(self) (still our subtype), finds
our own tp_dealloc, and calls it back — infinite recursion crashing
the compiled binary on 3.10/3.11. Our subtype_clear already walks the
full MRO and clears all ancestors' fields, so calling the static
ancestor's tp_dealloc directly is safe and correct.
Regression tests:
* testSubclassExceptionNoAttributeLeak (new): a Payload class with a
__del__ counter detects the leak deterministically — 1000 fresh
exceptions each hold a fresh payload; without the fix all 1000
payloads survive.
* testDelForDictSubclass (updated): the stale
`sys.version_info >= (3, 12)` guard is dropped since dict subclass
__del__ now works correctly on all Python versions.1 parent d5daed2 commit 05090ce
3 files changed
Lines changed: 66 additions & 30 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1434 | 1434 | | |
1435 | 1435 | | |
1436 | 1436 | | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
1437 | 1441 | | |
1438 | | - | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
1439 | 1448 | | |
1440 | 1449 | | |
1441 | 1450 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
267 | 267 | | |
268 | 268 | | |
269 | 269 | | |
270 | | - | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
271 | 280 | | |
272 | 281 | | |
273 | 282 | | |
| |||
340 | 349 | | |
341 | 350 | | |
342 | 351 | | |
343 | | - | |
| 352 | + | |
344 | 353 | | |
345 | 354 | | |
346 | 355 | | |
| |||
386 | 395 | | |
387 | 396 | | |
388 | 397 | | |
389 | | - | |
| 398 | + | |
| 399 | + | |
390 | 400 | | |
391 | 401 | | |
392 | 402 | | |
| |||
882 | 892 | | |
883 | 893 | | |
884 | 894 | | |
885 | | - | |
886 | | - | |
887 | | - | |
888 | | - | |
889 | | - | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
890 | 898 | | |
891 | | - | |
892 | | - | |
| 899 | + | |
893 | 900 | | |
894 | 901 | | |
895 | 902 | | |
| |||
908 | 915 | | |
909 | 916 | | |
910 | 917 | | |
911 | | - | |
912 | | - | |
913 | | - | |
914 | | - | |
915 | | - | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
916 | 921 | | |
917 | | - | |
918 | | - | |
| 922 | + | |
919 | 923 | | |
920 | 924 | | |
921 | 925 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
956 | 956 | | |
957 | 957 | | |
958 | 958 | | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
959 | 993 | | |
960 | 994 | | |
961 | 995 | | |
| |||
3452 | 3486 | | |
3453 | 3487 | | |
3454 | 3488 | | |
3455 | | - | |
3456 | | - | |
3457 | 3489 | | |
3458 | 3490 | | |
3459 | 3491 | | |
3460 | 3492 | | |
3461 | | - | |
3462 | | - | |
3463 | | - | |
3464 | | - | |
3465 | | - | |
3466 | | - | |
3467 | | - | |
3468 | | - | |
3469 | | - | |
3470 | | - | |
| 3493 | + | |
3471 | 3494 | | |
3472 | 3495 | | |
3473 | 3496 | | |
| |||
0 commit comments