]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] bpo-44172: Keep reference to original window in curses subwindow objects ...
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 4 May 2025 11:15:43 +0000 (14:15 +0300)
committerGitHub <noreply@github.com>
Sun, 4 May 2025 11:15:43 +0000 (11:15 +0000)
The X/Open curses specification[0] and ncurses documentation[1]
both state that subwindows must be deleted before the main window.

Deleting the windows in the wrong order causes a double-free with
NetBSD's curses implementation.

To fix this, keep track of the original window object in the subwindow
object, and keep a reference to the original for the lifetime of
the subwindow.

[0] https://pubs.opengroup.org/onlinepubs/7908799/xcurses/delwin.html
[1] https://invisible-island.net/ncurses/man/curs_window.3x.html
(cherry picked from commit 0af61fe2f41048d66b0a973bbff056690446d3df)

Co-authored-by: Michael Forney <mforney@mforney.org>
Include/py_curses.h
Lib/test/test_curses.py
Misc/NEWS.d/next/Library/2021-05-18-19-12-58.bpo-44172.rJ_-CI.rst [new file with mode: 0644]
Modules/_cursesmodule.c

index 79b1b01fcfa594e4cf54a4fd8edfb405cc74ea52..3e8b16c201f810207c72a259e646e57374038a2f 100644 (file)
@@ -75,10 +75,11 @@ extern "C" {
 
 /* Type declarations */
 
-typedef struct {
+typedef struct PyCursesWindowObject {
     PyObject_HEAD
     WINDOW *win;
     char *encoding;
+    struct PyCursesWindowObject *orig;
 } PyCursesWindowObject;
 
 #define PyCursesWindow_Check(v) Py_IS_TYPE((v), &PyCursesWindow_Type)
index 6fe0e7fd4b7fe9d35d85bbe69909b75f59e5a829..778f040f7a6e77d3a847551e343206109522a13e 100644 (file)
@@ -8,7 +8,8 @@ import unittest
 from unittest.mock import MagicMock
 
 from test.support import (requires, verbose, SaveSignals, cpython_only,
-                          check_disallow_instantiation, MISSING_C_DOCSTRINGS)
+                          check_disallow_instantiation, MISSING_C_DOCSTRINGS,
+                          gc_collect)
 from test.support.import_helper import import_module
 
 # Optionally test curses module.  This currently requires that the
@@ -187,6 +188,14 @@ class TestCurses(unittest.TestCase):
         self.assertEqual(win3.getparyx(), (2, 1))
         self.assertEqual(win3.getmaxyx(), (6, 11))
 
+    def test_subwindows_references(self):
+        win = curses.newwin(5, 10)
+        win2 = win.subwin(3, 7)
+        del win
+        gc_collect()
+        del win2
+        gc_collect()
+
     def test_move_cursor(self):
         stdscr = self.stdscr
         win = stdscr.subwin(10, 15, 2, 5)
diff --git a/Misc/NEWS.d/next/Library/2021-05-18-19-12-58.bpo-44172.rJ_-CI.rst b/Misc/NEWS.d/next/Library/2021-05-18-19-12-58.bpo-44172.rJ_-CI.rst
new file mode 100644 (file)
index 0000000..d53f372
--- /dev/null
@@ -0,0 +1,2 @@
+Keep a reference to original :mod:`curses` windows in subwindows so
+that the original window does not get deleted before subwindows.
index 02b04645036d72fa8ef368ae2aef4221f36097ab..06943aefde6c86149c8cb7b79bb98ac81379276c 100644 (file)
@@ -666,7 +666,8 @@ Window_TwoArgNoReturnFunction(wresize, int, "ii;lines,columns")
 /* Allocation and deallocation of Window Objects */
 
 static PyObject *
-PyCursesWindow_New(WINDOW *win, const char *encoding)
+PyCursesWindow_New(WINDOW *win, const char *encoding,
+                   PyCursesWindowObject *orig)
 {
     PyCursesWindowObject *wo;
 
@@ -697,6 +698,8 @@ PyCursesWindow_New(WINDOW *win, const char *encoding)
         PyErr_NoMemory();
         return NULL;
     }
+    wo->orig = orig;
+    Py_XINCREF(orig);
     return (PyObject *)wo;
 }
 
@@ -706,6 +709,7 @@ PyCursesWindow_Dealloc(PyCursesWindowObject *wo)
     if (wo->win != stdscr) delwin(wo->win);
     if (wo->encoding != NULL)
         PyMem_Free(wo->encoding);
+    Py_XDECREF(wo->orig);
     PyObject_Free(wo);
 }
 
@@ -1309,7 +1313,7 @@ _curses_window_derwin_impl(PyCursesWindowObject *self, int group_left_1,
         return NULL;
     }
 
-    return (PyObject *)PyCursesWindow_New(win, NULL);
+    return (PyObject *)PyCursesWindow_New(win, NULL, self);
 }
 
 /*[clinic input]
@@ -2336,7 +2340,7 @@ _curses_window_subwin_impl(PyCursesWindowObject *self, int group_left_1,
         return NULL;
     }
 
-    return (PyObject *)PyCursesWindow_New(win, self->encoding);
+    return (PyObject *)PyCursesWindow_New(win, self->encoding, self);
 }
 
 /*[clinic input]
@@ -3084,7 +3088,7 @@ _curses_getwin(PyObject *module, PyObject *file)
         PyErr_SetString(PyCursesError, catchall_NULL);
         goto error;
     }
-    res = PyCursesWindow_New(win, NULL);
+    res = PyCursesWindow_New(win, NULL, NULL);
 
 error:
     fclose(fp);
@@ -3257,7 +3261,7 @@ _curses_initscr_impl(PyObject *module)
 
     if (initialised) {
         wrefresh(stdscr);
-        return (PyObject *)PyCursesWindow_New(stdscr, NULL);
+        return (PyObject *)PyCursesWindow_New(stdscr, NULL, NULL);
     }
 
     win = initscr();
@@ -3349,7 +3353,7 @@ _curses_initscr_impl(PyObject *module)
     SetDictInt("LINES", LINES);
     SetDictInt("COLS", COLS);
 
-    winobj = (PyCursesWindowObject *)PyCursesWindow_New(win, NULL);
+    winobj = (PyCursesWindowObject *)PyCursesWindow_New(win, NULL, NULL);
     screen_encoding = winobj->encoding;
     return (PyObject *)winobj;
 }
@@ -3728,7 +3732,7 @@ _curses_newpad_impl(PyObject *module, int nlines, int ncols)
         return NULL;
     }
 
-    return (PyObject *)PyCursesWindow_New(win, NULL);
+    return (PyObject *)PyCursesWindow_New(win, NULL, NULL);
 }
 
 /*[clinic input]
@@ -3767,7 +3771,7 @@ _curses_newwin_impl(PyObject *module, int nlines, int ncols,
         return NULL;
     }
 
-    return (PyObject *)PyCursesWindow_New(win, NULL);
+    return (PyObject *)PyCursesWindow_New(win, NULL, NULL);
 }
 
 /*[clinic input]