8000 [3.13] bpo-44172: Keep reference to original window in curses subwind… · python/cpython@3c9d177 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3c9d177

Browse files
[3.13] bpo-44172: Keep reference to original window in curses subwindow objects (GH-26226) (GH-133370)
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 0af61fe) Co-authored-by: Michael Forney <mforney@mforney.org>
1 parent e090f8e commit 3c9d177

File tree

4 files changed

+26
-10
lines changed

4 files changed

+26
-10
lines changed

Include/py_curses.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,11 @@ extern "C" {
7575

7676
/* Type declarations */
7777

78-
typedef struct {
78+
typedef struct PyCursesWindowObject {
7979
PyObject_HEAD
8080
WINDOW *win;
8181
char *encoding;
82+
struct PyCursesWindowObject *orig;
8283
} PyCursesWindowObject;
8384

8485
#define PyCursesWindow_Check(v) Py_IS_TYPE((v), &PyCursesWindow_Type)

Lib/test/test_curses.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from unittest.mock import MagicMock
99

1010
from test.support import (requires, verbose, SaveSignals, cpython_only,
11-
check_disallow_instantiation, MISSING_C_DOCSTRINGS)
11+
check_disallow_instantiation, MISSING_C_DOCSTRINGS,
12+
gc_collect)
1213
from test.support.import_helper import import_module
1314

1415
# Optionally test curses module. This currently requires that the
@@ -187,6 +188,14 @@ def test_create_windows(self):
187188
self.assertEqual(win3.getparyx(), (2, 1))
188189
self.assertEqual(win3.getmaxyx(), (6, 11))
189190

191+
def test_subwindows_references(self):
192+
win = curses.newwin(5, 10)
193+
win2 = win.subwin(3, 7)
194+
del win
195+
gc_collect()
196+
del win2
197+
gc_collect()
198+
190199
def test_move_cursor(self):
191200
stdscr = self.stdscr
192201
win = stdscr.subwin(10, 15, 2, 5)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Keep a reference to original :mod:`curses` windows in subwindows so
2+
that the original window does not get deleted before subwindows.

Modules/_cursesmodule.c

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,8 @@ Window_TwoArgNoReturnFunction(wresize, int, "ii;lines,columns")
666666
/* Allocation and deallocation of Window Objects */
667667

668668
static PyObject *
669-
PyCursesWindow_New(WINDOW *win, const char *encoding)
669+
PyCursesWindow_New(WINDOW *win, const char *encoding,
670+
PyCursesWindowObject *orig)
670671
{
671672
PyCursesWindowObject *wo;
672673

@@ -697,6 +698,8 @@ PyCursesWindow_New(WINDOW *win, const char *encoding)
697698
PyErr_NoMemory();
698699
return NULL;
699700
}
701+
wo->orig = orig;
702+
Py_XINCREF(orig);
700703
return (PyObject *)wo;
701704
}
702705

@@ -706,6 +709,7 @@ PyCursesWindow_Dealloc(PyCursesWindowObject *wo)
706709
if (wo->win != stdscr) delwin(wo->win);
707710
if (wo->encoding != NULL)
708711
PyMem_Free(wo->encoding);
712+
Py_XDECREF(wo->orig);
709713
PyObject_Free(wo);
710714
}
711715

@@ -1309,7 +1313,7 @@ _curses_window_derwin_impl(PyCursesWindowObject *self, int group_left_1,
13091313
return NULL;
13101314
}
13111315

1312-
return (PyObject *)PyCursesWindow_New(win, NULL);
1316+
return (PyObject *)PyCursesWindow_New(win, NULL, self);
13131317
}
13141318

13151319
/*[clinic input]
@@ -2336,7 +2340,7 @@ _curses_window_subwin_impl(PyCursesWindowObject *self, int group_left_1,
23362340
return NULL;
23372341
}
23382342

2339-
return (PyObject *)PyCursesWindow_New(win, self->encoding);
2343+
return (PyObject *)PyCursesWindow_New(win, self->encoding, self);
23402344
}
23412345

23422346
/*[clinic input]
@@ -3084,7 +3088,7 @@ _curses_getwin(PyObject *module, PyObject *file)
30843088
PyErr_SetString(PyCursesError, catchall_NULL);
30853089
goto error;
30863090
}
3087-
res = PyCursesWindow_New(win, NULL);
3091+
res = PyCursesWindow_New(win, NULL, NULL);
30883092

30893093
error:
30903094
fclose(fp);
@@ -3257,7 +3261,7 @@ _curses_initscr_impl(PyObject *module)
32573261

32583262
if (initialised) {
32593263
wrefresh(stdscr);
3260-
return (PyObject *)PyCursesWindow_New(stdscr, NULL);
3264+
return (PyObject *)PyCursesWindow_New(stdscr, NULL, NULL);
32613265
}
32623266

32633267
win = initscr();
@@ -3349,7 +3353,7 @@ _curses_initscr_impl(PyObject *module)
33493353
SetDictInt("LINES", LINES);
33503354
SetDictInt("COLS", COLS);
33513355

3352-
winobj = (PyCursesWindowObject *)PyCursesWindow_New(win, NULL);
3356+
winobj = (PyCursesWindowObject *)PyCursesWindow_New(win, NULL, NULL);
33533357
screen_encoding = winobj->encoding;
33543358
return (PyObject *)winobj;
33553359
}
@@ -3728,7 +3732,7 @@ _curses_newpad_impl(PyObject *module, int nlines, int ncols)
37283732
return NULL;
37293733
}
37303734

3731-
return (PyObject *)PyCursesWindow_New(win, NULL);
3735+
return (PyObject *)PyCursesWindow_New(win, NULL, NULL);
37323736
}
37333737

37343738
/*[clinic input]
@@ -3767,7 +3771,7 @@ _curses_newwin_impl(PyObject *module, int nlines, int ncols,
37673771
return NULL;
37683772
}
37693773

3770-
return (PyObject *)PyCursesWindow_New(win, NULL);
3774+
return (PyObject *)PyCursesWindow_New(win, NULL, NULL);
37713775
}
37723776

37733777
/*[clinic input]

0 commit comments

Comments
 (0)
0