From 002b033792a4fc5a74d44eb7d873d6758faaa033 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Thu, 9 Dec 2021 11:40:45 +0000 Subject: [PATCH 1/3] bpo-46025: Fix a crash in the atexit module for auto-unregistering functions --- Lib/test/_test_atexit.py | 11 +++++++++++ .../2021-12-09-11-41-35.bpo-46025.pkEvW9.rst | 2 ++ Modules/atexitmodule.c | 6 +++++- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-12-09-11-41-35.bpo-46025.pkEvW9.rst diff --git a/Lib/test/_test_atexit.py b/Lib/test/_test_atexit.py index a31658531113ba0..be27843705d1518 100644 --- a/Lib/test/_test_atexit.py +++ b/Lib/test/_test_atexit.py @@ -116,6 +116,17 @@ def test_bound_methods(self): atexit._run_exitfuncs() self.assertEqual(l, [5]) + def test_atexit_with_unregistered_function(self): + # See bpo-46025 for more info + def func(): + atexit.unregister(func) + 1/0 + atexit.register(func) + try: + atexit._run_exitfuncs() + finally: + atexit.unregister(func) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-12-09-11-41-35.bpo-46025.pkEvW9.rst b/Misc/NEWS.d/next/Core and Builtins/2021-12-09-11-41-35.bpo-46025.pkEvW9.rst new file mode 100644 index 000000000000000..dd2f1ff4731e765 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-12-09-11-41-35.bpo-46025.pkEvW9.rst @@ -0,0 +1,2 @@ +Fix a crash in the :mod:`atexit` module involving functions that unregister +themselves before raising exceptions. Patch by Pablo Galindo. diff --git a/Modules/atexitmodule.c b/Modules/atexitmodule.c index e536b4abe295f00..120069320c0db34 100644 --- a/Modules/atexitmodule.c +++ b/Modules/atexitmodule.c @@ -93,13 +93,17 @@ atexit_callfuncs(struct atexit_state *state) continue; } + // Increment the refcount of cb->func as the call itself may unregister it + PyObject* the_func = cb->func; + Py_INCREF(the_func); PyObject *res = PyObject_Call(cb->func, cb->args, cb->kwargs); if (res == NULL) { - _PyErr_WriteUnraisableMsg("in atexit callback", cb->func); + _PyErr_WriteUnraisableMsg("in atexit callback", the_func); } else { Py_DECREF(res); } + Py_DECREF(the_func); } atexit_cleanup(state); From ebc949b79da4b1d80931da828fd2aa3d912543f2 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Thu, 9 Dec 2021 13:16:08 +0000 Subject: [PATCH 2/3] Update Modules/atexitmodule.c Co-authored-by: Victor Stinner --- Modules/atexitmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/atexitmodule.c b/Modules/atexitmodule.c index 120069320c0db34..9df2f0b8c971614 100644 --- a/Modules/atexitmodule.c +++ b/Modules/atexitmodule.c @@ -93,7 +93,7 @@ atexit_callfuncs(struct atexit_state *state) continue; } - // Increment the refcount of cb->func as the call itself may unregister it + // bpo-46025: Increment the refcount of cb->func as the call itself may unregister it PyObject* the_func = cb->func; Py_INCREF(the_func); PyObject *res = PyObject_Call(cb->func, cb->args, cb->kwargs); From ec3741ebb210a7cfddfae96fda729111c0f97ac5 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Thu, 9 Dec 2021 13:16:19 +0000 Subject: [PATCH 3/3] Update Modules/atexitmodule.c Co-authored-by: Christian Heimes --- Lib/test/_test_atexit.py | 6 +++++- Modules/atexitmodule.c | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/test/_test_atexit.py b/Lib/test/_test_atexit.py index be27843705d1518..55d28083349175b 100644 --- a/Lib/test/_test_atexit.py +++ b/Lib/test/_test_atexit.py @@ -123,7 +123,11 @@ def func(): 1/0 atexit.register(func) try: - atexit._run_exitfuncs() + with support.catch_unraisable_exception() as cm: + atexit._run_exitfuncs() + self.assertEqual(cm.unraisable.object, func) + self.assertEqual(cm.unraisable.exc_type, ZeroDivisionError) + self.assertEqual(type(cm.unraisable.exc_value), ZeroDivisionError) finally: atexit.unregister(func) diff --git a/Modules/atexitmodule.c b/Modules/atexitmodule.c index 9df2f0b8c971614..95c653cf4782a23 100644 --- a/Modules/atexitmodule.c +++ b/Modules/atexitmodule.c @@ -94,8 +94,7 @@ atexit_callfuncs(struct atexit_state *state) } // bpo-46025: Increment the refcount of cb->func as the call itself may unregister it - PyObject* the_func = cb->func; - Py_INCREF(the_func); + PyObject* the_func = Py_NewRef(cb->func); PyObject *res = PyObject_Call(cb->func, cb->args, cb->kwargs); if (res == NULL) { _PyErr_WriteUnraisableMsg("in atexit callback", the_func);