Skip to content

Commit 36cc494

Browse files
committed
* Moving null-check of tp_finalize from runtime to compile time.
* Calling PyObject_GC_IsFinalized(...) to ensure tp_finalize is called exactly once.
1 parent 868038a commit 36cc494

File tree

2 files changed

+47
-5
lines changed

2 files changed

+47
-5
lines changed

mypyc/codegen/emitclass.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ def emit_line() -> None:
311311
emit_line()
312312
generate_clear_for_class(cl, clear_name, emitter)
313313
emit_line()
314-
generate_dealloc_for_class(cl, dealloc_name, clear_name, emitter)
314+
generate_dealloc_for_class(cl, dealloc_name, clear_name, bool(del_method), emitter)
315315
emit_line()
316316
if del_method:
317317
generate_finalize_for_class(del_method, finalize_name, emitter)
@@ -782,14 +782,15 @@ def generate_clear_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> N
782782

783783

784784
def generate_dealloc_for_class(
785-
cl: ClassIR, dealloc_func_name: str, clear_func_name: str, emitter: Emitter
785+
cl: ClassIR, dealloc_func_name: str, clear_func_name: str, has_tp_finalize: bool, emitter: Emitter
786786
) -> None:
787787
emitter.emit_line("static void")
788788
emitter.emit_line(f"{dealloc_func_name}({cl.struct_name(emitter.names)} *self)")
789789
emitter.emit_line("{")
790-
emitter.emit_line("if (Py_TYPE(self)->tp_finalize) {")
791-
emitter.emit_line(" Py_TYPE(self)->tp_finalize((PyObject *)self);")
792-
emitter.emit_line("}")
790+
if has_tp_finalize:
791+
emitter.emit_line("if (!PyObject_GC_IsFinalized((PyObject *)self)) {")
792+
emitter.emit_line("Py_TYPE(self)->tp_finalize((PyObject *)self);")
793+
emitter.emit_line("}")
793794
emitter.emit_line("PyObject_GC_UnTrack(self);")
794795
# The trashcan is needed to handle deep recursive deallocations
795796
emitter.emit_line(f"CPy_TRASHCAN_BEGIN(self, {dealloc_func_name})")
@@ -805,13 +806,18 @@ def generate_finalize_for_class(
805806
emitter.emit_line("static void")
806807
emitter.emit_line(f"{finalize_func_name}(PyObject *self)")
807808
emitter.emit_line("{")
809+
emitter.emit_line("PyObject *type, *value, *traceback;")
810+
emitter.emit_line("PyErr_Fetch(&type, &value, &traceback);")
808811
emitter.emit_line(
809812
"{}{}{}(self);".format(
810813
emitter.get_group_prefix(del_method.decl),
811814
NATIVE_PREFIX,
812815
del_method.cname(emitter.names),
813816
)
814817
)
818+
emitter.emit_line("if (type != NULL) {")
819+
emitter.emit_line("PyErr_Restore(type, value, traceback);")
820+
emitter.emit_line("}")
815821
emitter.emit_line("}")
816822

817823

mypyc/test-data/run-classes.test

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2737,11 +2737,47 @@ class C(B):
27372737
print("deleting C...")
27382738
super().__del__()
27392739

2740+
class D(A):
2741+
pass
2742+
27402743
[file driver.py]
27412744
import native
27422745
native.C()
2746+
native.D()
27432747

27442748
[out]
27452749
deleting C...
27462750
deleting B...
27472751
deleting A...
2752+
deleting A...
2753+
2754+
[case testDelCircular]
2755+
import dataclasses
2756+
import typing
2757+
2758+
i: int = 1
2759+
2760+
@dataclasses.dataclass
2761+
class C:
2762+
var: typing.Optional["C"] = dataclasses.field(default=None)
2763+
2764+
def __del__(self):
2765+
global i
2766+
print(f"deleting C{i}...")
2767+
i = i + 1
2768+
2769+
[file driver.py]
2770+
import native
2771+
import gc
2772+
2773+
c1 = native.C()
2774+
c2 = native.C()
2775+
c1.var = c2
2776+
c2.var = c1
2777+
del c1
2778+
del c2
2779+
gc.collect()
2780+
2781+
[out]
2782+
deleting C1...
2783+
deleting C2...

0 commit comments

Comments
 (0)