From ef00ddef435589300a3407121221f3da895384c4 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 28 Aug 2025 01:51:45 +0200 Subject: [PATCH 1/5] [mypyc] Fix object finalization --- mypyc/codegen/emitclass.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index ecf8c37f83c9..ce8b9a2089c8 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -842,10 +842,15 @@ def generate_dealloc_for_class( emitter.emit_line("static void") emitter.emit_line(f"{dealloc_func_name}({cl.struct_name(emitter.names)} *self)") emitter.emit_line("{") + emitter.emit_line("int finalize_error = 0;") + emitter.emit_line("PyObject *type, *value, *traceback;") + emitter.emit_line("PyErr_Fetch(&type, &value, &traceback);") if has_tp_finalize: emitter.emit_line("if (!PyObject_GC_IsFinalized((PyObject *)self)) {") - emitter.emit_line("Py_TYPE(self)->tp_finalize((PyObject *)self);") + emitter.emit_line("PyObject_CallFinalizerFromDealloc((PyObject *)self);") emitter.emit_line("}") + emitter.emit_line("if (PyErr_Occurred() != NULL) finalize_error = 1;") + emitter.emit_line("PyErr_Restore(type, value, traceback);") emitter.emit_line("PyObject_GC_UnTrack(self);") if cl.reuse_freed_instance: emit_reuse_dealloc(cl, emitter) @@ -854,6 +859,13 @@ def generate_dealloc_for_class( emitter.emit_line(f"{clear_func_name}(self);") emitter.emit_line("Py_TYPE(self)->tp_free((PyObject *)self);") emitter.emit_line("CPy_TRASHCAN_END(self)") + # # CPython interpreter uses PyErr_WriteUnraisable: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_WriteUnraisable + # # However, the message is slightly different due to the way mypyc compiles classes. + # # CPython interpreter prints: Exception ignored in: + # # mypyc prints: Exception ignored in: + emitter.emit_line("if (finalize_error == 1) {") + emitter.emit_line("PyErr_WriteUnraisable((PyObject *)self);") + emitter.emit_line("}") emitter.emit_line("}") @@ -884,8 +896,6 @@ def generate_finalize_for_class( emitter.emit_line("static void") emitter.emit_line(f"{finalize_func_name}(PyObject *self)") emitter.emit_line("{") - emitter.emit_line("PyObject *type, *value, *traceback;") - emitter.emit_line("PyErr_Fetch(&type, &value, &traceback);") emitter.emit_line( "{}{}{}(self);".format( emitter.get_group_prefix(del_method.decl), @@ -893,21 +903,6 @@ def generate_finalize_for_class( del_method.cname(emitter.names), ) ) - emitter.emit_line("if (PyErr_Occurred() != NULL) {") - emitter.emit_line('PyObject *del_str = PyUnicode_FromString("__del__");') - emitter.emit_line( - "PyObject *del_method = (del_str == NULL) ? NULL : _PyType_Lookup(Py_TYPE(self), del_str);" - ) - # CPython interpreter uses PyErr_WriteUnraisable: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_WriteUnraisable - # However, the message is slightly different due to the way mypyc compiles classes. - # CPython interpreter prints: Exception ignored in: - # mypyc prints: Exception ignored in: - emitter.emit_line("PyErr_WriteUnraisable(del_method);") - emitter.emit_line("Py_XDECREF(del_method);") - emitter.emit_line("Py_XDECREF(del_str);") - emitter.emit_line("}") - # PyErr_Restore also clears exception raised in __del__. - emitter.emit_line("PyErr_Restore(type, value, traceback);") emitter.emit_line("}") From 5b92c8bd9ce18595fb04faf02a021499c475bae1 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 28 Aug 2025 02:26:00 +0200 Subject: [PATCH 2/5] Move PyErr_WriteUnraisable call --- mypyc/codegen/emitclass.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index ce8b9a2089c8..10f6a99960b9 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -842,15 +842,20 @@ def generate_dealloc_for_class( emitter.emit_line("static void") emitter.emit_line(f"{dealloc_func_name}({cl.struct_name(emitter.names)} *self)") emitter.emit_line("{") - emitter.emit_line("int finalize_error = 0;") - emitter.emit_line("PyObject *type, *value, *traceback;") - emitter.emit_line("PyErr_Fetch(&type, &value, &traceback);") if has_tp_finalize: emitter.emit_line("if (!PyObject_GC_IsFinalized((PyObject *)self)) {") + emitter.emit_line("PyObject *type, *value, *traceback;") + emitter.emit_line("PyErr_Fetch(&type, &value, &traceback);") emitter.emit_line("PyObject_CallFinalizerFromDealloc((PyObject *)self);") + # CPython interpreter uses PyErr_WriteUnraisable: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_WriteUnraisable + # However, the message is slightly different due to the way mypyc compiles classes. + # CPython interpreter prints: Exception ignored in: + # mypyc prints: Exception ignored in: + emitter.emit_line("if (PyErr_Occurred() != NULL) {") + emitter.emit_line("PyErr_WriteUnraisable((PyObject *)self);") + emitter.emit_line("}") + emitter.emit_line("PyErr_Restore(type, value, traceback);") emitter.emit_line("}") - emitter.emit_line("if (PyErr_Occurred() != NULL) finalize_error = 1;") - emitter.emit_line("PyErr_Restore(type, value, traceback);") emitter.emit_line("PyObject_GC_UnTrack(self);") if cl.reuse_freed_instance: emit_reuse_dealloc(cl, emitter) @@ -859,13 +864,6 @@ def generate_dealloc_for_class( emitter.emit_line(f"{clear_func_name}(self);") emitter.emit_line("Py_TYPE(self)->tp_free((PyObject *)self);") emitter.emit_line("CPy_TRASHCAN_END(self)") - # # CPython interpreter uses PyErr_WriteUnraisable: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_WriteUnraisable - # # However, the message is slightly different due to the way mypyc compiles classes. - # # CPython interpreter prints: Exception ignored in: - # # mypyc prints: Exception ignored in: - emitter.emit_line("if (finalize_error == 1) {") - emitter.emit_line("PyErr_WriteUnraisable((PyObject *)self);") - emitter.emit_line("}") emitter.emit_line("}") From d6b6d7966191240d46b38fac93ae58245810b206 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:18:20 +0200 Subject: [PATCH 3/5] Code review --- mypyc/codegen/emitclass.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index 10f6a99960b9..bc00fcff84f2 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -843,10 +843,9 @@ def generate_dealloc_for_class( emitter.emit_line(f"{dealloc_func_name}({cl.struct_name(emitter.names)} *self)") emitter.emit_line("{") if has_tp_finalize: - emitter.emit_line("if (!PyObject_GC_IsFinalized((PyObject *)self)) {") emitter.emit_line("PyObject *type, *value, *traceback;") emitter.emit_line("PyErr_Fetch(&type, &value, &traceback);") - emitter.emit_line("PyObject_CallFinalizerFromDealloc((PyObject *)self);") + emitter.emit_line("int res = PyObject_CallFinalizerFromDealloc((PyObject *)self);") # CPython interpreter uses PyErr_WriteUnraisable: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_WriteUnraisable # However, the message is slightly different due to the way mypyc compiles classes. # CPython interpreter prints: Exception ignored in: @@ -855,6 +854,8 @@ def generate_dealloc_for_class( emitter.emit_line("PyErr_WriteUnraisable((PyObject *)self);") emitter.emit_line("}") emitter.emit_line("PyErr_Restore(type, value, traceback);") + emitter.emit_line("if (res < 0) {") + emitter.emit_line("goto done;") emitter.emit_line("}") emitter.emit_line("PyObject_GC_UnTrack(self);") if cl.reuse_freed_instance: @@ -864,6 +865,7 @@ def generate_dealloc_for_class( emitter.emit_line(f"{clear_func_name}(self);") emitter.emit_line("Py_TYPE(self)->tp_free((PyObject *)self);") emitter.emit_line("CPy_TRASHCAN_END(self)") + emitter.emit_line("done: ;") emitter.emit_line("}") From 9a2bb0a24a08ac380065a05223ffce50a98b6ae7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 28 Aug 2025 21:55:21 +0200 Subject: [PATCH 4/5] Update test output --- mypy/test/helpers.py | 3 +++ mypyc/test-data/run-classes.test | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index ae432ff6981b..36ad5ad4ec1a 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -233,6 +233,9 @@ def clean_up(a: list[str]) -> list[str]: for p in prefix, prefix.replace(os.sep, "/"): if p != "/" and p != "//" and p != "\\" and p != "\\\\": ss = ss.replace(p, "") + # Replace memory address with zeros + if "at 0x" in ss: + ss = re.sub(r"(at 0x)\w+>", r"\g<1>000000000000>", ss) # Ignore spaces at end of line. ss = re.sub(" +$", "", ss) # Remove pwd from driver.py's path diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index dc64680f67c1..b052bfa9e3bc 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -3035,7 +3035,7 @@ f = native.F() del f [out] -Exception ignored in: +Exception ignored in: Traceback (most recent call last): File "native.py", line 5, in __del__ raise Exception("e2") From f62a22015ca51f9b14423578d0fa8bb36ceb0da0 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 2 Sep 2025 14:00:00 +0200 Subject: [PATCH 5/5] Fix segfault --- mypyc/codegen/emitclass.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index bc00fcff84f2..8af4e44d8600 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -851,7 +851,9 @@ def generate_dealloc_for_class( # CPython interpreter prints: Exception ignored in: # mypyc prints: Exception ignored in: emitter.emit_line("if (PyErr_Occurred() != NULL) {") + # Don't untrack instance if error occurred emitter.emit_line("PyErr_WriteUnraisable((PyObject *)self);") + emitter.emit_line("res = -1;") emitter.emit_line("}") emitter.emit_line("PyErr_Restore(type, value, traceback);") emitter.emit_line("if (res < 0) {")